From a1e39a862091eac0e9fdbfdd67572be917d0286a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 23 Oct 2023 23:50:01 +0100 Subject: [PATCH 001/635] Remove MemoryState from ExtraData and retrieve using lua_getallocf (recently added to Luau) --- mlua-sys/Cargo.toml | 2 +- mlua-sys/src/luau/lua.rs | 1 + src/lua.rs | 102 ++++++++++++++++++--------------------- src/memory.rs | 52 +++++++++----------- 4 files changed, 73 insertions(+), 84 deletions(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 8cfa28da..0da8fcca 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -40,4 +40,4 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 546.0.0, < 546.1.0", optional = true } luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } -luau0-src = { version = "0.7.0", optional = true } +luau0-src = { version = "0.7.7", optional = true } diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 51168c03..596db0c0 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -278,6 +278,7 @@ extern "C-unwind" { pub fn lua_getuserdatadtor(L: *mut lua_State, tag: c_int) -> Option; pub fn lua_clonefunction(L: *mut lua_State, idx: c_int); pub fn lua_cleartable(L: *mut lua_State, idx: c_int); + pub fn lua_getallocf(L: *mut lua_State, ud: *mut *mut c_void) -> lua_Alloc; } // diff --git a/src/lua.rs b/src/lua.rs index 11765cee..e80be652 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -3,15 +3,14 @@ use std::cell::{RefCell, UnsafeCell}; use std::ffi::{CStr, CString}; use std::fmt; use std::marker::PhantomData; -use std::mem::MaybeUninit; +use std::mem::{self, MaybeUninit}; use std::ops::Deref; use std::os::raw::{c_char, c_int, c_void}; use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe, Location}; -use std::ptr::NonNull; +use std::ptr; use std::result::Result as StdResult; use std::sync::atomic::{AtomicPtr, Ordering}; use std::sync::{Arc, Mutex}; -use std::{mem, ptr, str}; use rustc_hash::FxHashMap; @@ -60,6 +59,7 @@ use { crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue}, futures_util::future::{self, Future}, futures_util::task::{noop_waker_ref, Context, Poll, Waker}, + std::ptr::NonNull, }; #[cfg(feature = "serialize")] @@ -94,7 +94,6 @@ pub(crate) struct ExtraData { safe: bool, libs: StdLib, - mem_state: Option>, #[cfg(feature = "module")] skip_memory_check: bool, @@ -223,6 +222,7 @@ impl LuaOptions { #[cfg(feature = "async")] pub(crate) static ASYNC_POLL_PENDING: u8 = 0; +#[cfg(not(feature = "luau"))] pub(crate) static EXTRA_REGISTRY_KEY: u8 = 0; const WRAPPED_FAILURE_POOL_SIZE: usize = 64; @@ -244,11 +244,14 @@ impl Drop for Lua { impl Drop for LuaInner { fn drop(&mut self) { unsafe { - #[cfg(feature = "luau")] - { - (*ffi::lua_callbacks(self.state())).userdata = ptr::null_mut(); - } + let mem_state = MemoryState::get(self.main_state); + ffi::lua_close(self.main_state); + + // Deallocate MemoryState + if !mem_state.is_null() { + drop(Box::from_raw(mem_state)); + } } } } @@ -261,9 +264,6 @@ impl Drop for ExtraData { } *mlua_expect!(self.registry_unref_list.lock(), "unref list poisoned") = None; - if let Some(mem_state) = self.mem_state { - drop(unsafe { Box::from_raw(mem_state.as_ptr()) }); - } } } @@ -383,12 +383,11 @@ impl Lua { /// Creates a new Lua state with required `libs` and `options` unsafe fn inner_new(libs: StdLib, options: LuaOptions) -> Lua { - let mut mem_state: *mut MemoryState = Box::into_raw(Box::default()); + let mem_state: *mut MemoryState = Box::into_raw(Box::default()); let mut state = ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void); // If state is null then switch to Lua internal allocator if state.is_null() { drop(Box::from_raw(mem_state)); - mem_state = ptr::null_mut(); state = ffi::luaL_newstate(); } assert!(!state.is_null(), "Failed to instantiate Lua VM"); @@ -404,7 +403,6 @@ impl Lua { let lua = Lua::init_from_ptr(state); let extra = lua.extra.get(); - (*extra).mem_state = NonNull::new(mem_state); mlua_expect!( load_from_std_lib(state, libs), @@ -514,7 +512,6 @@ impl Lua { app_data: AppData::default(), safe: false, libs: StdLib::NONE, - mem_state: None, #[cfg(feature = "module")] skip_memory_check: false, ref_thread, @@ -547,14 +544,8 @@ impl Lua { // Store it in the registry mlua_expect!( - (|state| { - push_gc_userdata(state, Arc::clone(&extra), true)?; - protect_lua!(state, 1, 0, fn(state) { - let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void; - ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, extra_key); - }) - })(main_state), - "Error while storing extra data", + set_extra_data(main_state, &extra), + "Error while storing extra data" ); // Register `DestructedUserdata` type @@ -572,13 +563,6 @@ impl Lua { ); assert_stack(main_state, ffi::LUA_MINSTACK); - // Set Luau callbacks userdata to extra data - // We can use global callbacks userdata since we don't allow C modules in Luau - #[cfg(feature = "luau")] - { - (*ffi::lua_callbacks(main_state)).userdata = extra.get() as *mut c_void; - } - let inner = Arc::new(LuaInner { state: AtomicPtr::new(state), main_state, @@ -1098,9 +1082,9 @@ impl Lua { /// Returns the amount of memory (in bytes) currently used inside this Lua state. pub fn used_memory(&self) -> usize { unsafe { - match (*self.extra.get()).mem_state.map(|x| x.as_ref()) { - Some(mem_state) => mem_state.used_memory(), - None => { + match MemoryState::get(self.main_state) { + mem_state if !mem_state.is_null() => (*mem_state).used_memory(), + _ => { // Get data from the Lua GC let used_kbytes = ffi::lua_gc(self.main_state, ffi::LUA_GCCOUNT, 0); let used_kbytes_rem = ffi::lua_gc(self.main_state, ffi::LUA_GCCOUNTB, 0); @@ -1119,9 +1103,9 @@ impl Lua { /// Does not work in module mode where Lua state is managed externally. pub fn set_memory_limit(&self, limit: usize) -> Result { unsafe { - match (*self.extra.get()).mem_state.map(|mut x| x.as_mut()) { - Some(mem_state) => Ok(mem_state.set_memory_limit(limit)), - None => Err(Error::MemoryLimitNotAvailable), + match MemoryState::get(self.main_state) { + mem_state if !mem_state.is_null() => Ok((*mem_state).set_memory_limit(limit)), + _ => Err(Error::MemoryLimitNotAvailable), } } } @@ -3169,16 +3153,13 @@ impl Lua { #[inline] pub(crate) unsafe fn unlikely_memory_error(&self) -> bool { // MemoryInfo is empty in module mode so we cannot predict memory limits - (*self.extra.get()) - .mem_state - .map(|x| x.as_ref().memory_limit() == 0) - .unwrap_or_else(|| { - // Alternatively, check the special flag (only for module mode) - #[cfg(feature = "module")] - return (*self.extra.get()).skip_memory_check; - #[cfg(not(feature = "module"))] - return false; - }) + match MemoryState::get(self.main_state) { + mem_state if !mem_state.is_null() => (*mem_state).memory_limit() == 0, + #[cfg(feature = "module")] + _ => (*self.extra.get()).skip_memory_check, // Check the special flag (only for module mode) + #[cfg(not(feature = "module"))] + _ => false, + } } #[cfg(feature = "unstable")] @@ -3223,14 +3204,6 @@ impl LuaInner { } } -impl ExtraData { - #[cfg(feature = "luau")] - #[inline] - pub(crate) fn mem_state(&self) -> NonNull { - self.mem_state.unwrap() - } -} - struct StateGuard<'a>(&'a LuaInner, *mut ffi::lua_State); impl<'a> StateGuard<'a> { @@ -3251,6 +3224,15 @@ unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData { (*ffi::lua_callbacks(state)).userdata as *mut ExtraData } +#[cfg(feature = "luau")] +unsafe fn set_extra_data( + state: *mut ffi::lua_State, + extra: &Arc>, +) -> Result<()> { + (*ffi::lua_callbacks(state)).userdata = extra.get() as *mut _; + Ok(()) +} + #[cfg(not(feature = "luau"))] unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData { let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void; @@ -3265,6 +3247,18 @@ unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData { (*extra_ptr).get() } +#[cfg(not(feature = "luau"))] +unsafe fn set_extra_data( + state: *mut ffi::lua_State, + extra: &Arc>, +) -> Result<()> { + push_gc_userdata(state, Arc::clone(extra), true)?; + protect_lua!(state, 1, 0, fn(state) { + let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void; + ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, extra_key); + }) +} + // Creates required entries in the metatable cache (see `util::METATABLE_CACHE`) pub(crate) fn init_metatable_cache(cache: &mut FxHashMap) { cache.insert(TypeId::of::>>(), 0); diff --git a/src/memory.rs b/src/memory.rs index e199a759..ed685147 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -2,9 +2,6 @@ use std::alloc::{self, Layout}; use std::os::raw::c_void; use std::ptr; -#[cfg(feature = "luau")] -use crate::lua::ExtraData; - pub(crate) static ALLOCATOR: ffi::lua_Alloc = allocator; #[derive(Default)] @@ -20,6 +17,21 @@ pub(crate) struct MemoryState { } impl MemoryState { + #[inline] + pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { + let mut mem_state = ptr::null_mut(); + #[cfg(feature = "luau")] + { + ffi::lua_getallocf(state, &mut mem_state); + mlua_assert!(!mem_state.is_null(), "Luau state has no allocator userdata"); + } + #[cfg(not(feature = "luau"))] + if ffi::lua_getallocf(state, &mut mem_state) != ALLOCATOR { + mem_state = ptr::null_mut(); + } + mem_state as *mut MemoryState + } + #[inline] pub(crate) fn used_memory(&self) -> usize { self.used_memory as usize @@ -37,36 +49,21 @@ impl MemoryState { prev_limit as usize } - // This function is used primarily for calling `lua_pushcfunction` in lua5.1/jit + // This function is used primarily for calling `lua_pushcfunction` in lua5.1/jit/luau // to bypass the memory limit (if set). - #[cfg(any(feature = "lua51", feature = "luajit"))] + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] #[inline] pub(crate) unsafe fn relax_limit_with(state: *mut ffi::lua_State, f: impl FnOnce()) { - let mut mem_state: *mut c_void = ptr::null_mut(); - if ffi::lua_getallocf(state, &mut mem_state) == ALLOCATOR { - (*(mem_state as *mut MemoryState)).ignore_limit = true; + let mem_state = Self::get(state); + if !mem_state.is_null() { + (*mem_state).ignore_limit = true; f(); - (*(mem_state as *mut MemoryState)).ignore_limit = false; + (*mem_state).ignore_limit = false; } else { f(); } } - // Same as the above but for Luau - // It does not have `lua_getallocf` function, so instead we use `lua_callbacks` - #[cfg(feature = "luau")] - #[inline] - pub(crate) unsafe fn relax_limit_with(state: *mut ffi::lua_State, f: impl FnOnce()) { - let extra = (*ffi::lua_callbacks(state)).userdata as *mut ExtraData; - if extra.is_null() { - return f(); - } - let mem_state = (*extra).mem_state(); - (*mem_state.as_ptr()).ignore_limit = true; - f(); - (*mem_state.as_ptr()).ignore_limit = false; - } - // Does nothing apart from calling `f()`, we don't need to bypass any limits #[cfg(any(feature = "lua52", feature = "lua53", feature = "lua54"))] #[inline] @@ -76,12 +73,9 @@ impl MemoryState { // Returns `true` if the memory limit was reached on the last memory operation #[cfg(feature = "luau")] + #[inline] pub(crate) unsafe fn limit_reached(state: *mut ffi::lua_State) -> bool { - let extra = (*ffi::lua_callbacks(state)).userdata as *mut ExtraData; - if extra.is_null() { - return false; - } - (*(*extra).mem_state().as_ptr()).limit_reached + (*Self::get(state)).limit_reached } } From b879abc418475a375065c2156ef46c9c1aeaff80 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 24 Oct 2023 23:57:11 +0100 Subject: [PATCH 002/635] Add lua_newuserdata_t helper to mlua-sys/luau --- mlua-sys/src/luau/lua.rs | 11 +++++++++- src/util/mod.rs | 43 ++++++++++------------------------------ 2 files changed, 21 insertions(+), 33 deletions(-) diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 596db0c0..996f62ee 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -2,7 +2,7 @@ use std::marker::{PhantomData, PhantomPinned}; use std::os::raw::{c_char, c_double, c_float, c_int, c_uint, c_void}; -use std::ptr; +use std::{mem, ptr}; // Option for multiple returns in 'lua_pcall' and 'lua_call' pub const LUA_MULTRET: c_int = -1; @@ -326,6 +326,15 @@ pub unsafe fn lua_newuserdata(L: *mut lua_State, sz: usize) -> *mut c_void { lua_newuserdatatagged(L, sz, 0) } +#[inline(always)] +pub unsafe fn lua_newuserdata_t(L: *mut lua_State) -> *mut T { + unsafe extern "C-unwind" fn destructor(ud: *mut c_void) { + ptr::drop_in_place(ud as *mut T); + } + + lua_newuserdatadtor(L, mem::size_of::(), destructor::) as *mut T +} + // TODO: lua_strlen #[inline(always)] diff --git a/src/util/mod.rs b/src/util/mod.rs index 1351fbfc..72d359c1 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -6,7 +6,7 @@ use std::mem::MaybeUninit; use std::os::raw::{c_char, c_int, c_void}; use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe}; use std::sync::Arc; -use std::{mem, ptr, slice, str}; +use std::{ptr, slice, str}; use once_cell::sync::Lazy; use rustc_hash::FxHashMap; @@ -282,38 +282,23 @@ pub unsafe fn rawset_field(state: *mut ffi::lua_State, table: c_int, field: &str } // Internally uses 3 stack spaces, does not call checkstack. -#[cfg(not(feature = "luau"))] #[inline] pub unsafe fn push_userdata(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<()> { + #[cfg(not(feature = "luau"))] let ud = if protect { protect_lua!(state, 0, 1, |state| { - ffi::lua_newuserdata(state, mem::size_of::()) as *mut T + ffi::lua_newuserdata(state, std::mem::size_of::()) as *mut T })? } else { - ffi::lua_newuserdata(state, mem::size_of::()) as *mut T + ffi::lua_newuserdata(state, std::mem::size_of::()) as *mut T }; - ptr::write(ud, t); - Ok(()) -} - -// Internally uses 3 stack spaces, does not call checkstack. -#[cfg(feature = "luau")] -#[inline] -pub unsafe fn push_userdata(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<()> { - unsafe extern "C-unwind" fn destructor(ud: *mut c_void) { - ptr::drop_in_place(ud as *mut T); - } - - let size = mem::size_of::(); + #[cfg(feature = "luau")] let ud = if protect { - protect_lua!(state, 0, 1, |state| { - ffi::lua_newuserdatadtor(state, size, destructor::) as *mut T - })? + protect_lua!(state, 0, 1, |state| { ffi::lua_newuserdata_t::(state) })? } else { - ffi::lua_newuserdatadtor(state, size, destructor::) as *mut T + ffi::lua_newuserdata_t::(state) }; ptr::write(ud, t); - Ok(()) } @@ -328,10 +313,10 @@ pub unsafe fn push_userdata_uv( ) -> Result<()> { let ud = if protect { protect_lua!(state, 0, 1, |state| { - ffi::lua_newuserdatauv(state, mem::size_of::(), nuvalue) as *mut T + ffi::lua_newuserdatauv(state, std::mem::size_of::(), nuvalue) as *mut T })? } else { - ffi::lua_newuserdatauv(state, mem::size_of::(), nuvalue) as *mut T + ffi::lua_newuserdatauv(state, std::mem::size_of::(), nuvalue) as *mut T }; ptr::write(ud, t); Ok(()) @@ -1009,16 +994,10 @@ pub(crate) enum WrappedFailure { impl WrappedFailure { pub(crate) unsafe fn new_userdata(state: *mut ffi::lua_State) -> *mut Self { - let size = mem::size_of::(); #[cfg(feature = "luau")] - let ud = { - unsafe extern "C-unwind" fn destructor(p: *mut c_void) { - ptr::drop_in_place(p as *mut WrappedFailure); - } - ffi::lua_newuserdatadtor(state, size, destructor) as *mut Self - }; + let ud = ffi::lua_newuserdata_t::(state); #[cfg(not(feature = "luau"))] - let ud = ffi::lua_newuserdata(state, size) as *mut Self; + let ud = ffi::lua_newuserdata(state, std::mem::size_of::()) as *mut Self; ptr::write(ud, WrappedFailure::None); ud } From 5043447f230d0be2a4dde9512cb1aae1501a91a9 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 16 Nov 2023 12:55:58 +0000 Subject: [PATCH 003/635] Support Luau buffer type and and library. Buffer is an object that represents a fixed-size mutable block of memory and added to Luau 0.601. See https://luau-lang.org/library#buffer-library for more details. --- mlua-sys/Cargo.toml | 2 +- mlua-sys/src/luau/lauxlib.rs | 47 +++++++++++++++++++++++++++++++++++- mlua-sys/src/luau/lua.rs | 9 +++++++ mlua-sys/src/luau/lualib.rs | 2 ++ src/conversion.rs | 5 +++- src/lua.rs | 26 +++++++++++++++++--- src/scope.rs | 2 +- src/stdlib.rs | 15 ++++++++++++ src/types.rs | 4 +++ src/userdata.rs | 13 +++++++--- src/util/mod.rs | 2 ++ src/value.rs | 17 +++++++++++-- tests/luau.rs | 26 ++++++++++++++++++++ 13 files changed, 157 insertions(+), 13 deletions(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 0da8fcca..b47fead8 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -40,4 +40,4 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 546.0.0, < 546.1.0", optional = true } luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } -luau0-src = { version = "0.7.7", optional = true } +luau0-src = { version = "0.7.8", optional = true } diff --git a/mlua-sys/src/luau/lauxlib.rs b/mlua-sys/src/luau/lauxlib.rs index 4095155a..73788af8 100644 --- a/mlua-sys/src/luau/lauxlib.rs +++ b/mlua-sys/src/luau/lauxlib.rs @@ -52,6 +52,8 @@ extern "C-unwind" { pub fn luaL_newmetatable_(L: *mut lua_State, tname: *const c_char) -> c_int; pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void; + pub fn luaL_checkbuffer(L: *mut lua_State, narg: c_int, len: *mut usize) -> *mut c_void; + pub fn luaL_where(L: *mut lua_State, lvl: c_int); #[link_name = "luaL_errorL"] @@ -152,5 +154,48 @@ pub unsafe fn luaL_sandbox(L: *mut lua_State, enabled: c_int) { } // -// TODO: Generic Buffer Manipulation +// Generic Buffer Manipulation // + +/// Buffer size used for on-stack string operations. This limit depends on native stack size. +pub const LUA_BUFFERSIZE: usize = 512; + +#[repr(C)] +pub struct luaL_Strbuf { + p: *mut c_char, // current position in buffer + end: *mut c_char, // end of the current buffer + L: *mut lua_State, + storage: *mut c_void, // TString + buffer: [c_char; LUA_BUFFERSIZE], +} + +// For compatibility +pub type luaL_Buffer = luaL_Strbuf; + +extern "C-unwind" { + pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Strbuf); + pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Strbuf, size: usize) -> *mut c_char; + pub fn luaL_prepbuffsize(B: *mut luaL_Strbuf, size: usize) -> *mut c_char; + pub fn luaL_addlstring(B: *mut luaL_Strbuf, s: *const c_char, l: usize); + pub fn luaL_addvalue(B: *mut luaL_Strbuf); + pub fn luaL_addvalueany(B: *mut luaL_Strbuf, idx: c_int); + pub fn luaL_pushresult(B: *mut luaL_Strbuf); + pub fn luaL_pushresultsize(B: *mut luaL_Strbuf, size: usize); +} + +pub unsafe fn luaL_addchar(B: *mut luaL_Strbuf, c: c_char) { + if (*B).p >= (*B).end { + luaL_prepbuffsize(B, 1); + } + *(*B).p = c; + (*B).p = (*B).p.add(1); +} + +pub unsafe fn luaL_addstring(B: *mut luaL_Strbuf, s: *const c_char) { + // Calculate length of s + let mut len = 0; + while *s.add(len) != 0 { + len += 1; + } + luaL_addlstring(B, s, len); +} diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 996f62ee..2da5690d 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -55,6 +55,7 @@ pub const LUA_TTABLE: c_int = 6; pub const LUA_TFUNCTION: c_int = 7; pub const LUA_TUSERDATA: c_int = 8; pub const LUA_TTHREAD: c_int = 9; +pub const LUA_TBUFFER: c_int = 10; /// Guaranteed number of Lua stack slots available to a C function. pub const LUA_MINSTACK: c_int = 20; @@ -147,6 +148,7 @@ extern "C-unwind" { pub fn lua_touserdatatagged(L: *mut lua_State, idx: c_int, tag: c_int) -> *mut c_void; pub fn lua_userdatatag(L: *mut lua_State, idx: c_int) -> c_int; pub fn lua_tothread(L: *mut lua_State, idx: c_int) -> *mut lua_State; + pub fn lua_tobuffer(L: *mut lua_State, idx: c_int, len: *mut usize) -> *mut c_void; pub fn lua_topointer(L: *mut lua_State, idx: c_int) -> *const c_void; // @@ -181,6 +183,8 @@ extern "C-unwind" { pub fn lua_newuserdatatagged(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void; pub fn lua_newuserdatadtor(L: *mut lua_State, sz: usize, dtor: lua_Udestructor) -> *mut c_void; + pub fn lua_newbuffer(L: *mut lua_State, sz: usize) -> *mut c_void; + // // Get functions (Lua -> stack) // @@ -372,6 +376,11 @@ pub unsafe fn lua_isthread(L: *mut lua_State, n: c_int) -> c_int { (lua_type(L, n) == LUA_TTHREAD) as c_int } +#[inline(always)] +pub unsafe fn lua_isbuffer(L: *mut lua_State, n: c_int) -> c_int { + (lua_type(L, n) == LUA_TBUFFER) as c_int +} + #[inline(always)] pub unsafe fn lua_isnone(L: *mut lua_State, n: c_int) -> c_int { (lua_type(L, n) == LUA_TNONE) as c_int diff --git a/mlua-sys/src/luau/lualib.rs b/mlua-sys/src/luau/lualib.rs index 5593f8fc..96864a07 100644 --- a/mlua-sys/src/luau/lualib.rs +++ b/mlua-sys/src/luau/lualib.rs @@ -9,6 +9,7 @@ pub const LUA_TABLIBNAME: &str = "table"; pub const LUA_OSLIBNAME: &str = "os"; pub const LUA_STRLIBNAME: &str = "string"; pub const LUA_BITLIBNAME: &str = "bit32"; +pub const LUA_BUFFERLIBNAME: &str = "buffer"; pub const LUA_UTF8LIBNAME: &str = "utf8"; pub const LUA_MATHLIBNAME: &str = "math"; pub const LUA_DBLIBNAME: &str = "debug"; @@ -20,6 +21,7 @@ extern "C-unwind" { pub fn luaopen_os(L: *mut lua_State) -> c_int; pub fn luaopen_string(L: *mut lua_State) -> c_int; pub fn luaopen_bit32(L: *mut lua_State) -> c_int; + pub fn luaopen_buffer(L: *mut lua_State) -> c_int; pub fn luaopen_utf8(L: *mut lua_State) -> c_int; pub fn luaopen_math(L: *mut lua_State) -> c_int; pub fn luaopen_debug(L: *mut lua_State) -> c_int; diff --git a/src/conversion.rs b/src/conversion.rs index 112febbc..41fdf365 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -182,7 +182,10 @@ impl<'lua> FromLua<'lua> for AnyUserData<'lua> { impl<'lua> IntoLua<'lua> for OwnedAnyUserData { #[inline] fn into_lua(self, lua: &'lua Lua) -> Result> { - Ok(Value::UserData(AnyUserData(lua.adopt_owned_ref(self.0)))) + Ok(Value::UserData(AnyUserData( + lua.adopt_owned_ref(self.0), + self.1, + ))) } } diff --git a/src/lua.rs b/src/lua.rs index e80be652..7e1eb16b 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -2419,12 +2419,18 @@ impl Lua { ffi::lua_pop(state, 1); Nil } - _ => Value::UserData(AnyUserData(self.pop_ref())), + _ => Value::UserData(AnyUserData(self.pop_ref(), 0)), } } ffi::LUA_TTHREAD => Value::Thread(Thread::new(self.pop_ref())), + #[cfg(feature = "luau")] + ffi::LUA_TBUFFER => { + // Buffer is represented as a userdata type + Value::UserData(AnyUserData(self.pop_ref(), crate::types::BUFFER_SUBTYPE_ID)) + } + #[cfg(feature = "luajit")] ffi::LUA_TCDATA => { ffi::lua_pop(state, 1); @@ -2514,7 +2520,7 @@ impl Lua { } _ => { ffi::lua_xpush(state, self.ref_thread(), idx); - Value::UserData(AnyUserData(self.pop_ref_thread())) + Value::UserData(AnyUserData(self.pop_ref_thread(), 0)) } } } @@ -2524,6 +2530,14 @@ impl Lua { Value::Thread(Thread::new(self.pop_ref_thread())) } + #[cfg(feature = "luau")] + ffi::LUA_TBUFFER => { + // Buffer is represented as a userdata type + ffi::lua_xpush(state, self.ref_thread(), idx); + let subtype_id = crate::types::BUFFER_SUBTYPE_ID; + Value::UserData(AnyUserData(self.pop_ref_thread(), subtype_id)) + } + #[cfg(feature = "luajit")] ffi::LUA_TCDATA => { // TODO: Fix this in a next major release @@ -3112,7 +3126,7 @@ impl Lua { ffi::lua_setuservalue(state, -2); } - Ok(AnyUserData(self.pop_ref())) + Ok(AnyUserData(self.pop_ref(), 0)) } #[cfg(not(feature = "luau"))] @@ -3497,6 +3511,12 @@ unsafe fn load_from_std_lib(state: *mut ffi::lua_State, libs: StdLib) -> Result< } } + #[cfg(feature = "luau")] + if libs.contains(StdLib::BUFFER) { + requiref(state, ffi::LUA_BUFFERLIBNAME, ffi::luaopen_buffer, 1)?; + ffi::lua_pop(state, 1); + } + if libs.contains(StdLib::MATH) { requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?; ffi::lua_pop(state, 1); diff --git a/src/scope.rs b/src/scope.rs index 9874fd91..bad966b9 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -511,7 +511,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { #[cfg(not(feature = "luau"))] std::ptr::write(ud_ptr as _, UserDataCell::new(data)); ffi::lua_setmetatable(state, -2); - let ud = AnyUserData(lua.pop_ref()); + let ud = AnyUserData(lua.pop_ref(), 0); lua.register_raw_userdata_metatable(mt_ptr, None); #[cfg(any(feature = "lua51", feature = "luajit"))] diff --git a/src/stdlib.rs b/src/stdlib.rs index 34c7629c..a320a0aa 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -16,32 +16,46 @@ impl StdLib { feature = "luau" ))] pub const COROUTINE: StdLib = StdLib(1); + /// [`table`](https://www.lua.org/manual/5.4/manual.html#6.6) library pub const TABLE: StdLib = StdLib(1 << 1); + /// [`io`](https://www.lua.org/manual/5.4/manual.html#6.8) library #[cfg(not(feature = "luau"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub const IO: StdLib = StdLib(1 << 2); + /// [`os`](https://www.lua.org/manual/5.4/manual.html#6.9) library pub const OS: StdLib = StdLib(1 << 3); + /// [`string`](https://www.lua.org/manual/5.4/manual.html#6.4) library pub const STRING: StdLib = StdLib(1 << 4); + /// [`utf8`](https://www.lua.org/manual/5.4/manual.html#6.5) library /// /// Requires `feature = "lua54/lua53/luau"` #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] pub const UTF8: StdLib = StdLib(1 << 5); + /// [`bit`](https://www.lua.org/manual/5.2/manual.html#6.7) library /// /// Requires `feature = "lua52/luajit/luau"` #[cfg(any(feature = "lua52", feature = "luajit", feature = "luau", doc))] pub const BIT: StdLib = StdLib(1 << 6); + /// [`math`](https://www.lua.org/manual/5.4/manual.html#6.7) library pub const MATH: StdLib = StdLib(1 << 7); + /// [`package`](https://www.lua.org/manual/5.4/manual.html#6.3) library #[cfg(not(feature = "luau"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub const PACKAGE: StdLib = StdLib(1 << 8); + + /// [`buffer`](https://luau-lang.org/library#buffer-library) library + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub const BUFFER: StdLib = StdLib(1 << 9); + /// [`jit`](http://luajit.org/ext_jit.html) library /// /// Requires `feature = "luajit"` @@ -55,6 +69,7 @@ impl StdLib { #[cfg(any(feature = "luajit", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luajit")))] pub const FFI: StdLib = StdLib(1 << 30); + /// (**unsafe**) [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10) library pub const DEBUG: StdLib = StdLib(1 << 31); diff --git a/src/types.rs b/src/types.rs index a2b38e9d..c8775b77 100644 --- a/src/types.rs +++ b/src/types.rs @@ -29,6 +29,10 @@ pub type Integer = ffi::lua_Integer; /// Type of Lua floating point numbers. pub type Number = ffi::lua_Number; +// LUA_TBUFFER subtype +#[cfg(feature = "luau")] +pub(crate) const BUFFER_SUBTYPE_ID: u8 = 1; + /// A "light" userdata value. Equivalent to an unmanaged raw pointer. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct LightUserData(pub *mut c_void); diff --git a/src/userdata.rs b/src/userdata.rs index 7c5ef95c..ac68b1ab 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -791,7 +791,7 @@ impl Deref for UserDataVariant { /// [`is`]: crate::AnyUserData::is /// [`borrow`]: crate::AnyUserData::borrow #[derive(Clone, Debug)] -pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>); +pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>, pub(crate) u8); /// Owned handle to an internal Lua userdata. /// @@ -801,14 +801,14 @@ pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>); #[cfg(feature = "unstable")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] #[derive(Clone, Debug)] -pub struct OwnedAnyUserData(pub(crate) crate::types::LuaOwnedRef); +pub struct OwnedAnyUserData(pub(crate) crate::types::LuaOwnedRef, pub(crate) u8); #[cfg(feature = "unstable")] impl OwnedAnyUserData { /// Get borrowed handle to the underlying Lua userdata. #[cfg_attr(feature = "send", allow(unused))] pub const fn to_ref(&self) -> AnyUserData { - AnyUserData(self.0.to_ref()) + AnyUserData(self.0.to_ref(), self.1) } } @@ -1101,7 +1101,7 @@ impl<'lua> AnyUserData<'lua> { #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] #[inline] pub fn into_owned(self) -> OwnedAnyUserData { - OwnedAnyUserData(self.0.into_owned()) + OwnedAnyUserData(self.0.into_owned(), self.1) } #[cfg(feature = "async")] @@ -1112,6 +1112,11 @@ impl<'lua> AnyUserData<'lua> { /// Returns a type name of this `UserData` (from a metatable field). pub(crate) fn type_name(&self) -> Result> { + #[cfg(feature = "luau")] + if self.1 == crate::types::BUFFER_SUBTYPE_ID { + return Ok(Some("buffer".to_owned())); + } + let lua = self.0.lua; let state = lua.state(); unsafe { diff --git a/src/util/mod.rs b/src/util/mod.rs index 72d359c1..6043c24b 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1043,6 +1043,8 @@ pub(crate) unsafe fn to_string(state: *mut ffi::lua_State, index: c_int) -> Stri ffi::LUA_TFUNCTION => format!("", ffi::lua_topointer(state, index)), ffi::LUA_TUSERDATA => format!("", ffi::lua_topointer(state, index)), ffi::LUA_TTHREAD => format!("", ffi::lua_topointer(state, index)), + #[cfg(feature = "luau")] + ffi::LUA_TBUFFER => format!("", ffi::lua_topointer(state, index)), _ => "".to_string(), } } diff --git a/src/value.rs b/src/value.rs index 7525c66b..5575d82d 100644 --- a/src/value.rs +++ b/src/value.rs @@ -88,6 +88,8 @@ impl<'lua> Value<'lua> { Value::Table(_) => "table", Value::Function(_) => "function", Value::Thread(_) => "thread", + #[cfg(feature = "luau")] + Value::UserData(AnyUserData(_, crate::types::BUFFER_SUBTYPE_ID)) => "buffer", Value::UserData(_) => "userdata", Value::Error(_) => "error", } @@ -126,7 +128,7 @@ impl<'lua> Value<'lua> { | Value::Table(Table(r)) | Value::Function(Function(r)) | Value::Thread(Thread(r, ..)) - | Value::UserData(AnyUserData(r)) => r.to_pointer(), + | Value::UserData(AnyUserData(r, ..)) => r.to_pointer(), _ => ptr::null(), } } @@ -148,7 +150,7 @@ impl<'lua> Value<'lua> { Value::Table(Table(r)) | Value::Function(Function(r)) | Value::Thread(Thread(r, ..)) - | Value::UserData(AnyUserData(r)) => unsafe { + | Value::UserData(AnyUserData(r, ..)) => unsafe { let state = r.lua.state(); let _guard = StackGuard::new(state); check_stack(state, 3)?; @@ -410,6 +412,17 @@ impl<'lua> Value<'lua> { } } + /// Returns `true` if the value is a Buffer wrapped in [`AnyUserData`]. + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + #[doc(hidden)] + #[inline] + pub fn is_buffer(&self) -> bool { + self.as_userdata() + .map(|ud| ud.1 == crate::types::BUFFER_SUBTYPE_ID) + .unwrap_or_default() + } + /// Wrap reference to this Value into [`SerializableValue`]. /// /// This allows customizing serialization behavior using serde. diff --git a/tests/luau.rs b/tests/luau.rs index 93a34114..f42f12e9 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -407,3 +407,29 @@ fn test_coverage() -> Result<()> { Ok(()) } + +#[test] +fn test_buffer() -> Result<()> { + let lua = Lua::new(); + + let buf1 = lua + .load( + r#" + local buf = buffer.fromstring("hello") + assert(buffer.len(buf) == 5) + return buf + "#, + ) + .eval::()?; + assert!(buf1.is_userdata() && buf1.is_buffer()); + assert_eq!(buf1.type_name(), "buffer"); + + let buf2 = lua.load("buffer.fromstring('hello')").eval::()?; + assert_ne!(buf1, buf2); + + // Check that we can pass buffer type to Lua + let func = lua.create_function(|_, buf: Value| return buf.to_string())?; + assert!(func.call::<_, String>(buf1)?.starts_with("buffer:")); + + Ok(()) +} From 34476ebf532cc84e3016521518922bbe5bda74c1 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 16 Nov 2023 13:33:23 +0000 Subject: [PATCH 004/635] Support LuaJIT cdata type (produced by ffi module) --- src/lua.rs | 23 +++++++++++------------ src/scope.rs | 4 ++-- src/types.rs | 12 +++++++++--- src/userdata.rs | 15 +++++++++------ src/value.rs | 21 +++++++++++++++++---- tests/tests.rs | 13 +++++++++---- 6 files changed, 57 insertions(+), 31 deletions(-) diff --git a/src/lua.rs b/src/lua.rs index 7e1eb16b..e4f88a10 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -26,7 +26,7 @@ use crate::table::Table; use crate::thread::Thread; use crate::types::{ AppData, AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, - LightUserData, LuaRef, MaybeSend, Number, RegistryKey, + LightUserData, LuaRef, MaybeSend, Number, RegistryKey, SubtypeId, }; use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataCell}; use crate::userdata_impl::{UserDataProxy, UserDataRegistry}; @@ -2419,7 +2419,7 @@ impl Lua { ffi::lua_pop(state, 1); Nil } - _ => Value::UserData(AnyUserData(self.pop_ref(), 0)), + _ => Value::UserData(AnyUserData(self.pop_ref(), SubtypeId::None)), } } @@ -2428,14 +2428,13 @@ impl Lua { #[cfg(feature = "luau")] ffi::LUA_TBUFFER => { // Buffer is represented as a userdata type - Value::UserData(AnyUserData(self.pop_ref(), crate::types::BUFFER_SUBTYPE_ID)) + Value::UserData(AnyUserData(self.pop_ref(), SubtypeId::Buffer)) } #[cfg(feature = "luajit")] ffi::LUA_TCDATA => { - ffi::lua_pop(state, 1); - // TODO: Fix this in a next major release - panic!("cdata objects cannot be handled by mlua yet"); + // CDATA is represented as a userdata type + Value::UserData(AnyUserData(self.pop_ref(), SubtypeId::CData)) } _ => mlua_panic!("LUA_TNONE in pop_value"), @@ -2520,7 +2519,7 @@ impl Lua { } _ => { ffi::lua_xpush(state, self.ref_thread(), idx); - Value::UserData(AnyUserData(self.pop_ref_thread(), 0)) + Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::None)) } } } @@ -2534,14 +2533,14 @@ impl Lua { ffi::LUA_TBUFFER => { // Buffer is represented as a userdata type ffi::lua_xpush(state, self.ref_thread(), idx); - let subtype_id = crate::types::BUFFER_SUBTYPE_ID; - Value::UserData(AnyUserData(self.pop_ref_thread(), subtype_id)) + Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::Buffer)) } #[cfg(feature = "luajit")] ffi::LUA_TCDATA => { - // TODO: Fix this in a next major release - panic!("cdata objects cannot be handled by mlua yet"); + // CData is represented as a userdata type + ffi::lua_xpush(state, self.ref_thread(), idx); + Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::CData)) } _ => mlua_panic!("LUA_TNONE in pop_value"), @@ -3126,7 +3125,7 @@ impl Lua { ffi::lua_setuservalue(state, -2); } - Ok(AnyUserData(self.pop_ref(), 0)) + Ok(AnyUserData(self.pop_ref(), SubtypeId::None)) } #[cfg(not(feature = "luau"))] diff --git a/src/scope.rs b/src/scope.rs index bad966b9..350650e9 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -10,7 +10,7 @@ use serde::Serialize; use crate::error::{Error, Result}; use crate::function::Function; use crate::lua::Lua; -use crate::types::{Callback, CallbackUpvalue, LuaRef, MaybeSend}; +use crate::types::{Callback, CallbackUpvalue, LuaRef, MaybeSend, SubtypeId}; use crate::userdata::{ AnyUserData, MetaMethod, UserData, UserDataCell, UserDataFields, UserDataMethods, }; @@ -511,7 +511,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { #[cfg(not(feature = "luau"))] std::ptr::write(ud_ptr as _, UserDataCell::new(data)); ffi::lua_setmetatable(state, -2); - let ud = AnyUserData(lua.pop_ref(), 0); + let ud = AnyUserData(lua.pop_ref(), SubtypeId::None); lua.register_raw_userdata_metatable(mt_ptr, None); #[cfg(any(feature = "lua51", feature = "luajit"))] diff --git a/src/types.rs b/src/types.rs index c8775b77..f21af2dd 100644 --- a/src/types.rs +++ b/src/types.rs @@ -29,9 +29,15 @@ pub type Integer = ffi::lua_Integer; /// Type of Lua floating point numbers. pub type Number = ffi::lua_Number; -// LUA_TBUFFER subtype -#[cfg(feature = "luau")] -pub(crate) const BUFFER_SUBTYPE_ID: u8 = 1; +// Represents different subtypes wrapped to AnyUserData +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(crate) enum SubtypeId { + None, + #[cfg(feature = "luau")] + Buffer, + #[cfg(feature = "luajit")] + CData, +} /// A "light" userdata value. Equivalent to an unmanaged raw pointer. #[derive(Debug, Copy, Clone, Eq, PartialEq)] diff --git a/src/userdata.rs b/src/userdata.rs index ac68b1ab..15e21349 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -22,7 +22,7 @@ use crate::function::Function; use crate::lua::Lua; use crate::string::String; use crate::table::{Table, TablePairs}; -use crate::types::{LuaRef, MaybeSend}; +use crate::types::{LuaRef, MaybeSend, SubtypeId}; use crate::util::{check_stack, get_userdata, take_userdata, StackGuard}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; use crate::UserDataRegistry; @@ -791,7 +791,7 @@ impl Deref for UserDataVariant { /// [`is`]: crate::AnyUserData::is /// [`borrow`]: crate::AnyUserData::borrow #[derive(Clone, Debug)] -pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>, pub(crate) u8); +pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>, pub(crate) SubtypeId); /// Owned handle to an internal Lua userdata. /// @@ -801,7 +801,7 @@ pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>, pub(crate) u8); #[cfg(feature = "unstable")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] #[derive(Clone, Debug)] -pub struct OwnedAnyUserData(pub(crate) crate::types::LuaOwnedRef, pub(crate) u8); +pub struct OwnedAnyUserData(pub(crate) crate::types::LuaOwnedRef, pub(crate) SubtypeId); #[cfg(feature = "unstable")] impl OwnedAnyUserData { @@ -1112,9 +1112,12 @@ impl<'lua> AnyUserData<'lua> { /// Returns a type name of this `UserData` (from a metatable field). pub(crate) fn type_name(&self) -> Result> { - #[cfg(feature = "luau")] - if self.1 == crate::types::BUFFER_SUBTYPE_ID { - return Ok(Some("buffer".to_owned())); + match self.1 { + SubtypeId::None => {} + #[cfg(feature = "luau")] + SubtypeId::Buffer => return Ok(Some("buffer".to_owned())), + #[cfg(feature = "luajit")] + SubtypeId::CData => return Ok(Some("cdata".to_owned())), } let lua = self.0.lua; diff --git a/src/value.rs b/src/value.rs index 5575d82d..9ec4e048 100644 --- a/src/value.rs +++ b/src/value.rs @@ -24,7 +24,7 @@ use crate::lua::Lua; use crate::string::String; use crate::table::Table; use crate::thread::Thread; -use crate::types::{Integer, LightUserData, Number}; +use crate::types::{Integer, LightUserData, Number, SubtypeId}; use crate::userdata::AnyUserData; use crate::util::{check_stack, StackGuard}; @@ -88,9 +88,11 @@ impl<'lua> Value<'lua> { Value::Table(_) => "table", Value::Function(_) => "function", Value::Thread(_) => "thread", + Value::UserData(AnyUserData(_, SubtypeId::None)) => "userdata", #[cfg(feature = "luau")] - Value::UserData(AnyUserData(_, crate::types::BUFFER_SUBTYPE_ID)) => "buffer", - Value::UserData(_) => "userdata", + Value::UserData(AnyUserData(_, SubtypeId::Buffer)) => "buffer", + #[cfg(feature = "luajit")] + Value::UserData(AnyUserData(_, SubtypeId::CData)) => "cdata", Value::Error(_) => "error", } } @@ -419,7 +421,18 @@ impl<'lua> Value<'lua> { #[inline] pub fn is_buffer(&self) -> bool { self.as_userdata() - .map(|ud| ud.1 == crate::types::BUFFER_SUBTYPE_ID) + .map(|ud| ud.1 == SubtypeId::Buffer) + .unwrap_or_default() + } + + /// Returns `true` if the value is a CData wrapped in [`AnyUserData`]. + #[cfg(any(feature = "luajit", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luajit")))] + #[doc(hidden)] + #[inline] + pub fn is_cdata(&self) -> bool { + self.as_userdata() + .map(|ud| ud.1 == SubtypeId::CData) .unwrap_or_default() } diff --git a/tests/tests.rs b/tests/tests.rs index cd7ca105..0b7a0a61 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1325,10 +1325,10 @@ fn test_warnings() -> Result<()> { #[test] #[cfg(feature = "luajit")] -#[should_panic] -fn test_luajit_cdata() { +fn test_luajit_cdata() -> Result<()> { let lua = unsafe { Lua::unsafe_new() }; - let _v: Result = lua + + let cdata = lua .load( r#" local ffi = require("ffi") @@ -1341,7 +1341,12 @@ fn test_luajit_cdata() { return ptr "#, ) - .eval(); + .eval::()?; + assert!(cdata.is_userdata() && cdata.is_cdata()); + assert_eq!(cdata.type_name(), "cdata"); + assert!(cdata.to_string()?.starts_with("cdata:")); + + Ok(()) } #[test] From 2d775695efe2cd87a5940d5e1b02b02938efb188 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 16 Nov 2023 14:11:24 +0000 Subject: [PATCH 005/635] Rewrite Luau `require` function to support module loaders. Also add `package` library with `path`/`loaded`/`loaders`. --- src/lua.rs | 2 +- src/luau.rs | 192 +++++++++++++++++++++++++++++++++++------------- src/util/mod.rs | 2 +- tests/luau.rs | 6 +- 4 files changed, 147 insertions(+), 55 deletions(-) diff --git a/src/lua.rs b/src/lua.rs index e4f88a10..1f4f9a69 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -438,7 +438,7 @@ impl Lua { } #[cfg(feature = "luau")] - mlua_expect!(lua.prepare_luau_state(), "Error preparing Luau state"); + mlua_expect!(lua.prepare_luau_state(), "Error configuring Luau"); lua } diff --git a/src/luau.rs b/src/luau.rs index e17296e9..61b846b1 100644 --- a/src/luau.rs +++ b/src/luau.rs @@ -1,16 +1,22 @@ use std::ffi::CStr; +use std::fmt::Write; use std::os::raw::{c_float, c_int}; +use std::path::{PathBuf, MAIN_SEPARATOR_STR}; use std::string::String as StdString; +use std::{env, fs}; use crate::chunk::ChunkMode; -use crate::error::{Error, Result}; +use crate::error::Result; use crate::lua::Lua; use crate::table::Table; -use crate::util::{check_stack, StackGuard}; -use crate::value::Value; +use crate::types::RegistryKey; +use crate::value::{IntoLua, Value}; // Since Luau has some missing standard function, we re-implement them here +// We keep reference to the `package` table in registry under this key +struct PackageKey(RegistryKey); + impl Lua { pub(crate) unsafe fn prepare_luau_state(&self) -> Result<()> { let globals = self.globals(); @@ -19,7 +25,8 @@ impl Lua { "collectgarbage", self.create_c_function(lua_collectgarbage)?, )?; - globals.raw_set("require", self.create_function(lua_require)?)?; + globals.raw_set("require", self.create_c_function(lua_require)?)?; + globals.raw_set("package", create_package_table(self)?)?; globals.raw_set("vector", self.create_c_function(lua_vector)?)?; // Set `_VERSION` global to include version number @@ -69,56 +76,57 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_ } } -fn lua_require(lua: &Lua, name: Option) -> Result { - let name = name.ok_or_else(|| Error::runtime("invalid module name"))?; - - // Find module in the cache - let state = lua.state(); - let loaded = unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 2)?; - protect_lua!(state, 0, 1, fn(state) { - ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); - })?; - Table(lua.pop_ref()) - }; - if let Some(v) = loaded.raw_get(name.clone())? { - return Ok(v); - } - - // Load file from filesystem - let mut search_path = std::env::var("LUAU_PATH").unwrap_or_default(); - if search_path.is_empty() { - search_path = "?.luau;?.lua".into(); +unsafe extern "C-unwind" fn lua_require(state: *mut ffi::lua_State) -> c_int { + ffi::lua_settop(state, 1); + let name = ffi::luaL_checkstring(state, 1); + ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); // _LOADED is at index 2 + if ffi::lua_rawgetfield(state, 2, name) != ffi::LUA_TNIL { + return 1; // module is already loaded } - - let (mut source, mut source_name) = (None, String::new()); - for path in search_path.split(';') { - let file_path = path.replacen('?', &name, 1); - if let Ok(buf) = std::fs::read(&file_path) { - source = Some(buf); - source_name = file_path; - break; + ffi::lua_pop(state, 1); // remove nil + + // load the module + let err_buf = ffi::lua_newuserdata_t::(state); + err_buf.write(StdString::new()); + ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADERS")); // _LOADERS is at index 3 + for i in 1.. { + if ffi::lua_rawgeti(state, -1, i) == ffi::LUA_TNIL { + // no more loaders? + if (*err_buf).is_empty() { + ffi::luaL_error(state, cstr!("module '%s' not found"), name); + } else { + let bytes = (*err_buf).as_bytes(); + let extra = ffi::lua_pushlstring(state, bytes.as_ptr() as *const _, bytes.len()); + ffi::luaL_error(state, cstr!("module '%s' not found:%s"), name, extra); + } } + ffi::lua_pushvalue(state, 1); // name arg + ffi::lua_call(state, 1, 2); // call loader + match ffi::lua_type(state, -2) { + ffi::LUA_TFUNCTION => break, // loader found + ffi::LUA_TSTRING => { + // error message + let msg = ffi::lua_tostring(state, -2); + let msg = CStr::from_ptr(msg).to_string_lossy(); + _ = write!(&mut *err_buf, "\n\t{msg}"); + } + _ => {} + } + ffi::lua_pop(state, 2); // remove both results + } + ffi::lua_pushvalue(state, 1); // name is 1st argument to module loader + ffi::lua_rotate(state, -2, 1); // loader data <-> name + + // stack: ...; loader function; module name; loader data + ffi::lua_call(state, 2, 1); + // stack: ...; result from loader function + if ffi::lua_isnil(state, -1) != 0 { + ffi::lua_pop(state, 1); + ffi::lua_pushboolean(state, 1); // use true as result } - let source = source.ok_or_else(|| Error::runtime(format!("cannot find '{name}'")))?; - - let value = lua - .load(&source) - .set_name(&format!("={source_name}")) - .set_mode(ChunkMode::Text) - .call::<_, Value>(())?; - - // Save in the cache - loaded.raw_set( - name, - match value.clone() { - Value::Nil => Value::Boolean(true), - v => v, - }, - )?; - - Ok(value) + ffi::lua_pushvalue(state, -1); // make copy of entrypoint result + ffi::lua_setfield(state, 2, name); /* _LOADED[name] = returned value */ + 1 } // Luau vector datatype constructor @@ -135,3 +143,85 @@ unsafe extern "C-unwind" fn lua_vector(state: *mut ffi::lua_State) -> c_int { ffi::lua_pushvector(state, x, y, z, w); 1 } + +// +// package module +// + +fn create_package_table(lua: &Lua) -> Result { + // Create the package table and store it in app_data for later use (bypassing globals lookup) + let package = lua.create_table()?; + lua.set_app_data(PackageKey(lua.create_registry_value(package.clone())?)); + + // Set `package.path` + let mut search_path = env::var("LUAU_PATH") + .or_else(|_| env::var("LUA_PATH")) + .unwrap_or_default(); + if search_path.is_empty() { + search_path = "?.luau;?.lua".to_string(); + } + package.raw_set("path", search_path)?; + + // Set `package.loaded` (table with a list of loaded modules) + let loaded = lua.create_table()?; + package.raw_set("loaded", loaded.clone())?; + lua.set_named_registry_value("_LOADED", loaded)?; + + // Set `package.loaders` + let loaders = lua.create_sequence_from([lua.create_function(lua_loader)?])?; + package.raw_set("loaders", loaders.clone())?; + lua.set_named_registry_value("_LOADERS", loaders)?; + + Ok(package) +} + +/// Searches for the given `name` in the given `path`. +/// +/// `path` is a string containing a sequence of templates separated by semicolons. +fn package_searchpath(name: &str, search_path: &str, try_prefix: bool) -> Option { + let mut names = vec![name.replace('.', MAIN_SEPARATOR_STR)]; + if try_prefix && name.contains('.') { + let prefix = name.split_once('.').map(|(prefix, _)| prefix).unwrap(); + names.push(prefix.to_string()); + } + for path in search_path.split(';') { + for name in &names { + let file_path = PathBuf::from(path.replace('?', name)); + if let Ok(true) = fs::metadata(&file_path).map(|m| m.is_file()) { + return Some(file_path); + } + } + } + None +} + +// +// Module loaders +// + +/// Tries to load a lua (text) file +fn lua_loader(lua: &Lua, modname: StdString) -> Result { + let package = { + let key = lua.app_data_ref::().unwrap(); + lua.registry_value::
(&key.0) + }?; + let search_path = package.get::<_, StdString>("path").unwrap_or_default(); + + if let Some(file_path) = package_searchpath(&modname, &search_path, false) { + match fs::read(&file_path) { + Ok(buf) => { + return lua + .load(&buf) + .set_name(&format!("={}", file_path.display())) + .set_mode(ChunkMode::Text) + .into_function() + .map(Value::Function); + } + Err(err) => { + return format!("cannot open '{}': {err}", file_path.display()).into_lua(lua); + } + } + } + + Ok(Value::Nil) +} diff --git a/src/util/mod.rs b/src/util/mod.rs index 6043c24b..46960bd9 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -997,7 +997,7 @@ impl WrappedFailure { #[cfg(feature = "luau")] let ud = ffi::lua_newuserdata_t::(state); #[cfg(not(feature = "luau"))] - let ud = ffi::lua_newuserdata(state, std::mem::size_of::()) as *mut Self; + let ud = ffi::lua_newuserdata(state, std::mem::size_of::()) as *mut Self; ptr::write(ud, WrappedFailure::None); ud } diff --git a/tests/luau.rs b/tests/luau.rs index f42f12e9..71c5a591 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -1,6 +1,5 @@ #![cfg(feature = "luau")] -use std::env; use std::fmt::Debug; use std::fs; use std::panic::{catch_unwind, AssertUnwindSafe}; @@ -37,7 +36,10 @@ fn test_require() -> Result<()> { "#, )?; - env::set_var("LUAU_PATH", temp_dir.path().join("?.luau")); + lua.globals() + .get::<_, Table>("package")? + .set("path", temp_dir.path().join("?.luau").to_string_lossy())?; + lua.load( r#" local module = require("module") From 2bee5ed33a5d8a2559295b2cf40290f25654368c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 16 Nov 2023 15:54:25 +0000 Subject: [PATCH 006/635] Support binary modules for Luau on cfg(unix) --- Cargo.toml | 5 +- mlua-sys/build/main_inner.rs | 4 +- src/lua.rs | 56 +++++++--------- src/luau.rs | 104 ++++++++++++++++++++++++++++++ src/memory.rs | 1 + tests/module/Cargo.toml | 1 + tests/module/loader/Cargo.toml | 1 + tests/module/loader/tests/load.rs | 2 +- 8 files changed, 139 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2c9664af..d15f58b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ lua52 = ["ffi/lua52"] lua51 = ["ffi/lua51"] luajit = ["ffi/luajit"] luajit52 = ["luajit", "ffi/luajit52"] -luau = ["ffi/luau"] +luau = ["ffi/luau", "libloading"] luau-jit = ["luau", "ffi/luau-codegen"] luau-vector4 = ["luau", "ffi/luau-vector4"] vendored = ["ffi/vendored"] @@ -57,6 +57,9 @@ parking_lot = { version = "0.12", optional = true } ffi = { package = "mlua-sys", version = "0.3.2", path = "mlua-sys" } +[target.'cfg(unix)'.dependencies] +libloading = { version = "0.8", optional = true } + [dev-dependencies] rustyline = "12.0" criterion = { version = "0.5", features = ["async_tokio"] } diff --git a/mlua-sys/build/main_inner.rs b/mlua-sys/build/main_inner.rs index 9b8b15d5..668f40b3 100644 --- a/mlua-sys/build/main_inner.rs +++ b/mlua-sys/build/main_inner.rs @@ -9,8 +9,8 @@ cfg_if::cfg_if! { } fn main() { - #[cfg(all(feature = "luau", feature = "module"))] - compile_error!("Luau does not support `module` mode"); + #[cfg(all(feature = "luau", feature = "module", windows))] + compile_error!("Luau does not support `module` mode on Windows"); #[cfg(all(feature = "module", feature = "vendored"))] compile_error!("`vendored` and `module` features are mutually exclusive"); diff --git a/src/lua.rs b/src/lua.rs index 1f4f9a69..07051ebb 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -222,7 +222,6 @@ impl LuaOptions { #[cfg(feature = "async")] pub(crate) static ASYNC_POLL_PENDING: u8 = 0; -#[cfg(not(feature = "luau"))] pub(crate) static EXTRA_REGISTRY_KEY: u8 = 0; const WRAPPED_FAILURE_POOL_SIZE: usize = 64; @@ -359,23 +358,22 @@ impl Lua { /// /// [`StdLib`]: crate::StdLib pub unsafe fn unsafe_new_with(libs: StdLib, options: LuaOptions) -> Lua { + // Workaround to avoid stripping a few unused Lua symbols that could be imported + // by C modules in unsafe mode + let mut _symbols: Vec<*const extern "C-unwind" fn()> = + vec![ffi::lua_isuserdata as _, ffi::lua_tocfunction as _]; + #[cfg(not(feature = "luau"))] + _symbols.extend_from_slice(&[ + ffi::lua_atpanic as _, + ffi::luaL_loadstring as _, + ffi::luaL_openlibs as _, + ]); + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] { - // Workaround to avoid stripping a few unused Lua symbols that could be imported - // by C modules in unsafe mode - let mut _symbols: Vec<*const extern "C-unwind" fn()> = vec![ - ffi::lua_atpanic as _, - ffi::lua_isuserdata as _, - ffi::lua_tocfunction as _, - ffi::luaL_loadstring as _, - ffi::luaL_openlibs as _, - ]; - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - { - _symbols.push(ffi::lua_getglobal as _); - _symbols.push(ffi::lua_setglobal as _); - _symbols.push(ffi::luaL_setfuncs as _); - } + _symbols.push(ffi::lua_getglobal as _); + _symbols.push(ffi::lua_setglobal as _); + _symbols.push(ffi::luaL_setfuncs as _); } Self::inner_new(libs, options) @@ -3232,22 +3230,13 @@ impl<'a> Drop for StateGuard<'a> { } } -#[cfg(feature = "luau")] unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData { - (*ffi::lua_callbacks(state)).userdata as *mut ExtraData -} - -#[cfg(feature = "luau")] -unsafe fn set_extra_data( - state: *mut ffi::lua_State, - extra: &Arc>, -) -> Result<()> { - (*ffi::lua_callbacks(state)).userdata = extra.get() as *mut _; - Ok(()) -} + #[cfg(feature = "luau")] + if cfg!(not(feature = "module")) { + // In the main app we can use `lua_callbacks` to access ExtraData + return (*ffi::lua_callbacks(state)).userdata as *mut _; + } -#[cfg(not(feature = "luau"))] -unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData { let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void; if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, extra_key) != ffi::LUA_TUSERDATA { // `ExtraData` can be null only when Lua state is foreign. @@ -3260,11 +3249,16 @@ unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData { (*extra_ptr).get() } -#[cfg(not(feature = "luau"))] unsafe fn set_extra_data( state: *mut ffi::lua_State, extra: &Arc>, ) -> Result<()> { + #[cfg(feature = "luau")] + if cfg!(not(feature = "module")) { + (*ffi::lua_callbacks(state)).userdata = extra.get() as *mut _; + return Ok(()); + } + push_gc_userdata(state, Arc::clone(extra), true)?; protect_lua!(state, 1, 0, fn(state) { let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void; diff --git a/src/luau.rs b/src/luau.rs index 61b846b1..c1f279d9 100644 --- a/src/luau.rs +++ b/src/luau.rs @@ -12,11 +12,42 @@ use crate::table::Table; use crate::types::RegistryKey; use crate::value::{IntoLua, Value}; +#[cfg(unix)] +use {libloading::Library, rustc_hash::FxHashMap}; + // Since Luau has some missing standard function, we re-implement them here +#[cfg(unix)] +const TARGET_MLUA_LUAU_ABI_VERSION: u32 = 1; + +#[cfg(all(unix, feature = "module"))] +#[no_mangle] +#[used] +pub static MLUA_LUAU_ABI_VERSION: u32 = TARGET_MLUA_LUAU_ABI_VERSION; + // We keep reference to the `package` table in registry under this key struct PackageKey(RegistryKey); +// We keep reference to the loaded dylibs in application data +#[cfg(unix)] +struct LoadedDylibs(FxHashMap); + +#[cfg(unix)] +impl std::ops::Deref for LoadedDylibs { + type Target = FxHashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[cfg(unix)] +impl std::ops::DerefMut for LoadedDylibs { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + impl Lua { pub(crate) unsafe fn prepare_luau_state(&self) -> Result<()> { let globals = self.globals(); @@ -162,6 +193,22 @@ fn create_package_table(lua: &Lua) -> Result
{ } package.raw_set("path", search_path)?; + // Set `package.cpath` + #[cfg(unix)] + { + let mut search_cpath = env::var("LUAU_CPATH") + .or_else(|_| env::var("LUA_CPATH")) + .unwrap_or_default(); + if search_cpath.is_empty() { + if cfg!(any(target_os = "macos", target_os = "ios")) { + search_cpath = "?.dylib".to_string(); + } else { + search_cpath = "?.so".to_string(); + } + } + package.raw_set("cpath", search_cpath)?; + } + // Set `package.loaded` (table with a list of loaded modules) let loaded = lua.create_table()?; package.raw_set("loaded", loaded.clone())?; @@ -170,6 +217,12 @@ fn create_package_table(lua: &Lua) -> Result
{ // Set `package.loaders` let loaders = lua.create_sequence_from([lua.create_function(lua_loader)?])?; package.raw_set("loaders", loaders.clone())?; + #[cfg(unix)] + { + loaders.push(lua.create_function(dylib_loader)?)?; + let loaded_dylibs = LoadedDylibs(FxHashMap::default()); + lua.set_app_data(loaded_dylibs); + } lua.set_named_registry_value("_LOADERS", loaders)?; Ok(package) @@ -225,3 +278,54 @@ fn lua_loader(lua: &Lua, modname: StdString) -> Result { Ok(Value::Nil) } + +/// Tries to load a dynamic library +#[cfg(unix)] +fn dylib_loader(lua: &Lua, modname: StdString) -> Result { + let package = { + let key = lua.app_data_ref::().unwrap(); + lua.registry_value::
(&key.0) + }?; + let search_cpath = package.get::<_, StdString>("cpath").unwrap_or_default(); + + let find_symbol = |lib: &Library| unsafe { + if let Ok(entry) = lib.get::(format!("luaopen_{modname}\0").as_bytes()) + { + return lua.create_c_function(*entry).map(Value::Function); + } + // Try all in one mode + if let Ok(entry) = lib.get::( + format!("luaopen_{}\0", modname.replace('.', "_")).as_bytes(), + ) { + return lua.create_c_function(*entry).map(Value::Function); + } + "cannot find module entrypoint".into_lua(lua) + }; + + if let Some(file_path) = package_searchpath(&modname, &search_cpath, true) { + let file_path = file_path.canonicalize()?; + // Load the library and check for symbol + unsafe { + // Check if it's already loaded + if let Some(lib) = lua.app_data_ref::().unwrap().get(&file_path) { + return find_symbol(lib); + } + if let Ok(lib) = Library::new(&file_path) { + // Check version + let mod_version = lib.get::<*const u32>(b"MLUA_LUAU_ABI_VERSION"); + let mod_version = mod_version.map(|v| **v).unwrap_or_default(); + if mod_version != TARGET_MLUA_LUAU_ABI_VERSION { + let err = format!("wrong module ABI version (expected {TARGET_MLUA_LUAU_ABI_VERSION}, got {mod_version})"); + return err.into_lua(lua); + } + let symbol = find_symbol(&lib); + lua.app_data_mut::() + .unwrap() + .insert(file_path, lib); + return symbol; + } + } + } + + Ok(Value::Nil) +} diff --git a/src/memory.rs b/src/memory.rs index ed685147..672a8647 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -4,6 +4,7 @@ use std::ptr; pub(crate) static ALLOCATOR: ffi::lua_Alloc = allocator; +#[repr(C)] #[derive(Default)] pub(crate) struct MemoryState { used_memory: isize, diff --git a/tests/module/Cargo.toml b/tests/module/Cargo.toml index c2e0da8d..f107ad73 100644 --- a/tests/module/Cargo.toml +++ b/tests/module/Cargo.toml @@ -18,6 +18,7 @@ lua53 = ["mlua/lua53"] lua52 = ["mlua/lua52"] lua51 = ["mlua/lua51"] luajit = ["mlua/luajit"] +luau = ["mlua/luau"] [dependencies] mlua = { path = "../..", features = ["module"] } diff --git a/tests/module/loader/Cargo.toml b/tests/module/loader/Cargo.toml index b51f002c..64b196ff 100644 --- a/tests/module/loader/Cargo.toml +++ b/tests/module/loader/Cargo.toml @@ -10,6 +10,7 @@ lua53 = ["mlua/lua53"] lua52 = ["mlua/lua52"] lua51 = ["mlua/lua51"] luajit = ["mlua/luajit"] +luau = ["mlua/luau"] vendored = ["mlua/vendored"] [dependencies] diff --git a/tests/module/loader/tests/load.rs b/tests/module/loader/tests/load.rs index d06ece4f..25f85ab0 100644 --- a/tests/module/loader/tests/load.rs +++ b/tests/module/loader/tests/load.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; use mlua::{Lua, Result}; #[test] -fn test_module() -> Result<()> { +fn test_module_simple() -> Result<()> { let lua = make_lua()?; lua.load( r#" From 44f5688c32a7ed5122c599b64a45fc16af327968 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 16 Nov 2023 17:54:40 +0000 Subject: [PATCH 007/635] Include luau to ci module tests --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3026d6c8..bb20567b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -166,7 +166,7 @@ jobs: matrix: os: [ubuntu-22.04, macos-latest] rust: [stable] - lua: [lua54, lua53, lua52, lua51, luajit] + lua: [lua54, lua53, lua52, lua51, luajit, luau] include: - os: ubuntu-22.04 target: x86_64-unknown-linux-gnu From 93b505cff9c49ea3e476c383c04b36b7a2677845 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 20 Nov 2023 09:56:36 +0000 Subject: [PATCH 008/635] Integrate Luau package into mlua api. Eg. `Lua::load_from_std_lib` with `StdLib::PACKAGE` is now supported for Luau. --- src/lua.rs | 19 ++-- src/luau/mod.rs | 88 +++++++++++++++ src/{luau.rs => luau/package.rs} | 185 ++++++++++--------------------- src/stdlib.rs | 2 - tests/luau.rs | 32 +++++- 5 files changed, 188 insertions(+), 138 deletions(-) create mode 100644 src/luau/mod.rs rename src/{luau.rs => luau/package.rs} (72%) diff --git a/src/lua.rs b/src/lua.rs index 07051ebb..e56f77ec 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -340,7 +340,6 @@ impl Lua { let lua = unsafe { Self::inner_new(libs, options) }; - #[cfg(not(feature = "luau"))] if libs.contains(StdLib::PACKAGE) { mlua_expect!(lua.disable_c_modules(), "Error during disabling C modules"); } @@ -436,7 +435,7 @@ impl Lua { } #[cfg(feature = "luau")] - mlua_expect!(lua.prepare_luau_state(), "Error configuring Luau"); + mlua_expect!(lua.configure_luau(), "Error configuring Luau"); lua } @@ -580,7 +579,6 @@ impl Lua { /// /// [`StdLib`]: crate::StdLib pub fn load_from_std_lib(&self, libs: StdLib) -> Result<()> { - #[cfg(not(feature = "luau"))] let is_safe = unsafe { (*self.extra.get()).safe }; #[cfg(not(feature = "luau"))] @@ -599,12 +597,9 @@ impl Lua { let res = unsafe { load_from_std_lib(self.main_state, libs) }; // If `package` library loaded into a safe lua state then disable C modules - #[cfg(not(feature = "luau"))] - { - let curr_libs = unsafe { (*self.extra.get()).libs }; - if is_safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) { - mlua_expect!(self.disable_c_modules(), "Error during disabling C modules"); - } + let curr_libs = unsafe { (*self.extra.get()).libs }; + if is_safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) { + mlua_expect!(self.disable_c_modules(), "Error during disabling C modules"); } unsafe { (*self.extra.get()).libs |= libs }; @@ -3126,6 +3121,7 @@ impl Lua { Ok(AnyUserData(self.pop_ref(), SubtypeId::None)) } + // Luau version located in `luau/mod.rs` #[cfg(not(feature = "luau"))] fn disable_c_modules(&self) -> Result<()> { let package: Table = self.globals().get("package")?; @@ -3525,6 +3521,11 @@ unsafe fn load_from_std_lib(state: *mut ffi::lua_State, libs: StdLib) -> Result< requiref(state, ffi::LUA_LOADLIBNAME, ffi::luaopen_package, 1)?; ffi::lua_pop(state, 1); } + #[cfg(feature = "luau")] + if libs.contains(StdLib::PACKAGE) { + let lua: &Lua = mem::transmute((*extra_data(state)).inner.assume_init_ref()); + crate::luau::register_package_module(lua)?; + } #[cfg(feature = "luajit")] { diff --git a/src/luau/mod.rs b/src/luau/mod.rs new file mode 100644 index 00000000..1d185155 --- /dev/null +++ b/src/luau/mod.rs @@ -0,0 +1,88 @@ +use std::ffi::CStr; +use std::os::raw::{c_float, c_int}; + +use crate::error::Result; +use crate::lua::Lua; + +// Since Luau has some missing standard functions, we re-implement them here + +impl Lua { + pub(crate) unsafe fn configure_luau(&self) -> Result<()> { + let globals = self.globals(); + + globals.raw_set( + "collectgarbage", + self.create_c_function(lua_collectgarbage)?, + )?; + globals.raw_set("vector", self.create_c_function(lua_vector)?)?; + + // Set `_VERSION` global to include version number + // The environment variable `LUAU_VERSION` set by the build script + if let Some(version) = ffi::luau_version() { + globals.raw_set("_VERSION", format!("Luau {version}"))?; + } + + Ok(()) + } + + pub(crate) fn disable_c_modules(&self) -> Result<()> { + package::disable_dylibs(self); + Ok(()) + } +} + +unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_int { + let option = ffi::luaL_optstring(state, 1, cstr!("collect")); + let option = CStr::from_ptr(option); + let arg = ffi::luaL_optinteger(state, 2, 0); + match option.to_str() { + Ok("collect") => { + ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0); + 0 + } + Ok("stop") => { + ffi::lua_gc(state, ffi::LUA_GCSTOP, 0); + 0 + } + Ok("restart") => { + ffi::lua_gc(state, ffi::LUA_GCRESTART, 0); + 0 + } + Ok("count") => { + let kbytes = ffi::lua_gc(state, ffi::LUA_GCCOUNT, 0) as ffi::lua_Number; + let kbytes_rem = ffi::lua_gc(state, ffi::LUA_GCCOUNTB, 0) as ffi::lua_Number; + ffi::lua_pushnumber(state, kbytes + kbytes_rem / 1024.0); + 1 + } + Ok("step") => { + let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg); + ffi::lua_pushboolean(state, res); + 1 + } + Ok("isrunning") => { + let res = ffi::lua_gc(state, ffi::LUA_GCISRUNNING, 0); + ffi::lua_pushboolean(state, res); + 1 + } + _ => ffi::luaL_error(state, cstr!("collectgarbage called with invalid option")), + } +} + +// Luau vector datatype constructor +unsafe extern "C-unwind" fn lua_vector(state: *mut ffi::lua_State) -> c_int { + let x = ffi::luaL_checknumber(state, 1) as c_float; + let y = ffi::luaL_checknumber(state, 2) as c_float; + let z = ffi::luaL_checknumber(state, 3) as c_float; + #[cfg(feature = "luau-vector4")] + let w = ffi::luaL_checknumber(state, 4) as c_float; + + #[cfg(not(feature = "luau-vector4"))] + ffi::lua_pushvector(state, x, y, z); + #[cfg(feature = "luau-vector4")] + ffi::lua_pushvector(state, x, y, z, w); + 1 +} + +pub(crate) use package::register_package_module; + +mod package; diff --git a/src/luau.rs b/src/luau/package.rs similarity index 72% rename from src/luau.rs rename to src/luau/package.rs index c1f279d9..ed69b5dc 100644 --- a/src/luau.rs +++ b/src/luau/package.rs @@ -1,6 +1,6 @@ use std::ffi::CStr; use std::fmt::Write; -use std::os::raw::{c_float, c_int}; +use std::os::raw::c_int; use std::path::{PathBuf, MAIN_SEPARATOR_STR}; use std::string::String as StdString; use std::{env, fs}; @@ -15,7 +15,9 @@ use crate::value::{IntoLua, Value}; #[cfg(unix)] use {libloading::Library, rustc_hash::FxHashMap}; -// Since Luau has some missing standard function, we re-implement them here +// +// Luau package module +// #[cfg(unix)] const TARGET_MLUA_LUAU_ABI_VERSION: u32 = 1; @@ -48,63 +50,64 @@ impl std::ops::DerefMut for LoadedDylibs { } } -impl Lua { - pub(crate) unsafe fn prepare_luau_state(&self) -> Result<()> { - let globals = self.globals(); +pub(crate) fn register_package_module(lua: &Lua) -> Result<()> { + // Create the package table and store it in app_data for later use (bypassing globals lookup) + let package = lua.create_table()?; + lua.set_app_data(PackageKey(lua.create_registry_value(package.clone())?)); - globals.raw_set( - "collectgarbage", - self.create_c_function(lua_collectgarbage)?, - )?; - globals.raw_set("require", self.create_c_function(lua_require)?)?; - globals.raw_set("package", create_package_table(self)?)?; - globals.raw_set("vector", self.create_c_function(lua_vector)?)?; + // Set `package.path` + let mut search_path = env::var("LUAU_PATH") + .or_else(|_| env::var("LUA_PATH")) + .unwrap_or_default(); + if search_path.is_empty() { + search_path = "?.luau;?.lua".to_string(); + } + package.raw_set("path", search_path)?; - // Set `_VERSION` global to include version number - // The environment variable `LUAU_VERSION` set by the build script - if let Some(version) = ffi::luau_version() { - globals.raw_set("_VERSION", format!("Luau {version}"))?; + // Set `package.cpath` + #[cfg(unix)] + { + let mut search_cpath = env::var("LUAU_CPATH") + .or_else(|_| env::var("LUA_CPATH")) + .unwrap_or_default(); + if search_cpath.is_empty() { + if cfg!(any(target_os = "macos", target_os = "ios")) { + search_cpath = "?.dylib".to_string(); + } else { + search_cpath = "?.so".to_string(); + } } + package.raw_set("cpath", search_cpath)?; + } - Ok(()) + // Set `package.loaded` (table with a list of loaded modules) + let loaded = lua.create_table()?; + package.raw_set("loaded", loaded.clone())?; + lua.set_named_registry_value("_LOADED", loaded)?; + + // Set `package.loaders` + let loaders = lua.create_sequence_from([lua.create_function(lua_loader)?])?; + package.raw_set("loaders", loaders.clone())?; + #[cfg(unix)] + { + loaders.push(lua.create_function(dylib_loader)?)?; + lua.set_app_data(LoadedDylibs(FxHashMap::default())); } + lua.set_named_registry_value("_LOADERS", loaders)?; + + // Register the module and `require` function in globals + let globals = lua.globals(); + globals.raw_set("package", package)?; + globals.raw_set("require", unsafe { lua.create_c_function(lua_require)? })?; + + Ok(()) } -unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_int { - let option = ffi::luaL_optstring(state, 1, cstr!("collect")); - let option = CStr::from_ptr(option); - let arg = ffi::luaL_optinteger(state, 2, 0); - match option.to_str() { - Ok("collect") => { - ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0); - 0 - } - Ok("stop") => { - ffi::lua_gc(state, ffi::LUA_GCSTOP, 0); - 0 - } - Ok("restart") => { - ffi::lua_gc(state, ffi::LUA_GCRESTART, 0); - 0 - } - Ok("count") => { - let kbytes = ffi::lua_gc(state, ffi::LUA_GCCOUNT, 0) as ffi::lua_Number; - let kbytes_rem = ffi::lua_gc(state, ffi::LUA_GCCOUNTB, 0) as ffi::lua_Number; - ffi::lua_pushnumber(state, kbytes + kbytes_rem / 1024.0); - 1 - } - Ok("step") => { - let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg); - ffi::lua_pushboolean(state, res); - 1 - } - Ok("isrunning") => { - let res = ffi::lua_gc(state, ffi::LUA_GCISRUNNING, 0); - ffi::lua_pushboolean(state, res); - 1 - } - _ => ffi::luaL_error(state, cstr!("collectgarbage called with invalid option")), - } +pub(crate) fn disable_dylibs(lua: &Lua) { + // Presence of `LoadedDylibs` in app data is used as a flag + // to check whether binary modules are enabled + #[cfg(unix)] + lua.remove_app_data::(); } unsafe extern "C-unwind" fn lua_require(state: *mut ffi::lua_State) -> c_int { @@ -160,74 +163,6 @@ unsafe extern "C-unwind" fn lua_require(state: *mut ffi::lua_State) -> c_int { 1 } -// Luau vector datatype constructor -unsafe extern "C-unwind" fn lua_vector(state: *mut ffi::lua_State) -> c_int { - let x = ffi::luaL_checknumber(state, 1) as c_float; - let y = ffi::luaL_checknumber(state, 2) as c_float; - let z = ffi::luaL_checknumber(state, 3) as c_float; - #[cfg(feature = "luau-vector4")] - let w = ffi::luaL_checknumber(state, 4) as c_float; - - #[cfg(not(feature = "luau-vector4"))] - ffi::lua_pushvector(state, x, y, z); - #[cfg(feature = "luau-vector4")] - ffi::lua_pushvector(state, x, y, z, w); - 1 -} - -// -// package module -// - -fn create_package_table(lua: &Lua) -> Result
{ - // Create the package table and store it in app_data for later use (bypassing globals lookup) - let package = lua.create_table()?; - lua.set_app_data(PackageKey(lua.create_registry_value(package.clone())?)); - - // Set `package.path` - let mut search_path = env::var("LUAU_PATH") - .or_else(|_| env::var("LUA_PATH")) - .unwrap_or_default(); - if search_path.is_empty() { - search_path = "?.luau;?.lua".to_string(); - } - package.raw_set("path", search_path)?; - - // Set `package.cpath` - #[cfg(unix)] - { - let mut search_cpath = env::var("LUAU_CPATH") - .or_else(|_| env::var("LUA_CPATH")) - .unwrap_or_default(); - if search_cpath.is_empty() { - if cfg!(any(target_os = "macos", target_os = "ios")) { - search_cpath = "?.dylib".to_string(); - } else { - search_cpath = "?.so".to_string(); - } - } - package.raw_set("cpath", search_cpath)?; - } - - // Set `package.loaded` (table with a list of loaded modules) - let loaded = lua.create_table()?; - package.raw_set("loaded", loaded.clone())?; - lua.set_named_registry_value("_LOADED", loaded)?; - - // Set `package.loaders` - let loaders = lua.create_sequence_from([lua.create_function(lua_loader)?])?; - package.raw_set("loaders", loaders.clone())?; - #[cfg(unix)] - { - loaders.push(lua.create_function(dylib_loader)?)?; - let loaded_dylibs = LoadedDylibs(FxHashMap::default()); - lua.set_app_data(loaded_dylibs); - } - lua.set_named_registry_value("_LOADERS", loaders)?; - - Ok(package) -} - /// Searches for the given `name` in the given `path`. /// /// `path` is a string containing a sequence of templates separated by semicolons. @@ -306,8 +241,12 @@ fn dylib_loader(lua: &Lua, modname: StdString) -> Result { let file_path = file_path.canonicalize()?; // Load the library and check for symbol unsafe { + let mut loaded_dylibs = match lua.app_data_mut::() { + Some(loaded_dylibs) => loaded_dylibs, + None => return "dynamic libraries are disabled in safe mode".into_lua(lua), + }; // Check if it's already loaded - if let Some(lib) = lua.app_data_ref::().unwrap().get(&file_path) { + if let Some(lib) = loaded_dylibs.get(&file_path) { return find_symbol(lib); } if let Ok(lib) = Library::new(&file_path) { @@ -319,9 +258,7 @@ fn dylib_loader(lua: &Lua, modname: StdString) -> Result { return err.into_lua(lua); } let symbol = find_symbol(&lib); - lua.app_data_mut::() - .unwrap() - .insert(file_path, lib); + loaded_dylibs.insert(file_path, lib); return symbol; } } diff --git a/src/stdlib.rs b/src/stdlib.rs index a320a0aa..88e50cf9 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -47,8 +47,6 @@ impl StdLib { pub const MATH: StdLib = StdLib(1 << 7); /// [`package`](https://www.lua.org/manual/5.4/manual.html#6.3) library - #[cfg(not(feature = "luau"))] - #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub const PACKAGE: StdLib = StdLib(1 << 8); /// [`buffer`](https://luau-lang.org/library#buffer-library) library diff --git a/tests/luau.rs b/tests/luau.rs index 71c5a591..7ef0b235 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -7,7 +7,8 @@ use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; use mlua::{ - Compiler, CoverageInfo, Error, Lua, Result, Table, ThreadStatus, Value, Vector, VmState, + Compiler, CoverageInfo, Error, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, + Vector, VmState, }; #[test] @@ -22,7 +23,12 @@ fn test_version() -> Result<()> { #[test] fn test_require() -> Result<()> { - let lua = Lua::new(); + // Ensure that require() is not available if package module is not loaded + let mut lua = Lua::new_with(StdLib::NONE, LuaOptions::default())?; + assert!(lua.globals().get::<_, Option>("require")?.is_none()); + assert!(lua.globals().get::<_, Option>("package")?.is_none()); + + lua = Lua::new(); let temp_dir = tempfile::tempdir().unwrap(); fs::write( @@ -51,7 +57,27 @@ fn test_require() -> Result<()> { assert(not ok and string.find(err, "module.luau") ~= nil) "#, ) - .exec() + .exec()?; + + // Require non-existent module + match lua.load("require('non-existent')").exec() { + Err(Error::RuntimeError(e)) if e.contains("module 'non-existent' not found") => {} + r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"), + } + + // Require binary module in safe mode + lua.globals() + .get::<_, Table>("package")? + .set("cpath", temp_dir.path().join("?.so").to_string_lossy())?; + fs::write(temp_dir.path().join("dylib.so"), "")?; + match lua.load("require('dylib')").exec() { + Err(Error::RuntimeError(e)) + if e.contains("module 'dylib' not found") + && e.contains("dynamic libraries are disabled in safe mode") => {} + r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"), + } + + Ok(()) } #[cfg(not(feature = "luau-vector4"))] From 66e01548ced8af0d56b6199429a7eddfba6bb1ee Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 20 Nov 2023 22:16:44 +0000 Subject: [PATCH 009/635] Update Luau+windows `require` dylib failed test --- tests/luau.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/luau.rs b/tests/luau.rs index 7ef0b235..d6b38caa 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -71,9 +71,10 @@ fn test_require() -> Result<()> { .set("cpath", temp_dir.path().join("?.so").to_string_lossy())?; fs::write(temp_dir.path().join("dylib.so"), "")?; match lua.load("require('dylib')").exec() { - Err(Error::RuntimeError(e)) - if e.contains("module 'dylib' not found") - && e.contains("dynamic libraries are disabled in safe mode") => {} + Err(Error::RuntimeError(e)) if cfg!(unix) && e.contains("module 'dylib' not found") => { + assert!(e.contains("dynamic libraries are disabled in safe mode")) + } + Err(Error::RuntimeError(e)) if e.contains("module 'dylib' not found") => {} r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"), } From 2022de21564b04a1f13f248d2c77982e1fa1e691 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 21 Nov 2023 22:35:59 +0000 Subject: [PATCH 010/635] v0.9.2 --- CHANGELOG.md | 11 +++++++++++ Cargo.toml | 4 ++-- mlua-sys/Cargo.toml | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea6a5469..34fb03b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## v0.9.2 + +- Added binary modules support to Luau +- Added Luau package module (uses `StdLib::PACKAGE`) with loaders (follows lua5.1 interface) +- Added support of Luau 0.601+ buffer type (represented as userdata in Rust) +- LuaJIT `cdata` type is also represented as userdata in Rust (instead of panic) +- Vendored LuaJIT switched to rolling vanilla (from openresty) +- Added `Table::for_each` method for fast table pairs traversal (faster than `pairs`) +- Performance improvements around table traversal (and faster serialization) +- Bug fixes and improvements + ## v0.9.1 - impl Default for Lua diff --git a/Cargo.toml b/Cargo.toml index d15f58b2..dd34daa7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.9.1" # remember to update mlua_derive +version = "0.9.2" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.71" edition = "2021" @@ -55,7 +55,7 @@ erased-serde = { version = "0.3", optional = true } serde-value = { version = "0.7", optional = true } parking_lot = { version = "0.12", optional = true } -ffi = { package = "mlua-sys", version = "0.3.2", path = "mlua-sys" } +ffi = { package = "mlua-sys", version = "0.4.0", path = "mlua-sys" } [target.'cfg(unix)'.dependencies] libloading = { version = "0.8", optional = true } diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index b47fead8..53a2620c 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.3.2" +version = "0.4.0" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" From c36808b25191c8367093b8802433e9aede410e45 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 1 Dec 2023 11:59:56 +0000 Subject: [PATCH 011/635] Faster `Function::call()` for lua51/jit/luau --- src/function.rs | 7 +++---- src/lua.rs | 31 +++++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/function.rs b/src/function.rs index bc9416f9..7182b4a3 100644 --- a/src/function.rs +++ b/src/function.rs @@ -6,12 +6,11 @@ use std::slice; use crate::error::{Error, Result}; use crate::lua::Lua; -use crate::memory::MemoryState; use crate::table::Table; use crate::types::{Callback, LuaRef, MaybeSend}; use crate::util::{ - assert_stack, check_stack, error_traceback, linenumber_to_usize, pop_error, ptr_to_lossy_str, - ptr_to_str, StackGuard, + assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str, + StackGuard, }; use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti, Value}; @@ -131,7 +130,7 @@ impl<'lua> Function<'lua> { check_stack(state, 2)?; // Push error handler - MemoryState::relax_limit_with(state, || ffi::lua_pushcfunction(state, error_traceback)); + lua.push_error_traceback(); let stack_start = ffi::lua_gettop(state); // Push function and the arguments lua.push_ref(&self.0); diff --git a/src/lua.rs b/src/lua.rs index e56f77ec..9127e8eb 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -31,10 +31,10 @@ use crate::types::{ use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataCell}; use crate::userdata_impl::{UserDataProxy, UserDataRegistry}; use crate::util::{ - self, assert_stack, check_stack, get_destructed_userdata_metatable, get_gc_metatable, - get_gc_userdata, get_main_state, get_userdata, init_error_registry, init_gc_metatable, - init_userdata_metatable, pop_error, push_gc_userdata, push_string, push_table, rawset_field, - safe_pcall, safe_xpcall, short_type_name, StackGuard, WrappedFailure, + self, assert_stack, check_stack, error_traceback, get_destructed_userdata_metatable, + get_gc_metatable, get_gc_userdata, get_main_state, get_userdata, init_error_registry, + init_gc_metatable, init_userdata_metatable, pop_error, push_gc_userdata, push_string, + push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name, StackGuard, WrappedFailure, }; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value}; @@ -499,6 +499,13 @@ impl Lua { ptr }; + // Store `error_traceback` function on the ref stack + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + { + ffi::lua_pushcfunction(ref_thread, error_traceback); + assert_eq!(ffi::lua_gettop(ref_thread), ExtraData::ERROR_TRACEBACK_IDX); + } + // Create ExtraData let extra = Arc::new(UnsafeCell::new(ExtraData { inner: MaybeUninit::uninit(), @@ -2601,6 +2608,16 @@ impl Lua { LuaRef::new(self, index) } + #[inline] + pub(crate) unsafe fn push_error_traceback(&self) { + let state = self.state(); + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + ffi::lua_xpush(self.ref_thread(), state, ExtraData::ERROR_TRACEBACK_IDX); + // Lua 5.2+ support light C functions that does not require extra allocations + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + ffi::lua_pushcfunction(state, error_traceback); + } + unsafe fn register_userdata_metatable<'lua, T: 'static>( &'lua self, mut registry: UserDataRegistry<'lua, T>, @@ -3211,6 +3228,12 @@ impl LuaInner { } } +impl ExtraData { + // Index of `error_traceback` function in auxiliary thread stack + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + const ERROR_TRACEBACK_IDX: c_int = 1; +} + struct StateGuard<'a>(&'a LuaInner, *mut ffi::lua_State); impl<'a> StateGuard<'a> { From 642201a7e09f01092ca760821bee849dc94f8094 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 1 Dec 2023 18:11:06 +0000 Subject: [PATCH 012/635] Remove locals from __mlua_async_poll helper --- src/lua.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lua.rs b/src/lua.rs index 9127e8eb..e677dacf 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -3030,7 +3030,6 @@ impl Lua { self.load( r#" local poll = get_poll(...) - local pending, yield, unpack = pending, yield, unpack while true do local nres, res, res2 = poll() if nres ~= nil then From e4d6e9228785a603aeb1edfddbd62feac4031b7e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 2 Dec 2023 15:01:40 +0000 Subject: [PATCH 013/635] (async) Move "pending" poll value from env to poll_future() results --- src/lua.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/lua.rs b/src/lua.rs index e677dacf..13c348c0 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -2961,11 +2961,15 @@ impl Lua { let fut = &mut (*upvalue).data; let mut ctx = Context::from_waker(lua.waker()); match fut.as_mut().poll(&mut ctx) { - Poll::Pending => Ok(0), + Poll::Pending => { + ffi::lua_pushnil(state); + let pending = &ASYNC_POLL_PENDING as *const u8 as *mut c_void; + ffi::lua_pushlightuserdata(state, pending); + Ok(2) + } Poll::Ready(nresults) => { - let nresults = nresults?; - match nresults { - 0..=2 => { + match nresults? { + nresults @ 0..=2 => { // Fast path for up to 2 results without creating a table ffi::lua_pushinteger(state, nresults as _); if nresults > 0 { @@ -2973,7 +2977,7 @@ impl Lua { } Ok(nresults + 1) } - _ => { + nresults => { let results = MultiValue::from_stack_multi(nresults, lua)?; ffi::lua_pushinteger(state, nresults as _); lua.push_value(Value::Table(lua.create_sequence_from(results)?))?; @@ -3017,15 +3021,13 @@ impl Lua { let coroutine = self.globals().get::<_, Table>("coroutine")?; - let env = self.create_table_with_capacity(0, 4)?; + let env = self.create_table_with_capacity(0, 3)?; env.set("get_poll", get_poll)?; + // Cache `yield` function env.set("yield", coroutine.get::<_, Function>("yield")?)?; unsafe { env.set("unpack", self.create_c_function(unpack)?)?; } - env.set("pending", { - LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut c_void) - })?; self.load( r#" @@ -3043,7 +3045,7 @@ impl Lua { return unpack(res, nres) end end - yield(pending) + yield(res) -- `res` is a "pending" value end "#, ) From a4c919231c5bbd6ad65b419b7f08d5976272c288 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 3 Dec 2023 19:55:27 +0000 Subject: [PATCH 014/635] Don't clone function name when calling async userdata method --- src/userdata_impl.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/userdata_impl.rs b/src/userdata_impl.rs index 8692a4cd..97807586 100644 --- a/src/userdata_impl.rs +++ b/src/userdata_impl.rs @@ -218,7 +218,7 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> { MR: Future> + 's, R: IntoLuaMulti<'lua>, { - let name = get_function_name::(name); + let name = Arc::new(get_function_name::(name)); let method = Arc::new(method); Box::new(move |lua, mut args| unsafe { @@ -312,7 +312,7 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> { MR: Future> + 's, R: IntoLuaMulti<'lua>, { - let name = get_function_name::(name); + let name = Arc::new(get_function_name::(name)); let method = Arc::new(method); Box::new(move |lua, mut args| unsafe { From b16f3895a0d14b1ef80c9c0f07b97f990f124c32 Mon Sep 17 00:00:00 2001 From: Peter Marheine Date: Wed, 6 Dec 2023 21:09:25 +1100 Subject: [PATCH 015/635] lua54: use changed return type for lua_rawlen Lua 5.4 changed lua_rawlen to return lua_Unsigned, versus size_t in earlier versions. Change the C API signature to match, and add a wrapper function with the same name that maintains a stable Rust API by casting to usize. --- mlua-sys/src/lua54/lua.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mlua-sys/src/lua54/lua.rs b/mlua-sys/src/lua54/lua.rs index 66633bb3..7eab2af2 100644 --- a/mlua-sys/src/lua54/lua.rs +++ b/mlua-sys/src/lua54/lua.rs @@ -149,13 +149,20 @@ extern "C-unwind" { pub fn lua_tointegerx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Integer; pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int; pub fn lua_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char; - pub fn lua_rawlen(L: *mut lua_State, idx: c_int) -> usize; + #[link_name = "lua_rawlen"] + fn lua_rawlen_(L: *mut lua_State, idx: c_int) -> lua_Unsigned; pub fn lua_tocfunction(L: *mut lua_State, idx: c_int) -> Option; pub fn lua_touserdata(L: *mut lua_State, idx: c_int) -> *mut c_void; pub fn lua_tothread(L: *mut lua_State, idx: c_int) -> *mut lua_State; pub fn lua_topointer(L: *mut lua_State, idx: c_int) -> *const c_void; } +// lua_rawlen's return type changed from size_t to lua_Unsigned int in Lua 5.4. +// This adapts the crate API to the new Lua ABI. +pub unsafe fn lua_rawlen(L: *mut lua_State, idx: c_int) -> usize { + lua_rawlen_(L, idx) as usize +} + // // Comparison and arithmetic functions // From e3f34f319cc9eb5cc4f87074a313f98c1dff70ca Mon Sep 17 00:00:00 2001 From: Peter Marheine Date: Wed, 6 Dec 2023 21:16:52 +1100 Subject: [PATCH 016/635] Correct C return type for lua_error The definition of lua_error in all of Lua 5.1 through 5.4 says lua_error returns int, but the Rust definition of the same function treats it as void (because it's known not to return). This causes link-time errors when building for wasm targets because the wasm linker is aware of function return types and errors out if they differ between definition and declaration. --- mlua-sys/src/lua51/lua.rs | 11 ++++++++++- mlua-sys/src/lua52/lua.rs | 11 ++++++++++- mlua-sys/src/lua53/lua.rs | 11 ++++++++++- mlua-sys/src/lua54/lua.rs | 11 ++++++++++- 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/mlua-sys/src/lua51/lua.rs b/mlua-sys/src/lua51/lua.rs index 925e9c7f..5cd351d8 100644 --- a/mlua-sys/src/lua51/lua.rs +++ b/mlua-sys/src/lua51/lua.rs @@ -228,13 +228,22 @@ extern "C-unwind" { // #[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))] extern "C-unwind" { - pub fn lua_error(L: *mut lua_State) -> !; + #[link_name = "lua_error"] + fn lua_error_(L: *mut lua_State) -> c_int; pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int; pub fn lua_concat(L: *mut lua_State, n: c_int); pub fn lua_getallocf(L: *mut lua_State, ud: *mut *mut c_void) -> lua_Alloc; pub fn lua_setallocf(L: *mut lua_State, f: lua_Alloc, ud: *mut c_void); } +// lua_error does not return but is declared to return int, and Rust translates +// ! to void which can cause link-time errors if the platform linker is aware +// of return types and requires they match (for example: wasm does this). +pub unsafe fn lua_error(L: *mut lua_State) -> ! { + lua_error_(L); + unreachable!(); +} + // // Some useful macros (implemented as Rust functions) // diff --git a/mlua-sys/src/lua52/lua.rs b/mlua-sys/src/lua52/lua.rs index 760140b7..d668d678 100644 --- a/mlua-sys/src/lua52/lua.rs +++ b/mlua-sys/src/lua52/lua.rs @@ -308,7 +308,8 @@ extern "C-unwind" { // // Miscellaneous functions // - pub fn lua_error(L: *mut lua_State) -> !; + #[link_name = "lua_error"] + fn lua_error_(L: *mut lua_State) -> c_int; pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int; pub fn lua_concat(L: *mut lua_State, n: c_int); pub fn lua_len(L: *mut lua_State, idx: c_int); @@ -316,6 +317,14 @@ extern "C-unwind" { pub fn lua_setallocf(L: *mut lua_State, f: lua_Alloc, ud: *mut c_void); } +// lua_error does not return but is declared to return int, and Rust translates +// ! to void which can cause link-time errors if the platform linker is aware +// of return types and requires they match (for example: wasm does this). +pub unsafe fn lua_error(L: *mut lua_State) -> ! { + lua_error_(L); + unreachable!(); +} + // // Some useful macros (implemented as Rust functions) // diff --git a/mlua-sys/src/lua53/lua.rs b/mlua-sys/src/lua53/lua.rs index d918de48..393ff3c7 100644 --- a/mlua-sys/src/lua53/lua.rs +++ b/mlua-sys/src/lua53/lua.rs @@ -314,7 +314,8 @@ extern "C-unwind" { // // Miscellaneous functions // - pub fn lua_error(L: *mut lua_State) -> !; + #[link_name = "lua_error"] + fn lua_error_(L: *mut lua_State) -> c_int; pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int; pub fn lua_concat(L: *mut lua_State, n: c_int); pub fn lua_len(L: *mut lua_State, idx: c_int); @@ -323,6 +324,14 @@ extern "C-unwind" { pub fn lua_setallocf(L: *mut lua_State, f: lua_Alloc, ud: *mut c_void); } +// lua_error does not return but is declared to return int, and Rust translates +// ! to void which can cause link-time errors if the platform linker is aware +// of return types and requires they match (for example: wasm does this). +pub unsafe fn lua_error(L: *mut lua_State) -> ! { + lua_error_(L); + unreachable!(); +} + // // Some useful macros (implemented as Rust functions) // diff --git a/mlua-sys/src/lua54/lua.rs b/mlua-sys/src/lua54/lua.rs index 7eab2af2..b1348661 100644 --- a/mlua-sys/src/lua54/lua.rs +++ b/mlua-sys/src/lua54/lua.rs @@ -343,7 +343,8 @@ extern "C-unwind" { // // Miscellaneous functions // - pub fn lua_error(L: *mut lua_State) -> !; + #[link_name = "lua_error"] + fn lua_error_(L: *mut lua_State) -> c_int; pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int; pub fn lua_concat(L: *mut lua_State, n: c_int); pub fn lua_len(L: *mut lua_State, idx: c_int); @@ -355,6 +356,14 @@ extern "C-unwind" { pub fn lua_closeslot(L: *mut lua_State, idx: c_int); } +// lua_error does not return but is declared to return int, and Rust translates +// ! to void which can cause link-time errors if the platform linker is aware +// of return types and requires they match (for example: wasm does this). +pub unsafe fn lua_error(L: *mut lua_State) -> ! { + lua_error_(L); + unreachable!(); +} + // // Some useful macros (implemented as Rust functions) // From 4c9258020182b8c17c8e4479577937618e0ba938 Mon Sep 17 00:00:00 2001 From: eatradish Date: Fri, 8 Dec 2023 11:16:52 +0800 Subject: [PATCH 017/635] Add loongarch64 architecture support --- mlua-sys/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mlua-sys/src/lib.rs b/mlua-sys/src/lib.rs index 72a2fed5..48225e22 100644 --- a/mlua-sys/src/lib.rs +++ b/mlua-sys/src/lib.rs @@ -65,6 +65,7 @@ pub const SYS_MIN_ALIGN: usize = 8; target_arch = "sparc64", target_arch = "riscv64", target_arch = "wasm64", + target_arch = "loongarch64", ))] #[doc(hidden)] pub const SYS_MIN_ALIGN: usize = 16; From 61e846326c78ffa77601a55eb68136ce7d3dfbc0 Mon Sep 17 00:00:00 2001 From: Joel Natividad <1980690+jqnatividad@users.noreply.github.com> Date: Sun, 10 Dec 2023 12:04:37 -0500 Subject: [PATCH 018/635] Update Cargo.toml (#342) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index dd34daa7..4750c955 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ num-traits = { version = "0.2.14" } rustc-hash = "1.0" futures-util = { version = "0.3", optional = true, default-features = false, features = ["std"] } serde = { version = "1.0", optional = true } -erased-serde = { version = "0.3", optional = true } +erased-serde = { version = "0.4", optional = true } serde-value = { version = "0.7", optional = true } parking_lot = { version = "0.12", optional = true } From 0b9a85e1838bed67ae69f11b42de84bcf19da80c Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Thu, 14 Dec 2023 14:54:57 +0000 Subject: [PATCH 019/635] Add lua emscripten support (#338) --- Cargo.toml | 12 +++++++++--- examples/async_http_client.rs | 3 +++ examples/async_http_reqwest.rs | 2 +- examples/async_http_server.rs | 3 +++ examples/async_tcp_server.rs | 3 +++ examples/repl.rs | 2 ++ mlua-sys/src/lua51/lauxlib.rs | 2 +- mlua-sys/src/lua52/lauxlib.rs | 2 +- tests/chunk.rs | 3 +++ 9 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4750c955..3c72e1d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,20 +60,26 @@ ffi = { package = "mlua-sys", version = "0.4.0", path = "mlua-sys" } [target.'cfg(unix)'.dependencies] libloading = { version = "0.8", optional = true } +[target.'cfg(target_arch = "wasm32")'.dependencies] +ffi = { package = "mlua-sys", version = "0.4.0", path = "mlua-sys", features = ["vendored"] } + [dev-dependencies] -rustyline = "12.0" -criterion = { version = "0.5", features = ["async_tokio"] } trybuild = "1.0" futures = "0.3.5" hyper = { version = "0.14", features = ["client", "server"] } reqwest = { version = "0.11", features = ["json"] } -tokio = { version = "1.0", features = ["full"] } +tokio = { version = "1.0", features = ["macros", "rt", "time"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" maplit = "1.0" tempfile = "3" static_assertions = "1.0" +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] +criterion = { version = "0.5", features = ["async_tokio"] } +rustyline = "12.0" +tokio = { version = "1.0", features = ["full"] } + [[bench]] name = "benchmark" harness = false diff --git a/examples/async_http_client.rs b/examples/async_http_client.rs index bdd9dbc8..1424a62d 100644 --- a/examples/async_http_client.rs +++ b/examples/async_http_client.rs @@ -1,3 +1,6 @@ +#[cfg(target_arch = "wasm32")] +compile_error!("Not available for wasm"); + use std::collections::HashMap; use hyper::body::{Body as HyperBody, HttpBody as _}; diff --git a/examples/async_http_reqwest.rs b/examples/async_http_reqwest.rs index cd67748d..4d67564b 100644 --- a/examples/async_http_reqwest.rs +++ b/examples/async_http_reqwest.rs @@ -1,6 +1,6 @@ use mlua::{chunk, ExternalResult, Lua, LuaSerdeExt, Result}; -#[tokio::main] +#[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { let lua = Lua::new(); diff --git a/examples/async_http_server.rs b/examples/async_http_server.rs index 43ae7a95..159622bb 100644 --- a/examples/async_http_server.rs +++ b/examples/async_http_server.rs @@ -1,3 +1,6 @@ +#[cfg(target_arch = "wasm32")] +compile_error!("Not available for wasm"); + use std::future::Future; use std::net::SocketAddr; use std::pin::Pin; diff --git a/examples/async_tcp_server.rs b/examples/async_tcp_server.rs index edc41493..1b312fef 100644 --- a/examples/async_tcp_server.rs +++ b/examples/async_tcp_server.rs @@ -1,3 +1,6 @@ +#[cfg(target_arch = "wasm32")] +compile_error!("Not available for wasm"); + use std::io; use std::net::SocketAddr; use std::rc::Rc; diff --git a/examples/repl.rs b/examples/repl.rs index c0a34a6e..35a7d0d4 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -1,4 +1,6 @@ //! This example shows a simple read-evaluate-print-loop (REPL). +#[cfg(target_arch = "wasm32")] +compile_error!("Not available for wasm"); use mlua::{Error, Lua, MultiValue}; use rustyline::DefaultEditor; diff --git a/mlua-sys/src/lua51/lauxlib.rs b/mlua-sys/src/lua51/lauxlib.rs index b05b5837..1565d3f1 100644 --- a/mlua-sys/src/lua51/lauxlib.rs +++ b/mlua-sys/src/lua51/lauxlib.rs @@ -43,7 +43,7 @@ extern "C-unwind" { pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void; pub fn luaL_where(L: *mut lua_State, lvl: c_int); - pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> !; + pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> c_int; pub fn luaL_checkoption( L: *mut lua_State, diff --git a/mlua-sys/src/lua52/lauxlib.rs b/mlua-sys/src/lua52/lauxlib.rs index 51da767b..570023dc 100644 --- a/mlua-sys/src/lua52/lauxlib.rs +++ b/mlua-sys/src/lua52/lauxlib.rs @@ -49,7 +49,7 @@ extern "C-unwind" { pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void; pub fn luaL_where(L: *mut lua_State, lvl: c_int); - pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> !; + pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> c_int; pub fn luaL_checkoption( L: *mut lua_State, diff --git a/tests/chunk.rs b/tests/chunk.rs index 8cab671f..714e887b 100644 --- a/tests/chunk.rs +++ b/tests/chunk.rs @@ -1,9 +1,12 @@ +#![allow(unused_imports)] + use std::fs; use std::io; use mlua::{Lua, Result}; #[test] +#[cfg(not(target_arch = "wasm32"))] fn test_chunk_path() -> Result<()> { let lua = Lua::new(); From bf79d6c212662b4cb9ade599383e8419ba01213c Mon Sep 17 00:00:00 2001 From: Aymen-Hakim <112126321+Aymen-Hakim@users.noreply.github.com> Date: Wed, 27 Dec 2023 21:07:59 +0100 Subject: [PATCH 020/635] Update lauxlib.rs (#351) lua54 in lua53 src. --- mlua-sys/src/lua53/lauxlib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlua-sys/src/lua53/lauxlib.rs b/mlua-sys/src/lua53/lauxlib.rs index 5002daaf..67cde638 100644 --- a/mlua-sys/src/lua53/lauxlib.rs +++ b/mlua-sys/src/lua53/lauxlib.rs @@ -20,7 +20,7 @@ pub struct luaL_Reg { pub func: lua_CFunction, } -#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] +#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] extern "C-unwind" { pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number, sz: usize); From 69ff0c5509e40db0ce4183e8f1f997750e93a3d0 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 3 Jan 2024 10:22:42 +0000 Subject: [PATCH 021/635] mlua-sys: fix Lua 5.2 `lua_sethook` definition --- mlua-sys/src/lua52/lua.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mlua-sys/src/lua52/lua.rs b/mlua-sys/src/lua52/lua.rs index d668d678..f7bdbdf6 100644 --- a/mlua-sys/src/lua52/lua.rs +++ b/mlua-sys/src/lua52/lua.rs @@ -462,7 +462,12 @@ extern "C-unwind" { pub fn lua_upvalueid(L: *mut lua_State, fidx: c_int, n: c_int) -> *mut c_void; pub fn lua_upvaluejoin(L: *mut lua_State, fidx1: c_int, n1: c_int, fidx2: c_int, n2: c_int); - pub fn lua_sethook(L: *mut lua_State, func: Option, mask: c_int, count: c_int); + pub fn lua_sethook( + L: *mut lua_State, + func: Option, + mask: c_int, + count: c_int, + ) -> c_int; pub fn lua_gethook(L: *mut lua_State) -> Option; pub fn lua_gethookmask(L: *mut lua_State) -> c_int; pub fn lua_gethookcount(L: *mut lua_State) -> c_int; From c0a0983025ae41ab68d194f137ab61f918bb734b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 3 Jan 2024 11:31:39 +0000 Subject: [PATCH 022/635] mlua-sys: always inline lua_error --- mlua-sys/src/lua51/lua.rs | 1 + mlua-sys/src/lua52/lua.rs | 1 + mlua-sys/src/lua53/lua.rs | 1 + mlua-sys/src/lua54/lua.rs | 2 ++ 4 files changed, 5 insertions(+) diff --git a/mlua-sys/src/lua51/lua.rs b/mlua-sys/src/lua51/lua.rs index 5cd351d8..db409aa8 100644 --- a/mlua-sys/src/lua51/lua.rs +++ b/mlua-sys/src/lua51/lua.rs @@ -239,6 +239,7 @@ extern "C-unwind" { // lua_error does not return but is declared to return int, and Rust translates // ! to void which can cause link-time errors if the platform linker is aware // of return types and requires they match (for example: wasm does this). +#[inline(always)] pub unsafe fn lua_error(L: *mut lua_State) -> ! { lua_error_(L); unreachable!(); diff --git a/mlua-sys/src/lua52/lua.rs b/mlua-sys/src/lua52/lua.rs index f7bdbdf6..8040d5db 100644 --- a/mlua-sys/src/lua52/lua.rs +++ b/mlua-sys/src/lua52/lua.rs @@ -320,6 +320,7 @@ extern "C-unwind" { // lua_error does not return but is declared to return int, and Rust translates // ! to void which can cause link-time errors if the platform linker is aware // of return types and requires they match (for example: wasm does this). +#[inline(always)] pub unsafe fn lua_error(L: *mut lua_State) -> ! { lua_error_(L); unreachable!(); diff --git a/mlua-sys/src/lua53/lua.rs b/mlua-sys/src/lua53/lua.rs index 393ff3c7..b726911c 100644 --- a/mlua-sys/src/lua53/lua.rs +++ b/mlua-sys/src/lua53/lua.rs @@ -327,6 +327,7 @@ extern "C-unwind" { // lua_error does not return but is declared to return int, and Rust translates // ! to void which can cause link-time errors if the platform linker is aware // of return types and requires they match (for example: wasm does this). +#[inline(always)] pub unsafe fn lua_error(L: *mut lua_State) -> ! { lua_error_(L); unreachable!(); diff --git a/mlua-sys/src/lua54/lua.rs b/mlua-sys/src/lua54/lua.rs index b1348661..26ad0a68 100644 --- a/mlua-sys/src/lua54/lua.rs +++ b/mlua-sys/src/lua54/lua.rs @@ -159,6 +159,7 @@ extern "C-unwind" { // lua_rawlen's return type changed from size_t to lua_Unsigned int in Lua 5.4. // This adapts the crate API to the new Lua ABI. +#[inline(always)] pub unsafe fn lua_rawlen(L: *mut lua_State, idx: c_int) -> usize { lua_rawlen_(L, idx) as usize } @@ -359,6 +360,7 @@ extern "C-unwind" { // lua_error does not return but is declared to return int, and Rust translates // ! to void which can cause link-time errors if the platform linker is aware // of return types and requires they match (for example: wasm does this). +#[inline(always)] pub unsafe fn lua_error(L: *mut lua_State) -> ! { lua_error_(L); unreachable!(); From cf153f38de8688d0e400a77f16760c76e4d483a8 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 3 Jan 2024 11:33:42 +0000 Subject: [PATCH 023/635] Panic when try to build for wasm32 without vendored feature (except luau) --- Cargo.toml | 3 --- mlua-sys/build/find_normal.rs | 6 ++++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3c72e1d9..8f2ee525 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,9 +60,6 @@ ffi = { package = "mlua-sys", version = "0.4.0", path = "mlua-sys" } [target.'cfg(unix)'.dependencies] libloading = { version = "0.8", optional = true } -[target.'cfg(target_arch = "wasm32")'.dependencies] -ffi = { package = "mlua-sys", version = "0.4.0", path = "mlua-sys", features = ["vendored"] } - [dev-dependencies] trybuild = "1.0" futures = "0.3.5" diff --git a/mlua-sys/build/find_normal.rs b/mlua-sys/build/find_normal.rs index 655814b7..26b1200e 100644 --- a/mlua-sys/build/find_normal.rs +++ b/mlua-sys/build/find_normal.rs @@ -4,6 +4,12 @@ use std::env; use std::ops::Bound; pub fn probe_lua() { + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + + if target_arch == "wasm32" && cfg!(not(feature = "vendored")) { + panic!("Please enable `vendored` feature to build for wasm32"); + } + let lib_dir = env::var("LUA_LIB").unwrap_or_default(); let lua_lib = env::var("LUA_LIB_NAME").unwrap_or_default(); From 4749e3a22a94cec0364018c5968863c1f11da344 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 3 Jan 2024 11:34:25 +0000 Subject: [PATCH 024/635] Update minimal lua(u) versions (needed for wasm32) --- mlua-sys/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 53a2620c..9c4a8913 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -38,6 +38,6 @@ module = [] cc = "1.0" cfg-if = "1.0" pkg-config = "0.3.17" -lua-src = { version = ">= 546.0.0, < 546.1.0", optional = true } +lua-src = { version = ">= 546.0.2, < 546.1.0", optional = true } luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } -luau0-src = { version = "0.7.8", optional = true } +luau0-src = { version = "0.7.11", optional = true } From 244e6c9c129d32f7f4a050e8318c73346ac6c50e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 3 Jan 2024 11:34:50 +0000 Subject: [PATCH 025/635] Bump rustyline dependency --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8f2ee525..bffc8381 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,7 @@ static_assertions = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] criterion = { version = "0.5", features = ["async_tokio"] } -rustyline = "12.0" +rustyline = "13.0" tokio = { version = "1.0", features = ["full"] } [[bench]] From 514ec24252d438e04632f1de6ad47be80e614ebb Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 3 Jan 2024 12:02:32 +0000 Subject: [PATCH 026/635] Fix lua53/lua54 `luaL_error` definition (for wasm32) --- mlua-sys/src/lua53/lauxlib.rs | 2 +- mlua-sys/src/lua54/lauxlib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mlua-sys/src/lua53/lauxlib.rs b/mlua-sys/src/lua53/lauxlib.rs index 67cde638..258ab301 100644 --- a/mlua-sys/src/lua53/lauxlib.rs +++ b/mlua-sys/src/lua53/lauxlib.rs @@ -51,7 +51,7 @@ extern "C-unwind" { pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void; pub fn luaL_where(L: *mut lua_State, lvl: c_int); - pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> !; + pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> c_int; pub fn luaL_checkoption( L: *mut lua_State, diff --git a/mlua-sys/src/lua54/lauxlib.rs b/mlua-sys/src/lua54/lauxlib.rs index 9ce7d0cd..5be2ba6b 100644 --- a/mlua-sys/src/lua54/lauxlib.rs +++ b/mlua-sys/src/lua54/lauxlib.rs @@ -50,7 +50,7 @@ extern "C-unwind" { pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void; pub fn luaL_where(L: *mut lua_State, lvl: c_int); - pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> !; + pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> c_int; pub fn luaL_checkoption( L: *mut lua_State, From 9ed0d907460167e9edd90561377467b4a4aec4f1 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 4 Jan 2024 20:44:53 +0000 Subject: [PATCH 027/635] Run tests for wasm32-unknown-emscripten --- .github/workflows/main.yml | 23 +++++++++++++++++++++++ examples/async_http_client.rs | 5 +---- examples/async_http_server.rs | 3 --- examples/async_tcp_server.rs | 3 --- examples/repl.rs | 2 -- tests/async.rs | 7 +++++++ tests/chunk.rs | 9 ++++++--- tests/luau.rs | 6 ++++++ tests/static.rs | 13 +++++++++++-- tests/tests.rs | 30 +++++++++++++++++++++--------- 10 files changed, 75 insertions(+), 26 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bb20567b..5810651b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -206,6 +206,29 @@ jobs: (cd tests/module && cargo build --release --features "${{ matrix.lua }}") (cd tests/module/loader && cargo test --release --features "${{ matrix.lua }}") + test_wasm32_emscripten: + name: Test on wasm32-unknown-emscripten + runs-on: ubuntu-22.04 + needs: build + strategy: + matrix: + lua: [lua54, lua53, lua52, lua51, luau] + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + target: wasm32-unknown-emscripten + - name: Install Emscripten + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends emscripten + - name: Run ${{ matrix.lua }} tests + run: | + cargo test --tests --features "${{ matrix.lua }},vendored" + cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" + cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,parking_lot,unstable" + rustfmt: name: Rustfmt runs-on: ubuntu-22.04 diff --git a/examples/async_http_client.rs b/examples/async_http_client.rs index 1424a62d..3eccdc32 100644 --- a/examples/async_http_client.rs +++ b/examples/async_http_client.rs @@ -1,6 +1,3 @@ -#[cfg(target_arch = "wasm32")] -compile_error!("Not available for wasm"); - use std::collections::HashMap; use hyper::body::{Body as HyperBody, HttpBody as _}; @@ -22,7 +19,7 @@ impl UserData for BodyReader { } } -#[tokio::main] +#[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { let lua = Lua::new(); diff --git a/examples/async_http_server.rs b/examples/async_http_server.rs index 159622bb..43ae7a95 100644 --- a/examples/async_http_server.rs +++ b/examples/async_http_server.rs @@ -1,6 +1,3 @@ -#[cfg(target_arch = "wasm32")] -compile_error!("Not available for wasm"); - use std::future::Future; use std::net::SocketAddr; use std::pin::Pin; diff --git a/examples/async_tcp_server.rs b/examples/async_tcp_server.rs index 1b312fef..edc41493 100644 --- a/examples/async_tcp_server.rs +++ b/examples/async_tcp_server.rs @@ -1,6 +1,3 @@ -#[cfg(target_arch = "wasm32")] -compile_error!("Not available for wasm"); - use std::io; use std::net::SocketAddr; use std::rc::Rc; diff --git a/examples/repl.rs b/examples/repl.rs index 35a7d0d4..c0a34a6e 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -1,6 +1,4 @@ //! This example shows a simple read-evaluate-print-loop (REPL). -#[cfg(target_arch = "wasm32")] -compile_error!("Not available for wasm"); use mlua::{Error, Lua, MultiValue}; use rustyline::DefaultEditor; diff --git a/tests/async.rs b/tests/async.rs index fd4e9298..70559142 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -10,10 +10,17 @@ use mlua::{ UserData, UserDataMethods, Value, }; +#[cfg(not(target_arch = "wasm32"))] async fn sleep_ms(ms: u64) { tokio::time::sleep(Duration::from_millis(ms)).await; } +#[cfg(target_arch = "wasm32")] +async fn sleep_ms(_ms: u64) { + // I was unable to make sleep() work in wasm32-emscripten target + tokio::task::yield_now().await; +} + #[tokio::test] async fn test_async_function() -> Result<()> { let lua = Lua::new(); diff --git a/tests/chunk.rs b/tests/chunk.rs index 714e887b..a797064e 100644 --- a/tests/chunk.rs +++ b/tests/chunk.rs @@ -1,15 +1,18 @@ -#![allow(unused_imports)] - use std::fs; use std::io; use mlua::{Lua, Result}; #[test] -#[cfg(not(target_arch = "wasm32"))] fn test_chunk_path() -> Result<()> { let lua = Lua::new(); + if cfg!(target_arch = "wasm32") { + // TODO: figure out why emscripten fails on file operations + // Also see https://github.com/rust-lang/rust/issues/119250 + return Ok(()); + } + let temp_dir = tempfile::tempdir().unwrap(); fs::write( temp_dir.path().join("module.lua"), diff --git a/tests/luau.rs b/tests/luau.rs index d6b38caa..987ea4fa 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -28,6 +28,12 @@ fn test_require() -> Result<()> { assert!(lua.globals().get::<_, Option>("require")?.is_none()); assert!(lua.globals().get::<_, Option>("package")?.is_none()); + if cfg!(target_arch = "wasm32") { + // TODO: figure out why emscripten fails on file operations + // Also see https://github.com/rust-lang/rust/issues/119250 + return Ok(()); + } + lua = Lua::new(); let temp_dir = tempfile::tempdir().unwrap(); diff --git a/tests/static.rs b/tests/static.rs index 5bc286bf..92164d71 100644 --- a/tests/static.rs +++ b/tests/static.rs @@ -73,13 +73,22 @@ fn test_static_lua_coroutine() -> Result<()> { async fn test_static_async() -> Result<()> { let lua = Lua::new().into_static(); + #[cfg(not(target_arch = "wasm32"))] + async fn sleep_ms(ms: u64) { + tokio::time::sleep(std::time::Duration::from_millis(ms)).await; + } + + #[cfg(target_arch = "wasm32")] + async fn sleep_ms(_ms: u64) { + tokio::task::yield_now().await; + } + let timer = lua.create_async_function(|_, (i, n, f): (u64, u64, mlua::Function)| async move { tokio::task::spawn_local(async move { - let dur = std::time::Duration::from_millis(i); for _ in 0..n { tokio::task::spawn_local(f.call_async::<(), ()>(())); - tokio::time::sleep(dur).await; + sleep_ms(i).await; } }); Ok(()) diff --git a/tests/tests.rs b/tests/tests.rs index 0b7a0a61..2bfefe19 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +#[cfg(not(target_arch = "wasm32"))] use std::iter::FromIterator; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::string::String as StdString; @@ -312,30 +313,29 @@ fn test_error() -> Result<()> { globals.set("rust_error_function", rust_error_function)?; let no_error = globals.get::<_, Function>("no_error")?; - let lua_error = globals.get::<_, Function>("lua_error")?; - let rust_error = globals.get::<_, Function>("rust_error")?; - let return_error = globals.get::<_, Function>("return_error")?; - let return_string_error = globals.get::<_, Function>("return_string_error")?; - let test_pcall = globals.get::<_, Function>("test_pcall")?; - let understand_recursion = globals.get::<_, Function>("understand_recursion")?; - assert!(no_error.call::<_, ()>(()).is_ok()); + + let lua_error = globals.get::<_, Function>("lua_error")?; match lua_error.call::<_, ()>(()) { Err(Error::RuntimeError(_)) => {} Err(e) => panic!("error is not RuntimeError kind, got {:?}", e), _ => panic!("error not returned"), } + + let rust_error = globals.get::<_, Function>("rust_error")?; match rust_error.call::<_, ()>(()) { Err(Error::CallbackError { .. }) => {} Err(e) => panic!("error is not CallbackError kind, got {:?}", e), _ => panic!("error not returned"), } + let return_error = globals.get::<_, Function>("return_error")?; match return_error.call::<_, Value>(()) { Ok(Value::Error(_)) => {} _ => panic!("Value::Error not returned"), } + let return_string_error = globals.get::<_, Function>("return_string_error")?; assert!(return_string_error.call::<_, Error>(()).is_ok()); match lua @@ -358,9 +358,14 @@ fn test_error() -> Result<()> { _ => panic!("error not returned"), } + let test_pcall = globals.get::<_, Function>("test_pcall")?; test_pcall.call::<_, ()>(())?; - assert!(understand_recursion.call::<_, ()>(()).is_err()); + #[cfg(not(target_arch = "wasm32"))] + { + let understand_recursion = globals.get::<_, Function>("understand_recursion")?; + assert!(understand_recursion.call::<_, ()>(()).is_err()); + } Ok(()) } @@ -947,6 +952,7 @@ fn test_application_data() -> Result<()> { } #[test] +#[cfg(not(target_arch = "wasm32"))] fn test_recursion() -> Result<()> { let lua = Lua::new(); @@ -966,14 +972,16 @@ fn test_recursion() -> Result<()> { } #[test] +#[cfg(not(target_arch = "wasm32"))] fn test_too_many_returns() -> Result<()> { let lua = Lua::new(); let f = lua.create_function(|_, ()| Ok(Variadic::from_iter(1..1000000)))?; - assert!(f.call::<_, Vec>(()).is_err()); + assert!(f.call::<_, Variadic>(()).is_err()); Ok(()) } #[test] +#[cfg(not(target_arch = "wasm32"))] fn test_too_many_arguments() -> Result<()> { let lua = Lua::new(); lua.load("function test(...) end").exec()?; @@ -988,6 +996,7 @@ fn test_too_many_arguments() -> Result<()> { #[test] #[cfg(not(feature = "luajit"))] +#[cfg(not(target_arch = "wasm32"))] fn test_too_many_recursions() -> Result<()> { let lua = Lua::new(); @@ -1001,6 +1010,7 @@ fn test_too_many_recursions() -> Result<()> { } #[test] +#[cfg(not(target_arch = "wasm32"))] fn test_too_many_binds() -> Result<()> { let lua = Lua::new(); let globals = lua.globals(); @@ -1022,6 +1032,7 @@ fn test_too_many_binds() -> Result<()> { } #[test] +#[cfg(not(target_arch = "wasm32"))] fn test_ref_stack_exhaustion() { match catch_unwind(AssertUnwindSafe(|| -> Result<()> { let lua = Lua::new(); @@ -1351,6 +1362,7 @@ fn test_luajit_cdata() -> Result<()> { #[test] #[cfg(feature = "send")] +#[cfg(not(target_arch = "wasm32"))] fn test_send() { let lua = Lua::new(); std::thread::spawn(move || { From 60e859f6437d81998f05e71e0eb900b57529c6da Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 6 Jan 2024 12:55:18 +0000 Subject: [PATCH 028/635] Fix (nightly) warning in doc --- src/userdata.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/userdata.rs b/src/userdata.rs index 15e21349..a2d730b0 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -580,12 +580,12 @@ pub trait UserDataFields<'lua, T> { /// # use mlua::{Lua, Result, UserData}; /// # fn main() -> Result<()> { /// # let lua = Lua::new(); -/// struct MyUserData(i32); +/// struct MyUserData; /// /// impl UserData for MyUserData {} /// /// // `MyUserData` now implements `IntoLua`: -/// lua.globals().set("myobject", MyUserData(123))?; +/// lua.globals().set("myobject", MyUserData)?; /// /// lua.load("assert(type(myobject) == 'userdata')").exec()?; /// # Ok(()) From 4c0474d5731bd5979e935854f00f9e890d60577c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 6 Jan 2024 15:04:18 +0000 Subject: [PATCH 029/635] Fix docsrs attr for Thread::reset --- src/thread.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/thread.rs b/src/thread.rs index bf051fc2..415bd213 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -239,7 +239,7 @@ impl<'lua> Thread<'lua> { /// /// [Lua 5.4]: https://www.lua.org/manual/5.4/manual.html#lua_closethread #[cfg(any(feature = "lua54", feature = "luau"))] - #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "luau"))))] pub fn reset(&self, func: crate::function::Function<'lua>) -> Result<()> { let lua = self.0.lua; let thread_state = self.state(); From a68708c12e5ca24ded9dd5dc2a138da23e288004 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 8 Jan 2024 18:44:49 +0000 Subject: [PATCH 030/635] Update README & CHANGELOG --- CHANGELOG.md | 5 +++++ README.md | 2 ++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34fb03b5..4b778d18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## v0.9.3 + +- WebAssembly support (`wasm32-unknown-emscripten` target) +- Performance improvements (faster Lua function calls for lua51/jit/luau) + ## v0.9.2 - Added binary modules support to Luau diff --git a/README.md b/README.md index 5957caf1..ff6be708 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ Started as `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT `mlua` tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platform and cross-compilation to `aarch64` (other targets are also supported). +WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua versions excluding JIT. + [GitHub Actions]: https://github.com/khvzak/mlua/actions [Roblox Luau]: https://luau-lang.org From 12472de1d2088e29d56ec02d74e580effb109a1b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 8 Jan 2024 21:02:45 +0000 Subject: [PATCH 031/635] v0.9.3 --- Cargo.toml | 4 ++-- mlua-sys/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bffc8381..ca41039f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.9.2" # remember to update mlua_derive +version = "0.9.3" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.71" edition = "2021" @@ -55,7 +55,7 @@ erased-serde = { version = "0.4", optional = true } serde-value = { version = "0.7", optional = true } parking_lot = { version = "0.12", optional = true } -ffi = { package = "mlua-sys", version = "0.4.0", path = "mlua-sys" } +ffi = { package = "mlua-sys", version = "0.5.0", path = "mlua-sys" } [target.'cfg(unix)'.dependencies] libloading = { version = "0.8", optional = true } diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 9c4a8913..fa5ff6e7 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.4.0" +version = "0.5.0" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" From 205989f569f5d1ae3aa196545fc313180ed891c1 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 10 Jan 2024 00:14:09 +0000 Subject: [PATCH 032/635] Fix edge case when loading many-in-one module from thread without using its state. If Lua previously been initialized in main thread and then new module was loaded from thread we reuse old state which confuses Lua loader. --- mlua_derive/src/lib.rs | 2 +- src/lua.rs | 12 +++++++----- tests/module/loader/tests/load.rs | 26 +++++++++++++++++++++++++- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/mlua_derive/src/lib.rs b/mlua_derive/src/lib.rs index 9d406bcc..8f2b6daa 100644 --- a/mlua_derive/src/lib.rs +++ b/mlua_derive/src/lib.rs @@ -62,7 +62,7 @@ pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream { unsafe extern "C-unwind" fn #ext_entrypoint_name(state: *mut ::mlua::lua_State) -> ::std::os::raw::c_int { let lua = ::mlua::Lua::init_from_ptr(state); lua.skip_memory_check(#skip_memory_check); - lua.entrypoint1(#func_name) + lua.entrypoint1(state, #func_name) } }; diff --git a/src/lua.rs b/src/lua.rs index 13c348c0..30cfc5b6 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -708,18 +708,20 @@ impl Lua { // The returned value then pushed onto the stack. #[doc(hidden)] #[cfg(not(tarpaulin_include))] - pub unsafe fn entrypoint<'lua, A, R, F>(self, func: F) -> c_int + pub unsafe fn entrypoint<'lua, A, R, F>(self, state: *mut ffi::lua_State, func: F) -> c_int where A: FromLuaMulti<'lua>, R: IntoLua<'lua>, F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, { - let (state, extra) = (self.state(), self.extra.get()); - // It must be safe to drop `self` as in the module mode we keep strong reference to `Lua` in the registry + let extra = self.extra.get(); + // `self` is no longer needed and must be dropped at this point to avoid possible memory leak + // in case of possible longjmp (lua_error) below drop(self); callback_error_ext(state, extra, move |nargs| { let lua: &Lua = mem::transmute((*extra).inner.assume_init_ref()); + let _guard = StateGuard::new(&lua.0, state); let args = A::from_stack_args(nargs, 1, None, lua)?; func(lua, args)?.push_into_stack(lua)?; Ok(1) @@ -729,12 +731,12 @@ impl Lua { // A simple module entrypoint without arguments #[doc(hidden)] #[cfg(not(tarpaulin_include))] - pub unsafe fn entrypoint1<'lua, R, F>(self, func: F) -> c_int + pub unsafe fn entrypoint1<'lua, R, F>(self, state: *mut ffi::lua_State, func: F) -> c_int where R: IntoLua<'lua>, F: Fn(&'lua Lua) -> Result + MaybeSend + 'static, { - self.entrypoint(move |lua, _: ()| func(lua)) + self.entrypoint(state, move |lua, _: ()| func(lua)) } /// Skips memory checks for some operations. diff --git a/tests/module/loader/tests/load.rs b/tests/module/loader/tests/load.rs index 25f85ab0..8c4a5957 100644 --- a/tests/module/loader/tests/load.rs +++ b/tests/module/loader/tests/load.rs @@ -59,7 +59,8 @@ fn test_module_from_thread() -> Result<()> { assert(mod.sum(a, b) == a + b) end) - coroutine.resume(co, 3, 5) + local ok, err = coroutine.resume(co, 3, 5) + assert(ok, err) collectgarbage() assert(mod.used_memory() > 0) @@ -68,6 +69,29 @@ fn test_module_from_thread() -> Result<()> { .exec() } +#[cfg(any( + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "lua51" +))] +#[test] +fn test_module_multi_from_thread() -> Result<()> { + let lua = make_lua()?; + lua.load( + r#" + local mod = require("test_module") + local co = coroutine.create(function() + local mod2 = require("test_module.second") + assert(mod2.userdata ~= nil) + end) + local ok, err = coroutine.resume(co) + assert(ok, err) + "#, + ) + .exec() +} + fn make_lua() -> Result { let (dylib_path, dylib_ext, separator); if cfg!(target_os = "macos") { From b5896173fd1fc4ddd3fc47d0e67b2070c84b7210 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 10 Jan 2024 10:08:33 +0000 Subject: [PATCH 033/635] Include skip_memory_check code only when the corresponding attribute set for module --- mlua_derive/src/lib.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mlua_derive/src/lib.rs b/mlua_derive/src/lib.rs index 8f2b6daa..7b6fb306 100644 --- a/mlua_derive/src/lib.rs +++ b/mlua_derive/src/lib.rs @@ -51,7 +51,11 @@ pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream { let func_name = &func.sig.ident; let module_name = args.name.unwrap_or_else(|| func_name.clone()); let ext_entrypoint_name = Ident::new(&format!("luaopen_{module_name}"), Span::call_site()); - let skip_memory_check = args.skip_memory_check; + let skip_memory_check = if args.skip_memory_check { + quote! { lua.skip_memory_check(true); } + } else { + quote! {} + }; let wrapped = quote! { ::mlua::require_module_feature!(); @@ -61,7 +65,7 @@ pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream { #[no_mangle] unsafe extern "C-unwind" fn #ext_entrypoint_name(state: *mut ::mlua::lua_State) -> ::std::os::raw::c_int { let lua = ::mlua::Lua::init_from_ptr(state); - lua.skip_memory_check(#skip_memory_check); + #skip_memory_check lua.entrypoint1(state, #func_name) } }; From eed48889cd3e9ba5f98591d0412df41218a618ef Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 10 Jan 2024 15:41:31 +0000 Subject: [PATCH 034/635] v0.9.4 --- CHANGELOG.md | 4 ++++ Cargo.toml | 4 ++-- mlua_derive/Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b778d18..15ba8763 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## v0.9.4 + +- Fixed loading all-in-one modules under mixed states (eg. main state and coroutines) + ## v0.9.3 - WebAssembly support (`wasm32-unknown-emscripten` target) diff --git a/Cargo.toml b/Cargo.toml index ca41039f..43984844 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.9.3" # remember to update mlua_derive +version = "0.9.4" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.71" edition = "2021" @@ -44,7 +44,7 @@ macros = ["mlua_derive/macros"] unstable = [] [dependencies] -mlua_derive = { version = "=0.9.0", optional = true, path = "mlua_derive" } +mlua_derive = { version = "=0.9.1", optional = true, path = "mlua_derive" } bstr = { version = "1.0", features = ["std"], default_features = false } once_cell = { version = "1.0" } num-traits = { version = "0.2.14" } diff --git a/mlua_derive/Cargo.toml b/mlua_derive/Cargo.toml index 8021a16c..00ebe6ba 100644 --- a/mlua_derive/Cargo.toml +++ b/mlua_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua_derive" -version = "0.9.0" +version = "0.9.1" authors = ["Aleksandr Orlenko "] edition = "2021" description = "Procedural macros for the mlua crate." From a38e484fe9a84b15ec64c9518252046381d669df Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 18 Jan 2024 22:18:48 +0000 Subject: [PATCH 035/635] Increase luau max stack size to 1M from 100k --- mlua-sys/build/find_vendored.rs | 1 + mlua-sys/src/luau/lua.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mlua-sys/build/find_vendored.rs b/mlua-sys/build/find_vendored.rs index a00c2866..e3ddbecf 100644 --- a/mlua-sys/build/find_vendored.rs +++ b/mlua-sys/build/find_vendored.rs @@ -21,6 +21,7 @@ pub fn probe_lua() { #[cfg(feature = "luau")] let artifacts = luau0_src::Build::new() .enable_codegen(cfg!(feature = "luau-codegen")) + .set_max_cstack_size(1000000) .set_vector_size(if cfg!(feature = "luau-vector4") { 4 } else { 3 }) .build(); diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 2da5690d..43aebf1e 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -8,7 +8,7 @@ use std::{mem, ptr}; pub const LUA_MULTRET: c_int = -1; // Max number of Lua stack slots -const LUAI_MAXCSTACK: c_int = 100000; +const LUAI_MAXCSTACK: c_int = 1000000; // // Pseudo-indices From 3c801e7b172118450e1d35e4d35ae036534999ea Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 20 Jan 2024 15:51:27 +0000 Subject: [PATCH 036/635] Expose internal `POLL_PENDING` constant (hidden) --- mlua-sys/src/lua51/lua.rs | 8 ++++++++ mlua-sys/src/lua52/lua.rs | 8 ++++++++ mlua-sys/src/lua53/lua.rs | 8 ++++++++ mlua-sys/src/lua54/lua.rs | 8 ++++++++ src/lua.rs | 11 +++++++++-- src/thread.rs | 9 ++------- 6 files changed, 43 insertions(+), 9 deletions(-) diff --git a/mlua-sys/src/lua51/lua.rs b/mlua-sys/src/lua51/lua.rs index db409aa8..7103a72b 100644 --- a/mlua-sys/src/lua51/lua.rs +++ b/mlua-sys/src/lua51/lua.rs @@ -328,6 +328,14 @@ pub unsafe fn lua_getglobal_(L: *mut lua_State, var: *const c_char) { lua_getfield_(L, LUA_GLOBALSINDEX, var) } +#[inline(always)] +pub unsafe fn lua_tolightuserdata(L: *mut lua_State, idx: c_int) -> *mut c_void { + if lua_islightuserdata(L, idx) != 0 { + return lua_touserdata(L, idx); + } + ptr::null_mut() +} + #[inline(always)] pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char { lua_tolstring(L, i, ptr::null_mut()) diff --git a/mlua-sys/src/lua52/lua.rs b/mlua-sys/src/lua52/lua.rs index 8040d5db..c52bc9f2 100644 --- a/mlua-sys/src/lua52/lua.rs +++ b/mlua-sys/src/lua52/lua.rs @@ -417,6 +417,14 @@ pub unsafe fn lua_pushglobaltable(L: *mut lua_State) { lua_rawgeti_(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS as _) } +#[inline(always)] +pub unsafe fn lua_tolightuserdata(L: *mut lua_State, idx: c_int) -> *mut c_void { + if lua_islightuserdata(L, idx) != 0 { + return lua_touserdata(L, idx); + } + ptr::null_mut() +} + #[inline(always)] pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char { lua_tolstring(L, i, ptr::null_mut()) diff --git a/mlua-sys/src/lua53/lua.rs b/mlua-sys/src/lua53/lua.rs index b726911c..24dbba20 100644 --- a/mlua-sys/src/lua53/lua.rs +++ b/mlua-sys/src/lua53/lua.rs @@ -424,6 +424,14 @@ pub unsafe fn lua_pushglobaltable(L: *mut lua_State) -> c_int { lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS) } +#[inline(always)] +pub unsafe fn lua_tolightuserdata(L: *mut lua_State, idx: c_int) -> *mut c_void { + if lua_islightuserdata(L, idx) != 0 { + return lua_touserdata(L, idx); + } + ptr::null_mut() +} + #[inline(always)] pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char { lua_tolstring(L, i, ptr::null_mut()) diff --git a/mlua-sys/src/lua54/lua.rs b/mlua-sys/src/lua54/lua.rs index 26ad0a68..f628e69b 100644 --- a/mlua-sys/src/lua54/lua.rs +++ b/mlua-sys/src/lua54/lua.rs @@ -457,6 +457,14 @@ pub unsafe fn lua_pushglobaltable(L: *mut lua_State) -> c_int { lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS) } +#[inline(always)] +pub unsafe fn lua_tolightuserdata(L: *mut lua_State, idx: c_int) -> *mut c_void { + if lua_islightuserdata(L, idx) != 0 { + return lua_touserdata(L, idx); + } + ptr::null_mut() +} + #[inline(always)] pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char { lua_tolstring(L, i, ptr::null_mut()) diff --git a/src/lua.rs b/src/lua.rs index 30cfc5b6..6d22e053 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -2965,8 +2965,7 @@ impl Lua { match fut.as_mut().poll(&mut ctx) { Poll::Pending => { ffi::lua_pushnil(state); - let pending = &ASYNC_POLL_PENDING as *const u8 as *mut c_void; - ffi::lua_pushlightuserdata(state, pending); + ffi::lua_pushlightuserdata(state, Lua::poll_pending().0); Ok(2) } Poll::Ready(nresults) => { @@ -3069,6 +3068,14 @@ impl Lua { mem::replace(&mut (*self.extra.get()).waker, waker) } + /// Returns internal `Poll::Pending` constant used for executing async callbacks. + #[cfg(feature = "async")] + #[doc(hidden)] + #[inline] + pub fn poll_pending() -> LightUserData { + LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut c_void) + } + pub(crate) unsafe fn make_userdata(&self, data: UserDataCell) -> Result where T: UserData + 'static, diff --git a/src/thread.rs b/src/thread.rs index 415bd213..e594297d 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -15,7 +15,7 @@ use crate::{ #[cfg(feature = "async")] use { - crate::{lua::ASYNC_POLL_PENDING, value::MultiValue}, + crate::value::MultiValue, futures_util::stream::Stream, std::{ future::Future, @@ -530,12 +530,7 @@ where #[cfg(feature = "async")] #[inline(always)] unsafe fn is_poll_pending(state: *mut ffi::lua_State) -> bool { - if ffi::lua_islightuserdata(state, -1) != 0 { - let stack_ptr = ffi::lua_touserdata(state, -1) as *const u8; - let pending_ptr = &ASYNC_POLL_PENDING as *const u8; - return std::ptr::eq(stack_ptr, pending_ptr); - } - false + ffi::lua_tolightuserdata(state, -1) == Lua::poll_pending().0 } #[cfg(feature = "async")] From 727f99ee4d80825c03177fc99f0203615093ae6d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 20 Jan 2024 21:39:42 +0000 Subject: [PATCH 037/635] Implement IntoLua for `&RegistryKey` This would allow just passing registry keys to arguments with fasttrack to push directly into stack. --- src/conversion.rs | 22 +++++++++++++++++++++- tests/tests.rs | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index 41fdf365..1321b421 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -16,7 +16,7 @@ use crate::lua::Lua; use crate::string::String; use crate::table::Table; use crate::thread::Thread; -use crate::types::{LightUserData, MaybeSend}; +use crate::types::{LightUserData, MaybeSend, RegistryKey}; use crate::userdata::{AnyUserData, UserData, UserDataRef, UserDataRefMut}; use crate::value::{FromLua, IntoLua, Nil, Value}; @@ -240,6 +240,26 @@ impl<'lua> FromLua<'lua> for Error { } } +impl<'lua> IntoLua<'lua> for &RegistryKey { + #[inline] + fn into_lua(self, lua: &'lua Lua) -> Result> { + lua.registry_value(self) + } + + unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + if !lua.owns_registry_value(self) { + return Err(Error::MismatchedRegistryKey); + } + + if self.is_nil() { + ffi::lua_pushnil(lua.state()); + } else { + ffi::lua_rawgeti(lua.state(), ffi::LUA_REGISTRYINDEX, self.registry_id as _); + } + Ok(()) + } +} + impl<'lua> IntoLua<'lua> for bool { #[inline] fn into_lua(self, _: &'lua Lua) -> Result> { diff --git a/tests/tests.rs b/tests/tests.rs index 2bfefe19..55f283df 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -8,8 +8,8 @@ use std::sync::Arc; use std::{error, f32, f64, fmt}; use mlua::{ - ChunkMode, Error, ExternalError, Function, Lua, LuaOptions, Nil, Result, StdLib, String, Table, - UserData, Value, Variadic, + ChunkMode, Error, ExternalError, Function, IntoLua, Lua, LuaOptions, Nil, Result, StdLib, + String, Table, UserData, Value, Variadic, }; #[cfg(not(feature = "luau"))] @@ -779,6 +779,35 @@ fn test_registry_value() -> Result<()> { Ok(()) } +#[test] +fn test_registry_value_into_lua() -> Result<()> { + let lua = Lua::new(); + + let t = lua.create_table()?; + let r = lua.create_registry_value(t)?; + let f = lua.create_function(|_, t: Table| t.raw_set("hello", "world"))?; + + f.call(&r)?; + let v = r.into_lua(&lua)?; + let t = v.as_table().unwrap(); + assert_eq!(t.get::<_, String>("hello")?, "world"); + + // Try to set nil registry key + let r_nil = lua.create_registry_value(Value::Nil)?; + t.set("hello", &r_nil)?; + assert_eq!(t.get::<_, Value>("hello")?, Value::Nil); + + // Check non-owned registry key + let lua2 = Lua::new(); + let r2 = lua2.create_registry_value("abc")?; + assert!(matches!( + f.call::<_, ()>(&r2), + Err(Error::MismatchedRegistryKey) + )); + + Ok(()) +} + #[test] fn test_drop_registry_value() -> Result<()> { struct MyUserdata(Arc<()>); From 804972b0992755b0f532f6b1032e44b9602fdfea Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 23 Jan 2024 20:50:41 +0000 Subject: [PATCH 038/635] Fix typos in examples/guided_tour --- examples/guided_tour.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/guided_tour.rs b/examples/guided_tour.rs index becd6268..26b50adf 100644 --- a/examples/guided_tour.rs +++ b/examples/guided_tour.rs @@ -24,7 +24,7 @@ fn main() -> Result<()> { // You can load and evaluate Lua code. The returned type of `Lua::load` is a builder // that allows you to change settings before running Lua code. Here, we are using it to set - // the name of the laoded chunk to "example code", which will be used when Lua error + // the name of the loaded chunk to "example code", which will be used when Lua error // messages are printed. lua.load( @@ -89,7 +89,7 @@ fn main() -> Result<()> { let print: Function = globals.get("print")?; print.call::<_, ()>("hello from rust")?; - // This API generally handles variadics using tuples. This is one way to call a function with + // This API generally handles variadic using tuples. This is one way to call a function with // multiple parameters: print.call::<_, ()>(("hello", "again", "from", "rust"))?; @@ -100,7 +100,7 @@ fn main() -> Result<()> { ["hello", "yet", "again", "from", "rust"].iter().cloned(), ))?; - // You can bind rust functions to Lua as well. Callbacks receive the Lua state inself as their + // You can bind rust functions to Lua as well. Callbacks receive the Lua state itself as their // first parameter, and the arguments given to the function as the second parameter. The type // of the arguments can be anything that is convertible from the parameters given by Lua, in // this case, the function expects two string sequences. From fe6ab250bff646b84faf0fd0a40ef18b00fc3847 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 23 Jan 2024 21:56:00 +0000 Subject: [PATCH 039/635] Update codecov links after moving repo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ff6be708..6ae3958d 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ [crates.io]: https://crates.io/crates/mlua [API Documentation]: https://docs.rs/mlua/badge.svg [docs.rs]: https://docs.rs/mlua -[Coverage Status]: https://codecov.io/gh/khvzak/mlua/branch/master/graph/badge.svg?token=99339FS1CG -[codecov.io]: https://codecov.io/gh/khvzak/mlua +[Coverage Status]: https://codecov.io/gh/mlua-rs/mlua/branch/master/graph/badge.svg?token=99339FS1CG +[codecov.io]: https://codecov.io/gh/mlua-rs/mlua [MSRV]: https://img.shields.io/badge/rust-1.71+-brightgreen.svg?&logo=rust [Guided Tour] | [Benchmarks] | [FAQ] From 8200bee467bfe3f6c9f55813080d4d4dedd46d0b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 21 Jan 2024 23:38:02 +0000 Subject: [PATCH 040/635] Implement IntoLua for ref to String/Table/Function/AnyUserData This would prevent cloning plus has better performance when pushing values to Lua stack (`IntoLua::push_into_stack` method) --- src/conversion.rs | 102 ++++++++++++++++++++++++++ src/lua.rs | 9 +++ tests/async.rs | 2 +- tests/conversion.rs | 175 +++++++++++++++++++++++++++++++++++++++++++- tests/serde.rs | 2 +- tests/tests.rs | 37 +--------- tests/thread.rs | 2 +- tests/userdata.rs | 6 +- 8 files changed, 295 insertions(+), 40 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index 1321b421..74d532ea 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -44,6 +44,18 @@ impl<'lua> IntoLua<'lua> for String<'lua> { } } +impl<'lua> IntoLua<'lua> for &String<'lua> { + #[inline] + fn into_lua(self, _: &'lua Lua) -> Result> { + Ok(Value::String(self.clone())) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + Ok(lua.push_ref(&self.0)) + } +} + impl<'lua> FromLua<'lua> for String<'lua> { #[inline] fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result> { @@ -64,6 +76,18 @@ impl<'lua> IntoLua<'lua> for Table<'lua> { } } +impl<'lua> IntoLua<'lua> for &Table<'lua> { + #[inline] + fn into_lua(self, _: &'lua Lua) -> Result> { + Ok(Value::Table(self.clone())) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + Ok(lua.push_ref(&self.0)) + } +} + impl<'lua> FromLua<'lua> for Table<'lua> { #[inline] fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result> { @@ -87,6 +111,20 @@ impl<'lua> IntoLua<'lua> for OwnedTable { } } +#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] +impl<'lua> IntoLua<'lua> for &OwnedTable { + #[inline] + fn into_lua(self, lua: &'lua Lua) -> Result> { + OwnedTable::into_lua(self.clone(), lua) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + Ok(lua.push_owned_ref(&self.0)) + } +} + #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] impl<'lua> FromLua<'lua> for OwnedTable { @@ -103,6 +141,18 @@ impl<'lua> IntoLua<'lua> for Function<'lua> { } } +impl<'lua> IntoLua<'lua> for &Function<'lua> { + #[inline] + fn into_lua(self, _: &'lua Lua) -> Result> { + Ok(Value::Function(self.clone())) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + Ok(lua.push_ref(&self.0)) + } +} + impl<'lua> FromLua<'lua> for Function<'lua> { #[inline] fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result> { @@ -126,6 +176,20 @@ impl<'lua> IntoLua<'lua> for OwnedFunction { } } +#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] +impl<'lua> IntoLua<'lua> for &OwnedFunction { + #[inline] + fn into_lua(self, lua: &'lua Lua) -> Result> { + OwnedFunction::into_lua(self.clone(), lua) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + Ok(lua.push_owned_ref(&self.0)) + } +} + #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] impl<'lua> FromLua<'lua> for OwnedFunction { @@ -142,6 +206,18 @@ impl<'lua> IntoLua<'lua> for Thread<'lua> { } } +impl<'lua> IntoLua<'lua> for &Thread<'lua> { + #[inline] + fn into_lua(self, _: &'lua Lua) -> Result> { + Ok(Value::Thread(self.clone())) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + Ok(lua.push_ref(&self.0)) + } +} + impl<'lua> FromLua<'lua> for Thread<'lua> { #[inline] fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result> { @@ -163,6 +239,18 @@ impl<'lua> IntoLua<'lua> for AnyUserData<'lua> { } } +impl<'lua> IntoLua<'lua> for &AnyUserData<'lua> { + #[inline] + fn into_lua(self, _: &'lua Lua) -> Result> { + Ok(Value::UserData(self.clone())) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + Ok(lua.push_ref(&self.0)) + } +} + impl<'lua> FromLua<'lua> for AnyUserData<'lua> { #[inline] fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result> { @@ -189,6 +277,20 @@ impl<'lua> IntoLua<'lua> for OwnedAnyUserData { } } +#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] +impl<'lua> IntoLua<'lua> for &OwnedAnyUserData { + #[inline] + fn into_lua(self, lua: &'lua Lua) -> Result> { + OwnedAnyUserData::into_lua(self.clone(), lua) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + Ok(lua.push_owned_ref(&self.0)) + } +} + #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] impl<'lua> FromLua<'lua> for OwnedAnyUserData { diff --git a/src/lua.rs b/src/lua.rs index 6d22e053..358a34e3 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -2558,6 +2558,15 @@ impl Lua { ffi::lua_xpush(self.ref_thread(), self.state(), lref.index); } + #[cfg(all(feature = "unstable", not(feature = "send")))] + pub(crate) unsafe fn push_owned_ref(&self, loref: &crate::types::LuaOwnedRef) { + assert!( + Arc::ptr_eq(&loref.inner, &self.0), + "Lua instance passed Value created from a different main Lua state" + ); + ffi::lua_xpush(self.ref_thread(), self.state(), loref.index); + } + // Pops the topmost element of the stack and stores a reference to it. This pins the object, // preventing garbage collection until the returned `LuaRef` is dropped. // diff --git a/tests/async.rs b/tests/async.rs index 70559142..9c4c8652 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -443,7 +443,7 @@ async fn test_async_userdata() -> Result<()> { let globals = lua.globals(); let userdata = lua.create_userdata(MyUserData(11))?; - globals.set("userdata", userdata.clone())?; + globals.set("userdata", &userdata)?; lua.load( r#" diff --git a/tests/conversion.rs b/tests/conversion.rs index 6d17d0ad..8dc0ad21 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -3,7 +3,180 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::ffi::{CStr, CString}; use maplit::{btreemap, btreeset, hashmap, hashset}; -use mlua::{Error, Lua, Result}; +use mlua::{AnyUserData, Error, Function, IntoLua, Lua, Result, Table, Thread, UserDataRef, Value}; + +#[test] +fn test_string_into_lua() -> Result<()> { + let lua = Lua::new(); + + // Direct conversion + let s = lua.create_string("hello, world!")?; + let s2 = (&s).into_lua(&lua)?; + assert_eq!(s, s2.as_string().unwrap()); + + // Push into stack + let table = lua.create_table()?; + table.set("s", &s)?; + assert_eq!(s, table.get::<_, String>("s")?); + + Ok(()) +} + +#[test] +fn test_table_into_lua() -> Result<()> { + let lua = Lua::new(); + + // Direct conversion + let t = lua.create_table()?; + let t2 = (&t).into_lua(&lua)?; + assert_eq!(&t, t2.as_table().unwrap()); + + // Push into stack + let f = lua.create_function(|_, (t, s): (Table, String)| t.set("s", s))?; + f.call((&t, "hello"))?; + assert_eq!("hello", t.get::<_, String>("s")?); + + Ok(()) +} + +#[cfg(all(feature = "unstable", not(feature = "send")))] +#[test] +fn test_owned_table_into_lua() -> Result<()> { + let lua = Lua::new(); + + // Direct conversion + let t = lua.create_table()?.into_owned(); + let t2 = (&t).into_lua(&lua)?; + assert_eq!(t.to_ref(), *t2.as_table().unwrap()); + + // Push into stack + let f = lua.create_function(|_, (t, s): (Table, String)| t.set("s", s))?; + f.call((&t, "hello"))?; + assert_eq!("hello", t.to_ref().get::<_, String>("s")?); + + Ok(()) +} + +#[test] +fn test_function_into_lua() -> Result<()> { + let lua = Lua::new(); + + // Direct conversion + let f = lua.create_function(|_, ()| Ok::<_, Error>(()))?; + let f2 = (&f).into_lua(&lua)?; + assert_eq!(&f, f2.as_function().unwrap()); + + // Push into stack + let table = lua.create_table()?; + table.set("f", &f)?; + assert_eq!(f, table.get::<_, Function>("f")?); + + Ok(()) +} + +#[cfg(all(feature = "unstable", not(feature = "send")))] +#[test] +fn test_owned_function_into_lua() -> Result<()> { + let lua = Lua::new(); + + // Direct conversion + let f = lua + .create_function(|_, ()| Ok::<_, Error>(()))? + .into_owned(); + let f2 = (&f).into_lua(&lua)?; + assert_eq!(f.to_ref(), *f2.as_function().unwrap()); + + // Push into stack + let table = lua.create_table()?; + table.set("f", &f)?; + assert_eq!(f.to_ref(), table.get::<_, Function>("f")?); + + Ok(()) +} + +#[test] +fn test_thread_into_lua() -> Result<()> { + let lua = Lua::new(); + + // Direct conversion + let f = lua.create_function(|_, ()| Ok::<_, Error>(()))?; + let th = lua.create_thread(f)?; + let th2 = (&th).into_lua(&lua)?; + assert_eq!(&th, th2.as_thread().unwrap()); + + // Push into stack + let table = lua.create_table()?; + table.set("th", &th)?; + assert_eq!(th, table.get::<_, Thread>("th")?); + + Ok(()) +} + +#[test] +fn test_anyuserdata_into_lua() -> Result<()> { + let lua = Lua::new(); + + // Direct conversion + let ud = lua.create_any_userdata(String::from("hello"))?; + let ud2 = (&ud).into_lua(&lua)?; + assert_eq!(&ud, ud2.as_userdata().unwrap()); + + // Push into stack + let table = lua.create_table()?; + table.set("ud", &ud)?; + assert_eq!(ud, table.get::<_, AnyUserData>("ud")?); + assert_eq!("hello", *table.get::<_, UserDataRef>("ud")?); + + Ok(()) +} + +#[cfg(all(feature = "unstable", not(feature = "send")))] +#[test] +fn test_owned_anyuserdata_into_lua() -> Result<()> { + let lua = Lua::new(); + + // Direct conversion + let ud = lua.create_any_userdata(String::from("hello"))?.into_owned(); + let ud2 = (&ud).into_lua(&lua)?; + assert_eq!(ud.to_ref(), *ud2.as_userdata().unwrap()); + + // Push into stack + let table = lua.create_table()?; + table.set("ud", &ud)?; + assert_eq!(ud.to_ref(), table.get::<_, AnyUserData>("ud")?); + assert_eq!("hello", *table.get::<_, UserDataRef>("ud")?); + + Ok(()) +} + +#[test] +fn test_registry_value_into_lua() -> Result<()> { + let lua = Lua::new(); + + let t = lua.create_table()?; + let r = lua.create_registry_value(t)?; + let f = lua.create_function(|_, t: Table| t.raw_set("hello", "world"))?; + + f.call(&r)?; + let v = r.into_lua(&lua)?; + let t = v.as_table().unwrap(); + assert_eq!(t.get::<_, String>("hello")?, "world"); + + // Try to set nil registry key + let r_nil = lua.create_registry_value(Value::Nil)?; + t.set("hello", &r_nil)?; + assert_eq!(t.get::<_, Value>("hello")?, Value::Nil); + + // Check non-owned registry key + let lua2 = Lua::new(); + let r2 = lua2.create_registry_value("abc")?; + assert!(matches!( + f.call::<_, ()>(&r2), + Err(Error::MismatchedRegistryKey) + )); + + Ok(()) +} #[test] fn test_conv_vec() -> Result<()> { diff --git a/tests/serde.rs b/tests/serde.rs index 1fdd709d..168407fb 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -598,7 +598,7 @@ fn test_from_value_with_options() -> Result<(), Box> { // Check recursion when using `Serialize` impl let t = lua.create_table()?; - t.set("t", t.clone())?; + t.set("t", &t)?; assert!(serde_json::to_string(&t).is_err()); // Serialize Lua globals table diff --git a/tests/tests.rs b/tests/tests.rs index 55f283df..0310a824 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -8,8 +8,8 @@ use std::sync::Arc; use std::{error, f32, f64, fmt}; use mlua::{ - ChunkMode, Error, ExternalError, Function, IntoLua, Lua, LuaOptions, Nil, Result, StdLib, - String, Table, UserData, Value, Variadic, + ChunkMode, Error, ExternalError, Function, Lua, LuaOptions, Nil, Result, StdLib, String, Table, + UserData, Value, Variadic, }; #[cfg(not(feature = "luau"))] @@ -779,35 +779,6 @@ fn test_registry_value() -> Result<()> { Ok(()) } -#[test] -fn test_registry_value_into_lua() -> Result<()> { - let lua = Lua::new(); - - let t = lua.create_table()?; - let r = lua.create_registry_value(t)?; - let f = lua.create_function(|_, t: Table| t.raw_set("hello", "world"))?; - - f.call(&r)?; - let v = r.into_lua(&lua)?; - let t = v.as_table().unwrap(); - assert_eq!(t.get::<_, String>("hello")?, "world"); - - // Try to set nil registry key - let r_nil = lua.create_registry_value(Value::Nil)?; - t.set("hello", &r_nil)?; - assert_eq!(t.get::<_, Value>("hello")?, Value::Nil); - - // Check non-owned registry key - let lua2 = Lua::new(); - let r2 = lua2.create_registry_value("abc")?; - assert!(matches!( - f.call::<_, ()>(&r2), - Err(Error::MismatchedRegistryKey) - )); - - Ok(()) -} - #[test] fn test_drop_registry_value() -> Result<()> { struct MyUserdata(Arc<()>); @@ -994,7 +965,7 @@ fn test_recursion() -> Result<()> { Ok(()) })?; - lua.globals().set("f", f.clone())?; + lua.globals().set("f", &f)?; f.call::<_, ()>(1)?; Ok(()) @@ -1032,7 +1003,7 @@ fn test_too_many_recursions() -> Result<()> { let f = lua .create_function(move |lua, ()| lua.globals().get::<_, Function>("f")?.call::<_, ()>(()))?; - lua.globals().set("f", f.clone())?; + lua.globals().set("f", &f)?; assert!(f.call::<_, ()>(()).is_err()); Ok(()) diff --git a/tests/thread.rs b/tests/thread.rs index 9a84a2c8..0473e88f 100644 --- a/tests/thread.rs +++ b/tests/thread.rs @@ -182,7 +182,7 @@ fn test_coroutine_panic() { let thrd_main = lua.create_function(|_, ()| -> Result<()> { panic!("test_panic"); })?; - lua.globals().set("main", thrd_main.clone())?; + lua.globals().set("main", &thrd_main)?; let thrd: Thread = lua.create_thread(thrd_main)?; thrd.resume(()) }) { diff --git a/tests/userdata.rs b/tests/userdata.rs index da6c40d8..141e3294 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -58,7 +58,7 @@ fn test_methods() -> Result<()> { fn check_methods(lua: &Lua, userdata: AnyUserData) -> Result<()> { let globals = lua.globals(); - globals.set("userdata", userdata.clone())?; + globals.set("userdata", &userdata)?; lua.load( r#" function get_it() @@ -342,7 +342,7 @@ fn test_userdata_take() -> Result<()> { } fn check_userdata_take(lua: &Lua, userdata: AnyUserData, rc: Arc) -> Result<()> { - lua.globals().set("userdata", userdata.clone())?; + lua.globals().set("userdata", &userdata)?; assert_eq!(Arc::strong_count(&rc), 3); { let _value = userdata.borrow::()?; @@ -474,7 +474,7 @@ fn test_functions() -> Result<()> { let lua = Lua::new(); let globals = lua.globals(); let userdata = lua.create_userdata(MyUserData(42))?; - globals.set("userdata", userdata.clone())?; + globals.set("userdata", &userdata)?; lua.load( r#" function get_it() From 2ac7b235960ca6676b84f6b21cb2c199c17b5d99 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 24 Jan 2024 21:58:44 +0000 Subject: [PATCH 041/635] Impl Into/FromLua for `OwnedThread` --- src/conversion.rs | 36 +++++++++++++++++++++++++++++++++++- tests/conversion.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/conversion.rs b/src/conversion.rs index 74d532ea..3d26b341 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -21,7 +21,9 @@ use crate::userdata::{AnyUserData, UserData, UserDataRef, UserDataRefMut}; use crate::value::{FromLua, IntoLua, Nil, Value}; #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] -use crate::{function::OwnedFunction, table::OwnedTable, userdata::OwnedAnyUserData}; +use crate::{ + function::OwnedFunction, table::OwnedTable, thread::OwnedThread, userdata::OwnedAnyUserData, +}; impl<'lua> IntoLua<'lua> for Value<'lua> { #[inline] @@ -232,6 +234,38 @@ impl<'lua> FromLua<'lua> for Thread<'lua> { } } +#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] +impl<'lua> IntoLua<'lua> for OwnedThread { + #[inline] + fn into_lua(self, lua: &'lua Lua) -> Result> { + Ok(Value::Thread(Thread(lua.adopt_owned_ref(self.0), self.1))) + } +} + +#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] +impl<'lua> IntoLua<'lua> for &OwnedThread { + #[inline] + fn into_lua(self, lua: &'lua Lua) -> Result> { + OwnedThread::into_lua(self.clone(), lua) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + Ok(lua.push_owned_ref(&self.0)) + } +} + +#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] +impl<'lua> FromLua<'lua> for OwnedThread { + #[inline] + fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { + Thread::from_lua(value, lua).map(|s| s.into_owned()) + } +} + impl<'lua> IntoLua<'lua> for AnyUserData<'lua> { #[inline] fn into_lua(self, _: &'lua Lua) -> Result> { diff --git a/tests/conversion.rs b/tests/conversion.rs index 8dc0ad21..d3ee8f87 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -112,6 +112,36 @@ fn test_thread_into_lua() -> Result<()> { Ok(()) } +#[cfg(all(feature = "unstable", not(feature = "send")))] +#[test] +fn test_owned_thread_into_lua() -> Result<()> { + let lua = Lua::new(); + + // Direct conversion + let f = lua.create_function(|_, ()| Ok::<_, Error>(()))?; + let th = lua.create_thread(f)?.into_owned(); + let th2 = (&th).into_lua(&lua)?; + assert_eq!(&th.to_ref(), th2.as_thread().unwrap()); + + // Push into stack + let table = lua.create_table()?; + table.set("th", &th)?; + assert_eq!(th.to_ref(), table.get::<_, Thread>("th")?); + + Ok(()) +} + +#[cfg(all(feature = "unstable", not(feature = "send")))] +#[test] +fn test_owned_thread_from_lua() -> Result<()> { + let lua = Lua::new(); + + let th = lua.unpack::(Value::Thread(lua.current_thread()))?; + assert_eq!(th.to_ref(), lua.current_thread()); + + Ok(()) +} + #[test] fn test_anyuserdata_into_lua() -> Result<()> { let lua = Lua::new(); From e97e69a3091db7131c510a0c405402c44cb94079 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 25 Jan 2024 09:35:40 +0000 Subject: [PATCH 042/635] Update Luau to 0.609 (luau-src v0.8.0) --- mlua-sys/Cargo.toml | 2 +- mlua-sys/src/luau/lua.rs | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index fa5ff6e7..a8c27059 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -40,4 +40,4 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 546.0.2, < 546.1.0", optional = true } luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } -luau0-src = { version = "0.7.11", optional = true } +luau0-src = { version = "0.8.0", optional = true } diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 43aebf1e..084adb8b 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -10,6 +10,12 @@ pub const LUA_MULTRET: c_int = -1; // Max number of Lua stack slots const LUAI_MAXCSTACK: c_int = 1000000; +// Number of valid Lua userdata tags +const LUA_UTAG_LIMIT: c_int = 128; + +// Number of valid Lua lightuserdata tags +const LUA_LUTAG_LIMIT: c_int = 128; + // // Pseudo-indices // @@ -144,9 +150,11 @@ extern "C-unwind" { pub fn lua_objlen(L: *mut lua_State, idx: c_int) -> usize; pub fn lua_tocfunction(L: *mut lua_State, idx: c_int) -> Option; pub fn lua_tolightuserdata(L: *mut lua_State, idx: c_int) -> *mut c_void; + pub fn lua_tolightuserdatatagged(L: *mut lua_State, idx: c_int, tag: c_int) -> *mut c_void; pub fn lua_touserdata(L: *mut lua_State, idx: c_int) -> *mut c_void; pub fn lua_touserdatatagged(L: *mut lua_State, idx: c_int, tag: c_int) -> *mut c_void; pub fn lua_userdatatag(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_lightuserdatatag(L: *mut lua_State, idx: c_int) -> c_int; pub fn lua_tothread(L: *mut lua_State, idx: c_int) -> *mut lua_State; pub fn lua_tobuffer(L: *mut lua_State, idx: c_int, len: *mut usize) -> *mut c_void; pub fn lua_topointer(L: *mut lua_State, idx: c_int) -> *const c_void; @@ -179,7 +187,7 @@ extern "C-unwind" { pub fn lua_pushboolean(L: *mut lua_State, b: c_int); pub fn lua_pushthread(L: *mut lua_State) -> c_int; - pub fn lua_pushlightuserdata(L: *mut lua_State, p: *mut c_void); + pub fn lua_pushlightuserdatatagged(L: *mut lua_State, p: *mut c_void, tag: c_int); pub fn lua_newuserdatatagged(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void; pub fn lua_newuserdatadtor(L: *mut lua_State, sz: usize, dtor: lua_Udestructor) -> *mut c_void; @@ -280,6 +288,8 @@ extern "C-unwind" { pub fn lua_setuserdatatag(L: *mut lua_State, idx: c_int, tag: c_int); pub fn lua_setuserdatadtor(L: *mut lua_State, tag: c_int, dtor: Option); pub fn lua_getuserdatadtor(L: *mut lua_State, tag: c_int) -> Option; + pub fn lua_setlightuserdataname(L: *mut lua_State, tag: c_int, name: *const c_char); + pub fn lua_getlightuserdataname(L: *mut lua_State, tag: c_int) -> *const c_char; pub fn lua_clonefunction(L: *mut lua_State, idx: c_int); pub fn lua_cleartable(L: *mut lua_State, idx: c_int); pub fn lua_getallocf(L: *mut lua_State, ud: *mut *mut c_void) -> lua_Alloc; @@ -398,18 +408,22 @@ pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) { lua_pushlstring_(L, c_str.as_ptr(), c_str.as_bytes().len()) } +#[inline(always)] pub unsafe fn lua_pushcfunction(L: *mut lua_State, f: lua_CFunction) { lua_pushcclosurek(L, f, ptr::null(), 0, None) } +#[inline(always)] pub unsafe fn lua_pushcfunctiond(L: *mut lua_State, f: lua_CFunction, debugname: *const c_char) { lua_pushcclosurek(L, f, debugname, 0, None) } +#[inline(always)] pub unsafe fn lua_pushcclosure(L: *mut lua_State, f: lua_CFunction, nup: c_int) { lua_pushcclosurek(L, f, ptr::null(), nup, None) } +#[inline(always)] pub unsafe fn lua_pushcclosured( L: *mut lua_State, f: lua_CFunction, @@ -419,6 +433,11 @@ pub unsafe fn lua_pushcclosured( lua_pushcclosurek(L, f, debugname, nup, None) } +#[inline(always)] +pub unsafe fn lua_pushlightuserdata(L: *mut lua_State, p: *mut c_void) { + lua_pushlightuserdatatagged(L, p, 0) +} + #[inline(always)] pub unsafe fn lua_setglobal(L: *mut lua_State, var: *const c_char) { lua_setfield(L, LUA_GLOBALSINDEX, var) From 145c5b316b335b81b8b537641806b63193803761 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 25 Jan 2024 10:26:43 +0000 Subject: [PATCH 043/635] Fix `FromLua` derive proc macro to cover more cases --- mlua_derive/src/from_lua.rs | 7 ++++--- tests/userdata.rs | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/mlua_derive/src/from_lua.rs b/mlua_derive/src/from_lua.rs index 9e311e44..3d4e4edf 100644 --- a/mlua_derive/src/from_lua.rs +++ b/mlua_derive/src/from_lua.rs @@ -7,16 +7,17 @@ pub fn from_lua(input: TokenStream) -> TokenStream { ident, generics, .. } = parse_macro_input!(input as DeriveInput); + let ident_str = ident.to_string(); + let (impl_generics, ty_generics, _) = generics.split_for_impl(); let where_clause = match &generics.where_clause { Some(where_clause) => quote! { #where_clause, Self: 'static + Clone }, None => quote! { where Self: 'static + Clone }, }; - let ident_str = ident.to_string(); quote! { - impl #generics ::mlua::FromLua<'_> for #ident #generics #where_clause { + impl #impl_generics ::mlua::FromLua<'_> for #ident #ty_generics #where_clause { #[inline] - fn from_lua(value: ::mlua::Value<'_>, lua: &'_ ::mlua::Lua) -> ::mlua::Result { + fn from_lua(value: ::mlua::Value<'_>, _: &'_ ::mlua::Lua) -> ::mlua::Result { match value { ::mlua::Value::UserData(ud) => Ok(ud.borrow::()?.clone()), _ => Err(::mlua::Error::FromLuaConversionError { diff --git a/tests/userdata.rs b/tests/userdata.rs index 141e3294..0a8d3298 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -991,9 +991,9 @@ fn test_userdata_derive() -> Result<()> { // More complex struct where generics and where clause #[derive(Clone, Copy, mlua::FromLua)] - struct MyUserData2<'a, T>(&'a T) + struct MyUserData2<'a, T: ?Sized>(&'a T) where - T: ?Sized; + T: Copy; lua.register_userdata_type::>(|reg| { reg.add_function("val", |_, this: MyUserData2<'static, i32>| Ok(*this.0)); From 38eec1236c0147e38cf05c5f5cb1cca8950417f3 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 25 Jan 2024 10:49:39 +0000 Subject: [PATCH 044/635] Update itertools dependency --- mlua_derive/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlua_derive/Cargo.toml b/mlua_derive/Cargo.toml index 00ebe6ba..c4a95990 100644 --- a/mlua_derive/Cargo.toml +++ b/mlua_derive/Cargo.toml @@ -19,6 +19,6 @@ quote = "1.0" proc-macro2 = { version = "1.0", features = ["span-locations"] } proc-macro-error = { version = "1.0", optional = true } syn = { version = "2.0", features = ["full"] } -itertools = { version = "0.11", optional = true } +itertools = { version = "0.12", optional = true } regex = { version = "1.4", optional = true } once_cell = { version = "1.0", optional = true } From 45299c0ef1c6f1f45fdb6f527d4ae380f666d4e9 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 25 Jan 2024 12:56:57 +0000 Subject: [PATCH 045/635] Update authors --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 43984844..ef9a6c74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mlua" version = "0.9.4" # remember to update mlua_derive -authors = ["Aleksandr Orlenko ", "kyren "] +authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.71" edition = "2021" repository = "https://github.com/khvzak/mlua" From df778b7b33c9648ddd0ad20edc6ee9e12dffca31 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 25 Jan 2024 18:04:55 +0000 Subject: [PATCH 046/635] Impl Into/FromLua for `OwnedString` --- src/conversion.rs | 35 ++++++++++++++++++++++++++++++++++- tests/conversion.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/conversion.rs b/src/conversion.rs index 3d26b341..4542e2d8 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -22,7 +22,8 @@ use crate::value::{FromLua, IntoLua, Nil, Value}; #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] use crate::{ - function::OwnedFunction, table::OwnedTable, thread::OwnedThread, userdata::OwnedAnyUserData, + function::OwnedFunction, string::OwnedString, table::OwnedTable, thread::OwnedThread, + userdata::OwnedAnyUserData, }; impl<'lua> IntoLua<'lua> for Value<'lua> { @@ -71,6 +72,38 @@ impl<'lua> FromLua<'lua> for String<'lua> { } } +#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] +impl<'lua> IntoLua<'lua> for OwnedString { + #[inline] + fn into_lua(self, lua: &'lua Lua) -> Result> { + Ok(Value::String(String(lua.adopt_owned_ref(self.0)))) + } +} + +#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] +impl<'lua> IntoLua<'lua> for &OwnedString { + #[inline] + fn into_lua(self, lua: &'lua Lua) -> Result> { + OwnedString::into_lua(self.clone(), lua) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + Ok(lua.push_owned_ref(&self.0)) + } +} + +#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] +impl<'lua> FromLua<'lua> for OwnedString { + #[inline] + fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { + String::from_lua(value, lua).map(|s| s.into_owned()) + } +} + impl<'lua> IntoLua<'lua> for Table<'lua> { #[inline] fn into_lua(self, _: &'lua Lua) -> Result> { diff --git a/tests/conversion.rs b/tests/conversion.rs index d3ee8f87..4e011ed7 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -22,6 +22,35 @@ fn test_string_into_lua() -> Result<()> { Ok(()) } +#[cfg(all(feature = "unstable", not(feature = "send")))] +#[test] +fn test_owned_string_into_lua() -> Result<()> { + let lua = Lua::new(); + + // Direct conversion + let s = lua.create_string("hello, world")?.into_owned(); + let s2 = (&s).into_lua(&lua)?; + assert_eq!(s.to_ref(), *s2.as_string().unwrap()); + + // Push into stack + let table = lua.create_table()?; + table.set("s", &s)?; + assert_eq!(s.to_ref(), table.get::<_, String>("s")?); + + Ok(()) +} + +#[cfg(all(feature = "unstable", not(feature = "send")))] +#[test] +fn test_owned_string_from_lua() -> Result<()> { + let lua = Lua::new(); + + let s = lua.unpack::(lua.pack("hello, world")?)?; + assert_eq!(s.to_ref(), "hello, world"); + + Ok(()) +} + #[test] fn test_table_into_lua() -> Result<()> { let lua = Lua::new(); From 60730fd068fa14726cc923798d60bfd586e8f4a2 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 25 Jan 2024 18:19:16 +0000 Subject: [PATCH 047/635] Update compile tests messages --- .../compile/async_any_userdata_method.stderr | 4 +- tests/compile/lua_norefunwindsafe.stderr | 48 ++++++++++++--- tests/compile/non_send.stderr | 9 ++- tests/compile/ref_nounwindsafe.stderr | 60 +++++++++++++++---- tests/compile/scope_userdata_borrow.stderr | 2 + tests/compile/static_callback_args.stderr | 44 +++++++------- 6 files changed, 121 insertions(+), 46 deletions(-) diff --git a/tests/compile/async_any_userdata_method.stderr b/tests/compile/async_any_userdata_method.stderr index d5e5c38f..f1b5e179 100644 --- a/tests/compile/async_any_userdata_method.stderr +++ b/tests/compile/async_any_userdata_method.stderr @@ -4,7 +4,7 @@ error: lifetime may not live long enough 9 | reg.add_async_method("t", |_, this: &String, ()| async { | ___________________________________----------------------_^ | | | | - | | | return type of closure `[async block@$DIR/tests/compile/async_any_userdata_method.rs:9:58: 12:10]` contains a lifetime `'2` + | | | return type of closure `{async block@$DIR/tests/compile/async_any_userdata_method.rs:9:58: 12:10}` contains a lifetime `'2` | | lifetime `'1` represents this closure's body 10 | | s = this; 11 | | Ok(()) @@ -27,6 +27,8 @@ error[E0596]: cannot borrow `s` as mutable, as it is a captured variable in a `F error[E0597]: `s` does not live long enough --> tests/compile/async_any_userdata_method.rs:8:21 | +7 | let s = String::new(); + | - binding `s` declared here 8 | let mut s = &s; | ^^ borrowed value does not live long enough 9 | / reg.add_async_method("t", |_, this: &String, ()| async { diff --git a/tests/compile/lua_norefunwindsafe.stderr b/tests/compile/lua_norefunwindsafe.stderr index a442d0b8..9771dd11 100644 --- a/tests/compile/lua_norefunwindsafe.stderr +++ b/tests/compile/lua_norefunwindsafe.stderr @@ -7,14 +7,46 @@ error[E0277]: the type `UnsafeCell` may contain interior m | required by a bound introduced by this call | = help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell` - = note: required because it appears within the type `ArcInner>` - = note: required because it appears within the type `PhantomData>>` - = note: required because it appears within the type `Arc>` - = note: required because it appears within the type `LuaInner` - = note: required because it appears within the type `ArcInner` - = note: required because it appears within the type `PhantomData>` - = note: required because it appears within the type `Arc` - = note: required because it appears within the type `Lua` +note: required because it appears within the type `ArcInner>` + --> $RUST/alloc/src/sync.rs + | + | struct ArcInner { + | ^^^^^^^^ +note: required because it appears within the type `PhantomData>>` + --> $RUST/core/src/marker.rs + | + | pub struct PhantomData; + | ^^^^^^^^^^^ +note: required because it appears within the type `Arc>` + --> $RUST/alloc/src/sync.rs + | + | pub struct Arc< + | ^^^ +note: required because it appears within the type `LuaInner` + --> src/lua.rs + | + | pub struct LuaInner { + | ^^^^^^^^ +note: required because it appears within the type `ArcInner` + --> $RUST/alloc/src/sync.rs + | + | struct ArcInner { + | ^^^^^^^^ +note: required because it appears within the type `PhantomData>` + --> $RUST/core/src/marker.rs + | + | pub struct PhantomData; + | ^^^^^^^^^^^ +note: required because it appears within the type `Arc` + --> $RUST/alloc/src/sync.rs + | + | pub struct Arc< + | ^^^ +note: required because it appears within the type `Lua` + --> src/lua.rs + | + | pub struct Lua(Arc); + | ^^^ = note: required for `&Lua` to implement `UnwindSafe` note: required because it's used within this closure --> tests/compile/lua_norefunwindsafe.rs:7:18 diff --git a/tests/compile/non_send.stderr b/tests/compile/non_send.stderr index 38fc4041..408b682f 100644 --- a/tests/compile/non_send.stderr +++ b/tests/compile/non_send.stderr @@ -4,22 +4,25 @@ error[E0277]: `Rc>` cannot be sent between threads safely 11 | lua.create_function(move |_, ()| { | --------------- ^----------- | | | - | _________|_______________within this `[closure@$DIR/tests/compile/non_send.rs:11:25: 11:37]` + | _________|_______________within this `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}` | | | | | required by a bound introduced by this call 12 | | Ok(data.get()) 13 | | })? | |_____^ `Rc>` cannot be sent between threads safely | - = help: within `[closure@$DIR/tests/compile/non_send.rs:11:25: 11:37]`, the trait `Send` is not implemented for `Rc>` + = help: within `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}`, the trait `Send` is not implemented for `Rc>` note: required because it's used within this closure --> tests/compile/non_send.rs:11:25 | 11 | lua.create_function(move |_, ()| { | ^^^^^^^^^^^^ - = note: required for `[closure@$DIR/tests/compile/non_send.rs:11:25: 11:37]` to implement `mlua::types::MaybeSend` + = note: required for `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}` to implement `mlua::types::MaybeSend` note: required by a bound in `Lua::create_function` --> src/lua.rs | + | pub fn create_function<'lua, A, R, F>(&'lua self, func: F) -> Result> + | --------------- required by a bound in this associated function +... | F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, | ^^^^^^^^^ required by this bound in `Lua::create_function` diff --git a/tests/compile/ref_nounwindsafe.stderr b/tests/compile/ref_nounwindsafe.stderr index 0921840e..342dc7b4 100644 --- a/tests/compile/ref_nounwindsafe.stderr +++ b/tests/compile/ref_nounwindsafe.stderr @@ -7,17 +7,57 @@ error[E0277]: the type `UnsafeCell` may contain interior m | required by a bound introduced by this call | = help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell` - = note: required because it appears within the type `ArcInner>` - = note: required because it appears within the type `PhantomData>>` - = note: required because it appears within the type `Arc>` - = note: required because it appears within the type `LuaInner` - = note: required because it appears within the type `ArcInner` - = note: required because it appears within the type `PhantomData>` - = note: required because it appears within the type `Arc` - = note: required because it appears within the type `Lua` +note: required because it appears within the type `ArcInner>` + --> $RUST/alloc/src/sync.rs + | + | struct ArcInner { + | ^^^^^^^^ +note: required because it appears within the type `PhantomData>>` + --> $RUST/core/src/marker.rs + | + | pub struct PhantomData; + | ^^^^^^^^^^^ +note: required because it appears within the type `Arc>` + --> $RUST/alloc/src/sync.rs + | + | pub struct Arc< + | ^^^ +note: required because it appears within the type `LuaInner` + --> src/lua.rs + | + | pub struct LuaInner { + | ^^^^^^^^ +note: required because it appears within the type `ArcInner` + --> $RUST/alloc/src/sync.rs + | + | struct ArcInner { + | ^^^^^^^^ +note: required because it appears within the type `PhantomData>` + --> $RUST/core/src/marker.rs + | + | pub struct PhantomData; + | ^^^^^^^^^^^ +note: required because it appears within the type `Arc` + --> $RUST/alloc/src/sync.rs + | + | pub struct Arc< + | ^^^ +note: required because it appears within the type `Lua` + --> src/lua.rs + | + | pub struct Lua(Arc); + | ^^^ = note: required for `&Lua` to implement `UnwindSafe` - = note: required because it appears within the type `LuaRef<'_>` - = note: required because it appears within the type `Table<'_>` +note: required because it appears within the type `LuaRef<'_>` + --> src/types.rs + | + | pub(crate) struct LuaRef<'lua> { + | ^^^^^^ +note: required because it appears within the type `Table<'_>` + --> src/table.rs + | + | pub struct Table<'lua>(pub(crate) LuaRef<'lua>); + | ^^^^^ note: required because it's used within this closure --> tests/compile/ref_nounwindsafe.rs:8:18 | diff --git a/tests/compile/scope_userdata_borrow.stderr b/tests/compile/scope_userdata_borrow.stderr index 9d898049..132acddc 100644 --- a/tests/compile/scope_userdata_borrow.stderr +++ b/tests/compile/scope_userdata_borrow.stderr @@ -4,6 +4,8 @@ error[E0597]: `ibad` does not live long enough 11 | lua.scope(|scope| { | ----- has type `&mlua::Scope<'_, '1>` ... +14 | let ibad = 42; + | ---- binding `ibad` declared here 15 | scope.create_nonstatic_userdata(MyUserData(&ibad)).unwrap(); | -------------------------------------------^^^^^-- | | | diff --git a/tests/compile/static_callback_args.stderr b/tests/compile/static_callback_args.stderr index 81da66ca..2bf660b9 100644 --- a/tests/compile/static_callback_args.stderr +++ b/tests/compile/static_callback_args.stderr @@ -1,35 +1,31 @@ error[E0597]: `lua` does not live long enough --> tests/compile/static_callback_args.rs:12:5 | -10 | let lua = Lua::new(); - | --- binding `lua` declared here +10 | let lua = Lua::new(); + | --- binding `lua` declared here 11 | -12 | / lua.create_function(|_, table: Table| { -13 | |/ BAD_TIME.with(|bt| { -14 | || *bt.borrow_mut() = Some(table); -15 | || }); - | ||__________- argument requires that `lua` is borrowed for `'static` -16 | | Ok(()) -17 | | })? - | |_______^ borrowed value does not live long enough +12 | lua.create_function(|_, table: Table| { + | ^^^ borrowed value does not live long enough +13 | / BAD_TIME.with(|bt| { +14 | | *bt.borrow_mut() = Some(table); +15 | | }); + | |__________- argument requires that `lua` is borrowed for `'static` ... -32 | } - | - `lua` dropped here while still borrowed +32 | } + | - `lua` dropped here while still borrowed error[E0505]: cannot move out of `lua` because it is borrowed --> tests/compile/static_callback_args.rs:22:10 | -10 | let lua = Lua::new(); - | --- binding `lua` declared here +10 | let lua = Lua::new(); + | --- binding `lua` declared here 11 | -12 | / lua.create_function(|_, table: Table| { -13 | |/ BAD_TIME.with(|bt| { -14 | || *bt.borrow_mut() = Some(table); -15 | || }); - | ||__________- argument requires that `lua` is borrowed for `'static` -16 | | Ok(()) -17 | | })? - | |_______- borrow of `lua` occurs here +12 | lua.create_function(|_, table: Table| { + | --- borrow of `lua` occurs here +13 | / BAD_TIME.with(|bt| { +14 | | *bt.borrow_mut() = Some(table); +15 | | }); + | |__________- argument requires that `lua` is borrowed for `'static` ... -22 | drop(lua); - | ^^^ move out of `lua` occurs here +22 | drop(lua); + | ^^^ move out of `lua` occurs here From 75a15ceabf06403707d1b15f758611f86dea437b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 25 Jan 2024 22:24:58 +0000 Subject: [PATCH 048/635] v0.9.5 --- CHANGELOG.md | 8 ++++++++ Cargo.toml | 6 +++--- mlua-sys/Cargo.toml | 2 +- mlua_derive/Cargo.toml | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15ba8763..ebbc7f91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## v0.9.5 + +- Minimal Luau updated to 0.609 +- Luau max stack size increased to 1M (from 100K) +- Implemented `IntoLua` for refs to `String`/`Table`/`Function`/`AnyUserData`/`Thread` + `RegistryKey` +- Implemented `IntoLua` and `FromLua` for `OwnedThread`/`OwnedString` +- Fixed `FromLua` derive proc macro to cover more cases + ## v0.9.4 - Fixed loading all-in-one modules under mixed states (eg. main state and coroutines) diff --git a/Cargo.toml b/Cargo.toml index ef9a6c74..3324d467 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.9.4" # remember to update mlua_derive +version = "0.9.5" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.71" edition = "2021" @@ -44,7 +44,7 @@ macros = ["mlua_derive/macros"] unstable = [] [dependencies] -mlua_derive = { version = "=0.9.1", optional = true, path = "mlua_derive" } +mlua_derive = { version = "=0.9.2", optional = true, path = "mlua_derive" } bstr = { version = "1.0", features = ["std"], default_features = false } once_cell = { version = "1.0" } num-traits = { version = "0.2.14" } @@ -55,7 +55,7 @@ erased-serde = { version = "0.4", optional = true } serde-value = { version = "0.7", optional = true } parking_lot = { version = "0.12", optional = true } -ffi = { package = "mlua-sys", version = "0.5.0", path = "mlua-sys" } +ffi = { package = "mlua-sys", version = "0.5.1", path = "mlua-sys" } [target.'cfg(unix)'.dependencies] libloading = { version = "0.8", optional = true } diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index a8c27059..0def6c6d 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.5.0" +version = "0.5.1" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" diff --git a/mlua_derive/Cargo.toml b/mlua_derive/Cargo.toml index c4a95990..2badcc89 100644 --- a/mlua_derive/Cargo.toml +++ b/mlua_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua_derive" -version = "0.9.1" +version = "0.9.2" authors = ["Aleksandr Orlenko "] edition = "2021" description = "Procedural macros for the mlua crate." From dfd82edc426e991acc87394406cc37804575800c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 26 Jan 2024 13:52:19 +0000 Subject: [PATCH 049/635] Add `Lua::push()` helper --- src/lua.rs | 29 +++++++++++++++++------------ src/scope.rs | 10 +++++----- src/userdata.rs | 4 ++-- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/lua.rs b/src/lua.rs index 358a34e3..d1cc10aa 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1413,8 +1413,8 @@ impl Lua { let protect = !self.unlikely_memory_error(); push_table(state, 0, lower_bound, protect)?; for (k, v) in iter { - self.push_value(k.into_lua(self)?)?; - self.push_value(v.into_lua(self)?)?; + self.push(k)?; + self.push(v)?; if protect { protect_lua!(state, 3, 1, fn(state) ffi::lua_rawset(state, -3))?; } else { @@ -1442,7 +1442,7 @@ impl Lua { let protect = !self.unlikely_memory_error(); push_table(state, lower_bound, 0, protect)?; for (i, v) in iter.enumerate() { - self.push_value(v.into_lua(self)?)?; + self.push(v)?; if protect { protect_lua!(state, 2, 1, |state| { ffi::lua_rawseti(state, -2, (i + 1) as Integer); @@ -1977,12 +1977,11 @@ impl Lua { T: IntoLua<'lua>, { let state = self.state(); - let t = t.into_lua(self)?; unsafe { let _sg = StackGuard::new(state); check_stack(state, 5)?; - self.push_value(t)?; + self.push(t)?; rawset_field(state, ffi::LUA_REGISTRYINDEX, name) } } @@ -2269,6 +2268,12 @@ impl Lua { extra.app_data.remove() } + #[doc(hidden)] + #[inline(always)] + pub unsafe fn push<'lua>(&'lua self, value: impl IntoLua<'lua>) -> Result<()> { + value.push_into_stack(self) + } + /// Pushes a value onto the Lua stack. /// /// Uses 2 stack spaces, does not call checkstack. @@ -2643,12 +2648,12 @@ impl Lua { let metatable_nrec = metatable_nrec + registry.async_meta_methods.len(); push_table(state, 0, metatable_nrec, true)?; for (k, m) in registry.meta_methods { - self.push_value(Value::Function(self.create_callback(m)?))?; + self.push(self.create_callback(m)?)?; rawset_field(state, -2, MetaMethod::validate(&k)?)?; } #[cfg(feature = "async")] for (k, m) in registry.async_meta_methods { - self.push_value(Value::Function(self.create_async_callback(m)?))?; + self.push(self.create_async_callback(m)?)?; rawset_field(state, -2, MetaMethod::validate(&k)?)?; } let mut has_name = false; @@ -2699,7 +2704,7 @@ impl Lua { if field_getters_nrec > 0 { push_table(state, 0, field_getters_nrec, true)?; for (k, m) in registry.field_getters { - self.push_value(Value::Function(self.create_callback(m)?))?; + self.push(self.create_callback(m)?)?; rawset_field(state, -2, &k)?; } field_getters_index = Some(ffi::lua_absindex(state, -1)); @@ -2711,7 +2716,7 @@ impl Lua { if field_setters_nrec > 0 { push_table(state, 0, field_setters_nrec, true)?; for (k, m) in registry.field_setters { - self.push_value(Value::Function(self.create_callback(m)?))?; + self.push(self.create_callback(m)?)?; rawset_field(state, -2, &k)?; } field_setters_index = Some(ffi::lua_absindex(state, -1)); @@ -2734,12 +2739,12 @@ impl Lua { } } for (k, m) in registry.methods { - self.push_value(Value::Function(self.create_callback(m)?))?; + self.push(self.create_callback(m)?)?; rawset_field(state, -2, &k)?; } #[cfg(feature = "async")] for (k, m) in registry.async_methods { - self.push_value(Value::Function(self.create_async_callback(m)?))?; + self.push(self.create_async_callback(m)?)?; rawset_field(state, -2, &k)?; } match index_type { @@ -2990,7 +2995,7 @@ impl Lua { nresults => { let results = MultiValue::from_stack_multi(nresults, lua)?; ffi::lua_pushinteger(state, nresults as _); - lua.push_value(Value::Table(lua.create_sequence_from(results)?))?; + lua.push(lua.create_sequence_from(results)?)?; Ok(2) } } diff --git a/src/scope.rs b/src/scope.rs index 350650e9..004324ea 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -19,7 +19,7 @@ use crate::util::{ self, assert_stack, check_stack, init_userdata_metatable, push_string, push_table, rawset_field, short_type_name, take_userdata, StackGuard, }; -use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; +use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; #[cfg(feature = "lua54")] use crate::userdata::USER_VALUE_MAXSLOT; @@ -405,7 +405,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { let meta_methods_nrec = registry.meta_methods.len() + registry.meta_fields.len() + 1; push_table(state, 0, meta_methods_nrec, true)?; for (k, m) in registry.meta_methods { - lua.push_value(Value::Function(wrap_method(self, ud_ptr, &k, m)?))?; + lua.push(wrap_method(self, ud_ptr, &k, m)?)?; rawset_field(state, -2, MetaMethod::validate(&k)?)?; } let mut has_name = false; @@ -455,7 +455,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { if field_getters_nrec > 0 { push_table(state, 0, field_getters_nrec, true)?; for (k, m) in registry.field_getters { - lua.push_value(Value::Function(wrap_method(self, ud_ptr, &k, m)?))?; + lua.push(wrap_method(self, ud_ptr, &k, m)?)?; rawset_field(state, -2, &k)?; } field_getters_index = Some(ffi::lua_absindex(state, -1)); @@ -466,7 +466,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { if field_setters_nrec > 0 { push_table(state, 0, field_setters_nrec, true)?; for (k, m) in registry.field_setters { - lua.push_value(Value::Function(wrap_method(self, ud_ptr, &k, m)?))?; + lua.push(wrap_method(self, ud_ptr, &k, m)?)?; rawset_field(state, -2, &k)?; } field_setters_index = Some(ffi::lua_absindex(state, -1)); @@ -478,7 +478,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { // Create table used for methods lookup push_table(state, 0, methods_nrec, true)?; for (k, m) in registry.methods { - lua.push_value(Value::Function(wrap_method(self, ud_ptr, &k, m)?))?; + lua.push(wrap_method(self, ud_ptr, &k, m)?)?; rawset_field(state, -2, &k)?; } methods_index = Some(ffi::lua_absindex(state, -1)); diff --git a/src/userdata.rs b/src/userdata.rs index a2d730b0..21fc1d43 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -919,7 +919,7 @@ impl<'lua> AnyUserData<'lua> { check_stack(state, 5)?; lua.push_userdata_ref(&self.0)?; - lua.push_value(v.into_lua(lua)?)?; + lua.push(v)?; #[cfg(feature = "lua54")] if n < USER_VALUE_MAXSLOT { @@ -1014,7 +1014,7 @@ impl<'lua> AnyUserData<'lua> { check_stack(state, 5)?; lua.push_userdata_ref(&self.0)?; - lua.push_value(v.into_lua(lua)?)?; + lua.push(v)?; // Multiple (extra) user values are emulated by storing them in a table protect_lua!(state, 2, 0, |state| { From 512921404c7942e7c8a31bcafee7b264681b6007 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 26 Jan 2024 14:07:56 +0000 Subject: [PATCH 050/635] Add fastpath `push_into_stack`/`from_stack` methods for `bool` type --- src/conversion.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/conversion.rs b/src/conversion.rs index 4542e2d8..6820d450 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -434,6 +434,12 @@ impl<'lua> IntoLua<'lua> for bool { fn into_lua(self, _: &'lua Lua) -> Result> { Ok(Value::Boolean(self)) } + + #[inline] + unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + ffi::lua_pushboolean(lua.state(), self as c_int); + Ok(()) + } } impl<'lua> FromLua<'lua> for bool { @@ -445,6 +451,10 @@ impl<'lua> FromLua<'lua> for bool { _ => Ok(true), } } + + unsafe fn from_stack(idx: c_int, lua: &'lua Lua) -> Result { + Ok(ffi::lua_toboolean(lua.state(), idx) != 0) + } } impl<'lua> IntoLua<'lua> for LightUserData { From e30b42522433b31523eb929c1ac37bfbaf07ebfc Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 27 Jan 2024 11:51:59 +0000 Subject: [PATCH 051/635] Fix crash when initializing Luau sandbox without stdlibs (#361) --- mlua-sys/src/luau/lauxlib.rs | 9 ++++++--- tests/luau.rs | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/mlua-sys/src/luau/lauxlib.rs b/mlua-sys/src/luau/lauxlib.rs index 73788af8..9f4384c0 100644 --- a/mlua-sys/src/luau/lauxlib.rs +++ b/mlua-sys/src/luau/lauxlib.rs @@ -144,9 +144,12 @@ pub unsafe fn luaL_sandbox(L: *mut lua_State, enabled: c_int) { // set all builtin metatables to read-only lua_pushliteral(L, ""); - lua_getmetatable(L, -1); - lua_setreadonly(L, -1, enabled); - lua_pop(L, 2); + if lua_getmetatable(L, -1) != 0 { + lua_setreadonly(L, -1, enabled); + lua_pop(L, 2); + } else { + lua_pop(L, 1); + } // set globals to readonly and activate safeenv since the env is immutable lua_setreadonly(L, LUA_GLOBALSINDEX, enabled); diff --git a/tests/luau.rs b/tests/luau.rs index 987ea4fa..aedcc53c 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -275,6 +275,22 @@ fn test_sandbox() -> Result<()> { Ok(()) } +#[test] +fn test_sandbox_nolibs() -> Result<()> { + let lua = Lua::new_with(StdLib::NONE, LuaOptions::default()).unwrap(); + + lua.sandbox(true)?; + lua.load("global = 123").exec()?; + let n: i32 = lua.load("return global").eval()?; + assert_eq!(n, 123); + assert_eq!(lua.globals().get::<_, Option>("global")?, Some(123)); + + lua.sandbox(false)?; + assert_eq!(lua.globals().get::<_, Option>("global")?, None); + + Ok(()) +} + #[test] fn test_sandbox_threads() -> Result<()> { let lua = Lua::new(); From f5982bc204684da6452f5316b1192c3b7a565a3d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 27 Jan 2024 14:40:32 +0000 Subject: [PATCH 052/635] Add inline to FromLua::from_stack --- src/conversion.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/conversion.rs b/src/conversion.rs index 6820d450..d005cdf2 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -452,6 +452,7 @@ impl<'lua> FromLua<'lua> for bool { } } + #[inline] unsafe fn from_stack(idx: c_int, lua: &'lua Lua) -> Result { Ok(ffi::lua_toboolean(lua.state(), idx) != 0) } From f4d783cb414c9af1afbde9439b3d92c27fe952a6 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 1 Feb 2024 09:30:42 +0000 Subject: [PATCH 053/635] Impl push_into_stack for StdResult --- src/multi.rs | 28 +++++++++++++++++++++++ tests/multi.rs | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++ tests/tests.rs | 38 -------------------------------- 3 files changed, 88 insertions(+), 38 deletions(-) create mode 100644 tests/multi.rs diff --git a/src/multi.rs b/src/multi.rs index 3e8b4bc9..eaa1107a 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -23,6 +23,20 @@ impl<'lua, T: IntoLua<'lua>, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult< } Ok(result) } + + #[inline] + unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result { + match self { + Ok(v) => v.push_into_stack(lua).map(|_| 1), + Err(e) => { + let state = lua.state(); + check_stack(state, 3)?; + ffi::lua_pushnil(state); + e.push_into_stack(lua)?; + Ok(2) + } + } + } } impl<'lua, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult<(), E> { @@ -38,6 +52,20 @@ impl<'lua, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult<(), E> { } } } + + #[inline] + unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result { + match self { + Ok(_) => Ok(0), + Err(e) => { + let state = lua.state(); + check_stack(state, 3)?; + ffi::lua_pushnil(state); + e.push_into_stack(lua)?; + Ok(2) + } + } + } } impl<'lua, T: IntoLua<'lua>> IntoLuaMulti<'lua> for T { diff --git a/tests/multi.rs b/tests/multi.rs new file mode 100644 index 00000000..3ee7bdb2 --- /dev/null +++ b/tests/multi.rs @@ -0,0 +1,60 @@ +use mlua::{Error, ExternalError, IntoLuaMulti, Lua, Result, String, Value}; + +#[test] +fn test_result_conversions() -> Result<()> { + let lua = Lua::new(); + let globals = lua.globals(); + + let ok = lua.create_function(|_, ()| Ok(Ok::<(), Error>(())))?; + let err = lua.create_function(|_, ()| Ok(Err::<(), _>("failure1".into_lua_err())))?; + let ok2 = lua.create_function(|_, ()| Ok(Ok::<_, Error>("!".to_owned())))?; + let err2 = lua.create_function(|_, ()| Ok(Err::("failure2".into_lua_err())))?; + + globals.set("ok", ok)?; + globals.set("ok2", ok2)?; + globals.set("err", err)?; + globals.set("err2", err2)?; + + lua.load( + r#" + local r, e = ok() + assert(r == nil and e == nil) + + local r, e = err() + assert(r == nil) + assert(tostring(e):find("failure1") ~= nil) + + local r, e = ok2() + assert(r == "!") + assert(e == nil) + + local r, e = err2() + assert(r == nil) + assert(tostring(e):find("failure2") ~= nil) + "#, + ) + .exec()?; + + // Try to convert Result into MultiValue + let ok1 = Ok::<(), Error>(()); + let multi_ok1 = ok1.into_lua_multi(&lua)?; + assert_eq!(multi_ok1.len(), 0); + let err1 = Err::<(), _>("failure1"); + let multi_err1 = err1.into_lua_multi(&lua)?; + assert_eq!(multi_err1.len(), 2); + assert_eq!(multi_err1[0], Value::Nil); + assert_eq!(multi_err1[1].as_str().unwrap(), "failure1"); + + let ok2 = Ok::<_, Error>("!"); + let multi_ok2 = ok2.into_lua_multi(&lua)?; + assert_eq!(multi_ok2.len(), 1); + assert_eq!(multi_ok2[0].as_str().unwrap(), "!"); + let err2 = Err::("failure2".into_lua_err()); + let multi_err2 = err2.into_lua_multi(&lua)?; + assert_eq!(multi_err2.len(), 2); + assert_eq!(multi_err2[0], Value::Nil); + assert!(matches!(multi_err2[1], Value::Error(_))); + assert_eq!(multi_err2[1].to_string()?, "failure2"); + + Ok(()) +} diff --git a/tests/tests.rs b/tests/tests.rs index 0310a824..4d072bb2 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -504,44 +504,6 @@ fn test_panic() -> Result<()> { Ok(()) } -#[test] -fn test_result_conversions() -> Result<()> { - let lua = Lua::new(); - let globals = lua.globals(); - - let ok = lua.create_function(|_, ()| Ok(Ok::<(), Error>(())))?; - let err = lua.create_function(|_, ()| Ok(Err::<(), _>("failure1".into_lua_err())))?; - let ok2 = lua.create_function(|_, ()| Ok(Ok::<_, Error>("!".to_owned())))?; - let err2 = lua.create_function(|_, ()| Ok(Err::("failure2".into_lua_err())))?; - - globals.set("ok", ok)?; - globals.set("ok2", ok2)?; - globals.set("err", err)?; - globals.set("err2", err2)?; - - lua.load( - r#" - local r, e = ok() - assert(r == nil and e == nil) - - local r, e = err() - assert(r == nil) - assert(tostring(e):find("failure1") ~= nil) - - local r, e = ok2() - assert(r == "!") - assert(e == nil) - - local r, e = err2() - assert(r == nil) - assert(tostring(e):find("failure2") ~= nil) - "#, - ) - .exec()?; - - Ok(()) -} - #[test] fn test_num_conversion() -> Result<()> { let lua = Lua::new(); From 908f37656ac581db796556a9e0847ddab27d3ef9 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 2 Feb 2024 09:23:16 +0000 Subject: [PATCH 054/635] Add `to_pointer` function to `Function`/`Table`/`Thread` --- src/function.rs | 10 ++++++++++ src/string.rs | 2 +- src/table.rs | 2 +- src/thread.rs | 12 +++++++++++- src/userdata.rs | 12 +++++++++++- tests/function.rs | 13 +++++++++++++ tests/string.rs | 13 +++++++++++++ tests/table.rs | 13 +++++++++++++ tests/thread.rs | 13 +++++++++++++ tests/userdata.rs | 14 ++++++++++++++ 10 files changed, 100 insertions(+), 4 deletions(-) diff --git a/src/function.rs b/src/function.rs index 7182b4a3..263e3c13 100644 --- a/src/function.rs +++ b/src/function.rs @@ -494,6 +494,16 @@ impl<'lua> Function<'lua> { } } + /// Converts this function to a generic C pointer. + /// + /// There is no way to convert the pointer back to its original value. + /// + /// Typically this function is used only for hashing and debug information. + #[inline] + pub fn to_pointer(&self) -> *const c_void { + self.0.to_pointer() + } + /// Convert this handle to owned version. #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] diff --git a/src/string.rs b/src/string.rs index d35f618a..3b805d5b 100644 --- a/src/string.rs +++ b/src/string.rs @@ -132,7 +132,7 @@ impl<'lua> String<'lua> { } } - /// Converts the string to a generic C pointer. + /// Converts this string to a generic C pointer. /// /// There is no way to convert the pointer back to its original value. /// diff --git a/src/table.rs b/src/table.rs index 2a95b3a6..e1c2955a 100644 --- a/src/table.rs +++ b/src/table.rs @@ -591,7 +591,7 @@ impl<'lua> Table<'lua> { unsafe { ffi::lua_getreadonly(ref_thread, self.0.index) != 0 } } - /// Converts the table to a generic C pointer. + /// Converts this table to a generic C pointer. /// /// Different tables will give different pointers. /// There is no way to convert the pointer back to its original value. diff --git a/src/thread.rs b/src/thread.rs index e594297d..5bd634eb 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -1,4 +1,4 @@ -use std::os::raw::c_int; +use std::os::raw::{c_int, c_void}; use crate::error::{Error, Result}; #[allow(unused)] @@ -375,6 +375,16 @@ impl<'lua> Thread<'lua> { } } + /// Converts this thread to a generic C pointer. + /// + /// There is no way to convert the pointer back to its original value. + /// + /// Typically this function is used only for hashing and debug information. + #[inline] + pub fn to_pointer(&self) -> *const c_void { + self.0.to_pointer() + } + /// Convert this handle to owned version. #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] diff --git a/src/userdata.rs b/src/userdata.rs index 21fc1d43..d9496a36 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -5,7 +5,7 @@ use std::fmt; use std::hash::Hash; use std::mem; use std::ops::{Deref, DerefMut}; -use std::os::raw::{c_char, c_int}; +use std::os::raw::{c_char, c_int, c_void}; use std::string::String as StdString; #[cfg(feature = "async")] @@ -1096,6 +1096,16 @@ impl<'lua> AnyUserData<'lua> { } } + /// Converts this userdata to a generic C pointer. + /// + /// There is no way to convert the pointer back to its original value. + /// + /// Typically this function is used only for hashing and debug information. + #[inline] + pub fn to_pointer(&self) -> *const c_void { + self.0.to_pointer() + } + /// Convert this handle to owned version. #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] diff --git a/tests/function.rs b/tests/function.rs index 48efb8b3..8a59a635 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -231,6 +231,19 @@ fn test_function_info() -> Result<()> { Ok(()) } +#[test] +fn test_function_pointer() -> Result<()> { + let lua = Lua::new(); + + let func1 = lua.load("return function() end").into_function()?; + let func2 = func1.call::<_, Function>(())?; + + assert_eq!(func1.to_pointer(), func1.clone().to_pointer()); + assert_ne!(func1.to_pointer(), func2.to_pointer()); + + Ok(()) +} + #[test] fn test_function_wrap() -> Result<()> { use mlua::Error; diff --git a/tests/string.rs b/tests/string.rs index f4e99275..ad06c5f2 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -99,6 +99,19 @@ fn test_string_debug() -> Result<()> { Ok(()) } +#[test] +fn test_string_pointer() -> Result<()> { + let lua = Lua::new(); + + let str1 = lua.create_string("hello")?; + let str2 = lua.create_string("hello")?; + + // Lua uses string interning, so these should be the same + assert_eq!(str1.to_pointer(), str2.to_pointer()); + + Ok(()) +} + #[cfg(all(feature = "unstable", not(feature = "send")))] #[test] fn test_owned_string() -> Result<()> { diff --git a/tests/table.rs b/tests/table.rs index 23043245..04885fc3 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -369,6 +369,19 @@ fn test_table_eq() -> Result<()> { Ok(()) } +#[test] +fn test_table_pointer() -> Result<()> { + let lua = Lua::new(); + + let table1 = lua.create_table()?; + let table2 = lua.create_table()?; + + assert_eq!(table1.to_pointer(), table1.clone().to_pointer()); + assert_ne!(table1.to_pointer(), table2.to_pointer()); + + Ok(()) +} + #[test] fn test_table_error() -> Result<()> { let lua = Lua::new(); diff --git a/tests/thread.rs b/tests/thread.rs index 0473e88f..06bae95b 100644 --- a/tests/thread.rs +++ b/tests/thread.rs @@ -191,6 +191,19 @@ fn test_coroutine_panic() { } } +#[test] +fn test_thread_pointer() -> Result<()> { + let lua = Lua::new(); + + let func = lua.load("return 123").into_function()?; + let thread = lua.create_thread(func.clone())?; + + assert_eq!(thread.to_pointer(), thread.clone().to_pointer()); + assert_ne!(thread.to_pointer(), lua.current_thread().to_pointer()); + + Ok(()) +} + #[cfg(all(feature = "unstable", not(feature = "send")))] #[test] fn test_owned_thread() -> Result<()> { diff --git a/tests/userdata.rs b/tests/userdata.rs index 0a8d3298..1f410091 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -951,6 +951,20 @@ fn test_userdata_method_errors() -> Result<()> { Ok(()) } +#[test] +fn test_userdata_pointer() -> Result<()> { + let lua = Lua::new(); + + let ud1 = lua.create_any_userdata("hello")?; + let ud2 = lua.create_any_userdata("hello")?; + + assert_eq!(ud1.to_pointer(), ud1.clone().to_pointer()); + // Different userdata objects with the same value should have different pointers + assert_ne!(ud1.to_pointer(), ud2.to_pointer()); + + Ok(()) +} + #[cfg(all(feature = "unstable", not(feature = "send")))] #[test] fn test_owned_userdata() -> Result<()> { From 3014c4d7a10cb9f9bba29c0cf04f9cd9a3ae4c42 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 2 Feb 2024 23:26:33 +0000 Subject: [PATCH 055/635] Add `REF_STACK_RESERVE` constant --- src/lua.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lua.rs b/src/lua.rs index d1cc10aa..6597a60f 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -226,6 +226,7 @@ pub(crate) static EXTRA_REGISTRY_KEY: u8 = 0; const WRAPPED_FAILURE_POOL_SIZE: usize = 64; const MULTIVALUE_POOL_SIZE: usize = 64; +const REF_STACK_RESERVE: c_int = 1; /// Requires `feature = "send"` #[cfg(feature = "send")] @@ -519,8 +520,8 @@ impl Lua { #[cfg(feature = "module")] skip_memory_check: false, ref_thread, - // We need 1 extra stack space to move values in and out of the ref stack. - ref_stack_size: ffi::LUA_MINSTACK - 1, + // We need some reserved stack space to move values in and out of the ref stack. + ref_stack_size: ffi::LUA_MINSTACK - REF_STACK_RESERVE, ref_stack_top: ffi::lua_gettop(ref_thread), ref_free: Vec::new(), wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_SIZE), From 1754226c7440ec6c194d2d678ec083b621d46ceb Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 3 Feb 2024 21:32:49 +0000 Subject: [PATCH 056/635] Impl `IntoLua::push_into_stack` for integers --- src/conversion.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/conversion.rs b/src/conversion.rs index d005cdf2..cb683640 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -697,6 +697,15 @@ macro_rules! lua_convert_int { message: Some("out of range".to_owned()), }) } + + #[inline] + unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + match cast(self) { + Some(i) => ffi::lua_pushinteger(lua.state(), i), + None => ffi::lua_pushnumber(lua.state(), self as ffi::lua_Number), + } + Ok(()) + } } impl<'lua> FromLua<'lua> for $x { From 3ca7b4942ed6ccad816883a2a88582705916c917 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 4 Feb 2024 14:18:04 +0000 Subject: [PATCH 057/635] Implement `IntoLua` for `&Value` --- src/conversion.rs | 12 ++++++++ src/lua.rs | 70 +++++++++++++++++---------------------------- tests/conversion.rs | 17 +++++++++++ 3 files changed, 55 insertions(+), 44 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index cb683640..d50c645d 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -33,6 +33,18 @@ impl<'lua> IntoLua<'lua> for Value<'lua> { } } +impl<'lua> IntoLua<'lua> for &Value<'lua> { + #[inline] + fn into_lua(self, _: &'lua Lua) -> Result> { + Ok(self.clone()) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + lua.push_value_ref(self) + } +} + impl<'lua> FromLua<'lua> for Value<'lua> { #[inline] fn from_lua(lua_value: Value<'lua>, _: &'lua Lua) -> Result { diff --git a/src/lua.rs b/src/lua.rs index 6597a60f..9938a74c 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -2269,39 +2269,38 @@ impl Lua { extra.app_data.remove() } + /// Pushes a value that implements `IntoLua` onto the Lua stack. + /// + /// Uses 2 stack spaces, does not call checkstack. #[doc(hidden)] #[inline(always)] pub unsafe fn push<'lua>(&'lua self, value: impl IntoLua<'lua>) -> Result<()> { value.push_into_stack(self) } - /// Pushes a value onto the Lua stack. + /// Pushes a `Value` onto the Lua stack. /// /// Uses 2 stack spaces, does not call checkstack. #[doc(hidden)] pub unsafe fn push_value(&self, value: Value) -> Result<()> { + if let Value::Error(err) = value { + let protect = !self.unlikely_memory_error(); + return push_gc_userdata(self.state(), WrappedFailure::Error(err), protect); + } + self.push_value_ref(&value) + } + + /// Pushes a `&Value` (by reference) onto the Lua stack. + /// + /// Similar to [`Lua::push_value`], uses 2 stack spaces, does not call checkstack. + pub(crate) unsafe fn push_value_ref(&self, value: &Value) -> Result<()> { let state = self.state(); match value { - Value::Nil => { - ffi::lua_pushnil(state); - } - - Value::Boolean(b) => { - ffi::lua_pushboolean(state, b as c_int); - } - - Value::LightUserData(ud) => { - ffi::lua_pushlightuserdata(state, ud.0); - } - - Value::Integer(i) => { - ffi::lua_pushinteger(state, i); - } - - Value::Number(n) => { - ffi::lua_pushnumber(state, n); - } - + Value::Nil => ffi::lua_pushnil(state), + Value::Boolean(b) => ffi::lua_pushboolean(state, *b as c_int), + Value::LightUserData(ud) => ffi::lua_pushlightuserdata(state, ud.0), + Value::Integer(i) => ffi::lua_pushinteger(state, *i), + Value::Number(n) => ffi::lua_pushnumber(state, *n), #[cfg(feature = "luau")] Value::Vector(v) => { #[cfg(not(feature = "luau-vector4"))] @@ -2309,33 +2308,16 @@ impl Lua { #[cfg(feature = "luau-vector4")] ffi::lua_pushvector(state, v.x(), v.y(), v.z(), v.w()); } - - Value::String(s) => { - self.push_ref(&s.0); - } - - Value::Table(t) => { - self.push_ref(&t.0); - } - - Value::Function(f) => { - self.push_ref(&f.0); - } - - Value::Thread(t) => { - self.push_ref(&t.0); - } - - Value::UserData(ud) => { - self.push_ref(&ud.0); - } - + Value::String(s) => self.push_ref(&s.0), + Value::Table(t) => self.push_ref(&t.0), + Value::Function(f) => self.push_ref(&f.0), + Value::Thread(t) => self.push_ref(&t.0), + Value::UserData(ud) => self.push_ref(&ud.0), Value::Error(err) => { let protect = !self.unlikely_memory_error(); - push_gc_userdata(state, WrappedFailure::Error(err), protect)?; + push_gc_userdata(state, WrappedFailure::Error(err.clone()), protect)?; } } - Ok(()) } diff --git a/tests/conversion.rs b/tests/conversion.rs index 4e011ed7..0401b930 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -5,6 +5,23 @@ use std::ffi::{CStr, CString}; use maplit::{btreemap, btreeset, hashmap, hashset}; use mlua::{AnyUserData, Error, Function, IntoLua, Lua, Result, Table, Thread, UserDataRef, Value}; +#[test] +fn test_value_into_lua() -> Result<()> { + let lua = Lua::new(); + + // Direct conversion + let v = Value::Boolean(true); + let v2 = (&v).into_lua(&lua)?; + assert_eq!(v, v2); + + // Push into stack + let table = lua.create_table()?; + table.set("v", &v)?; + assert_eq!(v, table.get::<_, Value>("v")?); + + Ok(()) +} + #[test] fn test_string_into_lua() -> Result<()> { let lua = Lua::new(); From 020e8a78a85df45db3352362b430c28767b5b1a9 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 10 Feb 2024 15:41:48 +0000 Subject: [PATCH 058/635] Impl `FromLua` for `RegistryKey` --- src/conversion.rs | 7 +++++++ tests/conversion.rs | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/conversion.rs b/src/conversion.rs index d50c645d..47c1ce0c 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -441,6 +441,13 @@ impl<'lua> IntoLua<'lua> for &RegistryKey { } } +impl<'lua> FromLua<'lua> for RegistryKey { + #[inline] + fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { + lua.create_registry_value(value) + } +} + impl<'lua> IntoLua<'lua> for bool { #[inline] fn into_lua(self, _: &'lua Lua) -> Result> { diff --git a/tests/conversion.rs b/tests/conversion.rs index 0401b930..6854eff3 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -3,7 +3,10 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::ffi::{CStr, CString}; use maplit::{btreemap, btreeset, hashmap, hashset}; -use mlua::{AnyUserData, Error, Function, IntoLua, Lua, Result, Table, Thread, UserDataRef, Value}; +use mlua::{ + AnyUserData, Error, Function, IntoLua, Lua, RegistryKey, Result, Table, Thread, UserDataRef, + Value, +}; #[test] fn test_value_into_lua() -> Result<()> { @@ -254,6 +257,17 @@ fn test_registry_value_into_lua() -> Result<()> { Ok(()) } +#[test] +fn test_registry_key_from_lua() -> Result<()> { + let lua = Lua::new(); + + let fkey = lua.load("function() return 1 end").eval::()?; + let f = lua.registry_value::(&fkey)?; + assert_eq!(f.call::<_, i32>(())?, 1); + + Ok(()) +} + #[test] fn test_conv_vec() -> Result<()> { let lua = Lua::new(); From 34db5f985e6d35142f7dd4f9f9ff1a5d4251f6c6 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 11 Feb 2024 00:35:30 +0000 Subject: [PATCH 059/635] Refactor benchmarks --- benches/benchmark.rs | 321 +++++++++++++++++++++++-------------------- benches/serde.rs | 58 ++++---- 2 files changed, 196 insertions(+), 183 deletions(-) diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 14fb1fae..014bd3af 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -1,5 +1,7 @@ -use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::Duration; + +use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use tokio::runtime::Runtime; use tokio::task; @@ -10,10 +12,10 @@ fn collect_gc_twice(lua: &Lua) { lua.gc_collect().unwrap(); } -fn create_table(c: &mut Criterion) { +fn table_create_empty(c: &mut Criterion) { let lua = Lua::new(); - c.bench_function("create [table empty]", |b| { + c.bench_function("table [create empty]", |b| { b.iter_batched( || collect_gc_twice(&lua), |_| { @@ -24,35 +26,33 @@ fn create_table(c: &mut Criterion) { }); } -fn create_array(c: &mut Criterion) { +fn table_create_array(c: &mut Criterion) { let lua = Lua::new(); - c.bench_function("create [array] 10", |b| { + c.bench_function("table [create array]", |b| { b.iter_batched( || collect_gc_twice(&lua), |_| { - let table = lua.create_table().unwrap(); - for i in 1..=10 { - table.set(i, i).unwrap(); - } + lua.create_sequence_from(1..=10).unwrap(); }, BatchSize::SmallInput, ); }); } -fn create_string_table(c: &mut Criterion) { +fn table_create_hash(c: &mut Criterion) { let lua = Lua::new(); - c.bench_function("create [table string] 10", |b| { + c.bench_function("table [create hash]", |b| { b.iter_batched( || collect_gc_twice(&lua), |_| { - let table = lua.create_table().unwrap(); - for &s in &["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] { - let s = lua.create_string(s).unwrap(); - table.set(s.clone(), s).unwrap(); - } + lua.create_table_from( + ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] + .into_iter() + .map(|s| (s, s)), + ) + .unwrap(); }, BatchSize::SmallInput, ); @@ -62,17 +62,15 @@ fn create_string_table(c: &mut Criterion) { fn table_get_set(c: &mut Criterion) { let lua = Lua::new(); - let table = lua.create_table().unwrap(); - - c.bench_function("table raw_get and raw_set [10]", |b| { + c.bench_function("table [get and set]", |b| { b.iter_batched( || { collect_gc_twice(&lua); - table.clear().unwrap(); + lua.create_table().unwrap() }, - |_| { - for (i, &s) in ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"] - .iter() + |table| { + for (i, s) in ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"] + .into_iter() .enumerate() { table.raw_set(s, i).unwrap(); @@ -87,7 +85,7 @@ fn table_get_set(c: &mut Criterion) { fn table_traversal_pairs(c: &mut Criterion) { let lua = Lua::new(); - c.bench_function("table traversal [pairs]", |b| { + c.bench_function("table [traversal pairs]", |b| { b.iter_batched( || lua.globals(), |globals| { @@ -103,7 +101,7 @@ fn table_traversal_pairs(c: &mut Criterion) { fn table_traversal_for_each(c: &mut Criterion) { let lua = Lua::new(); - c.bench_function("table traversal [for_each]", |b| { + c.bench_function("table [traversal for_each]", |b| { b.iter_batched( || lua.globals(), |globals| globals.for_each::(|_k, _v| Ok(())), @@ -117,7 +115,7 @@ fn table_traversal_sequence(c: &mut Criterion) { let table = lua.create_sequence_from(1..1000).unwrap(); - c.bench_function("table traversal [sequence]", |b| { + c.bench_function("table [traversal sequence]", |b| { b.iter_batched( || table.clone(), |table| { @@ -130,236 +128,255 @@ fn table_traversal_sequence(c: &mut Criterion) { }); } -fn create_function(c: &mut Criterion) { +fn function_create(c: &mut Criterion) { let lua = Lua::new(); - c.bench_function("create [function] 10", |b| { + c.bench_function("function [create Rust]", |b| { b.iter_batched( || collect_gc_twice(&lua), |_| { - for i in 0..10 { - lua.create_function(move |_, ()| Ok(i)).unwrap(); - } + lua.create_function(|_, ()| Ok(123)).unwrap(); }, BatchSize::SmallInput, ); }); } -fn call_lua_function(c: &mut Criterion) { +fn function_call_sum(c: &mut Criterion) { let lua = Lua::new(); - c.bench_function("call Lua function [sum] 3 10", |b| { - b.iter_batched_ref( - || { - collect_gc_twice(&lua); - lua.load("function(a, b, c) return a + b + c end") - .eval::() - .unwrap() - }, - |function| { - for i in 0..10 { - let _result: i64 = function.call((i, i + 1, i + 2)).unwrap(); - } + let sum = lua + .create_function(|_, (a, b, c): (i64, i64, i64)| Ok(a + b - c)) + .unwrap(); + + c.bench_function("function [call Rust sum]", |b| { + b.iter_batched( + || collect_gc_twice(&lua), + |_| { + assert_eq!(sum.call::<_, i64>((10, 20, 30)).unwrap(), 0); }, BatchSize::SmallInput, ); }); } -fn call_sum_callback(c: &mut Criterion) { +fn function_call_lua_sum(c: &mut Criterion) { let lua = Lua::new(); - let callback = lua - .create_function(|_, (a, b, c): (i64, i64, i64)| Ok(a + b + c)) + + let sum = lua + .load("function(a, b, c) return a + b - c end") + .eval::() .unwrap(); - lua.globals().set("callback", callback).unwrap(); - c.bench_function("call Rust callback [sum] 3 10", |b| { - b.iter_batched_ref( - || { - collect_gc_twice(&lua); - lua.load("function() for i = 1,10 do callback(i, i+1, i+2) end end") - .eval::() - .unwrap() - }, - |function| { - function.call::<_, ()>(()).unwrap(); + c.bench_function("function [call Lua sum]", |b| { + b.iter_batched( + || collect_gc_twice(&lua), + |_| { + assert_eq!(sum.call::<_, i64>((10, 20, 30)).unwrap(), 0); }, BatchSize::SmallInput, ); }); } -fn call_async_sum_callback(c: &mut Criterion) { - let options = LuaOptions::new().thread_pool_size(1024); - let lua = Lua::new_with(LuaStdLib::ALL_SAFE, options).unwrap(); - let callback = lua - .create_async_function(|_, (a, b, c): (i64, i64, i64)| async move { - task::yield_now().await; - Ok(a + b + c) +fn function_call_concat(c: &mut Criterion) { + let lua = Lua::new(); + + let concat = lua + .create_function(|_, (a, b): (LuaString, LuaString)| { + Ok(format!("{}{}", a.to_str()?, b.to_str()?)) }) .unwrap(); - lua.globals().set("callback", callback).unwrap(); + let i = AtomicUsize::new(0); - c.bench_function("call async Rust callback [sum] 3 10", |b| { - let rt = Runtime::new().unwrap(); - b.to_async(rt).iter_batched( + c.bench_function("function [call Rust concat string]", |b| { + b.iter_batched( || { collect_gc_twice(&lua); - lua.load("function() for i = 1,10 do callback(i, i+1, i+2) end end") - .eval::() - .unwrap() + i.fetch_add(1, Ordering::Relaxed) }, - |function| async move { - function.call_async::<_, ()>(()).await.unwrap(); + |i| { + assert_eq!( + concat.call::<_, LuaString>(("num:", i)).unwrap(), + format!("num:{i}") + ); }, BatchSize::SmallInput, ); }); } -fn call_concat_callback(c: &mut Criterion) { +fn function_call_lua_concat(c: &mut Criterion) { let lua = Lua::new(); - let callback = lua - .create_function(|_, (a, b): (LuaString, LuaString)| { - Ok(format!("{}{}", a.to_str()?, b.to_str()?)) - }) + + let concat = lua + .load("function(a, b) return a..b end") + .eval::() .unwrap(); - lua.globals().set("callback", callback).unwrap(); + let i = AtomicUsize::new(0); - c.bench_function("call Rust callback [concat string] 10", |b| { - b.iter_batched_ref( + c.bench_function("function [call Lua concat string]", |b| { + b.iter_batched( || { collect_gc_twice(&lua); - lua.load("function() for i = 1,10 do callback('a', tostring(i)) end end") - .eval::() - .unwrap() + i.fetch_add(1, Ordering::Relaxed) }, - |function| { - function.call::<_, ()>(()).unwrap(); + |i| { + assert_eq!( + concat.call::<_, LuaString>(("num:", i)).unwrap(), + format!("num:{i}") + ); + }, + BatchSize::SmallInput, + ); + }); +} + +fn function_async_call_sum(c: &mut Criterion) { + let options = LuaOptions::new().thread_pool_size(1024); + let lua = Lua::new_with(LuaStdLib::ALL_SAFE, options).unwrap(); + + let sum = lua + .create_async_function(|_, (a, b, c): (i64, i64, i64)| async move { + task::yield_now().await; + Ok(a + b - c) + }) + .unwrap(); + + c.bench_function("function [async call Rust sum]", |b| { + let rt = Runtime::new().unwrap(); + b.to_async(rt).iter_batched( + || collect_gc_twice(&lua), + |_| async { + assert_eq!(sum.call_async::<_, i64>((10, 20, 30)).await.unwrap(), 0); }, BatchSize::SmallInput, ); }); } -fn create_registry_values(c: &mut Criterion) { +fn registry_value_create(c: &mut Criterion) { let lua = Lua::new(); + lua.gc_stop(); - c.bench_function("create [registry value] 10", |b| { + c.bench_function("registry value [create]", |b| { b.iter_batched( || collect_gc_twice(&lua), - |_| { - for _ in 0..10 { - lua.create_registry_value(lua.pack(true).unwrap()).unwrap(); - } - lua.expire_registry_values(); - }, + |_| lua.create_registry_value("hello").unwrap(), BatchSize::SmallInput, ); }); } -fn create_userdata(c: &mut Criterion) { +fn userdata_create(c: &mut Criterion) { struct UserData(i64); impl LuaUserData for UserData {} let lua = Lua::new(); - c.bench_function("create [table userdata] 10", |b| { + c.bench_function("userdata [create]", |b| { b.iter_batched( || collect_gc_twice(&lua), |_| { - let table: LuaTable = lua.create_table().unwrap(); - for i in 1..11 { - table.set(i, UserData(i)).unwrap(); - } + lua.create_userdata(UserData(123)).unwrap(); }, BatchSize::SmallInput, ); }); } -fn call_userdata_index(c: &mut Criterion) { +fn userdata_call_index(c: &mut Criterion) { struct UserData(i64); impl LuaUserData for UserData { fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_meta_method(LuaMetaMethod::Index, move |_, _, index: String| Ok(index)); + methods.add_meta_method(LuaMetaMethod::Index, move |_, _, key: LuaString| Ok(key)); } } let lua = Lua::new(); - lua.globals().set("userdata", UserData(10)).unwrap(); + let ud = lua.create_userdata(UserData(123)).unwrap(); + let index = lua + .load("function(ud) return ud.test end") + .eval::() + .unwrap(); - c.bench_function("call [userdata index] 10", |b| { - b.iter_batched_ref( - || { - collect_gc_twice(&lua); - lua.load("function() for i = 1,10 do local v = userdata.test end end") - .eval::() - .unwrap() - }, - |function| { - function.call::<_, ()>(()).unwrap(); + c.bench_function("userdata [call index]", |b| { + b.iter_batched( + || collect_gc_twice(&lua), + |_| { + assert_eq!(index.call::<_, LuaString>(&ud).unwrap(), "test"); }, BatchSize::SmallInput, ); }); } -fn call_userdata_method(c: &mut Criterion) { +fn userdata_call_method(c: &mut Criterion) { struct UserData(i64); impl LuaUserData for UserData { fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_method("method", |_, this, ()| Ok(this.0)); + methods.add_method("add", |_, this, i: i64| Ok(this.0 + i)); } } let lua = Lua::new(); - lua.globals().set("userdata", UserData(10)).unwrap(); + let ud = lua.create_userdata(UserData(123)).unwrap(); + let method = lua + .load("function(ud, i) return ud:add(i) end") + .eval::() + .unwrap(); + let i = AtomicUsize::new(0); - c.bench_function("call [userdata method] 10", |b| { - b.iter_batched_ref( + c.bench_function("userdata [call method]", |b| { + b.iter_batched( || { collect_gc_twice(&lua); - lua.load("function() for i = 1,10 do userdata:method() end end") - .eval::() - .unwrap() + i.fetch_add(1, Ordering::Relaxed) }, - |function| { - function.call::<_, ()>(()).unwrap(); + |i| { + assert_eq!(method.call::<_, usize>((&ud, i)).unwrap(), 123 + i); }, BatchSize::SmallInput, ); }); } -fn call_async_userdata_method(c: &mut Criterion) { - struct UserData(String); - +fn userdata_async_call_method(c: &mut Criterion) { + struct UserData(i64); impl LuaUserData for UserData { fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_async_method("method", |_, this, ()| async move { Ok(this.0.clone()) }); + methods.add_async_method("add", |_, this, i: i64| async move { + task::yield_now().await; + Ok(this.0 + i) + }); } } let options = LuaOptions::new().thread_pool_size(1024); let lua = Lua::new_with(LuaStdLib::ALL_SAFE, options).unwrap(); - lua.globals() - .set("userdata", UserData("hello".to_string())) + let ud = lua.create_userdata(UserData(123)).unwrap(); + let method = lua + .load("function(ud, i) return ud:add(i) end") + .eval::() .unwrap(); + let i = AtomicUsize::new(0); - c.bench_function("call async [userdata method] 10", |b| { + c.bench_function("userdata [async call method] 10", |b| { let rt = Runtime::new().unwrap(); b.to_async(rt).iter_batched( || { collect_gc_twice(&lua); - lua.load("function() for i = 1,10 do userdata:method() end end") - .eval::() - .unwrap() + ( + method.clone(), + ud.clone(), + i.fetch_add(1, Ordering::Relaxed), + ) }, - |function| async move { - function.call_async::<_, ()>(()).await.unwrap(); + |(method, ud, i)| async move { + assert_eq!( + method.call_async::<_, usize>((ud, i)).await.unwrap(), + 123 + i + ); }, BatchSize::SmallInput, ); @@ -369,27 +386,31 @@ fn call_async_userdata_method(c: &mut Criterion) { criterion_group! { name = benches; config = Criterion::default() - .sample_size(300) + .sample_size(500) .measurement_time(Duration::from_secs(10)) .noise_threshold(0.02); targets = - create_table, - create_array, - create_string_table, + table_create_empty, + table_create_array, + table_create_hash, table_get_set, table_traversal_pairs, table_traversal_for_each, table_traversal_sequence, - create_function, - call_lua_function, - call_sum_callback, - call_async_sum_callback, - call_concat_callback, - create_registry_values, - create_userdata, - call_userdata_index, - call_userdata_method, - call_async_userdata_method, + + function_create, + function_call_sum, + function_call_lua_sum, + function_call_concat, + function_call_lua_concat, + function_async_call_sum, + + registry_value_create, + + userdata_create, + userdata_call_index, + userdata_call_method, + userdata_async_call_method, } criterion_main!(benches); diff --git a/benches/serde.rs b/benches/serde.rs index d0fe74a1..6d2718b1 100644 --- a/benches/serde.rs +++ b/benches/serde.rs @@ -8,43 +8,35 @@ fn collect_gc_twice(lua: &Lua) { lua.gc_collect().unwrap(); } -fn serialize_json(c: &mut Criterion) { +fn encode_json(c: &mut Criterion) { let lua = Lua::new(); - lua.globals() - .set( - "encode", - LuaFunction::wrap(|_, t: LuaValue| Ok(serde_json::to_string(&t).unwrap())), + let encode = lua + .create_function(|_, t: LuaValue| Ok(serde_json::to_string(&t).unwrap())) + .unwrap(); + let table = lua + .load( + r#"{ + name = "Clark Kent", + address = { + city = "Smallville", + state = "Kansas", + country = "USA", + }, + age = 22, + parents = {"Jonathan Kent", "Martha Kent"}, + superman = true, + interests = {"flying", "saving the world", "kryptonite"}, + }"#, ) + .eval::() .unwrap(); - c.bench_function("serialize table to json [10]", |b| { + c.bench_function("serialize json", |b| { b.iter_batched( - || { - collect_gc_twice(&lua); - lua.load( - r#" - local encode = encode - return function() - for i = 1, 10 do - encode({ - name = "Clark Kent", - nickname = "Superman", - address = { - city = "Metropolis", - }, - age = 32, - superman = true, - }) - end - end - "#, - ) - .eval::() - .unwrap() - }, - |func| { - func.call::<_, ()>(()).unwrap(); + || collect_gc_twice(&lua), + |_| { + encode.call::<_, LuaString>(&table).unwrap(); }, BatchSize::SmallInput, ); @@ -54,11 +46,11 @@ fn serialize_json(c: &mut Criterion) { criterion_group! { name = benches; config = Criterion::default() - .sample_size(300) + .sample_size(500) .measurement_time(Duration::from_secs(10)) .noise_threshold(0.02); targets = - serialize_json, + encode_json, } criterion_main!(benches); From 8a9c4f0b15f9aeaf73c04d8cf1af165be72b734d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 11 Feb 2024 23:02:36 +0000 Subject: [PATCH 060/635] Optimize table array traversal during serialization --- src/table.rs | 73 +++++++++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/src/table.rs b/src/table.rs index e1c2955a..69e8a6dc 100644 --- a/src/table.rs +++ b/src/table.rs @@ -721,7 +721,6 @@ impl<'lua> Table<'lua> { TableSequence { table: self.0, index: 1, - len: None, _phantom: PhantomData, } } @@ -733,17 +732,25 @@ impl<'lua> Table<'lua> { } #[cfg(feature = "serialize")] - pub(crate) fn sequence_values_by_len>( - self, - len: Option, - ) -> TableSequence<'lua, V> { - let len = len.unwrap_or_else(|| self.raw_len()) as Integer; - TableSequence { - table: self.0, - index: 1, - len: Some(len), - _phantom: PhantomData, + pub(crate) fn for_each_value(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()> + where + V: FromLua<'lua>, + { + let lua = self.0.lua; + let state = lua.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 4)?; + + lua.push_ref(&self.0); + let len = ffi::lua_rawlen(state, -1); + for i in 1..=len { + ffi::lua_rawgeti(state, -1, i as _); + f(V::from_stack(-1, lua)?)?; + ffi::lua_pop(state, 1); + } } + Ok(()) } /// Sets element value at position `idx` without invoking metamethods. @@ -1085,6 +1092,13 @@ impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> { use crate::serde::de::{check_value_for_skip, MapPairs}; use crate::value::SerializableValue; + let convert_result = |res: Result<()>, serialize_err: Option| match res { + Ok(v) => Ok(v), + Err(Error::SerializeError(_)) if serialize_err.is_some() => Err(serialize_err.unwrap()), + Err(Error::SerializeError(msg)) => Err(serde::ser::Error::custom(msg)), + Err(err) => Err(serde::ser::Error::custom(err.to_string())), + }; + let options = self.options; let visited = &self.visited; visited.borrow_mut().insert(self.table.to_pointer()); @@ -1093,15 +1107,21 @@ impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> { let len = self.table.raw_len(); if len > 0 || self.table.is_array() { let mut seq = serializer.serialize_seq(Some(len))?; - for value in self.table.clone().sequence_values_by_len::(None) { - let value = &value.map_err(serde::ser::Error::custom)?; - let skip = check_value_for_skip(value, self.options, &self.visited) - .map_err(serde::ser::Error::custom)?; + let mut serialize_err = None; + let res = self.table.for_each_value::(|value| { + let skip = check_value_for_skip(&value, self.options, &self.visited) + .map_err(|err| Error::SerializeError(err.to_string()))?; if skip { - continue; + // continue iteration + return Ok(()); } - seq.serialize_element(&SerializableValue::new(value, options, Some(visited)))?; - } + seq.serialize_element(&SerializableValue::new(&value, options, Some(visited))) + .map_err(|err| { + serialize_err = Some(err); + Error::SerializeError(String::new()) + }) + }); + convert_result(res, serialize_err)?; return seq.end(); } @@ -1138,18 +1158,7 @@ impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> { process_pair(key, value) }) }; - match res { - Ok(_) => {} - Err(Error::SerializeError(_)) if serialize_err.is_some() => { - return Err(serialize_err.unwrap()); - } - Err(Error::SerializeError(msg)) => { - return Err(serde::ser::Error::custom(msg)); - } - Err(err) => { - return Err(serde::ser::Error::custom(err.to_string())); - } - } + convert_result(res, serialize_err)?; map.end() } } @@ -1219,9 +1228,9 @@ where /// /// [`Table::sequence_values`]: crate::Table::sequence_values pub struct TableSequence<'lua, V> { + // TODO: Use `&Table` table: LuaRef<'lua>, index: Integer, - len: Option, _phantom: PhantomData, } @@ -1242,7 +1251,7 @@ where lua.push_ref(&self.table); match ffi::lua_rawgeti(state, -1, self.index) { - ffi::LUA_TNIL if self.index > self.len.unwrap_or(0) => None, + ffi::LUA_TNIL => None, _ => { self.index += 1; Some(V::from_stack(-1, lua)) From 0ee3324462d9197a5689695ed5ee4f10f264b764 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 29 Feb 2024 12:56:54 +0000 Subject: [PATCH 061/635] Add `LUA_TCDATA` to `util::to_string()` helper --- src/util/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/util/mod.rs b/src/util/mod.rs index 46960bd9..699bfc1c 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1045,6 +1045,8 @@ pub(crate) unsafe fn to_string(state: *mut ffi::lua_State, index: c_int) -> Stri ffi::LUA_TTHREAD => format!("", ffi::lua_topointer(state, index)), #[cfg(feature = "luau")] ffi::LUA_TBUFFER => format!("", ffi::lua_topointer(state, index)), + #[cfg(feature = "luajit")] + ffi::LUA_TCDATA => format!("", ffi::lua_topointer(state, index)), _ => "".to_string(), } } From 270b98a429d940b80fc65b0ec3260f937172990b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 29 Feb 2024 15:39:14 +0000 Subject: [PATCH 062/635] v0.9.6 --- CHANGELOG.md | 8 ++++++++ Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebbc7f91..de1b93c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## v0.9.6 + +- Added `to_pointer` function to `Function`/`Table`/`Thread` +- Implemented `IntoLua` for `&Value` +- Implemented `FromLua` for `RegistryKey` +- Faster (~5%) table array traversal during serialization +- Some performance improvements for bool/int types + ## v0.9.5 - Minimal Luau updated to 0.609 diff --git a/Cargo.toml b/Cargo.toml index 3324d467..8bee1eb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.9.5" # remember to update mlua_derive +version = "0.9.6" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.71" edition = "2021" From 83c075c72bd07747f0ad59a448ae8a4a58d9e402 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 29 Feb 2024 22:45:23 +0000 Subject: [PATCH 063/635] Implement `IntoLua` for `RegistryKey` --- src/conversion.rs | 12 ++++++++++++ tests/conversion.rs | 20 ++++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index 47c1ce0c..dc076f90 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -421,6 +421,18 @@ impl<'lua> FromLua<'lua> for Error { } } +impl<'lua> IntoLua<'lua> for RegistryKey { + #[inline] + fn into_lua(self, lua: &'lua Lua) -> Result> { + lua.registry_value(&self) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + <&RegistryKey>::push_into_stack(&self, lua) + } +} + impl<'lua> IntoLua<'lua> for &RegistryKey { #[inline] fn into_lua(self, lua: &'lua Lua) -> Result> { diff --git a/tests/conversion.rs b/tests/conversion.rs index 6854eff3..a312f114 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -232,14 +232,22 @@ fn test_owned_anyuserdata_into_lua() -> Result<()> { fn test_registry_value_into_lua() -> Result<()> { let lua = Lua::new(); - let t = lua.create_table()?; - let r = lua.create_registry_value(t)?; - let f = lua.create_function(|_, t: Table| t.raw_set("hello", "world"))?; + // Direct conversion + let s = lua.create_string("hello, world")?; + let r = lua.create_registry_value(&s)?; + let value1 = lua.pack(&r)?; + let value2 = lua.pack(r)?; + assert_eq!(value1.as_str(), Some("hello, world")); + assert_eq!(value2.to_pointer(), value2.to_pointer()); - f.call(&r)?; - let v = r.into_lua(&lua)?; - let t = v.as_table().unwrap(); + // Push into stack + let t = lua.create_table()?; + let r = lua.create_registry_value(&t)?; + let f = lua.create_function(|_, (t, k, v): (Table, Value, Value)| t.set(k, v))?; + f.call((&r, "hello", "world"))?; + f.call((r, "welcome", "to the jungle"))?; assert_eq!(t.get::<_, String>("hello")?, "world"); + assert_eq!(t.get::<_, String>("welcome")?, "to the jungle"); // Try to set nil registry key let r_nil = lua.create_registry_value(Value::Nil)?; From 5a22437d5fe2c21276e17879e5e23db271bcffdc Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 16 Mar 2024 23:39:10 +0000 Subject: [PATCH 064/635] Assert that `luau_compile` returns non-null pointer. Fixes #381 --- mlua-sys/src/luau/luacode.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mlua-sys/src/luau/luacode.rs b/mlua-sys/src/luau/luacode.rs index ae90e504..357ad250 100644 --- a/mlua-sys/src/luau/luacode.rs +++ b/mlua-sys/src/luau/luacode.rs @@ -36,6 +36,7 @@ pub unsafe fn luau_compile(source: &[u8], mut options: lua_CompileOptions) -> Ve &mut options, &mut outsize, ); + assert!(!data_ptr.is_null(), "luau_compile failed"); let data = slice::from_raw_parts(data_ptr as *mut u8, outsize).to_vec(); free(data_ptr as *mut c_void); data From 3d431034310711818d96cdd8c64ad2c437710f84 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 20 Mar 2024 19:20:05 +0000 Subject: [PATCH 065/635] Make `__idiv` metamethod available for luau Closes #383 --- src/userdata.rs | 6 +++--- src/util/mod.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/userdata.rs b/src/userdata.rs index d9496a36..1cd6b823 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -54,8 +54,8 @@ pub enum MetaMethod { /// The unary minus (`-`) operator. Unm, /// The floor division (//) operator. - /// Requires `feature = "lua54/lua53"` - #[cfg(any(feature = "lua54", feature = "lua53"))] + /// Requires `feature = "lua54/lua53/luau"` + #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] IDiv, /// The bitwise AND (&) operator. /// Requires `feature = "lua54/lua53"` @@ -180,7 +180,7 @@ impl MetaMethod { MetaMethod::Pow => "__pow", MetaMethod::Unm => "__unm", - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] MetaMethod::IDiv => "__idiv", #[cfg(any(feature = "lua54", feature = "lua53"))] MetaMethod::BAnd => "__band", diff --git a/src/util/mod.rs b/src/util/mod.rs index 699bfc1c..595407e1 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -928,7 +928,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> { "__mod", "__pow", "__unm", - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] "__idiv", #[cfg(any(feature = "lua54", feature = "lua53"))] "__band", From 58be62422281b62918959bcd7d74baf05f85bcee Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 20 Mar 2024 19:29:47 +0000 Subject: [PATCH 066/635] Remove redundant "match" when checking userdata type via `AnyUserData::is`. Fixes #386 --- src/userdata.rs | 6 +----- tests/userdata.rs | 2 ++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/userdata.rs b/src/userdata.rs index 1cd6b823..04d62231 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -815,11 +815,7 @@ impl OwnedAnyUserData { impl<'lua> AnyUserData<'lua> { /// Checks whether the type of this userdata is `T`. pub fn is(&self) -> bool { - match self.inspect(|_: &UserDataCell| Ok(())) { - Ok(()) => true, - Err(Error::UserDataTypeMismatch) => false, - Err(_) => unreachable!(), - } + self.inspect(|_: &UserDataCell| Ok(())).is_ok() } /// Borrow this userdata immutably if it is of type `T`. diff --git a/tests/userdata.rs b/tests/userdata.rs index 1f410091..dca5ed2d 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -370,6 +370,8 @@ fn test_userdata_take() -> Result<()> { r => panic!("improper return for destructed userdata: {:?}", r), } + assert!(!userdata.is::()); + drop(userdata); lua.globals().raw_remove("userdata")?; lua.gc_collect()?; From 973414631342b316bc8fb5e65ce858c1b497ae60 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 21 Mar 2024 22:11:49 +0000 Subject: [PATCH 067/635] Add `Function::deep_clone()` (Luau only) --- src/function.rs | 21 +++++++++++++++++++++ tests/function.rs | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/function.rs b/src/function.rs index 263e3c13..67fc6f30 100644 --- a/src/function.rs +++ b/src/function.rs @@ -504,6 +504,27 @@ impl<'lua> Function<'lua> { self.0.to_pointer() } + /// Creates a deep clone of the Lua function. + /// + /// Copies the function prototype and all its upvalues to the + /// newly created function. + /// + /// This function returns shallow clone (same handle) for Rust/C functions. + /// Requires `feature = "luau"` + #[cfg(feature = "luau")] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn deep_clone(&self) -> Self { + let ref_thread = self.0.lua.ref_thread(); + unsafe { + if ffi::lua_iscfunction(ref_thread, self.0.index) != 0 { + return self.clone(); + } + + ffi::lua_clonefunction(ref_thread, self.0.index); + Function(self.0.lua.pop_ref_thread()) + } + } + /// Convert this handle to owned version. #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] diff --git a/tests/function.rs b/tests/function.rs index 8a59a635..c95b012a 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -244,6 +244,27 @@ fn test_function_pointer() -> Result<()> { Ok(()) } +#[cfg(feature = "luau")] +#[test] +fn test_function_deep_clone() -> Result<()> { + let lua = Lua::new(); + + lua.globals().set("a", 1)?; + let func1 = lua.load("a += 1; return a").into_function()?; + let func2 = func1.deep_clone(); + + assert_ne!(func1.to_pointer(), func2.to_pointer()); + assert_eq!(func1.call::<_, i32>(())?, 2); + assert_eq!(func2.call::<_, i32>(())?, 3); + + // Check that for Rust functions deep_clone is just a clone + let rust_func = lua.create_function(|_, ()| Ok(42))?; + let rust_func2 = rust_func.deep_clone(); + assert_eq!(rust_func.to_pointer(), rust_func2.to_pointer()); + + Ok(()) +} + #[test] fn test_function_wrap() -> Result<()> { use mlua::Error; From 80fff4f2e7f8250c28bc6f5de79dace1ec914b31 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 21 Mar 2024 22:29:34 +0000 Subject: [PATCH 068/635] Update `reqwest` to 0.12 --- Cargo.toml | 2 +- examples/async_http_reqwest.rs | 20 +++++++------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8bee1eb1..a83f0ae5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,7 @@ libloading = { version = "0.8", optional = true } trybuild = "1.0" futures = "0.3.5" hyper = { version = "0.14", features = ["client", "server"] } -reqwest = { version = "0.11", features = ["json"] } +reqwest = { version = "0.12", features = ["json"] } tokio = { version = "1.0", features = ["macros", "rt", "time"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/examples/async_http_reqwest.rs b/examples/async_http_reqwest.rs index 4d67564b..2021e849 100644 --- a/examples/async_http_reqwest.rs +++ b/examples/async_http_reqwest.rs @@ -1,11 +1,9 @@ -use mlua::{chunk, ExternalResult, Lua, LuaSerdeExt, Result}; +use mlua::{chunk, ExternalResult, Lua, LuaSerdeExt, Result, Value}; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { let lua = Lua::new(); - let null = lua.null(); - let fetch_json = lua.create_async_function(|lua, uri: String| async move { let resp = reqwest::get(&uri) .await @@ -15,19 +13,15 @@ async fn main() -> Result<()> { lua.to_value(&json) })?; + let dbg = lua.create_function(|_, value: Value| { + println!("{value:#?}"); + Ok(()) + })?; + let f = lua .load(chunk! { - function print_r(t, indent) - local indent = indent or "" - for k, v in pairs(t) do - io.write(indent, tostring(k)) - if type(v) == "table" then io.write(":\n") print_r(v, indent.." ") - else io.write(": ", v == $null and "null" or tostring(v), "\n") end - end - end - local res = $fetch_json(...) - print_r(res) + $dbg(res) }) .into_function()?; From 849206ef9d56b250424f34077a62a4e6ad96c21a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?19=E5=B9=B4=E6=A2=A6=E9=86=92?= <3949379+getong@users.noreply.github.com> Date: Fri, 22 Mar 2024 07:06:00 +0800 Subject: [PATCH 069/635] update hyper to v1, and add shell command example (#384) Co-authored-by: Alex Orlenko --- Cargo.toml | 5 +- README.md | 11 ++++ examples/async_http_client.rs | 53 ++++++++++------ examples/async_http_server.rs | 111 +++++++++++++++++----------------- 4 files changed, 106 insertions(+), 74 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a83f0ae5..1352d52b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,10 @@ libloading = { version = "0.8", optional = true } [dev-dependencies] trybuild = "1.0" futures = "0.3.5" -hyper = { version = "0.14", features = ["client", "server"] } +hyper = { version = "1", features = ["client", "server"] } +hyper-util = { version = "0.1.3", features = ["server", "client", "client-legacy", "tokio", "http1"] } +http-body-util = "0.1.1" +bytes = "1.5.0" reqwest = { version = "0.12", features = ["json"] } tokio = { version = "1.0", features = ["macros", "rt", "time"] } serde = { version = "1.0", features = ["derive"] } diff --git a/README.md b/README.md index 6ae3958d..434a2bab 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,17 @@ This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6 - [HTTP Server](examples/async_http_server.rs) - [TCP Server](examples/async_tcp_server.rs) + +**shell command example**: +``` shell +# async http client +cargo run --example async_http_client --features="async macros lua54" + +# async http server +cargo run --example async_http_server --features="async macros lua54" +curl http://localhost:3000 +``` + ### Serialization (serde) support With `serialize` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition `mlua` provides [`serde::Serialize`] trait implementation for it (including `UserData` support). diff --git a/examples/async_http_client.rs b/examples/async_http_client.rs index 3eccdc32..c0fa1ffc 100644 --- a/examples/async_http_client.rs +++ b/examples/async_http_client.rs @@ -1,20 +1,38 @@ +use bytes::Bytes; +use http_body_util::BodyExt; +use http_body_util::Empty; +use hyper::body::Incoming; +use hyper_util::client::legacy::Client as HyperClient; use std::collections::HashMap; -use hyper::body::{Body as HyperBody, HttpBody as _}; -use hyper::Client as HyperClient; - use mlua::{chunk, ExternalResult, Lua, Result, UserData, UserDataMethods}; -struct BodyReader(HyperBody); +struct BodyReader(Incoming); impl UserData for BodyReader { fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { methods.add_async_method_mut("read", |lua, reader, ()| async move { - if let Some(bytes) = reader.0.data().await { - let bytes = bytes.into_lua_err()?; - return Some(lua.create_string(&bytes)).transpose(); + let mut summarize = Vec::new(); // Create a vector to accumulate the bytes + + loop { + match reader.0.frame().await { + Some(Ok(bytes)) => { + if let Ok(data) = bytes.into_data() { + summarize.extend(data); // Append the bytes to the summarize variable + } + } + Some(Err(_)) => break, // Break on error + None => break, // Break if no more frames + } + } + + if !summarize.is_empty() { + // If summarize has collected data, return it as a Lua string + Ok(Some(lua.create_string(&summarize)?)) + } else { + // Return None if no data was collected + Ok(None) } - Ok(None) }); } } @@ -24,7 +42,8 @@ async fn main() -> Result<()> { let lua = Lua::new(); let fetch_url = lua.create_async_function(|lua, uri: String| async move { - let client = HyperClient::new(); + let client = + HyperClient::builder(hyper_util::rt::TokioExecutor::new()).build_http::>(); let uri = uri.parse().into_lua_err()?; let resp = client.get(uri).await.into_lua_err()?; @@ -47,18 +66,18 @@ async fn main() -> Result<()> { let f = lua .load(chunk! { - local res = $fetch_url(...) + local res = $fetch_url(...) print("status: "..res.status) for key, vals in pairs(res.headers) do - for _, val in ipairs(vals) do - print(key..": "..val) - end + for _, val in ipairs(vals) do + print(key..": "..val) + end end repeat - local body = res.body:read() - if body then - print(body) - end + local body = res.body:read() + if body then + print(body) + end until not body }) .into_function()?; diff --git a/examples/async_http_server.rs b/examples/async_http_server.rs index 43ae7a95..cd94e6eb 100644 --- a/examples/async_http_server.rs +++ b/examples/async_http_server.rs @@ -1,18 +1,14 @@ -use std::future::Future; -use std::net::SocketAddr; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; - -use hyper::server::conn::AddrStream; -use hyper::service::Service; -use hyper::{Body, Request, Response, Server}; - +use bytes::Bytes; +use http_body_util::{combinators::BoxBody, BodyExt, Empty, Full}; +use hyper::{body::Incoming, service::Service, Request, Response}; +use hyper_util::{rt::TokioIo, server::conn::auto}; use mlua::{ chunk, Error as LuaError, Function, Lua, String as LuaString, Table, UserData, UserDataMethods, }; +use std::{future::Future, net::SocketAddr, pin::Pin, rc::Rc}; +use tokio::{net::TcpListener, task::LocalSet}; -struct LuaRequest(SocketAddr, Request); +struct LuaRequest(SocketAddr, Request); impl UserData for LuaRequest { fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { @@ -23,16 +19,12 @@ impl UserData for LuaRequest { pub struct Svc(Rc, SocketAddr); -impl Service> for Svc { - type Response = Response; +impl Service> for Svc { + type Response = Response>; type Error = LuaError; type Future = Pin>>>; - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: Request) -> Self::Future { + fn call(&self, req: Request) -> Self::Future { // If handler returns an error then generate 5xx response let lua = self.0.clone(); let lua_req = LuaRequest(self.1, req); @@ -53,8 +45,16 @@ impl Service> for Svc { let body = lua_resp .get::<_, Option>("body")? - .map(|b| Body::from(b.as_bytes().to_vec())) - .unwrap_or_else(Body::empty); + .map(|b| { + Full::new(Bytes::copy_from_slice(b.clone().as_bytes())) + .map_err(|never| match never {}) + .boxed() + }) + .unwrap_or_else(|| { + Empty::::new() + .map_err(|never| match never {}) + .boxed() + }); Ok(resp.body(body).unwrap()) } @@ -62,7 +62,11 @@ impl Service> for Svc { eprintln!("{}", err); Ok(Response::builder() .status(500) - .body(Body::from("Internal Server Error")) + .body( + Full::new(Bytes::from("Internal Server Error".as_bytes())) + .map_err(|never| match never {}) + .boxed(), + ) .unwrap()) } } @@ -77,16 +81,16 @@ async fn main() { // Create Lua handler function let handler: Function = lua .load(chunk! { - function(req) - return { - status = 200, - headers = { - ["X-Req-Method"] = req:method(), - ["X-Remote-Addr"] = req:remote_addr(), - }, - body = "Hello from Lua!\n" - } - end + function(req) + return { + status = 200, + headers = { + ["X-Req-Method"] = req:method(), + ["X-Remote-Addr"] = req:remote_addr(), + }, + body = "Hello from Lua!\n" + } + end }) .eval() .expect("cannot create Lua handler"); @@ -95,31 +99,26 @@ async fn main() { lua.set_named_registry_value("http_handler", handler) .expect("cannot store Lua handler"); - let addr = ([127, 0, 0, 1], 3000).into(); - let server = Server::bind(&addr).executor(LocalExec).serve(MakeSvc(lua)); - - println!("Listening on http://{}", addr); - - // Create `LocalSet` to spawn !Send futures - let local = tokio::task::LocalSet::new(); - local.run_until(server).await.expect("cannot run server") -} - -struct MakeSvc(Rc); - -impl Service<&AddrStream> for MakeSvc { - type Response = Svc; - type Error = hyper::Error; - type Future = Pin>>>; - - fn poll_ready(&mut self, _: &mut Context) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, stream: &AddrStream) -> Self::Future { - let lua = self.0.clone(); - let remote_addr = stream.remote_addr(); - Box::pin(async move { Ok(Svc(lua, remote_addr)) }) + let addr = "127.0.0.1:3000"; + + let local = LocalSet::new(); + let listener = TcpListener::bind(addr).await.unwrap(); + loop { + let (stream, peer_addr) = listener.accept().await.unwrap(); + let io = TokioIo::new(stream); + + let svc = Svc(lua.clone(), peer_addr); + local + .run_until(async move { + if let Err(err) = auto::Builder::new(LocalExec) + .http1() + .serve_connection(io, svc) + .await + { + println!("Error serving connection: {:?}", err); + } + }) + .await; } } From 59b14000f3c12279183747781e6f802b29d90bd8 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 22 Mar 2024 00:30:29 +0000 Subject: [PATCH 070/635] Update hyper examples --- Cargo.toml | 5 +- README.md | 15 ++-- examples/async_http_client.rs | 53 +++++-------- examples/async_http_server.rs | 137 ++++++++++++++++++++-------------- 4 files changed, 109 insertions(+), 101 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1352d52b..c42075d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,10 +63,9 @@ libloading = { version = "0.8", optional = true } [dev-dependencies] trybuild = "1.0" futures = "0.3.5" -hyper = { version = "1", features = ["client", "server"] } -hyper-util = { version = "0.1.3", features = ["server", "client", "client-legacy", "tokio", "http1"] } +hyper = { version = "1.2", features = ["full"] } +hyper-util = { version = "0.1.3", features = ["full"] } http-body-util = "0.1.1" -bytes = "1.5.0" reqwest = { version = "0.12", features = ["json"] } tokio = { version = "1.0", features = ["macros", "rt", "time"] } serde = { version = "1.0", features = ["derive"] } diff --git a/README.md b/README.md index 434a2bab..3d362d74 100644 --- a/README.md +++ b/README.md @@ -85,14 +85,17 @@ This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6 - [TCP Server](examples/async_tcp_server.rs) -**shell command example**: -``` shell -# async http client -cargo run --example async_http_client --features="async macros lua54" +**shell command examples**: +```shell +# async http client (hyper) +cargo run --example async_http_client --features=lua54,async,macros + +# async http client (reqwest) +cargo run --example async_http_reqwest --features=lua54,async,macros,serialize # async http server -cargo run --example async_http_server --features="async macros lua54" -curl http://localhost:3000 +cargo run --example async_http_server --features=lua54,async,macros +curl -v http://localhost:3000 ``` ### Serialization (serde) support diff --git a/examples/async_http_client.rs b/examples/async_http_client.rs index c0fa1ffc..ca4eac9a 100644 --- a/examples/async_http_client.rs +++ b/examples/async_http_client.rs @@ -1,9 +1,9 @@ -use bytes::Bytes; -use http_body_util::BodyExt; -use http_body_util::Empty; +use std::collections::HashMap; + +use http_body_util::BodyExt as _; use hyper::body::Incoming; use hyper_util::client::legacy::Client as HyperClient; -use std::collections::HashMap; +use hyper_util::rt::TokioExecutor; use mlua::{chunk, ExternalResult, Lua, Result, UserData, UserDataMethods}; @@ -11,28 +11,14 @@ struct BodyReader(Incoming); impl UserData for BodyReader { fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + // Every call returns a next chunk methods.add_async_method_mut("read", |lua, reader, ()| async move { - let mut summarize = Vec::new(); // Create a vector to accumulate the bytes - - loop { - match reader.0.frame().await { - Some(Ok(bytes)) => { - if let Ok(data) = bytes.into_data() { - summarize.extend(data); // Append the bytes to the summarize variable - } - } - Some(Err(_)) => break, // Break on error - None => break, // Break if no more frames + if let Some(bytes) = reader.0.frame().await { + if let Some(bytes) = bytes.into_lua_err()?.data_ref() { + return Some(lua.create_string(&bytes)).transpose(); } } - - if !summarize.is_empty() { - // If summarize has collected data, return it as a Lua string - Ok(Some(lua.create_string(&summarize)?)) - } else { - // Return None if no data was collected - Ok(None) - } + Ok(None) }); } } @@ -42,8 +28,7 @@ async fn main() -> Result<()> { let lua = Lua::new(); let fetch_url = lua.create_async_function(|lua, uri: String| async move { - let client = - HyperClient::builder(hyper_util::rt::TokioExecutor::new()).build_http::>(); + let client = HyperClient::builder(TokioExecutor::new()).build_http::(); let uri = uri.parse().into_lua_err()?; let resp = client.get(uri).await.into_lua_err()?; @@ -66,19 +51,19 @@ async fn main() -> Result<()> { let f = lua .load(chunk! { - local res = $fetch_url(...) + local res = $fetch_url(...) print("status: "..res.status) for key, vals in pairs(res.headers) do - for _, val in ipairs(vals) do - print(key..": "..val) - end + for _, val in ipairs(vals) do + print(key..": "..val) + end end repeat - local body = res.body:read() - if body then - print(body) - end - until not body + local chunk = res.body:read() + if chunk then + print(chunk) + end + until not chunk }) .into_function()?; diff --git a/examples/async_http_server.rs b/examples/async_http_server.rs index cd94e6eb..7da84905 100644 --- a/examples/async_http_server.rs +++ b/examples/async_http_server.rs @@ -1,35 +1,63 @@ -use bytes::Bytes; -use http_body_util::{combinators::BoxBody, BodyExt, Empty, Full}; -use hyper::{body::Incoming, service::Service, Request, Response}; -use hyper_util::{rt::TokioIo, server::conn::auto}; +use std::convert::Infallible; +use std::future::Future; +use std::net::SocketAddr; +use std::rc::Rc; + +use futures::future::LocalBoxFuture; +use http_body_util::{combinators::BoxBody, BodyExt as _, Empty, Full}; +use hyper::body::{Bytes, Incoming}; +use hyper::{Request, Response}; +use hyper_util::rt::TokioIo; +use hyper_util::server::conn::auto::Builder as ServerConnBuilder; +use tokio::net::TcpListener; +use tokio::task::LocalSet; + use mlua::{ - chunk, Error as LuaError, Function, Lua, String as LuaString, Table, UserData, UserDataMethods, + chunk, Error as LuaError, Function, Lua, RegistryKey, String as LuaString, Table, UserData, + UserDataMethods, }; -use std::{future::Future, net::SocketAddr, pin::Pin, rc::Rc}; -use tokio::{net::TcpListener, task::LocalSet}; +/// Wrapper around incoming request that implements UserData struct LuaRequest(SocketAddr, Request); impl UserData for LuaRequest { fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_method("remote_addr", |_lua, req, ()| Ok((req.0).to_string())); - methods.add_method("method", |_lua, req, ()| Ok((req.1).method().to_string())); + methods.add_method("remote_addr", |_, req, ()| Ok((req.0).to_string())); + methods.add_method("method", |_, req, ()| Ok((req.1).method().to_string())); + methods.add_method("path", |_, req, ()| Ok(req.1.uri().path().to_string())); } } -pub struct Svc(Rc, SocketAddr); +/// Service that handles incoming requests +#[derive(Clone)] +pub struct Svc { + lua: Rc, + handler: Rc, + peer_addr: SocketAddr, +} + +impl Svc { + pub fn new(lua: Rc, handler: Rc, peer_addr: SocketAddr) -> Self { + Self { + lua, + handler, + peer_addr, + } + } +} -impl Service> for Svc { - type Response = Response>; +impl hyper::service::Service> for Svc { + type Response = Response>; type Error = LuaError; - type Future = Pin>>>; + type Future = LocalBoxFuture<'static, Result>; fn call(&self, req: Request) -> Self::Future { // If handler returns an error then generate 5xx response - let lua = self.0.clone(); - let lua_req = LuaRequest(self.1, req); + let lua = self.lua.clone(); + let handler_key = self.handler.clone(); + let lua_req = LuaRequest(self.peer_addr, req); Box::pin(async move { - let handler: Function = lua.named_registry_value("http_handler")?; + let handler: Function = lua.registry_value(&handler_key)?; match handler.call_async::<_, Table>(lua_req).await { Ok(lua_resp) => { let status = lua_resp.get::<_, Option>("status")?.unwrap_or(200); @@ -43,18 +71,11 @@ impl Service> for Svc { } } + // Set body let body = lua_resp .get::<_, Option>("body")? - .map(|b| { - Full::new(Bytes::copy_from_slice(b.clone().as_bytes())) - .map_err(|never| match never {}) - .boxed() - }) - .unwrap_or_else(|| { - Empty::::new() - .map_err(|never| match never {}) - .boxed() - }); + .map(|b| Full::new(Bytes::copy_from_slice(b.as_bytes())).boxed()) + .unwrap_or_else(|| Empty::::new().boxed()); Ok(resp.body(body).unwrap()) } @@ -62,11 +83,7 @@ impl Service> for Svc { eprintln!("{}", err); Ok(Response::builder() .status(500) - .body( - Full::new(Bytes::from("Internal Server Error".as_bytes())) - .map_err(|never| match never {}) - .boxed(), - ) + .body(Full::new(Bytes::from("Internal Server Error")).boxed()) .unwrap()) } } @@ -79,43 +96,47 @@ async fn main() { let lua = Rc::new(Lua::new()); // Create Lua handler function - let handler: Function = lua + let handler: RegistryKey = lua .load(chunk! { - function(req) - return { - status = 200, - headers = { - ["X-Req-Method"] = req:method(), - ["X-Remote-Addr"] = req:remote_addr(), - }, - body = "Hello from Lua!\n" - } - end + function(req) + return { + status = 200, + headers = { + ["X-Req-Method"] = req:method(), + ["X-Req-Path"] = req:path(), + ["X-Remote-Addr"] = req:remote_addr(), + }, + body = "Hello from Lua!\n" + } + end }) .eval() - .expect("cannot create Lua handler"); + .expect("Failed to create Lua handler"); + let handler = Rc::new(handler); - // Store it in the Registry - lua.set_named_registry_value("http_handler", handler) - .expect("cannot store Lua handler"); - - let addr = "127.0.0.1:3000"; + let listen_addr = "127.0.0.1:3000"; + let listener = TcpListener::bind(listen_addr).await.unwrap(); + println!("Listening on http://{listen_addr}"); let local = LocalSet::new(); - let listener = TcpListener::bind(addr).await.unwrap(); loop { - let (stream, peer_addr) = listener.accept().await.unwrap(); - let io = TokioIo::new(stream); + let (stream, peer_addr) = match listener.accept().await { + Ok(x) => x, + Err(err) => { + eprintln!("Failed to accept connection: {err}"); + continue; + } + }; - let svc = Svc(lua.clone(), peer_addr); + let svc = Svc::new(lua.clone(), handler.clone(), peer_addr); local .run_until(async move { - if let Err(err) = auto::Builder::new(LocalExec) + let result = ServerConnBuilder::new(LocalExec) .http1() - .serve_connection(io, svc) - .await - { - println!("Error serving connection: {:?}", err); + .serve_connection(TokioIo::new(stream), svc) + .await; + if let Err(err) = result { + eprintln!("Error serving connection: {err:?}"); } }) .await; @@ -127,7 +148,7 @@ struct LocalExec; impl hyper::rt::Executor for LocalExec where - F: std::future::Future + 'static, // not requiring `Send` + F: Future + 'static, // not requiring `Send` { fn execute(&self, fut: F) { tokio::task::spawn_local(fut); From 038cc5f974f81b0fc0925dcf69c997faea4b41c6 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 22 Mar 2024 00:40:07 +0000 Subject: [PATCH 071/635] Bump rustyline to 14.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c42075d6..e34c4067 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,7 @@ static_assertions = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] criterion = { version = "0.5", features = ["async_tokio"] } -rustyline = "13.0" +rustyline = "14.0" tokio = { version = "1.0", features = ["full"] } [[bench]] From 39afe4c6f7aa8f87266029bfcc1bd06224c5f268 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 23 Mar 2024 11:00:16 +0000 Subject: [PATCH 072/635] Add `SerializeOptions::detect_serde_json_arbitrary_precision` to detect `serde_json::Number` with `arbitrary_precision` and convert it to Lua number. By default the option is disabled and such numbers represented as Lua objects with `$serde_json::private::Number` key. Fixes #385 --- Cargo.toml | 2 +- src/serde/ser.rs | 85 +++++++++++++++++++++++++++++++++++++++++++----- tests/serde.rs | 31 ++++++++++++++++++ 3 files changed, 109 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e34c4067..6edf4a09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,7 @@ http-body-util = "0.1.1" reqwest = { version = "0.12", features = ["json"] } tokio = { version = "1.0", features = ["macros", "rt", "time"] } serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" +serde_json = { version = "1.0", features = ["arbitrary_precision"] } maplit = "1.0" tempfile = "3" static_assertions = "1.0" diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 6115cb20..9449ccbd 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -43,6 +43,12 @@ pub struct Options { /// [`null`]: crate::LuaSerdeExt::null /// [`Nil`]: crate::Value::Nil pub serialize_unit_to_null: bool, + + /// If true, serialize `serde_json::Number` with arbitrary_precision to a Lua number. + /// Otherwise it will be serialized as an object (what serde does). + /// + /// Default: **false** + pub detect_serde_json_arbitrary_precision: bool, } impl Default for Options { @@ -58,6 +64,7 @@ impl Options { set_array_metatable: true, serialize_none_to_null: true, serialize_unit_to_null: true, + detect_serde_json_arbitrary_precision: false, } } @@ -87,6 +94,20 @@ impl Options { self.serialize_unit_to_null = enabled; self } + + /// Sets [`detect_serde_json_arbitrary_precision`] option. + /// + /// This option is used to serialize `serde_json::Number` with arbitrary precision to a Lua number. + /// Otherwise it will be serialized as an object (what serde does). + /// + /// This option is disabled by default. + /// + /// [`detect_serde_json_arbitrary_precision`]: #structfield.detect_serde_json_arbitrary_precision + #[must_use] + pub const fn detect_serde_json_arbitrary_precision(mut self, enabled: bool) -> Self { + self.detect_serde_json_arbitrary_precision = enabled; + self + } } impl<'lua> Serializer<'lua> { @@ -121,7 +142,7 @@ impl<'lua> ser::Serializer for Serializer<'lua> { type SerializeTupleStruct = SerializeSeq<'lua>; type SerializeTupleVariant = SerializeTupleVariant<'lua>; type SerializeMap = SerializeMap<'lua>; - type SerializeStruct = SerializeMap<'lua>; + type SerializeStruct = SerializeStruct<'lua>; type SerializeStructVariant = SerializeStructVariant<'lua>; #[inline] @@ -282,8 +303,23 @@ impl<'lua> ser::Serializer for Serializer<'lua> { } #[inline] - fn serialize_struct(self, _name: &'static str, len: usize) -> Result { - self.serialize_map(Some(len)) + fn serialize_struct(self, name: &'static str, len: usize) -> Result { + if self.options.detect_serde_json_arbitrary_precision + && name == "$serde_json::private::Number" + && len == 1 + { + return Ok(SerializeStruct { + lua: self.lua, + inner: None, + options: self.options, + }); + } + + Ok(SerializeStruct { + lua: self.lua, + inner: Some(Value::Table(self.lua.create_table_with_capacity(0, len)?)), + options: self.options, + }) } #[inline] @@ -465,7 +501,14 @@ impl<'lua> ser::SerializeMap for SerializeMap<'lua> { } } -impl<'lua> ser::SerializeStruct for SerializeMap<'lua> { +#[doc(hidden)] +pub struct SerializeStruct<'lua> { + lua: &'lua Lua, + inner: Option>, + options: Options, +} + +impl<'lua> ser::SerializeStruct for SerializeStruct<'lua> { type Ok = Value<'lua>; type Error = Error; @@ -473,12 +516,38 @@ impl<'lua> ser::SerializeStruct for SerializeMap<'lua> { where T: Serialize + ?Sized, { - ser::SerializeMap::serialize_key(self, key)?; - ser::SerializeMap::serialize_value(self, value) + match self.inner { + Some(Value::Table(ref table)) => { + table.raw_set(key, self.lua.to_value_with(value, self.options)?)?; + } + None if self.options.detect_serde_json_arbitrary_precision => { + // A special case for `serde_json::Number` with arbitrary precision. + assert_eq!(key, "$serde_json::private::Number"); + self.inner = Some(self.lua.to_value_with(value, self.options)?); + } + _ => unreachable!(), + } + Ok(()) } fn end(self) -> Result> { - ser::SerializeMap::end(self) + match self.inner { + Some(table @ Value::Table(_)) => Ok(table), + Some(value) if self.options.detect_serde_json_arbitrary_precision => { + let number_s = value.as_str().expect("not an arbitrary precision number"); + if number_s.contains(&['.', 'e', 'E']) { + if let Ok(number) = number_s.parse().map(Value::Number) { + return Ok(number); + } + } + Ok(number_s + .parse() + .map(Value::Integer) + .or_else(|_| number_s.parse().map(Value::Number)) + .unwrap_or_else(|_| value)) + } + _ => unreachable!(), + } } } @@ -505,7 +574,7 @@ impl<'lua> ser::SerializeStructVariant for SerializeStructVariant<'lua> { fn end(self) -> Result> { let lua = self.table.0.lua; - let table = lua.create_table()?; + let table = lua.create_table_with_capacity(0, 1)?; table.raw_set(self.name, self.table)?; Ok(Value::Table(table)) } diff --git a/tests/serde.rs b/tests/serde.rs index 168407fb..d7a6801c 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -697,3 +697,34 @@ fn test_from_value_sorted() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_arbitrary_precision() { + let lua = Lua::new(); + + let opts = SerializeOptions::new().detect_serde_json_arbitrary_precision(true); + + // Number + let num = serde_json::Value::Number(serde_json::Number::from_f64(1.244e2).unwrap()); + let num = lua.to_value_with(&num, opts).unwrap(); + assert_eq!(num, Value::Number(1.244e2)); + + // Integer + let num = serde_json::Value::Number(serde_json::Number::from_f64(123.0).unwrap()); + let num = lua.to_value_with(&num, opts).unwrap(); + assert_eq!(num, Value::Integer(123)); + + // Max u64 + let num = serde_json::Value::Number(serde_json::Number::from(i64::MAX)); + let num = lua.to_value_with(&num, opts).unwrap(); + assert_eq!(num, Value::Number(i64::MAX as f64)); + + // Check that the option is disabled by default + let num = serde_json::Value::Number(serde_json::Number::from_f64(1.244e2).unwrap()); + let num = lua.to_value(&num).unwrap(); + assert_eq!(num.type_name(), "table"); + assert_eq!( + format!("{:#?}", num), + "{\n [\"$serde_json::private::Number\"] = \"124.4\",\n}" + ); +} From a79840afc903b14d14412e0075a4e0a1c640b798 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 23 Mar 2024 11:42:46 +0000 Subject: [PATCH 073/635] Suppress Rust 1.77 dead_code false warnings. --- benches/benchmark.rs | 4 ++-- src/userdata.rs | 4 ++-- tests/memory.rs | 2 +- tests/scope.rs | 20 ++++++++++---------- tests/serde.rs | 2 +- tests/tests.rs | 2 +- tests/thread.rs | 2 +- tests/userdata.rs | 2 +- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 014bd3af..245bd351 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -269,7 +269,7 @@ fn registry_value_create(c: &mut Criterion) { } fn userdata_create(c: &mut Criterion) { - struct UserData(i64); + struct UserData(#[allow(unused)] i64); impl LuaUserData for UserData {} let lua = Lua::new(); @@ -286,7 +286,7 @@ fn userdata_create(c: &mut Criterion) { } fn userdata_call_index(c: &mut Criterion) { - struct UserData(i64); + struct UserData(#[allow(unused)] i64); impl LuaUserData for UserData { fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { methods.add_meta_method(LuaMetaMethod::Index, move |_, _, key: LuaString| Ok(key)); diff --git a/src/userdata.rs b/src/userdata.rs index 04d62231..e2c4a748 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -1358,7 +1358,7 @@ impl<'lua> Serialize for AnyUserData<'lua> { /// A wrapper type for an immutably borrowed value from a `AnyUserData`. /// /// It implements [`FromLua`] and can be used to receive a typed userdata from Lua. -pub struct UserDataRef<'lua, T: 'static>(AnyUserData<'lua>, Ref<'lua, T>); +pub struct UserDataRef<'lua, T: 'static>(#[allow(unused)] AnyUserData<'lua>, Ref<'lua, T>); impl<'lua, T: 'static> Deref for UserDataRef<'lua, T> { type Target = T; @@ -1380,7 +1380,7 @@ impl<'lua, T: 'static> UserDataRef<'lua, T> { /// A wrapper type for a mutably borrowed value from a `AnyUserData`. /// /// It implements [`FromLua`] and can be used to receive a typed userdata from Lua. -pub struct UserDataRefMut<'lua, T: 'static>(AnyUserData<'lua>, RefMut<'lua, T>); +pub struct UserDataRefMut<'lua, T: 'static>(#[allow(unused)] AnyUserData<'lua>, RefMut<'lua, T>); impl<'lua, T: 'static> Deref for UserDataRefMut<'lua, T> { type Target = T; diff --git a/tests/memory.rs b/tests/memory.rs index 6c3d2e13..0f9e8d04 100644 --- a/tests/memory.rs +++ b/tests/memory.rs @@ -84,7 +84,7 @@ fn test_gc_control() -> Result<()> { assert_eq!(lua.gc_inc(200, 100, 13), GCMode::Incremental); - struct MyUserdata(Arc<()>); + struct MyUserdata(#[allow(unused)] Arc<()>); impl UserData for MyUserdata {} let rc = Arc::new(()); diff --git a/tests/scope.rs b/tests/scope.rs index 1cd5fc49..b04b7b50 100644 --- a/tests/scope.rs +++ b/tests/scope.rs @@ -155,14 +155,14 @@ fn test_scope_userdata_functions() -> Result<()> { impl<'a> UserData for MyUserData<'a> { fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_meta_function(MetaMethod::Add, |lua, ()| { + methods.add_meta_method(MetaMethod::Add, |lua, this, ()| { let globals = lua.globals(); - globals.set("i", globals.get::<_, i64>("i")? + 1)?; + globals.set("i", globals.get::<_, i64>("i")? + this.0)?; Ok(()) }); - methods.add_meta_function(MetaMethod::Sub, |lua, ()| { + methods.add_meta_method(MetaMethod::Sub, |lua, this, ()| { let globals = lua.globals(); - globals.set("i", globals.get::<_, i64>("i")? + 1)?; + globals.set("i", globals.get::<_, i64>("i")? + this.0)?; Ok(()) }); } @@ -170,7 +170,7 @@ fn test_scope_userdata_functions() -> Result<()> { let lua = Lua::new(); - let dummy = 0; + let dummy = 1; let f = lua .load( r#" @@ -178,7 +178,7 @@ fn test_scope_userdata_functions() -> Result<()> { return function(u) _ = u + u _ = u - 1 - _ = 1 + u + _ = u + 1 end "#, ) @@ -257,7 +257,7 @@ fn test_scope_userdata_mismatch() -> Result<()> { fn test_scope_userdata_drop() -> Result<()> { let lua = Lua::new(); - struct MyUserData(Rc<()>); + struct MyUserData(#[allow(unused)] Rc<()>); impl UserData for MyUserData { fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { @@ -265,7 +265,7 @@ fn test_scope_userdata_drop() -> Result<()> { } } - struct MyUserDataArc(Arc<()>); + struct MyUserDataArc(#[allow(unused)] Arc<()>); impl UserData for MyUserDataArc {} @@ -315,7 +315,7 @@ fn test_scope_userdata_drop() -> Result<()> { fn test_scope_nonstatic_userdata_drop() -> Result<()> { let lua = Lua::new(); - struct MyUserData<'a>(&'a Cell, Arc<()>); + struct MyUserData<'a>(&'a Cell, #[allow(unused)] Arc<()>); impl<'a> UserData for MyUserData<'a> { fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { @@ -326,7 +326,7 @@ fn test_scope_nonstatic_userdata_drop() -> Result<()> { } } - struct MyUserDataArc(Arc<()>); + struct MyUserDataArc(#[allow(unused)] Arc<()>); impl UserData for MyUserDataArc {} diff --git a/tests/serde.rs b/tests/serde.rs index d7a6801c..7e2e251a 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -99,7 +99,7 @@ fn test_serialize_in_scope() -> LuaResult<()> { Err(e) => panic!("expected destructed error, got {}", e), } - struct MyUserDataRef<'a>(&'a ()); + struct MyUserDataRef<'a>(#[allow(unused)] &'a ()); impl<'a> UserData for MyUserDataRef<'a> {} diff --git a/tests/tests.rs b/tests/tests.rs index 4d072bb2..9dd6a00f 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -743,7 +743,7 @@ fn test_registry_value() -> Result<()> { #[test] fn test_drop_registry_value() -> Result<()> { - struct MyUserdata(Arc<()>); + struct MyUserdata(#[allow(unused)] Arc<()>); impl UserData for MyUserdata {} diff --git a/tests/thread.rs b/tests/thread.rs index 06bae95b..83beb4f5 100644 --- a/tests/thread.rs +++ b/tests/thread.rs @@ -101,7 +101,7 @@ fn test_thread_reset() -> Result<()> { let lua = Lua::new(); - struct MyUserData(Arc<()>); + struct MyUserData(#[allow(unused)] Arc<()>); impl UserData for MyUserData {} let arc = Arc::new(()); diff --git a/tests/userdata.rs b/tests/userdata.rs index dca5ed2d..7d5b6233 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -402,7 +402,7 @@ fn test_userdata_take() -> Result<()> { #[test] fn test_userdata_destroy() -> Result<()> { - struct MyUserdata(Arc<()>); + struct MyUserdata(#[allow(unused)] Arc<()>); impl UserData for MyUserdata {} From 508517c45ec88ddf562fa33bf8af84745ab6a474 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 23 Mar 2024 19:47:42 +0000 Subject: [PATCH 074/635] Do not use dependencies as implicit features. Add `dep:` prefix instead. --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6edf4a09..c8ddd6d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,14 +32,14 @@ lua52 = ["ffi/lua52"] lua51 = ["ffi/lua51"] luajit = ["ffi/luajit"] luajit52 = ["luajit", "ffi/luajit52"] -luau = ["ffi/luau", "libloading"] +luau = ["ffi/luau", "dep:libloading"] luau-jit = ["luau", "ffi/luau-codegen"] luau-vector4 = ["luau", "ffi/luau-vector4"] vendored = ["ffi/vendored"] -module = ["mlua_derive", "ffi/module"] -async = ["futures-util"] +module = ["dep:mlua_derive", "ffi/module"] +async = ["dep:futures-util"] send = [] -serialize = ["serde", "erased-serde", "serde-value"] +serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value"] macros = ["mlua_derive/macros"] unstable = [] From 6e6c73e4c7312d4d5803c4d86e97850955ca51cb Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 23 Mar 2024 22:30:59 +0000 Subject: [PATCH 075/635] Add deserialize json benchmark --- benches/serde.rs | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/benches/serde.rs b/benches/serde.rs index 6d2718b1..2ff193d2 100644 --- a/benches/serde.rs +++ b/benches/serde.rs @@ -1,6 +1,7 @@ -use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use std::time::Duration; +use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; + use mlua::prelude::*; fn collect_gc_twice(lua: &Lua) { @@ -43,6 +44,38 @@ fn encode_json(c: &mut Criterion) { }); } +fn decode_json(c: &mut Criterion) { + let lua = Lua::new(); + + let decode = lua + .create_function(|lua, s: String| { + lua.to_value(&serde_json::from_str::(&s).unwrap()) + }) + .unwrap(); + let json = r#"{ + "name": "Clark Kent", + "address": { + "city": "Smallville", + "state": "Kansas", + "country": "USA" + }, + "age": 22, + "parents": ["Jonathan Kent", "Martha Kent"], + "superman": true, + "interests": ["flying", "saving the world", "kryptonite"] + }"#; + + c.bench_function("deserialize json", |b| { + b.iter_batched( + || collect_gc_twice(&lua), + |_| { + decode.call::<_, LuaTable>(json).unwrap(); + }, + BatchSize::SmallInput, + ); + }); +} + criterion_group! { name = benches; config = Criterion::default() @@ -51,6 +84,7 @@ criterion_group! { .noise_threshold(0.02); targets = encode_json, + decode_json, } criterion_main!(benches); From b62f2ee0f70dfa91f3a8f6ec5ae5be6c58b2f77f Mon Sep 17 00:00:00 2001 From: Yiyu Lin Date: Mon, 25 Mar 2024 21:26:06 +0800 Subject: [PATCH 076/635] chore: make clippy happy (#388) Co-authored-by: hzlinyiyu --- mlua-sys/src/lua51/compat.rs | 1 - mlua-sys/src/lua52/compat.rs | 1 - mlua_derive/src/token.rs | 1 - src/conversion.rs | 16 ++++++++++------ src/luau/package.rs | 1 + src/multi.rs | 1 - src/serde/de.rs | 5 +---- src/serde/ser.rs | 4 ++-- src/value.rs | 10 ++++------ 9 files changed, 18 insertions(+), 22 deletions(-) diff --git a/mlua-sys/src/lua51/compat.rs b/mlua-sys/src/lua51/compat.rs index 5216d333..890ba3be 100644 --- a/mlua-sys/src/lua51/compat.rs +++ b/mlua-sys/src/lua51/compat.rs @@ -2,7 +2,6 @@ //! //! Based on github.com/keplerproject/lua-compat-5.3 -use std::convert::TryInto; use std::mem; use std::os::raw::{c_char, c_int, c_void}; use std::ptr; diff --git a/mlua-sys/src/lua52/compat.rs b/mlua-sys/src/lua52/compat.rs index 9a75a01d..49dcc7bc 100644 --- a/mlua-sys/src/lua52/compat.rs +++ b/mlua-sys/src/lua52/compat.rs @@ -2,7 +2,6 @@ //! //! Based on github.com/keplerproject/lua-compat-5.3 -use std::convert::TryInto; use std::os::raw::{c_char, c_int, c_void}; use std::ptr; diff --git a/mlua_derive/src/token.rs b/mlua_derive/src/token.rs index 1bfa6b18..9ef10d7a 100644 --- a/mlua_derive/src/token.rs +++ b/mlua_derive/src/token.rs @@ -1,7 +1,6 @@ use std::{ cmp::{Eq, PartialEq}, fmt::{self, Display, Formatter}, - iter::IntoIterator, vec::IntoIter, }; diff --git a/src/conversion.rs b/src/conversion.rs index dc076f90..2c57269e 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -1,6 +1,5 @@ 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::os::raw::c_int; @@ -67,7 +66,8 @@ impl<'lua> IntoLua<'lua> for &String<'lua> { #[inline] unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { - Ok(lua.push_ref(&self.0)) + lua.push_ref(&self.0); + Ok(()) } } @@ -131,7 +131,8 @@ impl<'lua> IntoLua<'lua> for &Table<'lua> { #[inline] unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { - Ok(lua.push_ref(&self.0)) + lua.push_ref(&self.0); + Ok(()) } } @@ -196,7 +197,8 @@ impl<'lua> IntoLua<'lua> for &Function<'lua> { #[inline] unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { - Ok(lua.push_ref(&self.0)) + lua.push_ref(&self.0); + Ok(()) } } @@ -261,7 +263,8 @@ impl<'lua> IntoLua<'lua> for &Thread<'lua> { #[inline] unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { - Ok(lua.push_ref(&self.0)) + lua.push_ref(&self.0); + Ok(()) } } @@ -326,7 +329,8 @@ impl<'lua> IntoLua<'lua> for &AnyUserData<'lua> { #[inline] unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { - Ok(lua.push_ref(&self.0)) + lua.push_ref(&self.0); + Ok(()) } } diff --git a/src/luau/package.rs b/src/luau/package.rs index ed69b5dc..6a3e0c1e 100644 --- a/src/luau/package.rs +++ b/src/luau/package.rs @@ -103,6 +103,7 @@ pub(crate) fn register_package_module(lua: &Lua) -> Result<()> { Ok(()) } +#[allow(unused_variables)] pub(crate) fn disable_dylibs(lua: &Lua) { // Presence of `LoadedDylibs` in app data is used as a flag // to check whether binary modules are enabled diff --git a/src/multi.rs b/src/multi.rs index eaa1107a..f9b9a932 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -1,4 +1,3 @@ -use std::iter::FromIterator; use std::ops::{Deref, DerefMut}; use std::os::raw::c_int; use std::result::Result as StdResult; diff --git a/src/serde/de.rs b/src/serde/de.rs index 12f329be..5d3512c2 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -1,5 +1,4 @@ use std::cell::RefCell; -use std::convert::TryInto; use std::os::raw::c_void; use std::rc::Rc; use std::result::Result as StdResult; @@ -134,9 +133,7 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> { Value::Nil => visitor.visit_unit(), Value::Boolean(b) => visitor.visit_bool(b), #[allow(clippy::useless_conversion)] - Value::Integer(i) => { - visitor.visit_i64(i.try_into().expect("cannot convert lua_Integer to i64")) - } + Value::Integer(i) => visitor.visit_i64(i.into()), #[allow(clippy::useless_conversion)] Value::Number(n) => visitor.visit_f64(n.into()), #[cfg(feature = "luau")] diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 9449ccbd..3401693f 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -535,7 +535,7 @@ impl<'lua> ser::SerializeStruct for SerializeStruct<'lua> { Some(table @ Value::Table(_)) => Ok(table), Some(value) if self.options.detect_serde_json_arbitrary_precision => { let number_s = value.as_str().expect("not an arbitrary precision number"); - if number_s.contains(&['.', 'e', 'E']) { + if number_s.contains(['.', 'e', 'E']) { if let Ok(number) = number_s.parse().map(Value::Number) { return Ok(number); } @@ -544,7 +544,7 @@ impl<'lua> ser::SerializeStruct for SerializeStruct<'lua> { .parse() .map(Value::Integer) .or_else(|_| number_s.parse().map(Value::Number)) - .unwrap_or_else(|_| value)) + .unwrap_or(value)) } _ => unreachable!(), } diff --git a/src/value.rs b/src/value.rs index 9ec4e048..ed80f279 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use std::cmp::Ordering; use std::collections::HashSet; -use std::iter::{self, FromIterator}; +use std::iter; use std::ops::Index; use std::os::raw::{c_int, c_void}; use std::string::String as StdString; @@ -15,7 +15,7 @@ use { crate::table::SerializableTable, rustc_hash::FxHashSet, serde::ser::{self, Serialize, Serializer}, - std::{cell::RefCell, convert::TryInto, rc::Rc, result::Result as StdResult}, + std::{cell::RefCell, rc::Rc, result::Result as StdResult}, }; use crate::error::{Error, Result}; @@ -252,8 +252,7 @@ impl<'lua> Value<'lua> { /// If the value is a Lua [`Integer`], try to convert it to `i64` or return `None` otherwise. #[inline] pub fn as_i64(&self) -> Option { - #[allow(clippy::useless_conversion)] - self.as_integer().and_then(|i| i64::try_from(i).ok()) + self.as_integer().map(i64::from) } /// Cast the value to `u64`. @@ -659,8 +658,7 @@ impl<'a, 'lua> Serialize for SerializableValue<'a, 'lua> { Value::Nil => serializer.serialize_unit(), Value::Boolean(b) => serializer.serialize_bool(*b), #[allow(clippy::useless_conversion)] - Value::Integer(i) => serializer - .serialize_i64((*i).try_into().expect("cannot convert Lua Integer to i64")), + Value::Integer(i) => serializer.serialize_i64((*i).into()), Value::Number(n) => serializer.serialize_f64(*n), #[cfg(feature = "luau")] Value::Vector(v) => v.serialize(serializer), From fa217d3706ebebfd1519743c3e620a940c218967 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 28 Mar 2024 13:05:01 +0000 Subject: [PATCH 077/635] Better Luau buffer type support. - Add `Lua::create_buffer()` function - Support serializing buffer type as a byte slice - Support accessing copy of underlying bytes using `BString` --- src/conversion.rs | 40 +++++++++++++++++++++++++++++++++++----- src/lua.rs | 21 +++++++++++++++++++++ src/serde/de.rs | 8 ++++++++ src/userdata.rs | 13 +++++++++++++ src/util/mod.rs | 14 ++++++++++++++ tests/conversion.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ tests/serde.rs | 27 +++++++++++++++++++++++++++ 7 files changed, 162 insertions(+), 5 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index 2c57269e..8ca8fe86 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -679,19 +679,49 @@ impl<'lua> IntoLua<'lua> for BString { } impl<'lua> FromLua<'lua> for BString { - #[inline] fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { let ty = value.type_name(); - Ok(BString::from( - lua.coerce_string(value)? + match value { + Value::String(s) => Ok(s.as_bytes().into()), + #[cfg(feature = "luau")] + Value::UserData(ud) if ud.1 == crate::types::SubtypeId::Buffer => unsafe { + let mut size = 0usize; + let buf = ffi::lua_tobuffer(ud.0.lua.ref_thread(), ud.0.index, &mut size); + mlua_assert!(!buf.is_null(), "invalid Luau buffer"); + Ok(slice::from_raw_parts(buf as *const u8, size).into()) + }, + _ => Ok(lua + .coerce_string(value)? .ok_or_else(|| Error::FromLuaConversionError { from: ty, to: "BString", message: Some("expected string or number".to_string()), })? .as_bytes() - .to_vec(), - )) + .into()), + } + } + + unsafe fn from_stack(idx: c_int, lua: &'lua Lua) -> Result { + let state = lua.state(); + match ffi::lua_type(state, idx) { + ffi::LUA_TSTRING => { + let mut size = 0; + let data = ffi::lua_tolstring(state, idx, &mut size); + Ok(slice::from_raw_parts(data as *const u8, size).into()) + } + #[cfg(feature = "luau")] + ffi::LUA_TBUFFER => { + let mut size = 0; + let buf = ffi::lua_tobuffer(state, idx, &mut size); + mlua_assert!(!buf.is_null(), "invalid Luau buffer"); + Ok(slice::from_raw_parts(buf as *const u8, size).into()) + } + _ => { + // Fallback to default + Self::from_lua(lua.stack_value(idx), lua) + } + } } } diff --git a/src/lua.rs b/src/lua.rs index 9938a74c..6691bf0e 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1373,6 +1373,27 @@ impl Lua { } } + /// Create and return a Luau [buffer] object from a byte slice of data. + /// + /// Requires `feature = "luau"` + /// + /// [buffer]: https://luau-lang.org/library#buffer-library + #[cfg(feature = "luau")] + pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result { + let state = self.state(); + unsafe { + if self.unlikely_memory_error() { + crate::util::push_buffer(self.ref_thread(), buf.as_ref(), false)?; + return Ok(AnyUserData(self.pop_ref_thread(), SubtypeId::Buffer)); + } + + let _sg = StackGuard::new(state); + check_stack(state, 4)?; + crate::util::push_buffer(state, buf.as_ref(), true)?; + Ok(AnyUserData(self.pop_ref(), SubtypeId::Buffer)) + } + } + /// Creates and returns a new empty table. pub fn create_table(&self) -> Result
{ self.create_table_with_capacity(0, 0) diff --git a/src/serde/de.rs b/src/serde/de.rs index 5d3512c2..6933e4e2 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -148,6 +148,14 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> { Value::UserData(ud) if ud.is_serializable() => { serde_userdata(ud, |value| value.deserialize_any(visitor)) } + #[cfg(feature = "luau")] + Value::UserData(ud) if ud.1 == crate::types::SubtypeId::Buffer => unsafe { + let mut size = 0usize; + let buf = ffi::lua_tobuffer(ud.0.lua.ref_thread(), ud.0.index, &mut size); + mlua_assert!(!buf.is_null(), "invalid Luau buffer"); + let buf = std::slice::from_raw_parts(buf as *const u8, size); + visitor.visit_bytes(buf) + }, Value::Function(_) | Value::Thread(_) | Value::UserData(_) diff --git a/src/userdata.rs b/src/userdata.rs index e2c4a748..dbbbffa3 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -1340,6 +1340,19 @@ impl<'lua> Serialize for AnyUserData<'lua> { S: Serializer, { let lua = self.0.lua; + + // Special case for Luau buffer type + #[cfg(feature = "luau")] + if self.1 == SubtypeId::Buffer { + let buf = unsafe { + let mut size = 0usize; + let buf = ffi::lua_tobuffer(lua.ref_thread(), self.0.index, &mut size); + mlua_assert!(!buf.is_null(), "invalid Luau buffer"); + std::slice::from_raw_parts(buf as *const u8, size) + }; + return serializer.serialize_bytes(buf); + } + let data = unsafe { let _ = lua .get_userdata_ref_type_id(&self.0) diff --git a/src/util/mod.rs b/src/util/mod.rs index 595407e1..ff8b28e9 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -253,6 +253,20 @@ pub unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: bool) - } } +// Uses 3 stack spaces (when protect), does not call checkstack. +#[cfg(feature = "luau")] +#[inline(always)] +pub unsafe fn push_buffer(state: *mut ffi::lua_State, b: &[u8], protect: bool) -> Result<()> { + let data = if protect { + protect_lua!(state, 0, 1, |state| ffi::lua_newbuffer(state, b.len()))? + } else { + ffi::lua_newbuffer(state, b.len()) + }; + let buf = slice::from_raw_parts_mut(data as *mut u8, b.len()); + buf.copy_from_slice(b); + Ok(()) +} + // Uses 3 stack spaces, does not call checkstack. #[inline] pub unsafe fn push_table( diff --git a/tests/conversion.rs b/tests/conversion.rs index a312f114..ad2c09ad 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::ffi::{CStr, CString}; +use bstr::BString; use maplit::{btreemap, btreeset, hashmap, hashset}; use mlua::{ AnyUserData, Error, Function, IntoLua, Lua, RegistryKey, Result, Table, Thread, UserDataRef, @@ -409,3 +410,46 @@ fn test_conv_array() -> Result<()> { Ok(()) } + +#[test] +fn test_bstring_from_lua() -> Result<()> { + let lua = Lua::new(); + + let s = lua.create_string("hello, world")?; + let bstr = lua.unpack::(Value::String(s))?; + assert_eq!(bstr, "hello, world"); + + let bstr = lua.unpack::(Value::Integer(123))?; + assert_eq!(bstr, "123"); + + let bstr = lua.unpack::(Value::Number(-123.55))?; + assert_eq!(bstr, "-123.55"); + + // Test from stack + let f = lua.create_function(|_, bstr: BString| Ok(bstr))?; + let bstr = f.call::<_, BString>("hello, world")?; + assert_eq!(bstr, "hello, world"); + + let bstr = f.call::<_, BString>(-43.22)?; + assert_eq!(bstr, "-43.22"); + + Ok(()) +} + +#[cfg(feature = "luau")] +#[test] +fn test_bstring_from_lua_buffer() -> Result<()> { + let lua = Lua::new(); + + let b = lua.create_buffer("hello, world")?; + let bstr = lua.unpack::(Value::UserData(b))?; + assert_eq!(bstr, "hello, world"); + + // Test from stack + let f = lua.create_function(|_, bstr: BString| Ok(bstr))?; + let buf = lua.create_buffer("hello, world")?; + let bstr = f.call::<_, BString>(buf)?; + assert_eq!(bstr, "hello, world"); + + Ok(()) +} diff --git a/tests/serde.rs b/tests/serde.rs index 7e2e251a..9c287a47 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -728,3 +728,30 @@ fn test_arbitrary_precision() { "{\n [\"$serde_json::private::Number\"] = \"124.4\",\n}" ); } + +#[cfg(feature = "luau")] +#[test] +fn test_buffer_serialize() { + let lua = Lua::new(); + + let buf = lua.create_buffer(&[1, 2, 3, 4]).unwrap(); + let val = serde_value::to_value(&buf).unwrap(); + assert_eq!(val, serde_value::Value::Bytes(vec![1, 2, 3, 4])); + + // Try empty buffer + let buf = lua.create_buffer(&[]).unwrap(); + let val = serde_value::to_value(&buf).unwrap(); + assert_eq!(val, serde_value::Value::Bytes(vec![])); +} + +#[cfg(feature = "luau")] +#[test] +fn test_buffer_from_value() { + let lua = Lua::new(); + + let buf = lua.create_buffer(&[1, 2, 3, 4]).unwrap(); + let val = lua + .from_value::(Value::UserData(buf)) + .unwrap(); + assert_eq!(val, serde_value::Value::Bytes(vec![1, 2, 3, 4])); +} From f67f8646ae799bac59392e6e9a5b81330bedbd0c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 28 Mar 2024 18:18:08 +0000 Subject: [PATCH 078/635] Implement `push_into_stack`/`from_stack` for `Option` --- src/conversion.rs | 18 ++++++++++++++++++ tests/conversion.rs | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/conversion.rs b/src/conversion.rs index 8ca8fe86..dc12c0bd 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -1063,6 +1063,15 @@ impl<'lua, T: IntoLua<'lua>> IntoLua<'lua> for Option { None => Ok(Nil), } } + + #[inline] + unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + match self { + Some(val) => val.push_into_stack(lua)?, + None => ffi::lua_pushnil(lua.state()), + } + Ok(()) + } } impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Option { @@ -1073,4 +1082,13 @@ impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Option { value => Ok(Some(T::from_lua(value, lua)?)), } } + + #[inline] + unsafe fn from_stack(idx: c_int, lua: &'lua Lua) -> Result { + if ffi::lua_isnil(lua.state(), idx) != 0 { + Ok(None) + } else { + Ok(Some(T::from_stack(idx, lua)?)) + } + } } diff --git a/tests/conversion.rs b/tests/conversion.rs index ad2c09ad..5897d75e 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -453,3 +453,21 @@ fn test_bstring_from_lua_buffer() -> Result<()> { Ok(()) } + +#[test] +fn test_option_into_from_lua() -> Result<()> { + let lua = Lua::new(); + + // Direct conversion + let v = Some(42); + let v2 = v.into_lua(&lua)?; + assert_eq!(v, v2.as_i32()); + + // Push into stack / get from stack + let f = lua.create_function(|_, v: Option| Ok(v))?; + assert_eq!(f.call::<_, Option>(Some(42))?, Some(42)); + assert_eq!(f.call::<_, Option>(Option::::None)?, None); + assert_eq!(f.call::<_, Option>(())?, None); + + Ok(()) +} From 62f0bb97b019081e286f7eb5eafe62f09519349e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 5 Apr 2024 12:03:40 +0100 Subject: [PATCH 079/635] Add `Lua::create_ser_any_userdata()` function --- src/lua.rs | 15 +++++++++++++++ tests/serde.rs | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/lua.rs b/src/lua.rs index 6691bf0e..35bdf4be 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1746,6 +1746,21 @@ impl Lua { unsafe { self.make_any_userdata(UserDataCell::new(data)) } } + /// Creates a Lua userdata object from a custom serializable Rust type. + /// + /// See [`Lua::create_any_userdata()`] for more details. + /// + /// Requires `feature = "serialize"` + #[cfg(feature = "serialize")] + #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] + #[inline] + pub fn create_ser_any_userdata(&self, data: T) -> Result + where + T: Serialize + MaybeSend + 'static, + { + unsafe { self.make_any_userdata(UserDataCell::new_ser(data)) } + } + /// Registers a custom Rust type in Lua to use in userdata objects. /// /// This methods provides a way to add fields or methods to userdata objects of a type `T`. diff --git a/tests/serde.rs b/tests/serde.rs index 9c287a47..1eabf429 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -115,6 +115,21 @@ fn test_serialize_in_scope() -> LuaResult<()> { Ok(()) } +#[test] +fn test_serialize_any_userdata() -> Result<(), Box> { + let lua = Lua::new(); + + let json_val = serde_json::json!({ + "a": 1, + "b": "test", + }); + let json_ud = lua.create_ser_any_userdata(json_val)?; + let json_str = serde_json::to_string_pretty(&json_ud)?; + assert_eq!(json_str, "{\n \"a\": 1,\n \"b\": \"test\"\n}"); + + Ok(()) +} + #[test] fn test_serialize_failure() -> Result<(), Box> { #[derive(Serialize)] From ad31bed1db761503d6b3e0e3df292548449ad0ff Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 5 Apr 2024 12:32:14 +0100 Subject: [PATCH 080/635] Minor improvements in serializing: Use `&str` instead of creating Lua string when serializing tuple variant and struct variant --- src/serde/ser.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 3401693f..6ee57a1f 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -3,7 +3,6 @@ use serde::{ser, Serialize}; use super::LuaSerdeExt; use crate::error::{Error, Result}; use crate::lua::Lua; -use crate::string::String; use crate::table::Table; use crate::value::{IntoLua, Value}; @@ -287,7 +286,7 @@ impl<'lua> ser::Serializer for Serializer<'lua> { _len: usize, ) -> Result { Ok(SerializeTupleVariant { - name: self.lua.create_string(variant)?, + variant, table: self.lua.create_table()?, options: self.options, }) @@ -331,7 +330,7 @@ impl<'lua> ser::Serializer for Serializer<'lua> { len: usize, ) -> Result { Ok(SerializeStructVariant { - name: self.lua.create_string(variant)?, + variant, table: self.lua.create_table_with_capacity(0, len)?, options: self.options, }) @@ -438,7 +437,7 @@ impl<'lua> ser::SerializeTupleStruct for SerializeSeq<'lua> { #[doc(hidden)] pub struct SerializeTupleVariant<'lua> { - name: String<'lua>, + variant: &'static str, table: Table<'lua>, options: Options, } @@ -458,7 +457,7 @@ impl<'lua> ser::SerializeTupleVariant for SerializeTupleVariant<'lua> { fn end(self) -> Result> { let lua = self.table.0.lua; let table = lua.create_table()?; - table.raw_set(self.name, self.table)?; + table.raw_set(self.variant, self.table)?; Ok(Value::Table(table)) } } @@ -553,7 +552,7 @@ impl<'lua> ser::SerializeStruct for SerializeStruct<'lua> { #[doc(hidden)] pub struct SerializeStructVariant<'lua> { - name: String<'lua>, + variant: &'static str, table: Table<'lua>, options: Options, } @@ -575,7 +574,7 @@ impl<'lua> ser::SerializeStructVariant for SerializeStructVariant<'lua> { fn end(self) -> Result> { let lua = self.table.0.lua; let table = lua.create_table_with_capacity(0, 1)?; - table.raw_set(self.name, self.table)?; + table.raw_set(self.variant, self.table)?; Ok(Value::Table(table)) } } From a64404908786a829077760092f2ba6477b5a3575 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 5 Apr 2024 12:42:45 +0100 Subject: [PATCH 081/635] Cosmetic changes for clippy --- src/conversion.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index dc12c0bd..f13b35a5 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -103,7 +103,8 @@ impl<'lua> IntoLua<'lua> for &OwnedString { #[inline] unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { - Ok(lua.push_owned_ref(&self.0)) + lua.push_owned_ref(&self.0); + Ok(()) } } @@ -169,7 +170,8 @@ impl<'lua> IntoLua<'lua> for &OwnedTable { #[inline] unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { - Ok(lua.push_owned_ref(&self.0)) + lua.push_owned_ref(&self.0); + Ok(()) } } @@ -235,7 +237,8 @@ impl<'lua> IntoLua<'lua> for &OwnedFunction { #[inline] unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { - Ok(lua.push_owned_ref(&self.0)) + lua.push_owned_ref(&self.0); + Ok(()) } } @@ -301,7 +304,8 @@ impl<'lua> IntoLua<'lua> for &OwnedThread { #[inline] unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { - Ok(lua.push_owned_ref(&self.0)) + lua.push_owned_ref(&self.0); + Ok(()) } } @@ -370,7 +374,8 @@ impl<'lua> IntoLua<'lua> for &OwnedAnyUserData { #[inline] unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { - Ok(lua.push_owned_ref(&self.0)) + lua.push_owned_ref(&self.0); + Ok(()) } } From 806bd202d64db47898166f00d60762c06a0bc901 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 5 Apr 2024 12:18:21 +0100 Subject: [PATCH 082/635] v0.9.7 --- CHANGELOG.md | 11 +++++++++++ Cargo.toml | 2 +- README.md | 4 ++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de1b93c0..de2d190f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## v0.9.7 + +- Implemented `IntoLua` for `RegistryKey` +- Mark `__idiv` metamethod as available for luau +- Added `Function::deep_clone()` method (Luau) +- Added `SerializeOptions::detect_serde_json_arbitrary_precision` option +- Added `Lua::create_buffer()` method (Luau) +- Support serializing buffer type as a byte slice (Luau) +- Perf: Implemented `push_into_stack`/`from_stack` for `Option` +- Added `Lua::create_ser_any_userdata()` method + ## v0.9.6 - Added `to_pointer` function to `Function`/`Table`/`Thread` diff --git a/Cargo.toml b/Cargo.toml index c8ddd6d8..34d5bc1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.9.6" # remember to update mlua_derive +version = "0.9.7" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.71" edition = "2021" diff --git a/README.md b/README.md index 3d362d74..563073b2 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ Add to `Cargo.toml` : ``` toml [dependencies] -mlua = { version = "0.9.1", features = ["lua54", "vendored"] } +mlua = { version = "0.9.7", features = ["lua54", "vendored"] } ``` `main.rs` @@ -168,7 +168,7 @@ Add to `Cargo.toml` : crate-type = ["cdylib"] [dependencies] -mlua = { version = "0.9.1", features = ["lua54", "module"] } +mlua = { version = "0.9.7", features = ["lua54", "module"] } ``` `lib.rs` : From 45fd2fa40afd53c4e3ed324035ce1bb72304970b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 5 Apr 2024 14:04:15 +0100 Subject: [PATCH 083/635] mlua-sys: v0.5.2 --- Cargo.toml | 2 +- mlua-sys/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 34d5bc1a..64904fd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ erased-serde = { version = "0.4", optional = true } serde-value = { version = "0.7", optional = true } parking_lot = { version = "0.12", optional = true } -ffi = { package = "mlua-sys", version = "0.5.1", path = "mlua-sys" } +ffi = { package = "mlua-sys", version = "0.5.2", path = "mlua-sys" } [target.'cfg(unix)'.dependencies] libloading = { version = "0.8", optional = true } diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 0def6c6d..727bff08 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.5.1" +version = "0.5.2" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" From 1c969da2869bcf148587b19d20762f3a1d126cec Mon Sep 17 00:00:00 2001 From: Eric Stokes Date: Wed, 17 Apr 2024 15:29:41 -0400 Subject: [PATCH 084/635] update build script to fix cross compilation of windows dlls from unix (#397) --- mlua-sys/build/main_inner.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mlua-sys/build/main_inner.rs b/mlua-sys/build/main_inner.rs index 668f40b3..05ac53b5 100644 --- a/mlua-sys/build/main_inner.rs +++ b/mlua-sys/build/main_inner.rs @@ -1,3 +1,5 @@ +use std::env; + cfg_if::cfg_if! { if #[cfg(any(feature = "luau", feature = "vendored"))] { #[path = "find_vendored.rs"] @@ -17,8 +19,8 @@ fn main() { println!("cargo:rerun-if-changed=build"); - #[cfg(windows)] - if cfg!(feature = "module") { + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); + if target_os == "windows" && cfg!(feature = "module") { if !std::env::var("LUA_LIB_NAME").unwrap_or_default().is_empty() { // Don't use raw-dylib linking find::probe_lua(); From ffc4bd599c65bae4bf8580e178fd37c0a341b1c9 Mon Sep 17 00:00:00 2001 From: Joris Willems Date: Thu, 18 Apr 2024 11:22:41 +0200 Subject: [PATCH 085/635] Fix module imports for export (#394) --- mlua_derive/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mlua_derive/src/lib.rs b/mlua_derive/src/lib.rs index 7b6fb306..74605cb9 100644 --- a/mlua_derive/src/lib.rs +++ b/mlua_derive/src/lib.rs @@ -58,13 +58,13 @@ pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream { }; let wrapped = quote! { - ::mlua::require_module_feature!(); + mlua::require_module_feature!(); #func #[no_mangle] - unsafe extern "C-unwind" fn #ext_entrypoint_name(state: *mut ::mlua::lua_State) -> ::std::os::raw::c_int { - let lua = ::mlua::Lua::init_from_ptr(state); + unsafe extern "C-unwind" fn #ext_entrypoint_name(state: *mut mlua::lua_State) -> ::std::os::raw::c_int { + let lua = mlua::Lua::init_from_ptr(state); #skip_memory_check lua.entrypoint1(state, #func_name) } @@ -95,7 +95,7 @@ pub fn chunk(input: TokenStream) -> TokenStream { }); let wrapped_code = quote! {{ - use ::mlua::{AsChunk, ChunkMode, Lua, Result, Table}; + use mlua::{AsChunk, ChunkMode, Lua, Result, Table}; use ::std::borrow::Cow; use ::std::cell::Cell; use ::std::io::Result as IoResult; From 3d46fad4595fe75f43f2ce78223843d15a2af84e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 4 May 2024 21:10:24 +0100 Subject: [PATCH 086/635] Update `luau-src` to v0.9 Mark `lua_CompileOptions` as non exhaustive --- mlua-sys/Cargo.toml | 2 +- mlua-sys/src/luau/luacode.rs | 19 ++++++++++++++++++- src/chunk.rs | 30 +++++++++++++++++++++--------- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 727bff08..411964f7 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -40,4 +40,4 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 546.0.2, < 546.1.0", optional = true } luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } -luau0-src = { version = "0.8.0", optional = true } +luau0-src = { version = "0.9.0", optional = true } diff --git a/mlua-sys/src/luau/luacode.rs b/mlua-sys/src/luau/luacode.rs index 357ad250..16a97eb8 100644 --- a/mlua-sys/src/luau/luacode.rs +++ b/mlua-sys/src/luau/luacode.rs @@ -1,12 +1,14 @@ //! Contains definitions from `luacode.h`. use std::os::raw::{c_char, c_int, c_void}; -use std::slice; +use std::{ptr, slice}; #[repr(C)] +#[non_exhaustive] pub struct lua_CompileOptions { pub optimizationLevel: c_int, pub debugLevel: c_int, + pub typeInfoLevel: c_int, pub coverageLevel: c_int, pub vectorLib: *const c_char, pub vectorCtor: *const c_char, @@ -14,6 +16,21 @@ pub struct lua_CompileOptions { pub mutableGlobals: *const *const c_char, } +impl Default for lua_CompileOptions { + fn default() -> Self { + Self { + optimizationLevel: 1, + debugLevel: 1, + typeInfoLevel: 0, + coverageLevel: 0, + vectorLib: ptr::null(), + vectorCtor: ptr::null(), + vectorType: ptr::null(), + mutableGlobals: ptr::null(), + } + } +} + extern "C-unwind" { #[link_name = "luau_compile"] pub fn luau_compile_( diff --git a/src/chunk.rs b/src/chunk.rs index f3db5de2..16b35cc5 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -122,6 +122,7 @@ pub enum ChunkMode { pub struct Compiler { optimization_level: u8, debug_level: u8, + type_info_level: u8, coverage_level: u8, vector_lib: Option, vector_ctor: Option, @@ -144,6 +145,7 @@ impl Compiler { Compiler { optimization_level: 1, debug_level: 1, + type_info_level: 0, coverage_level: 0, vector_lib: None, vector_ctor: None, @@ -176,6 +178,16 @@ impl Compiler { self } + /// Sets Luau type information level used to guide native code generation decisions. + /// + /// Possible values: + /// * 0 - generate for native modules (default) + /// * 1 - generate for all modules + pub const fn set_type_info_level(mut self, level: u8) -> Self { + self.type_info_level = level; + self + } + /// Sets Luau compiler code coverage level. /// /// Possible values: @@ -250,15 +262,15 @@ impl Compiler { } unsafe { - let options = ffi::lua_CompileOptions { - optimizationLevel: self.optimization_level as c_int, - debugLevel: self.debug_level as c_int, - coverageLevel: self.coverage_level as c_int, - vectorLib: vector_lib.map_or(ptr::null(), |s| s.as_ptr()), - vectorCtor: vector_ctor.map_or(ptr::null(), |s| s.as_ptr()), - vectorType: vector_type.map_or(ptr::null(), |s| s.as_ptr()), - mutableGlobals: mutable_globals_ptr, - }; + let mut options = ffi::lua_CompileOptions::default(); + options.optimizationLevel = self.optimization_level as c_int; + options.debugLevel = self.debug_level as c_int; + options.typeInfoLevel = self.type_info_level as c_int; + options.coverageLevel = self.coverage_level as c_int; + options.vectorLib = vector_lib.map_or(ptr::null(), |s| s.as_ptr()); + options.vectorCtor = vector_ctor.map_or(ptr::null(), |s| s.as_ptr()); + options.vectorType = vector_type.map_or(ptr::null(), |s| s.as_ptr()); + options.mutableGlobals = mutable_globals_ptr; ffi::luau_compile(source.as_ref(), options) } } From 3a44729a48e216fd581193a9f234ac6fb6c0c32a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 4 May 2024 21:11:29 +0100 Subject: [PATCH 087/635] Mark `lua_Callbacks` as non exhaustive (Luau) --- mlua-sys/src/luau/lua.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 084adb8b..3db6a7b7 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -525,6 +525,7 @@ pub struct lua_Debug { // #[repr(C)] +#[non_exhaustive] pub struct lua_Callbacks { /// arbitrary userdata pointer that is never overwritten by Luau pub userdata: *mut c_void, From 317ce7caa6984b5fda0282308b2e269ece26ae7a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 4 May 2024 21:15:22 +0100 Subject: [PATCH 088/635] Add `Lua::set_fflag()` to control Luau feature flags --- mlua-sys/src/luau/lua.rs | 5 +++++ src/lua.rs | 14 ++++++++++++++ tests/luau.rs | 6 ++++++ 3 files changed, 25 insertions(+) diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 3db6a7b7..a2077e19 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -553,3 +553,8 @@ pub struct lua_Callbacks { extern "C" { pub fn lua_callbacks(L: *mut lua_State) -> *mut lua_Callbacks; } + +// Functions from customization lib +extern "C" { + pub fn luau_setfflag(name: *const c_char, value: c_int) -> c_int; +} diff --git a/src/lua.rs b/src/lua.rs index 35bdf4be..6def0c07 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1288,6 +1288,20 @@ impl Lua { unsafe { (*self.extra.get()).enable_jit = enable }; } + /// Sets Luau feature flag (global setting). + /// + /// See https://github.com/luau-lang/luau/blob/master/CONTRIBUTING.md#feature-flags for details. + #[cfg(feature = "luau")] + #[doc(hidden)] + pub fn set_fflag(name: &str, enabled: bool) -> StdResult<(), ()> { + if let Ok(name) = CString::new(name) { + if unsafe { ffi::luau_setfflag(name.as_ptr(), enabled as c_int) != 0 } { + return Ok(()); + } + } + Err(()) + } + /// Returns Lua source code as a `Chunk` builder type. /// /// In order to actually compile or run the resulting code, you must call [`Chunk::exec`] or diff --git a/tests/luau.rs b/tests/luau.rs index aedcc53c..29af6cf2 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -484,3 +484,9 @@ fn test_buffer() -> Result<()> { Ok(()) } + +#[test] +fn test_fflags() { + // We cannot really on any particular feature flag to be present + assert!(Lua::set_fflag("UnknownFlag", true).is_err()); +} From 8f3de8aa19ac7263297b080862c0a7a0ddb5de04 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 4 May 2024 21:22:17 +0100 Subject: [PATCH 089/635] mlua-sys: v0.6.0 --- Cargo.toml | 2 +- mlua-sys/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 64904fd6..b2c821f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ erased-serde = { version = "0.4", optional = true } serde-value = { version = "0.7", optional = true } parking_lot = { version = "0.12", optional = true } -ffi = { package = "mlua-sys", version = "0.5.2", path = "mlua-sys" } +ffi = { package = "mlua-sys", version = "0.6.0", path = "mlua-sys" } [target.'cfg(unix)'.dependencies] libloading = { version = "0.8", optional = true } diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 411964f7..6f242115 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.5.2" +version = "0.6.0" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" From 59c9abbac76b7ec60c470c94698dfb29f6683b2e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 14 May 2024 00:41:21 +0100 Subject: [PATCH 090/635] Fix serializing same table multiple times. Fixes #408 --- src/serde/de.rs | 4 ++-- src/table.rs | 10 +++++----- tests/serde.rs | 21 +++++++++++++++++++++ 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/serde/de.rs b/src/serde/de.rs index 6933e4e2..3cc182d0 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -660,14 +660,14 @@ impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> { // Adds `ptr` to the `visited` map and removes on drop // Used to track recursive tables but allow to traverse same tables multiple times -struct RecursionGuard { +pub(crate) struct RecursionGuard { ptr: *const c_void, visited: Rc>>, } impl RecursionGuard { #[inline] - fn new(table: &Table, visited: &Rc>>) -> Self { + pub(crate) fn new(table: &Table, visited: &Rc>>) -> Self { let visited = Rc::clone(visited); let ptr = table.to_pointer(); visited.borrow_mut().insert(ptr); diff --git a/src/table.rs b/src/table.rs index 69e8a6dc..f0b4f822 100644 --- a/src/table.rs +++ b/src/table.rs @@ -1089,7 +1089,7 @@ impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> { where S: Serializer, { - use crate::serde::de::{check_value_for_skip, MapPairs}; + use crate::serde::de::{check_value_for_skip, MapPairs, RecursionGuard}; use crate::value::SerializableValue; let convert_result = |res: Result<()>, serialize_err: Option| match res { @@ -1101,7 +1101,7 @@ impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> { let options = self.options; let visited = &self.visited; - visited.borrow_mut().insert(self.table.to_pointer()); + let _guard = RecursionGuard::new(&self.table, visited); // Array let len = self.table.raw_len(); @@ -1109,7 +1109,7 @@ impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> { let mut seq = serializer.serialize_seq(Some(len))?; let mut serialize_err = None; let res = self.table.for_each_value::(|value| { - let skip = check_value_for_skip(&value, self.options, &self.visited) + let skip = check_value_for_skip(&value, self.options, visited) .map_err(|err| Error::SerializeError(err.to_string()))?; if skip { // continue iteration @@ -1129,9 +1129,9 @@ impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> { let mut map = serializer.serialize_map(None)?; let mut serialize_err = None; let mut process_pair = |key, value| { - let skip_key = check_value_for_skip(&key, self.options, &self.visited) + let skip_key = check_value_for_skip(&key, self.options, visited) .map_err(|err| Error::SerializeError(err.to_string()))?; - let skip_value = check_value_for_skip(&value, self.options, &self.visited) + let skip_value = check_value_for_skip(&value, self.options, visited) .map_err(|err| Error::SerializeError(err.to_string()))?; if skip_key || skip_value { // continue iteration diff --git a/tests/serde.rs b/tests/serde.rs index 1eabf429..11c1d712 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -269,6 +269,27 @@ fn test_serialize_globals() -> LuaResult<()> { Ok(()) } +#[test] +fn test_serialize_same_table_twice() -> LuaResult<()> { + let lua = Lua::new(); + + let value = lua + .load( + r#" + local foo = {} + return { + a = foo, + b = foo, + } + "#, + ) + .eval::()?; + let json = serde_json::to_string(&value.to_serializable().sort_keys(true)).unwrap(); + assert_eq!(json, r#"{"a":{},"b":{}}"#); + + Ok(()) +} + #[test] fn test_to_value_struct() -> LuaResult<()> { let lua = Lua::new(); From ea2faa37556f0483ca59353c224a17a4569d5a3d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 15 May 2024 23:11:33 +0100 Subject: [PATCH 091/635] clippy --- src/lua.rs | 1 + src/table.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lua.rs b/src/lua.rs index 6def0c07..88820131 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1293,6 +1293,7 @@ impl Lua { /// See https://github.com/luau-lang/luau/blob/master/CONTRIBUTING.md#feature-flags for details. #[cfg(feature = "luau")] #[doc(hidden)] + #[allow(clippy::result_unit_err)] pub fn set_fflag(name: &str, enabled: bool) -> StdResult<(), ()> { if let Ok(name) = CString::new(name) { if unsafe { ffi::luau_setfflag(name.as_ptr(), enabled as c_int) != 0 } { diff --git a/src/table.rs b/src/table.rs index f0b4f822..70b049bb 100644 --- a/src/table.rs +++ b/src/table.rs @@ -1101,7 +1101,7 @@ impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> { let options = self.options; let visited = &self.visited; - let _guard = RecursionGuard::new(&self.table, visited); + let _guard = RecursionGuard::new(self.table, visited); // Array let len = self.table.raw_len(); From 0aa86c4d47dd418e7534afe133560dc77ff03bd0 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 15 May 2024 23:11:50 +0100 Subject: [PATCH 092/635] Update CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index de2d190f..236c910c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v0.9.8 + +- Fixed serializing same table multiple times (#408) +- Use `mlua-sys` v0.6 (to support Luau 0.624+) +- Fixed cross compilation of windows dlls from unix (#394) + ## v0.9.7 - Implemented `IntoLua` for `RegistryKey` From 0fa39d431d319c2381bda2748c0d56c5c40422d5 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 15 May 2024 23:14:08 +0100 Subject: [PATCH 093/635] mlua_derive: v0.9.3 --- Cargo.toml | 2 +- mlua_derive/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b2c821f7..94d465f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ macros = ["mlua_derive/macros"] unstable = [] [dependencies] -mlua_derive = { version = "=0.9.2", optional = true, path = "mlua_derive" } +mlua_derive = { version = "=0.9.3", optional = true, path = "mlua_derive" } bstr = { version = "1.0", features = ["std"], default_features = false } once_cell = { version = "1.0" } num-traits = { version = "0.2.14" } diff --git a/mlua_derive/Cargo.toml b/mlua_derive/Cargo.toml index 2badcc89..9cfeeddf 100644 --- a/mlua_derive/Cargo.toml +++ b/mlua_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua_derive" -version = "0.9.2" +version = "0.9.3" authors = ["Aleksandr Orlenko "] edition = "2021" description = "Procedural macros for the mlua crate." From f2d48ce296697954595bcb05d255af0dc4e006a5 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 15 May 2024 23:17:30 +0100 Subject: [PATCH 094/635] v0.9.8 --- Cargo.toml | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 94d465f8..00ab7de5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.9.7" # remember to update mlua_derive +version = "0.9.8" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.71" edition = "2021" diff --git a/README.md b/README.md index 563073b2..c9b23760 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ Add to `Cargo.toml` : ``` toml [dependencies] -mlua = { version = "0.9.7", features = ["lua54", "vendored"] } +mlua = { version = "0.9.8", features = ["lua54", "vendored"] } ``` `main.rs` @@ -168,7 +168,7 @@ Add to `Cargo.toml` : crate-type = ["cdylib"] [dependencies] -mlua = { version = "0.9.7", features = ["lua54", "module"] } +mlua = { version = "0.9.8", features = ["lua54", "module"] } ``` `lib.rs` : From b46cad1db1b7220b38da3a1be44d8f2070be29d3 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 9 Jun 2024 00:37:58 +0100 Subject: [PATCH 095/635] Support Luau v0.629 --- Cargo.toml | 2 +- mlua-sys/Cargo.toml | 4 ++-- mlua-sys/src/luau/lua.rs | 2 ++ mlua-sys/src/luau/luacode.rs | 2 ++ src/chunk.rs | 42 ++++++++++++++++++++++++------------ 5 files changed, 35 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 00ab7de5..a6c7992c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ erased-serde = { version = "0.4", optional = true } serde-value = { version = "0.7", optional = true } parking_lot = { version = "0.12", optional = true } -ffi = { package = "mlua-sys", version = "0.6.0", path = "mlua-sys" } +ffi = { package = "mlua-sys", version = "0.6.1", path = "mlua-sys" } [target.'cfg(unix)'.dependencies] libloading = { version = "0.8", optional = true } diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 6f242115..4d68e653 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.6.0" +version = "0.6.1" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" @@ -40,4 +40,4 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 546.0.2, < 546.1.0", optional = true } luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } -luau0-src = { version = "0.9.0", optional = true } +luau0-src = { version = "0.10.0", optional = true } diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index a2077e19..cc2e6012 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -288,6 +288,8 @@ extern "C-unwind" { pub fn lua_setuserdatatag(L: *mut lua_State, idx: c_int, tag: c_int); pub fn lua_setuserdatadtor(L: *mut lua_State, tag: c_int, dtor: Option); pub fn lua_getuserdatadtor(L: *mut lua_State, tag: c_int) -> Option; + pub fn lua_setuserdatametatable(L: *mut lua_State, tag: c_int, idx: c_int); + pub fn lua_getuserdatametatable(L: *mut lua_State, tag: c_int); pub fn lua_setlightuserdataname(L: *mut lua_State, tag: c_int, name: *const c_char); pub fn lua_getlightuserdataname(L: *mut lua_State, tag: c_int) -> *const c_char; pub fn lua_clonefunction(L: *mut lua_State, idx: c_int); diff --git a/mlua-sys/src/luau/luacode.rs b/mlua-sys/src/luau/luacode.rs index 16a97eb8..81ac22ed 100644 --- a/mlua-sys/src/luau/luacode.rs +++ b/mlua-sys/src/luau/luacode.rs @@ -14,6 +14,7 @@ pub struct lua_CompileOptions { pub vectorCtor: *const c_char, pub vectorType: *const c_char, pub mutableGlobals: *const *const c_char, + pub userdataTypes: *const *const c_char, } impl Default for lua_CompileOptions { @@ -27,6 +28,7 @@ impl Default for lua_CompileOptions { vectorCtor: ptr::null(), vectorType: ptr::null(), mutableGlobals: ptr::null(), + userdataTypes: ptr::null(), } } } diff --git a/src/chunk.rs b/src/chunk.rs index 16b35cc5..3114365a 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -128,6 +128,7 @@ pub struct Compiler { vector_ctor: Option, vector_type: Option, mutable_globals: Vec, + userdata_types: Vec, } #[cfg(any(feature = "luau", doc))] @@ -151,6 +152,7 @@ impl Compiler { vector_ctor: None, vector_type: None, mutable_globals: Vec::new(), + userdata_types: Vec::new(), } } @@ -230,6 +232,13 @@ impl Compiler { self } + /// Sets a list of userdata types that will be included in the type information. + #[must_use] + pub fn set_userdata_types(mut self, types: Vec) -> Self { + self.userdata_types = types; + self + } + /// Compiles the `source` into bytecode. pub fn compile(&self, source: impl AsRef<[u8]>) -> Vec { use std::os::raw::c_int; @@ -245,22 +254,26 @@ impl Compiler { let vector_type = vector_type.and_then(|t| CString::new(t).ok()); let vector_type = vector_type.as_ref(); - let mutable_globals = self - .mutable_globals - .iter() - .map(|name| CString::new(name.clone()).ok()) - .collect::>>() - .unwrap_or_default(); - let mut mutable_globals = mutable_globals - .iter() - .map(|s| s.as_ptr()) - .collect::>(); - let mut mutable_globals_ptr = ptr::null(); - if !mutable_globals.is_empty() { - mutable_globals.push(ptr::null()); - mutable_globals_ptr = mutable_globals.as_ptr(); + macro_rules! vec2cstring_ptr { + ($name:ident, $name_ptr:ident) => { + let $name = self + .$name + .iter() + .map(|name| CString::new(name.clone()).ok()) + .collect::>>() + .unwrap_or_default(); + let mut $name = $name.iter().map(|s| s.as_ptr()).collect::>(); + let mut $name_ptr = ptr::null(); + if !$name.is_empty() { + $name.push(ptr::null()); + $name_ptr = $name.as_ptr(); + } + }; } + vec2cstring_ptr!(mutable_globals, mutable_globals_ptr); + vec2cstring_ptr!(userdata_types, userdata_types_ptr); + unsafe { let mut options = ffi::lua_CompileOptions::default(); options.optimizationLevel = self.optimization_level as c_int; @@ -271,6 +284,7 @@ impl Compiler { options.vectorCtor = vector_ctor.map_or(ptr::null(), |s| s.as_ptr()); options.vectorType = vector_type.map_or(ptr::null(), |s| s.as_ptr()); options.mutableGlobals = mutable_globals_ptr; + options.userdataTypes = userdata_types_ptr; ffi::luau_compile(source.as_ref(), options) } } From a25e81036eb07049db2a20b48bc243410ea1b757 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 12 Jun 2024 23:18:46 +0100 Subject: [PATCH 096/635] Do not allow already running coroutines to be reset or resumed. This is a wrong use of the Lua API and is not supported. See #416 for the reference. --- src/error.rs | 2 +- src/thread.rs | 15 +++++++++++---- tests/thread.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/error.rs b/src/error.rs index 2a86eacc..8c563c28 100644 --- a/src/error.rs +++ b/src/error.rs @@ -101,7 +101,7 @@ pub enum Error { /// [`Thread::resume`] was called on an inactive coroutine. /// /// A coroutine is inactive if its main function has returned or if an error has occurred inside - /// the coroutine. + /// the coroutine. Already running coroutines are also marked as inactive (unresumable). /// /// [`Thread::status`] can be used to check if the coroutine can be resumed without causing this /// error. diff --git a/src/thread.rs b/src/thread.rs index 5bd634eb..4c003a72 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -142,6 +142,10 @@ impl<'lua> Thread<'lua> { A: IntoLuaMulti<'lua>, R: FromLuaMulti<'lua>, { + if self.status() != ThreadStatus::Resumable { + return Err(Error::CoroutineInactive); + } + let lua = self.0.lua; let state = lua.state(); let thread_state = self.state(); @@ -165,10 +169,6 @@ impl<'lua> Thread<'lua> { let state = lua.state(); let thread_state = self.state(); - if self.status() != ThreadStatus::Resumable { - return Err(Error::CoroutineInactive); - } - let nargs = args.push_into_stack_multi(lua)?; if nargs > 0 { check_stack(thread_state, nargs)?; @@ -196,6 +196,10 @@ impl<'lua> Thread<'lua> { /// Gets the status of the thread. pub fn status(&self) -> ThreadStatus { let thread_state = self.state(); + if thread_state == self.0.lua.state() { + // The coroutine is currently running + return ThreadStatus::Unresumable; + } unsafe { let status = ffi::lua_status(thread_state); if status != ffi::LUA_OK && status != ffi::LUA_YIELD { @@ -243,6 +247,9 @@ impl<'lua> Thread<'lua> { pub fn reset(&self, func: crate::function::Function<'lua>) -> Result<()> { let lua = self.0.lua; let thread_state = self.state(); + if thread_state == lua.state() { + return Err(Error::runtime("cannot reset a running thread")); + } unsafe { #[cfg(all(feature = "lua54", not(feature = "vendored")))] let status = ffi::lua_resetthread(thread_state); diff --git a/tests/thread.rs b/tests/thread.rs index 83beb4f5..2bc1988f 100644 --- a/tests/thread.rs +++ b/tests/thread.rs @@ -90,6 +90,19 @@ fn test_thread() -> Result<()> { _ => panic!("resuming dead coroutine did not return error"), } + // Already running thread must be unresumable + let thread = lua.create_thread(lua.create_function(|lua, ()| { + assert_eq!(lua.current_thread().status(), ThreadStatus::Unresumable); + let result = lua.current_thread().resume::<_, ()>(()); + assert!( + matches!(result, Err(Error::CoroutineInactive)), + "unexpected result: {result:?}", + ); + Ok(()) + })?)?; + let result = thread.resume::<_, ()>(()); + assert!(result.is_ok(), "unexpected result: {result:?}"); + Ok(()) } @@ -146,6 +159,21 @@ fn test_thread_reset() -> Result<()> { assert_eq!(thread.status(), ThreadStatus::Resumable); } + // Try reset running thread + let thread = lua.create_thread(lua.create_function(|lua, ()| { + let this = lua.current_thread(); + this.reset(lua.create_function(|_, ()| Ok(()))?)?; + Ok(()) + })?)?; + let result = thread.resume::<_, ()>(()); + assert!( + matches!(result, Err(Error::CallbackError{ ref cause, ..}) + if matches!(cause.as_ref(), Error::RuntimeError(ref err) + if err == "cannot reset a running thread") + ), + "unexpected result: {result:?}", + ); + Ok(()) } From 4f1d2abbcbe66eef207f2b1206cfd6d6c402fb8f Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 18 Jun 2024 11:18:55 +0100 Subject: [PATCH 097/635] Bump rustc-hash to 2.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a6c7992c..7a826fc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ mlua_derive = { version = "=0.9.3", optional = true, path = "mlua_derive" } bstr = { version = "1.0", features = ["std"], default_features = false } once_cell = { version = "1.0" } num-traits = { version = "0.2.14" } -rustc-hash = "1.0" +rustc-hash = "2.0" futures-util = { version = "0.3", optional = true, default-features = false, features = ["std"] } serde = { version = "1.0", optional = true } erased-serde = { version = "0.4", optional = true } From c23fa5aa6ccb84146a96a0cdfbdf9117c35d9657 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 18 Jun 2024 15:15:07 +0100 Subject: [PATCH 098/635] Optimize `RegistryKey` internals - Store single `AtomicI32` field instead of pair i32,AtomicBool - Make creation faster by skipping intermediate `Value` layer Add new `RegistryKey::id()` method to return underlying identifier --- benches/benchmark.rs | 18 +++++++++++ src/conversion.rs | 9 +++--- src/lua.rs | 75 ++++++++++++++++++++++---------------------- src/types.rs | 54 +++++++++++++++---------------- tests/tests.rs | 9 +++--- 5 files changed, 89 insertions(+), 76 deletions(-) diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 245bd351..f8352e0e 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -268,6 +268,23 @@ fn registry_value_create(c: &mut Criterion) { }); } +fn registry_value_get(c: &mut Criterion) { + let lua = Lua::new(); + lua.gc_stop(); + + let value = lua.create_registry_value("hello").unwrap(); + + c.bench_function("registry value [get]", |b| { + b.iter_batched( + || collect_gc_twice(&lua), + |_| { + assert_eq!(lua.registry_value::(&value).unwrap(), "hello"); + }, + BatchSize::SmallInput, + ); + }); +} + fn userdata_create(c: &mut Criterion) { struct UserData(#[allow(unused)] i64); impl LuaUserData for UserData {} @@ -406,6 +423,7 @@ criterion_group! { function_async_call_sum, registry_value_create, + registry_value_get, userdata_create, userdata_call_index, diff --git a/src/conversion.rs b/src/conversion.rs index f13b35a5..6013ebf9 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -453,10 +453,11 @@ impl<'lua> IntoLua<'lua> for &RegistryKey { return Err(Error::MismatchedRegistryKey); } - if self.is_nil() { - ffi::lua_pushnil(lua.state()); - } else { - ffi::lua_rawgeti(lua.state(), ffi::LUA_REGISTRYINDEX, self.registry_id as _); + match self.id() { + ffi::LUA_REFNIL => ffi::lua_pushnil(lua.state()), + id => { + ffi::lua_rawgeti(lua.state(), ffi::LUA_REGISTRYINDEX, id as _); + } } Ok(()) } diff --git a/src/lua.rs b/src/lua.rs index 88820131..f059eee2 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -2049,7 +2049,7 @@ impl Lua { T: FromLua<'lua>, { let state = self.state(); - let value = unsafe { + unsafe { let _sg = StackGuard::new(state); check_stack(state, 3)?; @@ -2057,9 +2057,8 @@ impl Lua { push_string(state, name.as_bytes(), protect)?; ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX); - self.pop_value() - }; - T::from_lua(value, self) + T::from_stack(-1, self) + } } /// Removes a named value in the Lua registry. @@ -2082,22 +2081,21 @@ impl Lua { /// /// [`RegistryKey`]: crate::RegistryKey pub fn create_registry_value<'lua, T: IntoLua<'lua>>(&'lua self, t: T) -> Result { - let t = t.into_lua(self)?; - if t == Value::Nil { - // Special case to skip calling `luaL_ref` and use `LUA_REFNIL` instead - let unref_list = unsafe { (*self.extra.get()).registry_unref_list.clone() }; - return Ok(RegistryKey::new(ffi::LUA_REFNIL, unref_list)); - } - let state = self.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 4)?; - self.push_value(t)?; + self.push(t)?; - // Try to reuse previously allocated slot let unref_list = (*self.extra.get()).registry_unref_list.clone(); + + // Check if the value is nil (no need to store it in the registry) + if ffi::lua_isnil(state, -1) != 0 { + return Ok(RegistryKey::new(ffi::LUA_REFNIL, unref_list)); + } + + // Try to reuse previously allocated slot let free_registry_id = mlua_expect!(unref_list.lock(), "unref list poisoned") .as_mut() .and_then(|x| x.pop()); @@ -2107,7 +2105,7 @@ impl Lua { return Ok(RegistryKey::new(registry_id, unref_list)); } - // Allocate a new RegistryKey + // Allocate a new RegistryKey slot let registry_id = if self.unlikely_memory_error() { ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) } else { @@ -2131,18 +2129,16 @@ impl Lua { } let state = self.state(); - let value = match key.is_nil() { - true => Value::Nil, - false => unsafe { + match key.id() { + ffi::LUA_REFNIL => T::from_lua(Value::Nil, self), + registry_id => unsafe { let _sg = StackGuard::new(state); check_stack(state, 1)?; - let id = key.registry_id as Integer; - ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, id); - self.pop_value() + ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); + T::from_stack(-1, self) }, - }; - T::from_lua(value, self) + } } /// Removes a value from the Lua registry. @@ -2180,29 +2176,32 @@ impl Lua { } let t = t.into_lua(self)?; - if t == Value::Nil && key.is_nil() { - // Nothing to replace - return Ok(()); - } else if t != Value::Nil && key.registry_id == ffi::LUA_REFNIL { - // We cannot update `LUA_REFNIL` slot - return Err(Error::runtime("cannot replace nil value with non-nil")); - } let state = self.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 2)?; - let id = key.registry_id as Integer; - if t == Value::Nil { - self.push_value(Value::Integer(id))?; - key.set_nil(true); - } else { - self.push_value(t)?; - key.set_nil(false); + match (t, key.id()) { + (Value::Nil, ffi::LUA_REFNIL) => { + // Do nothing, no need to replace nil with nil + } + (Value::Nil, registry_id) => { + // Remove the value + ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, registry_id); + key.set_id(ffi::LUA_REFNIL); + } + (value, ffi::LUA_REFNIL) => { + // Allocate a new `RegistryKey` + let new_key = self.create_registry_value(value)?; + key.set_id(new_key.take()); + } + (value, registry_id) => { + // It must be safe to replace the value without triggering memory error + self.push_value(value)?; + ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); + } } - // It must be safe to replace the value without triggering memory error - ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, id); } Ok(()) } diff --git a/src/types.rs b/src/types.rs index f21af2dd..a87fdf67 100644 --- a/src/types.rs +++ b/src/types.rs @@ -4,7 +4,7 @@ use std::hash::{Hash, Hasher}; use std::ops::{Deref, DerefMut}; use std::os::raw::{c_int, c_void}; use std::result::Result as StdResult; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicI32, Ordering}; use std::sync::{Arc, Mutex}; use std::{fmt, mem, ptr}; @@ -204,26 +204,25 @@ pub(crate) struct DestructedUserdata; /// [`AnyUserData::set_user_value`]: crate::AnyUserData::set_user_value /// [`AnyUserData::user_value`]: crate::AnyUserData::user_value pub struct RegistryKey { - pub(crate) registry_id: c_int, - pub(crate) is_nil: AtomicBool, + pub(crate) registry_id: AtomicI32, pub(crate) unref_list: Arc>>>, } impl fmt::Debug for RegistryKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "RegistryKey({})", self.registry_id) + write!(f, "RegistryKey({})", self.id()) } } impl Hash for RegistryKey { fn hash(&self, state: &mut H) { - self.registry_id.hash(state) + self.id().hash(state) } } impl PartialEq for RegistryKey { fn eq(&self, other: &RegistryKey) -> bool { - self.registry_id == other.registry_id && Arc::ptr_eq(&self.unref_list, &other.unref_list) + self.id() == other.id() && Arc::ptr_eq(&self.unref_list, &other.unref_list) } } @@ -231,49 +230,46 @@ impl Eq for RegistryKey {} impl Drop for RegistryKey { fn drop(&mut self) { + let registry_id = self.id(); // We don't need to collect nil slot - if self.registry_id > ffi::LUA_REFNIL { + if registry_id > ffi::LUA_REFNIL { let mut unref_list = mlua_expect!(self.unref_list.lock(), "unref list poisoned"); if let Some(list) = unref_list.as_mut() { - list.push(self.registry_id); + list.push(registry_id); } } } } impl RegistryKey { - // Creates a new instance of `RegistryKey` + /// Creates a new instance of `RegistryKey` pub(crate) const fn new(id: c_int, unref_list: Arc>>>) -> Self { RegistryKey { - registry_id: id, - is_nil: AtomicBool::new(id == ffi::LUA_REFNIL), + registry_id: AtomicI32::new(id), unref_list, } } - // Destroys the `RegistryKey` without adding to the unref list - pub(crate) fn take(self) -> c_int { - let registry_id = self.registry_id; - unsafe { - ptr::read(&self.unref_list); - mem::forget(self); - } - registry_id + /// Returns the underlying Lua reference of this `RegistryKey` + #[inline(always)] + pub fn id(&self) -> c_int { + self.registry_id.load(Ordering::Relaxed) } - // Returns true if this `RegistryKey` holds a nil value + /// Sets the unique Lua reference key of this `RegistryKey` #[inline(always)] - pub(crate) fn is_nil(&self) -> bool { - self.is_nil.load(Ordering::Relaxed) + pub(crate) fn set_id(&self, id: c_int) { + self.registry_id.store(id, Ordering::Relaxed); } - // Marks value of this `RegistryKey` as `Nil` - #[inline(always)] - pub(crate) fn set_nil(&self, enabled: bool) { - // We cannot replace previous value with nil in as this will break - // Lua mechanism to find free keys. - // Instead, we set a special flag to mark value as nil. - self.is_nil.store(enabled, Ordering::Relaxed); + /// Destroys the `RegistryKey` without adding to the unref list + pub(crate) fn take(self) -> i32 { + let registry_id = self.id(); + unsafe { + ptr::read(&self.unref_list); + mem::forget(self); + } + registry_id } } diff --git a/tests/tests.rs b/tests/tests.rs index 9dd6a00f..aab4f483 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -775,12 +775,11 @@ fn test_replace_registry_value() -> Result<()> { lua.replace_registry_value(&key, 123)?; assert_eq!(lua.registry_value::(&key)?, 123); - // It should be impossible to replace (initial) nil value with non-nil let key2 = lua.create_registry_value(Value::Nil)?; - match lua.replace_registry_value(&key2, "abc") { - Err(Error::RuntimeError(_)) => {} - r => panic!("expected RuntimeError, got {r:?}"), - } + lua.replace_registry_value(&key2, Value::Nil)?; + assert_eq!(lua.registry_value::(&key2)?, Value::Nil); + lua.replace_registry_value(&key2, "abc")?; + assert_eq!(lua.registry_value::(&key2)?, "abc"); Ok(()) } From 884a025b521e4b110a6bd733c65a658c5a9e43d2 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 18 Jun 2024 12:13:49 +0100 Subject: [PATCH 099/635] Fix some clippy warnings --- Cargo.toml | 5 ++++- mlua-sys/Cargo.toml | 3 +++ mlua-sys/src/lib.rs | 19 +++++++++++++------ src/chunk.rs | 2 +- src/lib.rs | 2 +- src/stdlib.rs | 1 - 6 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7a826fc9..a2f1c01f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ unstable = [] [dependencies] mlua_derive = { version = "=0.9.3", optional = true, path = "mlua_derive" } -bstr = { version = "1.0", features = ["std"], default_features = false } +bstr = { version = "1.0", features = ["std"], default-features = false } once_cell = { version = "1.0" } num-traits = { version = "0.2.14" } rustc-hash = "2.0" @@ -79,6 +79,9 @@ criterion = { version = "0.5", features = ["async_tokio"] } rustyline = "14.0" tokio = { version = "1.0", features = ["full"] } +[lints.rust] +unexpected_cfgs = { level = "allow", check-cfg = ['cfg(tarpaulin_include)'] } + [[bench]] name = "benchmark" harness = false diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 4d68e653..8dc99c10 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -41,3 +41,6 @@ pkg-config = "0.3.17" lua-src = { version = ">= 546.0.2, < 546.1.0", optional = true } luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } luau0-src = { version = "0.10.0", optional = true } + +[lints.rust] +unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/mlua-sys/src/lib.rs b/mlua-sys/src/lib.rs index 48225e22..0e73ea73 100644 --- a/mlua-sys/src/lib.rs +++ b/mlua-sys/src/lib.rs @@ -39,20 +39,25 @@ pub const LUA_MAX_UPVALUES: c_int = 200; #[doc(hidden)] pub const LUA_TRACEBACK_STACK: c_int = 11; +// Copied from https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/common/alloc.rs // The minimum alignment guaranteed by the architecture. This value is used to // add fast paths for low alignment values. -// Copied from https://github.com/rust-lang/rust/blob/master/library/std/src/sys/common/alloc.rs #[cfg(any( target_arch = "x86", target_arch = "arm", + target_arch = "m68k", + target_arch = "csky", target_arch = "mips", + target_arch = "mips32r6", target_arch = "powerpc", target_arch = "powerpc64", target_arch = "sparc", - target_arch = "asmjs", target_arch = "wasm32", target_arch = "hexagon", - all(target_arch = "riscv32", not(target_os = "espidf")), + all( + target_arch = "riscv32", + not(any(target_os = "espidf", target_os = "zkvm")) + ), all(target_arch = "xtensa", not(target_os = "espidf")), ))] #[doc(hidden)] @@ -60,18 +65,20 @@ pub const SYS_MIN_ALIGN: usize = 8; #[cfg(any( target_arch = "x86_64", target_arch = "aarch64", + target_arch = "arm64ec", + target_arch = "loongarch64", target_arch = "mips64", + target_arch = "mips64r6", target_arch = "s390x", target_arch = "sparc64", target_arch = "riscv64", target_arch = "wasm64", - target_arch = "loongarch64", ))] #[doc(hidden)] pub const SYS_MIN_ALIGN: usize = 16; -// The allocator on the esp-idf platform guarentees 4 byte alignment. +// The allocator on the esp-idf and zkvm platforms guarantee 4 byte alignment. #[cfg(any( - all(target_arch = "riscv32", target_os = "espidf"), + all(target_arch = "riscv32", any(target_os = "espidf", target_os = "zkvm")), all(target_arch = "xtensa", target_os = "espidf"), ))] #[doc(hidden)] diff --git a/src/chunk.rs b/src/chunk.rs index 3114365a..b3b1a7c7 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -341,7 +341,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> { /// /// This is equivalent to calling the chunk function with no arguments and no return values. pub fn exec(self) -> Result<()> { - self.call(())?; + self.call::<_, ()>(())?; Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 5489a69f..9345f5f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,7 +73,7 @@ // Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any* // warnings at all. -#![doc(test(attr(deny(warnings))))] +#![doc(test(attr(warn(warnings))))] // FIXME: Remove this when rust-lang/rust#123748 is fixed #![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] diff --git a/src/stdlib.rs b/src/stdlib.rs index 88e50cf9..e3630d44 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -1,5 +1,4 @@ use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign}; -use std::u32; /// Flags describing the set of lua standard libraries to load. #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] From f1ceaf0ff19285f326b84228fc9ccad73e7c968d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 18 Jun 2024 11:13:04 +0100 Subject: [PATCH 100/635] v0.9.9 --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- README.md | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 236c910c..91574951 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v0.9.9 + +- Minimal Luau updated to 0.629 +- Fixed bug when attempting to reset or resume already running coroutines (#416). +- Added `RegistryKey::id()` method to get the underlying Lua registry key id. + ## v0.9.8 - Fixed serializing same table multiple times (#408) diff --git a/Cargo.toml b/Cargo.toml index a2f1c01f..5a065523 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.9.8" # remember to update mlua_derive +version = "0.9.9" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.71" edition = "2021" diff --git a/README.md b/README.md index c9b23760..56a84158 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ Add to `Cargo.toml` : ``` toml [dependencies] -mlua = { version = "0.9.8", features = ["lua54", "vendored"] } +mlua = { version = "0.9.9", features = ["lua54", "vendored"] } ``` `main.rs` @@ -168,7 +168,7 @@ Add to `Cargo.toml` : crate-type = ["cdylib"] [dependencies] -mlua = { version = "0.9.8", features = ["lua54", "module"] } +mlua = { version = "0.9.9", features = ["lua54", "module"] } ``` `lib.rs` : From b77836920a5db89067892f4fd9c88db1a0483a8a Mon Sep 17 00:00:00 2001 From: Caleb Maclennan Date: Fri, 5 Jul 2024 01:31:05 +0300 Subject: [PATCH 101/635] Link to other mlua projects published to LuaRocks (#425) * Add a link to decasify, another mlua success story published on LuaRocks * Add a few more downstream project links that seem reasonably maintained --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 56a84158..7a291b5d 100644 --- a/README.md +++ b/README.md @@ -224,7 +224,11 @@ Module builds don't require Lua lib or headers to be installed on the system. There is a LuaRocks build backend for mlua modules [`luarocks-build-rust-mlua`]. Modules written in Rust and published to luarocks: +- [`decasify`](https://github.com/alerque/decasify) - [`lua-ryaml`](https://github.com/khvzak/lua-ryaml) +- [`tiktoken_core`](https://github.com/gptlang/lua-tiktoken) +- [`toml-edit`](https://github.com/vhyrro/toml-edit.lua) +- [`typst-lua`](https://github.com/rousbound/typst-lua) [`luarocks-build-rust-mlua`]: https://luarocks.org/modules/khvzak/luarocks-build-rust-mlua From 9ce0e11518663b0b8041aaaf6fd3431f4621ce10 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 25 Jul 2024 22:45:40 +0100 Subject: [PATCH 102/635] Use Lua 5.4.7 --- mlua-sys/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 8dc99c10..c2e3b6ba 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -38,7 +38,7 @@ module = [] cc = "1.0" cfg-if = "1.0" pkg-config = "0.3.17" -lua-src = { version = ">= 546.0.2, < 546.1.0", optional = true } +lua-src = { version = ">= 547.0.0, < 547.1.0", optional = true } luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } luau0-src = { version = "0.10.0", optional = true } From 496c8fa0e94271045c76ce15304d31af0ad7d590 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 25 Jul 2024 22:49:32 +0100 Subject: [PATCH 103/635] Fix references to master branch --- README.md | 4 ++-- docs/release_notes/v0.9.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7a291b5d..ac04b0fc 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [crates.io]: https://crates.io/crates/mlua [API Documentation]: https://docs.rs/mlua/badge.svg [docs.rs]: https://docs.rs/mlua -[Coverage Status]: https://codecov.io/gh/mlua-rs/mlua/branch/master/graph/badge.svg?token=99339FS1CG +[Coverage Status]: https://codecov.io/gh/mlua-rs/mlua/branch/main/graph/badge.svg?token=99339FS1CG [codecov.io]: https://codecov.io/gh/mlua-rs/mlua [MSRV]: https://img.shields.io/badge/rust-1.71+-brightgreen.svg?&logo=rust @@ -19,7 +19,7 @@ > **Note** > -> See v0.9 [release notes](https://github.com/khvzak/mlua/blob/master/docs/release_notes/v0.9.md). +> See v0.9 [release notes](https://github.com/khvzak/mlua/blob/main/docs/release_notes/v0.9.md). `mlua` is bindings to [Lua](https://www.lua.org) programming language for Rust with a goal to provide _safe_ (as far as it's possible), high level, easy to use, practical and flexible API. diff --git a/docs/release_notes/v0.9.md b/docs/release_notes/v0.9.md index 93f811e1..33c6a2a5 100644 --- a/docs/release_notes/v0.9.md +++ b/docs/release_notes/v0.9.md @@ -3,7 +3,7 @@ The v0.9 version of mlua is a major release that includes a number of API changes and improvements. This release is a stepping stone towards the v1.0. This document highlights the most important changes. For a full list of changes, see the [CHANGELOG]. -[CHANGELOG]: https://github.com/khvzak/mlua/blob/master/CHANGELOG.md +[CHANGELOG]: https://github.com/khvzak/mlua/blob/main/CHANGELOG.md ### New features From 7d3704c222cece2080f7101898a44c424f86403f Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 25 Jul 2024 22:46:03 +0100 Subject: [PATCH 104/635] mlua-sys: v0.6.2 --- mlua-sys/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index c2e3b6ba..e213b42b 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.6.1" +version = "0.6.2" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" From fe21ef43baded874640bb967c54aff61f63b2793 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 29 Feb 2024 23:26:04 +0000 Subject: [PATCH 105/635] Replace AtomicPtr with Cell --- Cargo.toml | 2 +- src/lua.rs | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5a065523..0a6aee7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ futures-util = { version = "0.3", optional = true, default-features = false, fea serde = { version = "1.0", optional = true } erased-serde = { version = "0.4", optional = true } serde-value = { version = "0.7", optional = true } -parking_lot = { version = "0.12", optional = true } +parking_lot = { version = "0.12" } ffi = { package = "mlua-sys", version = "0.6.1", path = "mlua-sys" } diff --git a/src/lua.rs b/src/lua.rs index f059eee2..58e20e13 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1,5 +1,5 @@ use std::any::TypeId; -use std::cell::{RefCell, UnsafeCell}; +use std::cell::{Cell, RefCell, UnsafeCell}; use std::ffi::{CStr, CString}; use std::fmt; use std::marker::PhantomData; @@ -9,7 +9,6 @@ use std::os::raw::{c_char, c_int, c_void}; use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe, Location}; use std::ptr; use std::result::Result as StdResult; -use std::sync::atomic::{AtomicPtr, Ordering}; use std::sync::{Arc, Mutex}; use rustc_hash::FxHashMap; @@ -72,7 +71,7 @@ pub struct Lua(Arc); /// An inner Lua struct which holds a raw Lua state. pub struct LuaInner { // The state is dynamic and depends on context - state: AtomicPtr, + state: Cell<*mut ffi::lua_State>, main_state: *mut ffi::lua_State, extra: Arc>, } @@ -569,7 +568,7 @@ impl Lua { assert_stack(main_state, ffi::LUA_MINSTACK); let inner = Arc::new(LuaInner { - state: AtomicPtr::new(state), + state: Cell::new(state), main_state, extra: Arc::clone(&extra), }); @@ -3253,7 +3252,7 @@ impl Lua { impl LuaInner { #[inline(always)] pub(crate) fn state(&self) -> *mut ffi::lua_State { - self.state.load(Ordering::Relaxed) + self.state.get() } #[cfg(feature = "luau")] @@ -3295,14 +3294,14 @@ struct StateGuard<'a>(&'a LuaInner, *mut ffi::lua_State); impl<'a> StateGuard<'a> { fn new(inner: &'a LuaInner, mut state: *mut ffi::lua_State) -> Self { - state = inner.state.swap(state, Ordering::Relaxed); + state = inner.state.replace(state); Self(inner, state) } } impl<'a> Drop for StateGuard<'a> { fn drop(&mut self) { - self.0.state.store(self.1, Ordering::Relaxed); + self.0.state.set(self.1); } } From 98ca880f8adc10d423cfe935d9a8097ed79138dd Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 2 Mar 2024 10:56:30 +0000 Subject: [PATCH 106/635] Change `Value::Error` variant to store `Box` instead of `Error`. This will help to reduce size of `Value` enum on the stack. --- src/conversion.rs | 4 ++-- src/lua.rs | 8 ++++---- src/value.rs | 2 +- tests/value.rs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index 6013ebf9..2d093a06 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -412,7 +412,7 @@ impl<'lua, T: 'static> FromLua<'lua> for UserDataRefMut<'lua, T> { impl<'lua> IntoLua<'lua> for Error { #[inline] fn into_lua(self, _: &'lua Lua) -> Result> { - Ok(Value::Error(self)) + Ok(Value::Error(Box::new(self))) } } @@ -420,7 +420,7 @@ impl<'lua> FromLua<'lua> for Error { #[inline] fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { match value { - Value::Error(err) => Ok(err), + Value::Error(err) => Ok(*err), val => Ok(Error::runtime( lua.coerce_string(val)? .and_then(|s| Some(s.to_str().ok()?.to_owned())) diff --git a/src/lua.rs b/src/lua.rs index 58e20e13..75347ba0 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -2334,7 +2334,7 @@ impl Lua { pub unsafe fn push_value(&self, value: Value) -> Result<()> { if let Value::Error(err) = value { let protect = !self.unlikely_memory_error(); - return push_gc_userdata(self.state(), WrappedFailure::Error(err), protect); + return push_gc_userdata(self.state(), WrappedFailure::Error(*err), protect); } self.push_value_ref(&value) } @@ -2364,7 +2364,7 @@ impl Lua { Value::UserData(ud) => self.push_ref(&ud.0), Value::Error(err) => { let protect = !self.unlikely_memory_error(); - push_gc_userdata(state, WrappedFailure::Error(err.clone()), protect)?; + push_gc_userdata(state, WrappedFailure::Error(*err.clone()), protect)?; } } Ok(()) @@ -2447,7 +2447,7 @@ impl Lua { Some(WrappedFailure::Error(err)) => { let err = err.clone(); ffi::lua_pop(state, 1); - Value::Error(err) + Value::Error(Box::new(err)) } Some(WrappedFailure::Panic(panic)) => { if let Some(panic) = panic.take() { @@ -2548,7 +2548,7 @@ impl Lua { // WrappedPanics are automatically resumed. match get_gc_userdata::(state, idx, wrapped_failure_mt_ptr).as_mut() { - Some(WrappedFailure::Error(err)) => Value::Error(err.clone()), + Some(WrappedFailure::Error(err)) => Value::Error(Box::new(err.clone())), Some(WrappedFailure::Panic(panic)) => { if let Some(panic) = panic.take() { resume_unwind(panic); diff --git a/src/value.rs b/src/value.rs index ed80f279..858d56d6 100644 --- a/src/value.rs +++ b/src/value.rs @@ -63,7 +63,7 @@ pub enum Value<'lua> { /// Special builtin userdata types will be represented as other `Value` variants. UserData(AnyUserData<'lua>), /// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned. - Error(Error), + Error(Box), } pub use self::Value::Nil; diff --git a/tests/value.rs b/tests/value.rs index e49f9934..7b060258 100644 --- a/tests/value.rs +++ b/tests/value.rs @@ -140,7 +140,7 @@ fn test_value_to_string() -> Result<()> { let ud: Value = Value::UserData(lua.create_userdata(MyUserData)?); assert!(ud.to_string()?.starts_with("MyUserData:")); - let err = Value::Error(Error::runtime("test error")); + let err = Value::Error(Box::new(Error::runtime("test error"))); assert_eq!(err.to_string()?, "runtime error: test error"); Ok(()) From 4aa178fcc005c765956e7645c54b740b26369c3f Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 2 Mar 2024 16:10:48 +0000 Subject: [PATCH 107/635] Take `&Value` in `Lua::push_value()` --- src/conversion.rs | 4 ++-- src/function.rs | 2 +- src/lua.rs | 28 ++++++++-------------------- src/table.rs | 2 +- src/value.rs | 8 ++++---- 5 files changed, 16 insertions(+), 28 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index 2d093a06..3dfcbc84 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -40,7 +40,7 @@ impl<'lua> IntoLua<'lua> for &Value<'lua> { #[inline] unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { - lua.push_value_ref(self) + lua.push_value(self) } } @@ -750,7 +750,7 @@ where return Ok(()); } // Fallback to default - lua.push_value(T::into_lua(this, lua)?) + lua.push_value(&T::into_lua(this, lua)?) } macro_rules! lua_convert_int { diff --git a/src/function.rs b/src/function.rs index 67fc6f30..88abf132 100644 --- a/src/function.rs +++ b/src/function.rs @@ -252,7 +252,7 @@ impl<'lua> Function<'lua> { check_stack(state, nargs + 3)?; ffi::lua_pushinteger(state, nargs as ffi::lua_Integer); - for arg in args { + for arg in &args { lua.push_value(arg)?; } protect_lua!(state, nargs + 1, 1, fn(state) { diff --git a/src/lua.rs b/src/lua.rs index 75347ba0..b1d6f378 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1928,7 +1928,7 @@ impl Lua { let _sg = StackGuard::new(state); check_stack(state, 4)?; - self.push_value(v)?; + self.push_value(&v)?; let res = if self.unlikely_memory_error() { ffi::lua_tolstring(state, -1, ptr::null_mut()) } else { @@ -1959,7 +1959,7 @@ impl Lua { let _sg = StackGuard::new(state); check_stack(state, 2)?; - self.push_value(v)?; + self.push_value(&v)?; let mut isint = 0; let i = ffi::lua_tointegerx(state, -1, &mut isint); if isint == 0 { @@ -1984,7 +1984,7 @@ impl Lua { let _sg = StackGuard::new(state); check_stack(state, 2)?; - self.push_value(v)?; + self.push_value(&v)?; let mut isnum = 0; let n = ffi::lua_tonumberx(state, -1, &mut isnum); if isnum == 0 { @@ -2085,7 +2085,7 @@ impl Lua { let _sg = StackGuard::new(state); check_stack(state, 4)?; - self.push(t)?; + self.push_value(&t)?; let unref_list = (*self.extra.get()).registry_unref_list.clone(); @@ -2197,7 +2197,7 @@ impl Lua { } (value, registry_id) => { // It must be safe to replace the value without triggering memory error - self.push_value(value)?; + self.push_value(&value)?; ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); } } @@ -2327,22 +2327,10 @@ impl Lua { value.push_into_stack(self) } - /// Pushes a `Value` onto the Lua stack. - /// - /// Uses 2 stack spaces, does not call checkstack. - #[doc(hidden)] - pub unsafe fn push_value(&self, value: Value) -> Result<()> { - if let Value::Error(err) = value { - let protect = !self.unlikely_memory_error(); - return push_gc_userdata(self.state(), WrappedFailure::Error(*err), protect); - } - self.push_value_ref(&value) - } - - /// Pushes a `&Value` (by reference) onto the Lua stack. + /// Pushes a `Value` (by reference) onto the Lua stack. /// - /// Similar to [`Lua::push_value`], uses 2 stack spaces, does not call checkstack. - pub(crate) unsafe fn push_value_ref(&self, value: &Value) -> Result<()> { + /// Uses 2 stack spaces, does not call `checkstack`. + pub(crate) unsafe fn push_value(&self, value: &Value) -> Result<()> { let state = self.state(); match value { Value::Nil => ffi::lua_pushnil(state), diff --git a/src/table.rs b/src/table.rs index 70b049bb..d144631e 100644 --- a/src/table.rs +++ b/src/table.rs @@ -1191,7 +1191,7 @@ where check_stack(state, 5)?; lua.push_ref(&self.table); - lua.push_value(prev_key)?; + lua.push_value(&prev_key)?; // It must be safe to call `lua_next` unprotected as deleting a key from a table is // a permitted operation. diff --git a/src/value.rs b/src/value.rs index 858d56d6..94a562aa 100644 --- a/src/value.rs +++ b/src/value.rs @@ -699,7 +699,7 @@ pub trait IntoLua<'lua>: Sized { #[doc(hidden)] #[inline] unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { - lua.push_value(self.into_lua(lua)?) + lua.push_value(&self.into_lua(lua)?) } } @@ -929,12 +929,12 @@ pub trait IntoLuaMulti<'lua>: Sized { #[doc(hidden)] #[inline] unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result { - let mut values = self.into_lua_multi(lua)?; + let values = self.into_lua_multi(lua)?; let len: c_int = values.len().try_into().unwrap(); unsafe { check_stack(lua.state(), len + 1)?; - for v in values.drain_all() { - lua.push_value(v)?; + for val in &values { + lua.push_value(val)?; } } Ok(len) From aa05eb4c81e86d2d40567984d9913ea7577549e9 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 2 Mar 2024 17:00:23 +0000 Subject: [PATCH 108/635] Switch `MultiValue` to use `VecDeque` under the hood. --- src/lua.rs | 7 ++- src/multi.rs | 44 ++++---------- src/value.rs | 168 +++++++++++++++++---------------------------------- 3 files changed, 69 insertions(+), 150 deletions(-) diff --git a/src/lua.rs b/src/lua.rs index b1d6f378..dd04cc66 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1,5 +1,6 @@ use std::any::TypeId; use std::cell::{Cell, RefCell, UnsafeCell}; +use std::collections::VecDeque; use std::ffi::{CStr, CString}; use std::fmt; use std::marker::PhantomData; @@ -105,7 +106,7 @@ pub(crate) struct ExtraData { // Pool of `WrappedFailure` enums in the ref thread (as userdata) wrapped_failure_pool: Vec, // Pool of `MultiValue` containers - multivalue_pool: Vec>>, + multivalue_pool: Vec>>, // Pool of `Thread`s (coroutines) for async execution #[cfg(feature = "async")] thread_pool: Vec, @@ -3255,13 +3256,13 @@ impl LuaInner { } #[inline] - pub(crate) fn pop_multivalue_from_pool(&self) -> Option> { + pub(crate) fn pop_multivalue_from_pool(&self) -> Option> { let extra = unsafe { &mut *self.extra.get() }; extra.multivalue_pool.pop() } #[inline] - pub(crate) fn push_multivalue_to_pool(&self, mut multivalue: Vec) { + pub(crate) fn push_multivalue_to_pool(&self, mut multivalue: VecDeque) { let extra = unsafe { &mut *self.extra.get() }; if extra.multivalue_pool.len() < MULTIVALUE_POOL_SIZE { multivalue.clear(); diff --git a/src/multi.rs b/src/multi.rs index f9b9a932..c3210780 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -12,28 +12,17 @@ use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil impl<'lua, T: IntoLua<'lua>, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult { #[inline] fn into_lua_multi(self, lua: &'lua Lua) -> Result> { - let mut result = MultiValue::with_lua_and_capacity(lua, 2); match self { - Ok(v) => result.push_front(v.into_lua(lua)?), - Err(e) => { - result.push_front(e.into_lua(lua)?); - result.push_front(Nil); - } + Ok(val) => (val,).into_lua_multi(lua), + Err(err) => (Nil, err).into_lua_multi(lua), } - Ok(result) } #[inline] unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result { match self { - Ok(v) => v.push_into_stack(lua).map(|_| 1), - Err(e) => { - let state = lua.state(); - check_stack(state, 3)?; - ffi::lua_pushnil(state); - e.push_into_stack(lua)?; - Ok(2) - } + Ok(val) => (val,).push_into_stack_multi(lua), + Err(err) => (Nil, err).push_into_stack_multi(lua), } } } @@ -42,13 +31,8 @@ impl<'lua, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult<(), E> { #[inline] fn into_lua_multi(self, lua: &'lua Lua) -> Result> { match self { - Ok(_) => return Ok(MultiValue::new()), - Err(e) => { - let mut result = MultiValue::with_lua_and_capacity(lua, 2); - result.push_front(e.into_lua(lua)?); - result.push_front(Nil); - Ok(result) - } + Ok(_) => Ok(MultiValue::new()), + Err(err) => (Nil, err).into_lua_multi(lua), } } @@ -56,13 +40,7 @@ impl<'lua, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult<(), E> { unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result { match self { Ok(_) => Ok(0), - Err(e) => { - let state = lua.state(); - check_stack(state, 3)?; - ffi::lua_pushnil(state); - e.push_into_stack(lua)?; - Ok(2) - } + Err(err) => (Nil, err).push_into_stack_multi(lua), } } } @@ -71,7 +49,7 @@ impl<'lua, T: IntoLua<'lua>> IntoLuaMulti<'lua> for T { #[inline] fn into_lua_multi(self, lua: &'lua Lua) -> Result> { let mut v = MultiValue::with_lua_and_capacity(lua, 1); - v.push_front(self.into_lua(lua)?); + v.push_back(self.into_lua(lua)?); Ok(v) } @@ -209,7 +187,7 @@ impl<'lua, T: IntoLua<'lua>> IntoLuaMulti<'lua> for Variadic { #[inline] fn into_lua_multi(self, lua: &'lua Lua) -> Result> { let mut values = MultiValue::with_lua_and_capacity(lua, self.0.len()); - values.refill(self.0.into_iter().map(|e| e.into_lua(lua)))?; + values.extend_from_values(self.0.into_iter().map(|val| val.into_lua(lua)))?; Ok(values) } } @@ -218,8 +196,8 @@ impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for Variadic { #[inline] fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result { values - .drain_all() - .map(|e| T::from_lua(e, lua)) + .drain(..) + .map(|val| T::from_lua(val, lua)) .collect::>>() .map(Variadic) } diff --git a/src/value.rs b/src/value.rs index 94a562aa..8ebfc42b 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,12 +1,11 @@ use std::borrow::Cow; use std::cmp::Ordering; -use std::collections::HashSet; -use std::iter; -use std::ops::Index; +use std::collections::{vec_deque, HashSet, VecDeque}; +use std::ops::{Deref, DerefMut}; use std::os::raw::{c_int, c_void}; use std::string::String as StdString; use std::sync::Arc; -use std::{fmt, mem, ptr, slice, str, vec}; +use std::{fmt, mem, ptr, str}; use num_traits::FromPrimitive; @@ -751,24 +750,47 @@ pub trait FromLua<'lua>: Sized { /// Multiple Lua values used for both argument passing and also for multiple return values. #[derive(Debug, Clone)] pub struct MultiValue<'lua> { - vec: Vec>, + deque: VecDeque>, lua: Option<&'lua Lua>, } impl Drop for MultiValue<'_> { fn drop(&mut self) { if let Some(lua) = self.lua { - let vec = mem::take(&mut self.vec); + let vec = mem::take(&mut self.deque); lua.push_multivalue_to_pool(vec); } } } +impl<'lua> Default for MultiValue<'lua> { + #[inline] + fn default() -> MultiValue<'lua> { + MultiValue::new() + } +} + +impl<'lua> Deref for MultiValue<'lua> { + type Target = VecDeque>; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.deque + } +} + +impl<'lua> DerefMut for MultiValue<'lua> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.deque + } +} + impl<'lua> MultiValue<'lua> { /// Creates an empty `MultiValue` containing no values. pub const fn new() -> MultiValue<'lua> { MultiValue { - vec: Vec::new(), + deque: VecDeque::new(), lua: None, } } @@ -776,142 +798,60 @@ impl<'lua> MultiValue<'lua> { /// Similar to `new` but can reuse previously used container with allocated capacity. #[inline] pub(crate) fn with_lua_and_capacity(lua: &'lua Lua, capacity: usize) -> MultiValue<'lua> { - let vec = lua + let deque = lua .pop_multivalue_from_pool() - .map(|mut vec| { - vec.reserve(capacity); - vec + .map(|mut deque| { + if capacity > 0 { + deque.reserve(capacity); + } + deque }) - .unwrap_or_else(|| Vec::with_capacity(capacity)); + .unwrap_or_else(|| VecDeque::with_capacity(capacity)); MultiValue { - vec, + deque, lua: Some(lua), } } -} -impl<'lua> Default for MultiValue<'lua> { #[inline] - fn default() -> MultiValue<'lua> { - MultiValue::new() + pub(crate) fn extend_from_values( + &mut self, + iter: impl IntoIterator>>, + ) -> Result<()> { + for value in iter { + self.push_back(value?); + } + Ok(()) } } impl<'lua> FromIterator> for MultiValue<'lua> { #[inline] fn from_iter>>(iter: I) -> Self { - MultiValue::from_vec(Vec::from_iter(iter)) + let deque = VecDeque::from_iter(iter); + MultiValue { deque, lua: None } } } impl<'lua> IntoIterator for MultiValue<'lua> { type Item = Value<'lua>; - type IntoIter = iter::Rev>>; + type IntoIter = vec_deque::IntoIter>; #[inline] fn into_iter(mut self) -> Self::IntoIter { - let vec = mem::take(&mut self.vec); + let deque = mem::take(&mut self.deque); mem::forget(self); - vec.into_iter().rev() + deque.into_iter() } } impl<'a, 'lua> IntoIterator for &'a MultiValue<'lua> { type Item = &'a Value<'lua>; - type IntoIter = iter::Rev>>; + type IntoIter = vec_deque::Iter<'a, Value<'lua>>; #[inline] fn into_iter(self) -> Self::IntoIter { - self.vec.iter().rev() - } -} - -impl<'lua> Index for MultiValue<'lua> { - type Output = Value<'lua>; - - #[inline] - fn index(&self, index: usize) -> &Self::Output { - if let Some(result) = self.get(index) { - result - } else { - panic!( - "index out of bounds: the len is {} but the index is {}", - self.len(), - index - ) - } - } -} - -impl<'lua> MultiValue<'lua> { - #[inline] - pub fn from_vec(mut vec: Vec>) -> MultiValue<'lua> { - vec.reverse(); - MultiValue { vec, lua: None } - } - - #[inline] - pub fn into_vec(mut self) -> Vec> { - let mut vec = mem::take(&mut self.vec); - mem::forget(self); - vec.reverse(); - vec - } - - #[inline] - pub fn get(&self, index: usize) -> Option<&Value<'lua>> { - if index < self.vec.len() { - return self.vec.get(self.vec.len() - index - 1); - } - None - } - - #[inline] - pub fn pop_front(&mut self) -> Option> { - self.vec.pop() - } - - #[inline] - pub fn push_front(&mut self, value: Value<'lua>) { - self.vec.push(value); - } - - #[inline] - pub fn clear(&mut self) { - self.vec.clear(); - } - - #[inline] - pub fn len(&self) -> usize { - self.vec.len() - } - - #[inline] - pub fn is_empty(&self) -> bool { - self.vec.is_empty() - } - - #[inline] - pub fn iter(&self) -> iter::Rev>> { - self.vec.iter().rev() - } - - #[inline] - pub(crate) fn drain_all(&mut self) -> iter::Rev>> { - self.vec.drain(..).rev() - } - - #[inline] - pub(crate) fn refill( - &mut self, - iter: impl IntoIterator>>, - ) -> Result<()> { - self.vec.clear(); - for value in iter { - self.vec.push(value?); - } - self.vec.reverse(); - Ok(()) + self.deque.iter() } } @@ -975,8 +915,8 @@ pub trait FromLuaMulti<'lua>: Sized { #[inline] unsafe fn from_stack_multi(nvals: c_int, lua: &'lua Lua) -> Result { let mut values = MultiValue::with_lua_and_capacity(lua, nvals as usize); - for idx in 1..=nvals { - values.push_front(lua.stack_value(-idx)); + for idx in 0..nvals { + values.push_back(lua.stack_value(-nvals + idx)); } if nvals > 0 { // It's safe to clear the stack as all references moved to ref thread From 08c7429531d9e57d92a2ef5daa203c7772e286c0 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 2 Mar 2024 17:16:32 +0000 Subject: [PATCH 109/635] Optimize various parts of the code to remove unnecessary clone calls. This is became possible after implementing `IntoLua` for references. --- src/function.rs | 2 +- src/lua.rs | 8 ++++---- src/luau/package.rs | 6 +++--- src/table.rs | 33 +++++++++++---------------------- src/userdata.rs | 4 +--- src/userdata_ext.rs | 13 ++++++------- 6 files changed, 26 insertions(+), 40 deletions(-) diff --git a/src/function.rs b/src/function.rs index 88abf132..9918ca60 100644 --- a/src/function.rs +++ b/src/function.rs @@ -272,7 +272,7 @@ impl<'lua> Function<'lua> { ) .try_cache() .set_name("__mlua_bind") - .call((self.clone(), args_wrapper)) + .call((self, args_wrapper)) } /// Returns the environment of the Lua function. diff --git a/src/lua.rs b/src/lua.rs index dd04cc66..8d165d7b 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -644,13 +644,13 @@ impl Lua { }; let modname = self.create_string(modname)?; - let value = match loaded.raw_get(modname.clone())? { + let value = match loaded.raw_get(&modname)? { Value::Nil => { - let result = match func.call(modname.clone())? { + let result = match func.call(&modname)? { Value::Nil => Value::Boolean(true), res => res, }; - loaded.raw_set(modname, result.clone())?; + loaded.raw_set(modname, &result)?; result } res => res, @@ -3205,7 +3205,7 @@ impl Lua { let loader = self.create_function(|_, ()| Ok("\n\tcan't load C modules in safe mode"))?; // The third and fourth searchers looks for a loader as a C library - searchers.raw_set(3, loader.clone())?; + searchers.raw_set(3, loader)?; searchers.raw_remove(4)?; Ok(()) diff --git a/src/luau/package.rs b/src/luau/package.rs index 6a3e0c1e..880f2e88 100644 --- a/src/luau/package.rs +++ b/src/luau/package.rs @@ -53,7 +53,7 @@ impl std::ops::DerefMut for LoadedDylibs { pub(crate) fn register_package_module(lua: &Lua) -> Result<()> { // Create the package table and store it in app_data for later use (bypassing globals lookup) let package = lua.create_table()?; - lua.set_app_data(PackageKey(lua.create_registry_value(package.clone())?)); + lua.set_app_data(PackageKey(lua.create_registry_value(&package)?)); // Set `package.path` let mut search_path = env::var("LUAU_PATH") @@ -82,12 +82,12 @@ pub(crate) fn register_package_module(lua: &Lua) -> Result<()> { // Set `package.loaded` (table with a list of loaded modules) let loaded = lua.create_table()?; - package.raw_set("loaded", loaded.clone())?; + package.raw_set("loaded", &loaded)?; lua.set_named_registry_value("_LOADED", loaded)?; // Set `package.loaders` let loaders = lua.create_sequence_from([lua.create_function(lua_loader)?])?; - package.raw_set("loaders", loaders.clone())?; + package.raw_set("loaders", &loaders)?; #[cfg(unix)] { loaders.push(lua.create_function(dylib_loader)?)?; diff --git a/src/table.rs b/src/table.rs index d144631e..5d7decf2 100644 --- a/src/table.rs +++ b/src/table.rs @@ -240,16 +240,12 @@ impl<'lua> Table<'lua> { // If self does not define it, then check the other table. if let Some(mt) = self.get_metatable() { if mt.contains_key("__eq")? { - return mt - .get::<_, Function>("__eq")? - .call((self.clone(), other.clone())); + return mt.get::<_, Function>("__eq")?.call((self, other)); } } if let Some(mt) = other.get_metatable() { if mt.contains_key("__eq")? { - return mt - .get::<_, Function>("__eq")? - .call((self.clone(), other.clone())); + return mt.get::<_, Function>("__eq")?.call((self, other)); } } @@ -1004,10 +1000,7 @@ impl<'lua> TableExt<'lua> for Table<'lua> { A: IntoLuaMulti<'lua>, R: FromLuaMulti<'lua>, { - let lua = self.0.lua; - let mut args = args.into_lua_multi(lua)?; - args.push_front(Value::Table(self.clone())); - self.get::<_, Function>(name)?.call(args) + self.get::<_, Function>(name)?.call((self, args)) } fn call_function(&self, name: &str, args: A) -> Result @@ -1024,13 +1017,7 @@ impl<'lua> TableExt<'lua> for Table<'lua> { A: IntoLuaMulti<'lua>, R: FromLuaMulti<'lua> + 'lua, { - let lua = self.0.lua; - let mut args = match args.into_lua_multi(lua) { - Ok(args) => args, - Err(e) => return Box::pin(future::err(e)), - }; - args.push_front(Value::Table(self.clone())); - self.call_async_function(name, args) + self.call_async_function(name, (self, args)) } #[cfg(feature = "async")] @@ -1040,12 +1027,14 @@ impl<'lua> TableExt<'lua> for Table<'lua> { R: FromLuaMulti<'lua> + 'lua, { let lua = self.0.lua; - let args = match args.into_lua_multi(lua) { - Ok(args) => args, - Err(e) => return Box::pin(future::err(e)), - }; match self.get::<_, Function>(name) { - Ok(func) => Box::pin(async move { func.call_async(args).await }), + Ok(func) => { + let args = match args.into_lua_multi(lua) { + Ok(args) => args, + Err(e) => return Box::pin(future::err(e)), + }; + Box::pin(async move { func.call_async(args).await }) + } Err(e) => Box::pin(future::err(e)), } } diff --git a/src/userdata.rs b/src/userdata.rs index dbbbffa3..621910cf 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -1161,9 +1161,7 @@ impl<'lua> AnyUserData<'lua> { } if mt.contains_key("__eq")? { - return mt - .get::<_, Function>("__eq")? - .call((self.clone(), other.clone())); + return mt.get::<_, Function>("__eq")?.call((self, other)); } Ok(false) diff --git a/src/userdata_ext.rs b/src/userdata_ext.rs index 63abffe5..71c36f96 100644 --- a/src/userdata_ext.rs +++ b/src/userdata_ext.rs @@ -83,7 +83,7 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { let metatable = self.get_metatable()?; match metatable.get::(MetaMethod::Index)? { Value::Table(table) => table.raw_get(key), - Value::Function(func) => func.call((self.clone(), key)), + Value::Function(func) => func.call((self, key)), _ => Err(Error::runtime("attempt to index a userdata value")), } } @@ -92,7 +92,7 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { let metatable = self.get_metatable()?; match metatable.get::(MetaMethod::NewIndex)? { Value::Table(table) => table.raw_set(key, value), - Value::Function(func) => func.call((self.clone(), key, value)), + Value::Function(func) => func.call((self, key, value)), _ => Err(Error::runtime("attempt to index a userdata value")), } } @@ -104,7 +104,7 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { { let metatable = self.get_metatable()?; match metatable.get::(MetaMethod::Call)? { - Value::Function(func) => func.call((self.clone(), args)), + Value::Function(func) => func.call((self, args)), _ => Err(Error::runtime("attempt to call a userdata value")), } } @@ -121,11 +121,10 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { }; match metatable.get::(MetaMethod::Call) { Ok(Value::Function(func)) => { - let mut args = match args.into_lua_multi(self.0.lua) { + let args = match (self, args).into_lua_multi(self.0.lua) { Ok(args) => args, Err(e) => return Box::pin(future::err(e)), }; - args.push_front(Value::UserData(self.clone())); Box::pin(async move { func.call_async(args).await }) } Ok(_) => Box::pin(future::err(Error::runtime( @@ -140,7 +139,7 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { A: IntoLuaMulti<'lua>, R: FromLuaMulti<'lua>, { - self.call_function(name, (self.clone(), args)) + self.call_function(name, (self, args)) } #[cfg(feature = "async")] @@ -149,7 +148,7 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { A: IntoLuaMulti<'lua>, R: FromLuaMulti<'lua> + 'lua, { - self.call_async_function(name, (self.clone(), args)) + self.call_async_function(name, (self, args)) } fn call_function(&self, name: &str, args: A) -> Result From 24b0672d99ba7f598b67a0ef3c652634836ed6af Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 2 Mar 2024 23:07:14 +0000 Subject: [PATCH 110/635] Rename `LuaRef` to `ValueRef` --- src/function.rs | 6 ++--- src/lua.rs | 63 ++++++++++++++++++++++++++----------------------- src/scope.rs | 6 ++--- src/string.rs | 6 ++--- src/table.rs | 10 ++++---- src/thread.rs | 8 +++---- src/types.rs | 38 ++++++++++++++--------------- src/userdata.rs | 6 ++--- 8 files changed, 73 insertions(+), 70 deletions(-) diff --git a/src/function.rs b/src/function.rs index 9918ca60..39b9fce1 100644 --- a/src/function.rs +++ b/src/function.rs @@ -7,7 +7,7 @@ use std::slice; use crate::error::{Error, Result}; use crate::lua::Lua; use crate::table::Table; -use crate::types::{Callback, LuaRef, MaybeSend}; +use crate::types::{Callback, MaybeSend, ValueRef}; use crate::util::{ assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str, StackGuard, @@ -22,7 +22,7 @@ use { /// Handle to an internal Lua function. #[derive(Clone, Debug)] -pub struct Function<'lua>(pub(crate) LuaRef<'lua>); +pub struct Function<'lua>(pub(crate) ValueRef<'lua>); /// Owned handle to an internal Lua function. /// @@ -34,7 +34,7 @@ pub struct Function<'lua>(pub(crate) LuaRef<'lua>); #[cfg(feature = "unstable")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] #[derive(Clone, Debug)] -pub struct OwnedFunction(pub(crate) crate::types::LuaOwnedRef); +pub struct OwnedFunction(pub(crate) crate::types::OwnedValueRef); #[cfg(feature = "unstable")] impl OwnedFunction { diff --git a/src/lua.rs b/src/lua.rs index 8d165d7b..f47c945d 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -26,7 +26,7 @@ use crate::table::Table; use crate::thread::Thread; use crate::types::{ AppData, AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, - LightUserData, LuaRef, MaybeSend, Number, RegistryKey, SubtypeId, + LightUserData, MaybeSend, Number, RegistryKey, SubtypeId, ValueRef, }; use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataCell}; use crate::userdata_impl::{UserDataProxy, UserDataRegistry}; @@ -1691,7 +1691,7 @@ impl Lua { ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX); } - return Ok(Thread::new(LuaRef::new(self, index))); + return Ok(Thread::new(ValueRef::new(self, index))); } }; self.create_thread_inner(func) @@ -2575,26 +2575,26 @@ impl Lua { } } - // Pushes a LuaRef value onto the stack, uses 1 stack space, does not call checkstack - pub(crate) unsafe fn push_ref(&self, lref: &LuaRef) { + // Pushes a ValueRef value onto the stack, uses 1 stack space, does not call checkstack + pub(crate) unsafe fn push_ref(&self, vref: &ValueRef) { assert!( - Arc::ptr_eq(&lref.lua.0, &self.0), + Arc::ptr_eq(&vref.lua.0, &self.0), "Lua instance passed Value created from a different main Lua state" ); - ffi::lua_xpush(self.ref_thread(), self.state(), lref.index); + ffi::lua_xpush(self.ref_thread(), self.state(), vref.index); } #[cfg(all(feature = "unstable", not(feature = "send")))] - pub(crate) unsafe fn push_owned_ref(&self, loref: &crate::types::LuaOwnedRef) { + pub(crate) unsafe fn push_owned_ref(&self, vref: &crate::types::OwnedValueRef) { assert!( - Arc::ptr_eq(&loref.inner, &self.0), + Arc::ptr_eq(&vref.inner, &self.0), "Lua instance passed Value created from a different main Lua state" ); - ffi::lua_xpush(self.ref_thread(), self.state(), loref.index); + ffi::lua_xpush(self.ref_thread(), self.state(), vref.index); } // Pops the topmost element of the stack and stores a reference to it. This pins the object, - // preventing garbage collection until the returned `LuaRef` is dropped. + // preventing garbage collection until the returned `ValueRef` is dropped. // // References are stored in the stack of a specially created auxiliary thread that exists only // to store reference values. This is much faster than storing these in the registry, and also @@ -2602,23 +2602,23 @@ impl Lua { // used stack. The implementation is somewhat biased towards the use case of a relatively small // number of short term references being created, and `RegistryKey` being used for long term // references. - pub(crate) unsafe fn pop_ref(&self) -> LuaRef { + pub(crate) unsafe fn pop_ref(&self) -> ValueRef { ffi::lua_xmove(self.state(), self.ref_thread(), 1); let index = ref_stack_pop(self.extra.get()); - LuaRef::new(self, index) + ValueRef::new(self, index) } // Same as `pop_ref` but assumes the value is already on the reference thread - pub(crate) unsafe fn pop_ref_thread(&self) -> LuaRef { + pub(crate) unsafe fn pop_ref_thread(&self) -> ValueRef { let index = ref_stack_pop(self.extra.get()); - LuaRef::new(self, index) + ValueRef::new(self, index) } - pub(crate) fn clone_ref(&self, lref: &LuaRef) -> LuaRef { + pub(crate) fn clone_ref(&self, vref: &ValueRef) -> ValueRef { unsafe { - ffi::lua_pushvalue(self.ref_thread(), lref.index); + ffi::lua_pushvalue(self.ref_thread(), vref.index); let index = ref_stack_pop(self.extra.get()); - LuaRef::new(self, index) + ValueRef::new(self, index) } } @@ -2632,17 +2632,17 @@ impl Lua { } #[cfg(all(feature = "unstable", not(feature = "send")))] - pub(crate) fn adopt_owned_ref(&self, loref: crate::types::LuaOwnedRef) -> LuaRef { + pub(crate) fn adopt_owned_ref(&self, vref: crate::types::OwnedValueRef) -> ValueRef { assert!( - Arc::ptr_eq(&loref.inner, &self.0), + Arc::ptr_eq(&vref.inner, &self.0), "Lua instance passed Value created from a different main Lua state" ); - let index = loref.index; + let index = vref.index; unsafe { - ptr::read(&loref.inner); - mem::forget(loref); + ptr::read(&vref.inner); + mem::forget(vref); } - LuaRef::new(self, index) + ValueRef::new(self, index) } #[inline] @@ -2835,11 +2835,14 @@ impl Lua { } } - // Returns `TypeId` for the `lref` userdata, checking that it's registered and not destructed. + // Returns `TypeId` for the userdata ref, checking that it's registered and not destructed. // // Returns `None` if the userdata is registered but non-static. - pub(crate) unsafe fn get_userdata_ref_type_id(&self, lref: &LuaRef) -> Result> { - self.get_userdata_type_id_inner(self.ref_thread(), lref.index) + pub(crate) unsafe fn get_userdata_ref_type_id( + &self, + vref: &ValueRef, + ) -> Result> { + self.get_userdata_type_id_inner(self.ref_thread(), vref.index) } // Same as `get_userdata_ref_type_id` but assumes the userdata is already on the stack. @@ -2876,11 +2879,11 @@ impl Lua { } } - // Pushes a LuaRef (userdata) value onto the stack, returning their `TypeId`. + // Pushes a ValueRef (userdata) value onto the stack, returning their `TypeId`. // Uses 1 stack space, does not call checkstack. - pub(crate) unsafe fn push_userdata_ref(&self, lref: &LuaRef) -> Result> { - let type_id = self.get_userdata_type_id_inner(self.ref_thread(), lref.index)?; - self.push_ref(lref); + pub(crate) unsafe fn push_userdata_ref(&self, vref: &ValueRef) -> Result> { + let type_id = self.get_userdata_type_id_inner(self.ref_thread(), vref.index)?; + self.push_ref(vref); Ok(type_id) } diff --git a/src/scope.rs b/src/scope.rs index 004324ea..fddbf369 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -10,7 +10,7 @@ use serde::Serialize; use crate::error::{Error, Result}; use crate::function::Function; use crate::lua::Lua; -use crate::types::{Callback, CallbackUpvalue, LuaRef, MaybeSend, SubtypeId}; +use crate::types::{Callback, CallbackUpvalue, MaybeSend, SubtypeId, ValueRef}; use crate::userdata::{ AnyUserData, MetaMethod, UserData, UserDataCell, UserDataFields, UserDataMethods, }; @@ -38,11 +38,11 @@ where 'lua: 'scope, { lua: &'lua Lua, - destructors: RefCell, DestructorCallback<'lua>)>>, + destructors: RefCell, DestructorCallback<'lua>)>>, _scope_invariant: PhantomData>, } -type DestructorCallback<'lua> = Box) -> Vec> + 'lua>; +type DestructorCallback<'lua> = Box) -> Vec> + 'lua>; impl<'lua, 'scope> Scope<'lua, 'scope> { pub(crate) fn new(lua: &'lua Lua) -> Scope<'lua, 'scope> { diff --git a/src/string.rs b/src/string.rs index 3b805d5b..add8bdcd 100644 --- a/src/string.rs +++ b/src/string.rs @@ -11,13 +11,13 @@ use { }; use crate::error::{Error, Result}; -use crate::types::LuaRef; +use crate::types::ValueRef; /// Handle to an internal Lua string. /// /// Unlike Rust strings, Lua strings may not be valid UTF-8. #[derive(Clone)] -pub struct String<'lua>(pub(crate) LuaRef<'lua>); +pub struct String<'lua>(pub(crate) ValueRef<'lua>); /// Owned handle to an internal Lua string. /// @@ -29,7 +29,7 @@ pub struct String<'lua>(pub(crate) LuaRef<'lua>); #[cfg(feature = "unstable")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] #[derive(Clone)] -pub struct OwnedString(pub(crate) crate::types::LuaOwnedRef); +pub struct OwnedString(pub(crate) crate::types::OwnedValueRef); #[cfg(feature = "unstable")] impl OwnedString { diff --git a/src/table.rs b/src/table.rs index 5d7decf2..43b942ec 100644 --- a/src/table.rs +++ b/src/table.rs @@ -13,7 +13,7 @@ use { use crate::error::{Error, Result}; use crate::function::Function; use crate::private::Sealed; -use crate::types::{Integer, LuaRef}; +use crate::types::{Integer, ValueRef}; use crate::util::{assert_stack, check_stack, StackGuard}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Nil, Value}; @@ -22,7 +22,7 @@ use futures_util::future::{self, LocalBoxFuture}; /// Handle to an internal Lua table. #[derive(Clone)] -pub struct Table<'lua>(pub(crate) LuaRef<'lua>); +pub struct Table<'lua>(pub(crate) ValueRef<'lua>); /// Owned handle to an internal Lua table. /// @@ -34,7 +34,7 @@ pub struct Table<'lua>(pub(crate) LuaRef<'lua>); #[cfg(feature = "unstable")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] #[derive(Clone, Debug)] -pub struct OwnedTable(pub(crate) crate::types::LuaOwnedRef); +pub struct OwnedTable(pub(crate) crate::types::OwnedValueRef); #[cfg(feature = "unstable")] impl OwnedTable { @@ -1158,7 +1158,7 @@ impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> { /// /// [`Table::pairs`]: crate::Table::pairs pub struct TablePairs<'lua, K, V> { - table: LuaRef<'lua>, + table: ValueRef<'lua>, key: Option>, _phantom: PhantomData<(K, V)>, } @@ -1218,7 +1218,7 @@ where /// [`Table::sequence_values`]: crate::Table::sequence_values pub struct TableSequence<'lua, V> { // TODO: Use `&Table` - table: LuaRef<'lua>, + table: ValueRef<'lua>, index: Integer, _phantom: PhantomData, } diff --git a/src/thread.rs b/src/thread.rs index 4c003a72..f90dabae 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -3,7 +3,7 @@ use std::os::raw::{c_int, c_void}; use crate::error::{Error, Result}; #[allow(unused)] use crate::lua::Lua; -use crate::types::LuaRef; +use crate::types::ValueRef; use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard}; use crate::value::{FromLuaMulti, IntoLuaMulti}; @@ -43,7 +43,7 @@ pub enum ThreadStatus { /// Handle to an internal Lua thread (coroutine). #[derive(Clone, Debug)] -pub struct Thread<'lua>(pub(crate) LuaRef<'lua>, pub(crate) *mut ffi::lua_State); +pub struct Thread<'lua>(pub(crate) ValueRef<'lua>, pub(crate) *mut ffi::lua_State); /// Owned handle to an internal Lua thread (coroutine). /// @@ -56,7 +56,7 @@ pub struct Thread<'lua>(pub(crate) LuaRef<'lua>, pub(crate) *mut ffi::lua_State) #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] #[derive(Clone, Debug)] pub struct OwnedThread( - pub(crate) crate::types::LuaOwnedRef, + pub(crate) crate::types::OwnedValueRef, pub(crate) *mut ffi::lua_State, ); @@ -87,7 +87,7 @@ pub struct AsyncThread<'lua, R> { impl<'lua> Thread<'lua> { #[inline(always)] - pub(crate) fn new(r#ref: LuaRef<'lua>) -> Self { + pub(crate) fn new(r#ref: ValueRef<'lua>) -> Self { let state = unsafe { ffi::lua_tothread(r#ref.lua.ref_thread(), r#ref.index) }; Thread(r#ref, state) } diff --git a/src/types.rs b/src/types.rs index a87fdf67..aa8ccad4 100644 --- a/src/types.rs +++ b/src/types.rs @@ -273,15 +273,15 @@ impl RegistryKey { } } -pub(crate) struct LuaRef<'lua> { +pub(crate) struct ValueRef<'lua> { pub(crate) lua: &'lua Lua, pub(crate) index: c_int, pub(crate) drop: bool, } -impl<'lua> LuaRef<'lua> { +impl<'lua> ValueRef<'lua> { pub(crate) const fn new(lua: &'lua Lua, index: c_int) -> Self { - LuaRef { + ValueRef { lua, index, drop: true, @@ -295,27 +295,27 @@ impl<'lua> LuaRef<'lua> { #[cfg(feature = "unstable")] #[inline] - pub(crate) fn into_owned(self) -> LuaOwnedRef { + pub(crate) fn into_owned(self) -> OwnedValueRef { assert!(self.drop, "Cannot turn non-drop reference into owned"); - let owned_ref = LuaOwnedRef::new(self.lua.clone(), self.index); + let owned_ref = OwnedValueRef::new(self.lua.clone(), self.index); mem::forget(self); owned_ref } } -impl<'lua> fmt::Debug for LuaRef<'lua> { +impl<'lua> fmt::Debug for ValueRef<'lua> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Ref({:p})", self.to_pointer()) } } -impl<'lua> Clone for LuaRef<'lua> { +impl<'lua> Clone for ValueRef<'lua> { fn clone(&self) -> Self { self.lua.clone_ref(self) } } -impl<'lua> Drop for LuaRef<'lua> { +impl<'lua> Drop for ValueRef<'lua> { fn drop(&mut self) { if self.drop { self.lua.drop_ref_index(self.index); @@ -323,7 +323,7 @@ impl<'lua> Drop for LuaRef<'lua> { } } -impl<'lua> PartialEq for LuaRef<'lua> { +impl<'lua> PartialEq for ValueRef<'lua> { fn eq(&self, other: &Self) -> bool { let ref_thread = self.lua.ref_thread(); assert!( @@ -335,28 +335,28 @@ impl<'lua> PartialEq for LuaRef<'lua> { } #[cfg(feature = "unstable")] -pub(crate) struct LuaOwnedRef { +pub(crate) struct OwnedValueRef { pub(crate) inner: Arc, pub(crate) index: c_int, _non_send: PhantomData<*const ()>, } #[cfg(feature = "unstable")] -impl fmt::Debug for LuaOwnedRef { +impl fmt::Debug for OwnedValueRef { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "OwnedRef({:p})", self.to_ref().to_pointer()) } } #[cfg(feature = "unstable")] -impl Clone for LuaOwnedRef { +impl Clone for OwnedValueRef { fn clone(&self) -> Self { self.to_ref().clone().into_owned() } } #[cfg(feature = "unstable")] -impl Drop for LuaOwnedRef { +impl Drop for OwnedValueRef { fn drop(&mut self) { let lua: &Lua = unsafe { mem::transmute(&self.inner) }; lua.drop_ref_index(self.index); @@ -364,17 +364,17 @@ impl Drop for LuaOwnedRef { } #[cfg(feature = "unstable")] -impl LuaOwnedRef { +impl OwnedValueRef { pub(crate) const fn new(inner: Arc, index: c_int) -> Self { - LuaOwnedRef { + OwnedValueRef { inner, index, _non_send: PhantomData, } } - pub(crate) const fn to_ref(&self) -> LuaRef { - LuaRef { + pub(crate) const fn to_ref(&self) -> ValueRef { + ValueRef { lua: unsafe { mem::transmute(&self.inner) }, index: self.index, drop: false, @@ -531,8 +531,8 @@ mod assertions { use super::*; static_assertions::assert_impl_all!(RegistryKey: Send, Sync); - static_assertions::assert_not_impl_any!(LuaRef: Send); + static_assertions::assert_not_impl_any!(ValueRef: Send); #[cfg(feature = "unstable")] - static_assertions::assert_not_impl_any!(LuaOwnedRef: Send); + static_assertions::assert_not_impl_any!(OwnedValueRef: Send); } diff --git a/src/userdata.rs b/src/userdata.rs index 621910cf..d7858e10 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -22,7 +22,7 @@ use crate::function::Function; use crate::lua::Lua; use crate::string::String; use crate::table::{Table, TablePairs}; -use crate::types::{LuaRef, MaybeSend, SubtypeId}; +use crate::types::{MaybeSend, SubtypeId, ValueRef}; use crate::util::{check_stack, get_userdata, take_userdata, StackGuard}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; use crate::UserDataRegistry; @@ -791,7 +791,7 @@ impl Deref for UserDataVariant { /// [`is`]: crate::AnyUserData::is /// [`borrow`]: crate::AnyUserData::borrow #[derive(Clone, Debug)] -pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>, pub(crate) SubtypeId); +pub struct AnyUserData<'lua>(pub(crate) ValueRef<'lua>, pub(crate) SubtypeId); /// Owned handle to an internal Lua userdata. /// @@ -801,7 +801,7 @@ pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>, pub(crate) SubtypeId); #[cfg(feature = "unstable")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] #[derive(Clone, Debug)] -pub struct OwnedAnyUserData(pub(crate) crate::types::LuaOwnedRef, pub(crate) SubtypeId); +pub struct OwnedAnyUserData(pub(crate) crate::types::OwnedValueRef, pub(crate) SubtypeId); #[cfg(feature = "unstable")] impl OwnedAnyUserData { From 1eb2ecb3b0fcb6db1f04c240367b76392c160679 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 18 May 2024 01:03:25 +0100 Subject: [PATCH 111/635] Drop lifetime from IntoLua --- src/chunk.rs | 6 +- src/conversion.rs | 255 +++++++++++++++++++++---------------------- src/function.rs | 38 ++++--- src/lua.rs | 30 ++--- src/multi.rs | 46 ++++---- src/scope.rs | 42 +++---- src/table.rs | 50 ++++----- src/thread.rs | 8 +- src/userdata.rs | 52 ++++----- src/userdata_ext.rs | 32 +++--- src/userdata_impl.rs | 54 ++++----- src/value.rs | 12 +- 12 files changed, 314 insertions(+), 311 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index b3b1a7c7..9de6f476 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -308,7 +308,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> { /// All global variables (including the standard library!) are looked up in `_ENV`, so it may be /// necessary to populate the environment in order for scripts using custom environments to be /// useful. - pub fn set_environment>(mut self, env: V) -> Self { + pub fn set_environment(mut self, env: V) -> Self { self.env = env .into_lua(self.lua) .and_then(|val| self.lua.unpack(val)) @@ -402,7 +402,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> { /// Load the chunk function and call it with the given arguments. /// /// This is equivalent to `into_function` and calling the resulting function. - pub fn call, R: FromLuaMulti<'lua>>(self, args: A) -> Result { + pub fn call>(self, args: A) -> Result { self.into_function()?.call(args) } @@ -417,7 +417,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> { #[cfg_attr(docsrs, doc(cfg(feature = "async")))] pub async fn call_async(self, args: A) -> Result where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua> + 'lua, { self.into_function()?.call_async(args).await diff --git a/src/conversion.rs b/src/conversion.rs index 3dfcbc84..f5210977 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::ffi::{CStr, CString}; use std::hash::{BuildHasher, Hash}; +use std::mem::transmute; use std::os::raw::c_int; use std::string::String as StdString; use std::{slice, str}; @@ -25,21 +26,21 @@ use crate::{ userdata::OwnedAnyUserData, }; -impl<'lua> IntoLua<'lua> for Value<'lua> { +impl IntoLua for Value<'_> { #[inline] - fn into_lua(self, _: &'lua Lua) -> Result> { - Ok(self) + fn into_lua(self, _: &Lua) -> Result> { + unsafe { Ok(transmute(self)) } } } -impl<'lua> IntoLua<'lua> for &Value<'lua> { +impl IntoLua for &Value<'_> { #[inline] - fn into_lua(self, _: &'lua Lua) -> Result> { - Ok(self.clone()) + fn into_lua(self, _: &Lua) -> Result> { + unsafe { Ok(transmute(self.clone())) } } #[inline] - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { lua.push_value(self) } } @@ -51,21 +52,21 @@ impl<'lua> FromLua<'lua> for Value<'lua> { } } -impl<'lua> IntoLua<'lua> for String<'lua> { +impl IntoLua for String<'_> { #[inline] - fn into_lua(self, _: &'lua Lua) -> Result> { - Ok(Value::String(self)) + fn into_lua(self, _: &Lua) -> Result> { + unsafe { Ok(Value::String(transmute(self))) } } } -impl<'lua> IntoLua<'lua> for &String<'lua> { +impl IntoLua for &String<'_> { #[inline] - fn into_lua(self, _: &'lua Lua) -> Result> { - Ok(Value::String(self.clone())) + fn into_lua(self, _: &Lua) -> Result> { + unsafe { Ok(Value::String(transmute(self.clone()))) } } #[inline] - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { lua.push_ref(&self.0); Ok(()) } @@ -86,18 +87,18 @@ impl<'lua> FromLua<'lua> for String<'lua> { #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl<'lua> IntoLua<'lua> for OwnedString { +impl IntoLua for OwnedString { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &'_ Lua) -> Result> { Ok(Value::String(String(lua.adopt_owned_ref(self.0)))) } } #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl<'lua> IntoLua<'lua> for &OwnedString { +impl IntoLua for &OwnedString { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { OwnedString::into_lua(self.clone(), lua) } @@ -117,21 +118,21 @@ impl<'lua> FromLua<'lua> for OwnedString { } } -impl<'lua> IntoLua<'lua> for Table<'lua> { +impl IntoLua for Table<'_> { #[inline] - fn into_lua(self, _: &'lua Lua) -> Result> { - Ok(Value::Table(self)) + fn into_lua(self, _: &Lua) -> Result> { + unsafe { Ok(Value::Table(transmute(self))) } } } -impl<'lua> IntoLua<'lua> for &Table<'lua> { +impl IntoLua for &Table<'_> { #[inline] - fn into_lua(self, _: &'lua Lua) -> Result> { - Ok(Value::Table(self.clone())) + fn into_lua(self, _: &'_ Lua) -> Result> { + unsafe { Ok(Value::Table(transmute(self.clone()))) } } #[inline] - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { lua.push_ref(&self.0); Ok(()) } @@ -153,18 +154,18 @@ impl<'lua> FromLua<'lua> for Table<'lua> { #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl<'lua> IntoLua<'lua> for OwnedTable { +impl IntoLua for OwnedTable { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::Table(Table(lua.adopt_owned_ref(self.0)))) } } #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl<'lua> IntoLua<'lua> for &OwnedTable { +impl IntoLua for &OwnedTable { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { OwnedTable::into_lua(self.clone(), lua) } @@ -184,21 +185,21 @@ impl<'lua> FromLua<'lua> for OwnedTable { } } -impl<'lua> IntoLua<'lua> for Function<'lua> { +impl IntoLua for Function<'_> { #[inline] - fn into_lua(self, _: &'lua Lua) -> Result> { - Ok(Value::Function(self)) + fn into_lua(self, _: &Lua) -> Result> { + unsafe { Ok(Value::Function(transmute(self))) } } } -impl<'lua> IntoLua<'lua> for &Function<'lua> { +impl IntoLua for &Function<'_> { #[inline] - fn into_lua(self, _: &'lua Lua) -> Result> { - Ok(Value::Function(self.clone())) + fn into_lua(self, _: &Lua) -> Result> { + unsafe { Ok(Value::Function(transmute(self.clone()))) } } #[inline] - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { lua.push_ref(&self.0); Ok(()) } @@ -220,18 +221,18 @@ impl<'lua> FromLua<'lua> for Function<'lua> { #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl<'lua> IntoLua<'lua> for OwnedFunction { +impl IntoLua for OwnedFunction { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::Function(Function(lua.adopt_owned_ref(self.0)))) } } #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl<'lua> IntoLua<'lua> for &OwnedFunction { +impl IntoLua for &OwnedFunction { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { OwnedFunction::into_lua(self.clone(), lua) } @@ -251,21 +252,21 @@ impl<'lua> FromLua<'lua> for OwnedFunction { } } -impl<'lua> IntoLua<'lua> for Thread<'lua> { +impl IntoLua for Thread<'_> { #[inline] - fn into_lua(self, _: &'lua Lua) -> Result> { - Ok(Value::Thread(self)) + fn into_lua(self, _: &Lua) -> Result> { + unsafe { Ok(Value::Thread(transmute(self))) } } } -impl<'lua> IntoLua<'lua> for &Thread<'lua> { +impl IntoLua for &Thread<'_> { #[inline] - fn into_lua(self, _: &'lua Lua) -> Result> { - Ok(Value::Thread(self.clone())) + fn into_lua(self, _: &Lua) -> Result> { + unsafe { Ok(Value::Thread(transmute(self.clone()))) } } #[inline] - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { lua.push_ref(&self.0); Ok(()) } @@ -287,18 +288,18 @@ impl<'lua> FromLua<'lua> for Thread<'lua> { #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl<'lua> IntoLua<'lua> for OwnedThread { +impl IntoLua for OwnedThread { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::Thread(Thread(lua.adopt_owned_ref(self.0), self.1))) } } #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl<'lua> IntoLua<'lua> for &OwnedThread { +impl IntoLua for &OwnedThread { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { OwnedThread::into_lua(self.clone(), lua) } @@ -318,21 +319,21 @@ impl<'lua> FromLua<'lua> for OwnedThread { } } -impl<'lua> IntoLua<'lua> for AnyUserData<'lua> { +impl IntoLua for AnyUserData<'_> { #[inline] - fn into_lua(self, _: &'lua Lua) -> Result> { - Ok(Value::UserData(self)) + fn into_lua(self, _: &Lua) -> Result> { + unsafe { Ok(Value::UserData(transmute(self))) } } } -impl<'lua> IntoLua<'lua> for &AnyUserData<'lua> { +impl IntoLua for &AnyUserData<'_> { #[inline] - fn into_lua(self, _: &'lua Lua) -> Result> { - Ok(Value::UserData(self.clone())) + fn into_lua(self, _: &Lua) -> Result> { + unsafe { Ok(Value::UserData(transmute(self.clone()))) } } #[inline] - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { lua.push_ref(&self.0); Ok(()) } @@ -354,9 +355,9 @@ impl<'lua> FromLua<'lua> for AnyUserData<'lua> { #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl<'lua> IntoLua<'lua> for OwnedAnyUserData { +impl IntoLua for OwnedAnyUserData { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::UserData(AnyUserData( lua.adopt_owned_ref(self.0), self.1, @@ -366,9 +367,9 @@ impl<'lua> IntoLua<'lua> for OwnedAnyUserData { #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl<'lua> IntoLua<'lua> for &OwnedAnyUserData { +impl IntoLua for &OwnedAnyUserData { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { OwnedAnyUserData::into_lua(self.clone(), lua) } @@ -388,9 +389,9 @@ impl<'lua> FromLua<'lua> for OwnedAnyUserData { } } -impl<'lua, T: UserData + MaybeSend + 'static> IntoLua<'lua> for T { +impl IntoLua for T { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::UserData(lua.create_userdata(self)?)) } } @@ -409,9 +410,9 @@ impl<'lua, T: 'static> FromLua<'lua> for UserDataRefMut<'lua, T> { } } -impl<'lua> IntoLua<'lua> for Error { +impl IntoLua for Error { #[inline] - fn into_lua(self, _: &'lua Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result> { Ok(Value::Error(Box::new(self))) } } @@ -430,25 +431,25 @@ impl<'lua> FromLua<'lua> for Error { } } -impl<'lua> IntoLua<'lua> for RegistryKey { +impl IntoLua for RegistryKey { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { lua.registry_value(&self) } #[inline] - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { <&RegistryKey>::push_into_stack(&self, lua) } } -impl<'lua> IntoLua<'lua> for &RegistryKey { +impl IntoLua for &RegistryKey { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { lua.registry_value(self) } - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { if !lua.owns_registry_value(self) { return Err(Error::MismatchedRegistryKey); } @@ -470,14 +471,14 @@ impl<'lua> FromLua<'lua> for RegistryKey { } } -impl<'lua> IntoLua<'lua> for bool { +impl IntoLua for bool { #[inline] - fn into_lua(self, _: &'lua Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result> { Ok(Value::Boolean(self)) } #[inline] - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { ffi::lua_pushboolean(lua.state(), self as c_int); Ok(()) } @@ -499,9 +500,9 @@ impl<'lua> FromLua<'lua> for bool { } } -impl<'lua> IntoLua<'lua> for LightUserData { +impl IntoLua for LightUserData { #[inline] - fn into_lua(self, _: &'lua Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result> { Ok(Value::LightUserData(self)) } } @@ -521,9 +522,9 @@ impl<'lua> FromLua<'lua> for LightUserData { } #[cfg(feature = "luau")] -impl<'lua> IntoLua<'lua> for crate::types::Vector { +impl IntoLua for crate::types::Vector { #[inline] - fn into_lua(self, _: &'lua Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result> { Ok(Value::Vector(self)) } } @@ -543,14 +544,14 @@ impl<'lua> FromLua<'lua> for crate::types::Vector { } } -impl<'lua> IntoLua<'lua> for StdString { +impl IntoLua for StdString { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::String(lua.create_string(&self)?)) } #[inline] - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { push_bytes_into_stack(self, lua) } } @@ -590,28 +591,28 @@ impl<'lua> FromLua<'lua> for StdString { } } -impl<'lua> IntoLua<'lua> for &str { +impl IntoLua for &str { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::String(lua.create_string(self)?)) } #[inline] - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { push_bytes_into_stack(self, lua) } } -impl<'lua> IntoLua<'lua> for Cow<'_, str> { +impl IntoLua for Cow<'_, str> { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::String(lua.create_string(self.as_bytes())?)) } } -impl<'lua> IntoLua<'lua> for Box { +impl IntoLua for Box { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::String(lua.create_string(&*self)?)) } } @@ -633,9 +634,9 @@ impl<'lua> FromLua<'lua> for Box { } } -impl<'lua> IntoLua<'lua> for CString { +impl IntoLua for CString { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::String(lua.create_string(self.as_bytes())?)) } } @@ -663,23 +664,23 @@ impl<'lua> FromLua<'lua> for CString { } } -impl<'lua> IntoLua<'lua> for &CStr { +impl IntoLua for &CStr { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::String(lua.create_string(self.to_bytes())?)) } } -impl<'lua> IntoLua<'lua> for Cow<'_, CStr> { +impl IntoLua for Cow<'_, CStr> { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::String(lua.create_string(self.to_bytes())?)) } } -impl<'lua> IntoLua<'lua> for BString { +impl IntoLua for BString { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::String(lua.create_string(&self)?)) } } @@ -731,9 +732,9 @@ impl<'lua> FromLua<'lua> for BString { } } -impl<'lua> IntoLua<'lua> for &BStr { +impl IntoLua for &BStr { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::String(lua.create_string(self)?)) } } @@ -741,7 +742,7 @@ impl<'lua> IntoLua<'lua> for &BStr { #[inline] unsafe fn push_bytes_into_stack<'lua, T>(this: T, lua: &'lua Lua) -> Result<()> where - T: IntoLua<'lua> + AsRef<[u8]>, + T: IntoLua + AsRef<[u8]>, { let bytes = this.as_ref(); if lua.unlikely_memory_error() && bytes.len() < (1 << 30) { @@ -755,9 +756,9 @@ where macro_rules! lua_convert_int { ($x:ty) => { - impl<'lua> IntoLua<'lua> for $x { + impl IntoLua for $x { #[inline] - fn into_lua(self, _: &'lua Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result> { cast(self) .map(Value::Integer) .or_else(|| cast(self).map(Value::Number)) @@ -770,7 +771,7 @@ macro_rules! lua_convert_int { } #[inline] - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { match cast(self) { Some(i) => ffi::lua_pushinteger(lua.state(), i), None => ffi::lua_pushnumber(lua.state(), self as ffi::lua_Number), @@ -827,9 +828,9 @@ lua_convert_int!(usize); macro_rules! lua_convert_float { ($x:ty) => { - impl<'lua> IntoLua<'lua> for $x { + impl IntoLua for $x { #[inline] - fn into_lua(self, _: &'lua Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result> { cast(self) .ok_or_else(|| Error::ToLuaConversionError { from: stringify!($x), @@ -865,24 +866,24 @@ macro_rules! lua_convert_float { lua_convert_float!(f32); lua_convert_float!(f64); -impl<'lua, T> IntoLua<'lua> for &[T] +impl IntoLua for &[T] where - T: IntoLua<'lua> + Clone, + T: IntoLua + Clone, { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::Table( lua.create_sequence_from(self.iter().cloned())?, )) } } -impl<'lua, T, const N: usize> IntoLua<'lua> for [T; N] +impl IntoLua for [T; N] where - T: IntoLua<'lua>, + T: IntoLua, { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::Table(lua.create_sequence_from(self)?)) } } @@ -924,9 +925,9 @@ where } } -impl<'lua, T: IntoLua<'lua>> IntoLua<'lua> for Box<[T]> { +impl IntoLua for Box<[T]> { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::Table(lua.create_sequence_from(self.into_vec())?)) } } @@ -938,9 +939,9 @@ impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Box<[T]> { } } -impl<'lua, T: IntoLua<'lua>> IntoLua<'lua> for Vec { +impl IntoLua for Vec { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::Table(lua.create_sequence_from(self)?)) } } @@ -959,11 +960,9 @@ impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Vec { } } -impl<'lua, K: Eq + Hash + IntoLua<'lua>, V: IntoLua<'lua>, S: BuildHasher> IntoLua<'lua> - for HashMap -{ +impl IntoLua for HashMap { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::Table(lua.create_table_from(self)?)) } } @@ -985,9 +984,9 @@ impl<'lua, K: Eq + Hash + FromLua<'lua>, V: FromLua<'lua>, S: BuildHasher + Defa } } -impl<'lua, K: Ord + IntoLua<'lua>, V: IntoLua<'lua>> IntoLua<'lua> for BTreeMap { +impl IntoLua for BTreeMap { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::Table(lua.create_table_from(self)?)) } } @@ -1007,9 +1006,9 @@ impl<'lua, K: Ord + FromLua<'lua>, V: FromLua<'lua>> FromLua<'lua> for BTreeMap< } } -impl<'lua, T: Eq + Hash + IntoLua<'lua>, S: BuildHasher> IntoLua<'lua> for HashSet { +impl IntoLua for HashSet { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::Table(lua.create_table_from( self.into_iter().map(|val| (val, true)), )?)) @@ -1034,9 +1033,9 @@ impl<'lua, T: Eq + Hash + FromLua<'lua>, S: BuildHasher + Default> FromLua<'lua> } } -impl<'lua, T: Ord + IntoLua<'lua>> IntoLua<'lua> for BTreeSet { +impl IntoLua for BTreeSet { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { Ok(Value::Table(lua.create_table_from( self.into_iter().map(|val| (val, true)), )?)) @@ -1061,9 +1060,9 @@ impl<'lua, T: Ord + FromLua<'lua>> FromLua<'lua> for BTreeSet { } } -impl<'lua, T: IntoLua<'lua>> IntoLua<'lua> for Option { +impl IntoLua for Option { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { match self { Some(val) => val.into_lua(lua), None => Ok(Nil), @@ -1071,7 +1070,7 @@ impl<'lua, T: IntoLua<'lua>> IntoLua<'lua> for Option { } #[inline] - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { match self { Some(val) => val.push_into_stack(lua)?, None => ffi::lua_pushnil(lua.state()), diff --git a/src/function.rs b/src/function.rs index 39b9fce1..81a7cdac 100644 --- a/src/function.rs +++ b/src/function.rs @@ -122,7 +122,7 @@ impl<'lua> Function<'lua> { /// # Ok(()) /// # } /// ``` - pub fn call, R: FromLuaMulti<'lua>>(&self, args: A) -> Result { + pub fn call>(&self, args: A) -> Result { let lua = self.0.lua; let state = lua.state(); unsafe { @@ -178,7 +178,7 @@ impl<'lua> Function<'lua> { #[cfg_attr(docsrs, doc(cfg(feature = "async")))] pub fn call_async(&self, args: A) -> impl Future> + 'lua where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua> + 'lua, { let lua = self.0.lua; @@ -217,7 +217,7 @@ impl<'lua> Function<'lua> { /// # Ok(()) /// # } /// ``` - pub fn bind>(&self, args: A) -> Result> { + pub fn bind(&self, args: A) -> Result> { unsafe extern "C-unwind" fn args_wrapper_impl(state: *mut ffi::lua_State) -> c_int { let nargs = ffi::lua_gettop(state); let nbinds = ffi::lua_tointeger(state, ffi::lua_upvalueindex(1)) as c_int; @@ -549,7 +549,7 @@ impl OwnedFunction { #[inline] pub fn call<'lua, A, R>(&'lua self, args: A) -> Result where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua>, { self.to_ref().call(args) @@ -564,7 +564,7 @@ impl OwnedFunction { #[inline] pub async fn call_async<'lua, A, R>(&'lua self, args: A) -> Result where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua> + 'lua, { self.to_ref().call_async(args).await @@ -579,10 +579,10 @@ pub(crate) struct WrappedAsyncFunction<'lua>(pub(crate) AsyncCallback<'lua, 'sta impl<'lua> Function<'lua> { /// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`] trait. #[inline] - pub fn wrap(func: F) -> impl IntoLua<'lua> + pub fn wrap(func: F) -> impl IntoLua where A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, { WrappedFunction(Box::new(move |lua, nargs| unsafe { @@ -593,10 +593,10 @@ impl<'lua> Function<'lua> { /// Wraps a Rust mutable closure, returning an opaque type that implements [`IntoLua`] trait. #[inline] - pub fn wrap_mut(func: F) -> impl IntoLua<'lua> + pub fn wrap_mut(func: F) -> impl IntoLua where A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, F: FnMut(&'lua Lua, A) -> Result + MaybeSend + 'static, { let func = RefCell::new(func); @@ -612,12 +612,12 @@ impl<'lua> Function<'lua> { /// Wraps a Rust async function or closure, returning an opaque type that implements [`IntoLua`] trait. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub fn wrap_async(func: F) -> impl IntoLua<'lua> + pub fn wrap_async(func: F) -> impl IntoLua where A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static, - FR: Future> + 'lua, + FR: Future> + 'static, { WrappedAsyncFunction(Box::new(move |lua, args| unsafe { let args = match A::from_lua_args(args, 1, None, lua) { @@ -630,18 +630,20 @@ impl<'lua> Function<'lua> { } } -impl<'lua> IntoLua<'lua> for WrappedFunction<'lua> { +impl IntoLua for WrappedFunction<'_> { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { - lua.create_callback(self.0).map(Value::Function) + fn into_lua(self, lua: &Lua) -> Result> { + lua.create_callback(unsafe { mem::transmute(self.0) }) + .map(Value::Function) } } #[cfg(feature = "async")] -impl<'lua> IntoLua<'lua> for WrappedAsyncFunction<'lua> { +impl IntoLua for WrappedAsyncFunction<'_> { #[inline] - fn into_lua(self, lua: &'lua Lua) -> Result> { - lua.create_async_callback(self.0).map(Value::Function) + fn into_lua(self, lua: &Lua) -> Result> { + lua.create_async_callback(unsafe { mem::transmute(self.0) }) + .map(Value::Function) } } diff --git a/src/lua.rs b/src/lua.rs index f47c945d..df34c252 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -712,7 +712,7 @@ impl Lua { pub unsafe fn entrypoint<'lua, A, R, F>(self, state: *mut ffi::lua_State, func: F) -> c_int where A: FromLuaMulti<'lua>, - R: IntoLua<'lua>, + R: IntoLua, F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, { let extra = self.extra.get(); @@ -734,7 +734,7 @@ impl Lua { #[cfg(not(tarpaulin_include))] pub unsafe fn entrypoint1<'lua, R, F>(self, state: *mut ffi::lua_State, func: F) -> c_int where - R: IntoLua<'lua>, + R: IntoLua, F: Fn(&'lua Lua) -> Result + MaybeSend + 'static, { self.entrypoint(state, move |lua, _: ()| func(lua)) @@ -1436,8 +1436,8 @@ impl Lua { /// Creates a table and fills it with values from an iterator. pub fn create_table_from<'lua, K, V, I>(&'lua self, iter: I) -> Result> where - K: IntoLua<'lua>, - V: IntoLua<'lua>, + K: IntoLua, + V: IntoLua, I: IntoIterator, { let state = self.state(); @@ -1466,7 +1466,7 @@ impl Lua { /// Creates a table from an iterator of values, using `1..` as the keys. pub fn create_sequence_from<'lua, T, I>(&'lua self, iter: I) -> Result> where - T: IntoLua<'lua>, + T: IntoLua, I: IntoIterator, { let state = self.state(); @@ -1541,7 +1541,7 @@ impl Lua { pub fn create_function<'lua, A, R, F>(&'lua self, func: F) -> Result> where A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, { self.create_callback(Box::new(move |lua, nargs| unsafe { @@ -1559,7 +1559,7 @@ impl Lua { pub fn create_function_mut<'lua, A, R, F>(&'lua self, func: F) -> Result> where A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, F: FnMut(&'lua Lua, A) -> Result + MaybeSend + 'static, { let func = RefCell::new(func); @@ -1625,7 +1625,7 @@ impl Lua { pub fn create_async_function<'lua, A, R, F, FR>(&'lua self, func: F) -> Result> where A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static, FR: Future> + 'lua, { @@ -1998,7 +1998,7 @@ impl Lua { } /// Converts a value that implements `IntoLua` into a `Value` instance. - pub fn pack<'lua, T: IntoLua<'lua>>(&'lua self, t: T) -> Result> { + pub fn pack<'lua, T: IntoLua>(&'lua self, t: T) -> Result> { t.into_lua(self) } @@ -2008,7 +2008,7 @@ impl Lua { } /// Converts a value that implements `IntoLuaMulti` into a `MultiValue` instance. - pub fn pack_multi<'lua, T: IntoLuaMulti<'lua>>(&'lua self, t: T) -> Result> { + pub fn pack_multi<'lua, T: IntoLuaMulti>(&'lua self, t: T) -> Result> { t.into_lua_multi(self) } @@ -2026,7 +2026,7 @@ impl Lua { /// state. pub fn set_named_registry_value<'lua, T>(&'lua self, name: &str, t: T) -> Result<()> where - T: IntoLua<'lua>, + T: IntoLua, { let state = self.state(); unsafe { @@ -2080,13 +2080,13 @@ impl Lua { /// However, dropped [`RegistryKey`]s automatically reused to store new values. /// /// [`RegistryKey`]: crate::RegistryKey - pub fn create_registry_value<'lua, T: IntoLua<'lua>>(&'lua self, t: T) -> Result { + pub fn create_registry_value(&self, t: T) -> Result { let state = self.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 4)?; - self.push_value(&t)?; + self.push(t)?; let unref_list = (*self.extra.get()).registry_unref_list.clone(); @@ -2166,7 +2166,7 @@ impl Lua { /// See [`create_registry_value`] for more details. /// /// [`create_registry_value`]: #method.create_registry_value - pub fn replace_registry_value<'lua, T: IntoLua<'lua>>( + pub fn replace_registry_value<'lua, T: IntoLua>( &'lua self, key: &RegistryKey, t: T, @@ -2324,7 +2324,7 @@ impl Lua { /// Uses 2 stack spaces, does not call checkstack. #[doc(hidden)] #[inline(always)] - pub unsafe fn push<'lua>(&'lua self, value: impl IntoLua<'lua>) -> Result<()> { + pub unsafe fn push<'lua>(&'lua self, value: impl IntoLua) -> Result<()> { value.push_into_stack(self) } diff --git a/src/multi.rs b/src/multi.rs index c3210780..00f9e627 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -1,3 +1,5 @@ +use std::iter::FromIterator; +use std::mem::transmute; use std::ops::{Deref, DerefMut}; use std::os::raw::c_int; use std::result::Result as StdResult; @@ -9,9 +11,9 @@ use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil /// Result is convertible to `MultiValue` following the common Lua idiom of returning the result /// on success, or in the case of an error, returning `nil` and an error message. -impl<'lua, T: IntoLua<'lua>, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult { +impl IntoLuaMulti for StdResult { #[inline] - fn into_lua_multi(self, lua: &'lua Lua) -> Result> { + fn into_lua_multi(self, lua: &Lua) -> Result> { match self { Ok(val) => (val,).into_lua_multi(lua), Err(err) => (Nil, err).into_lua_multi(lua), @@ -19,7 +21,7 @@ impl<'lua, T: IntoLua<'lua>, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult< } #[inline] - unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result { + unsafe fn push_into_stack_multi(self, lua: &Lua) -> Result { match self { Ok(val) => (val,).push_into_stack_multi(lua), Err(err) => (Nil, err).push_into_stack_multi(lua), @@ -27,9 +29,9 @@ impl<'lua, T: IntoLua<'lua>, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult< } } -impl<'lua, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult<(), E> { +impl IntoLuaMulti for StdResult<(), E> { #[inline] - fn into_lua_multi(self, lua: &'lua Lua) -> Result> { + fn into_lua_multi(self, lua: &Lua) -> Result> { match self { Ok(_) => Ok(MultiValue::new()), Err(err) => (Nil, err).into_lua_multi(lua), @@ -37,7 +39,7 @@ impl<'lua, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult<(), E> { } #[inline] - unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result { + unsafe fn push_into_stack_multi(self, lua: &Lua) -> Result { match self { Ok(_) => Ok(0), Err(err) => (Nil, err).push_into_stack_multi(lua), @@ -45,16 +47,16 @@ impl<'lua, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult<(), E> { } } -impl<'lua, T: IntoLua<'lua>> IntoLuaMulti<'lua> for T { +impl IntoLuaMulti for T { #[inline] - fn into_lua_multi(self, lua: &'lua Lua) -> Result> { + fn into_lua_multi(self, lua: &Lua) -> Result> { let mut v = MultiValue::with_lua_and_capacity(lua, 1); v.push_back(self.into_lua(lua)?); Ok(v) } #[inline] - unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result { + unsafe fn push_into_stack_multi(self, lua: &Lua) -> Result { self.push_into_stack(lua)?; Ok(1) } @@ -98,10 +100,10 @@ impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for T { } } -impl<'lua> IntoLuaMulti<'lua> for MultiValue<'lua> { +impl IntoLuaMulti for MultiValue<'_> { #[inline] - fn into_lua_multi(self, _: &'lua Lua) -> Result> { - Ok(self) + fn into_lua_multi(self, _: &Lua) -> Result> { + unsafe { Ok(transmute(self)) } } } @@ -183,9 +185,9 @@ impl DerefMut for Variadic { } } -impl<'lua, T: IntoLua<'lua>> IntoLuaMulti<'lua> for Variadic { +impl IntoLuaMulti for Variadic { #[inline] - fn into_lua_multi(self, lua: &'lua Lua) -> Result> { + fn into_lua_multi(self, lua: &Lua) -> Result> { let mut values = MultiValue::with_lua_and_capacity(lua, self.0.len()); values.extend_from_values(self.0.into_iter().map(|val| val.into_lua(lua)))?; Ok(values) @@ -205,14 +207,14 @@ impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for Variadic { macro_rules! impl_tuple { () => ( - impl<'lua> IntoLuaMulti<'lua> for () { + impl IntoLuaMulti for () { #[inline] - fn into_lua_multi(self, lua: &'lua Lua) -> Result> { + fn into_lua_multi(self, lua: &Lua) -> Result> { Ok(MultiValue::with_lua_and_capacity(lua, 0)) } #[inline] - unsafe fn push_into_stack_multi(self, _lua: &'lua Lua) -> Result { + unsafe fn push_into_stack_multi(self, _lua: &Lua) -> Result { Ok(0) } } @@ -234,13 +236,13 @@ macro_rules! impl_tuple { ); ($last:ident $($name:ident)*) => ( - impl<'lua, $($name,)* $last> IntoLuaMulti<'lua> for ($($name,)* $last,) - where $($name: IntoLua<'lua>,)* - $last: IntoLuaMulti<'lua> + impl<$($name,)* $last> IntoLuaMulti for ($($name,)* $last,) + where $($name: IntoLua,)* + $last: IntoLuaMulti { #[allow(unused_mut, non_snake_case)] #[inline] - fn into_lua_multi(self, lua: &'lua Lua) -> Result> { + fn into_lua_multi(self, lua: &Lua) -> Result> { let ($($name,)* $last,) = self; let mut results = $last.into_lua_multi(lua)?; @@ -250,7 +252,7 @@ macro_rules! impl_tuple { #[allow(non_snake_case)] #[inline] - unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result { + unsafe fn push_into_stack_multi(self, lua: &Lua) -> Result { let ($($name,)* $last,) = self; let mut nresults = 0; $( diff --git a/src/scope.rs b/src/scope.rs index fddbf369..25678c90 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -63,7 +63,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { pub fn create_function<'callback, A, R, F>(&'callback self, func: F) -> Result> where A: FromLuaMulti<'callback>, - R: IntoLuaMulti<'callback>, + R: IntoLuaMulti, F: Fn(&'callback Lua, A) -> Result + 'scope, { // Safe, because 'scope must outlive 'callback (due to Self containing 'scope), however the @@ -97,7 +97,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { ) -> Result> where A: FromLuaMulti<'callback>, - R: IntoLuaMulti<'callback>, + R: IntoLuaMulti, F: FnMut(&'callback Lua, A) -> Result + 'scope, { let func = RefCell::new(func); @@ -656,7 +656,7 @@ impl<'lua, T> NonStaticUserDataRegistry<'lua, T> { impl<'lua, T> UserDataFields<'lua, T> for NonStaticUserDataRegistry<'lua, T> { fn add_field(&mut self, name: impl AsRef, value: V) where - V: IntoLua<'lua> + Clone + 'static, + V: IntoLua + Clone + 'static, { let name = name.as_ref().to_string(); self.fields.push(( @@ -670,7 +670,7 @@ impl<'lua, T> UserDataFields<'lua, T> for NonStaticUserDataRegistry<'lua, T> { fn add_field_method_get(&mut self, name: impl AsRef, method: M) where M: Fn(&'lua Lua, &T) -> Result + MaybeSend + 'static, - R: IntoLua<'lua>, + R: IntoLua, { let method = NonStaticMethod::Method(Box::new(move |lua, ud, _| unsafe { method(lua, ud)?.push_into_stack_multi(lua) @@ -694,7 +694,7 @@ impl<'lua, T> UserDataFields<'lua, T> for NonStaticUserDataRegistry<'lua, T> { fn add_field_function_get(&mut self, name: impl AsRef, function: F) where F: Fn(&'lua Lua, AnyUserData<'lua>) -> Result + MaybeSend + 'static, - R: IntoLua<'lua>, + R: IntoLua, { let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); let func = NonStaticMethod::Function(Box::new(move |lua, nargs| unsafe { @@ -719,7 +719,7 @@ impl<'lua, T> UserDataFields<'lua, T> for NonStaticUserDataRegistry<'lua, T> { fn add_meta_field(&mut self, name: impl AsRef, value: V) where - V: IntoLua<'lua> + Clone + 'static, + V: IntoLua + Clone + 'static, { let name = name.as_ref().to_string(); let name2 = name.clone(); @@ -735,7 +735,7 @@ impl<'lua, T> UserDataFields<'lua, T> for NonStaticUserDataRegistry<'lua, T> { fn add_meta_field_with(&mut self, name: impl AsRef, f: F) where F: Fn(&'lua Lua) -> Result + MaybeSend + 'static, - R: IntoLua<'lua>, + R: IntoLua, { let name = name.as_ref().to_string(); let name2 = name.clone(); @@ -754,7 +754,7 @@ impl<'lua, T> UserDataMethods<'lua, T> for NonStaticUserDataRegistry<'lua, T> { where M: Fn(&'lua Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); let method = NonStaticMethod::Method(Box::new(move |lua, ud, nargs| unsafe { @@ -768,7 +768,7 @@ impl<'lua, T> UserDataMethods<'lua, T> for NonStaticUserDataRegistry<'lua, T> { where M: FnMut(&'lua Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); let method = NonStaticMethod::MethodMut(Box::new(move |lua, ud, nargs| unsafe { @@ -786,7 +786,7 @@ impl<'lua, T> UserDataMethods<'lua, T> for NonStaticUserDataRegistry<'lua, T> { M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti<'lua>, MR: Future> + 's, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { // The panic should never happen as async non-static code wouldn't compile // Non-static lifetime must be bounded to 'lua lifetime @@ -801,7 +801,7 @@ impl<'lua, T> UserDataMethods<'lua, T> for NonStaticUserDataRegistry<'lua, T> { M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti<'lua>, MR: Future> + 's, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { // The panic should never happen as async non-static code wouldn't compile // Non-static lifetime must be bounded to 'lua lifetime @@ -812,7 +812,7 @@ impl<'lua, T> UserDataMethods<'lua, T> for NonStaticUserDataRegistry<'lua, T> { where F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); let func = NonStaticMethod::Function(Box::new(move |lua, nargs| unsafe { @@ -826,7 +826,7 @@ impl<'lua, T> UserDataMethods<'lua, T> for NonStaticUserDataRegistry<'lua, T> { where F: FnMut(&'lua Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); let func = NonStaticMethod::FunctionMut(Box::new(move |lua, nargs| unsafe { @@ -842,7 +842,7 @@ impl<'lua, T> UserDataMethods<'lua, T> for NonStaticUserDataRegistry<'lua, T> { F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti<'lua>, FR: Future> + 'lua, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { // The panic should never happen as async non-static code wouldn't compile // Non-static lifetime must be bounded to 'lua lifetime @@ -853,7 +853,7 @@ impl<'lua, T> UserDataMethods<'lua, T> for NonStaticUserDataRegistry<'lua, T> { where M: Fn(&'lua Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); let method = NonStaticMethod::Method(Box::new(move |lua, ud, nargs| unsafe { @@ -867,7 +867,7 @@ impl<'lua, T> UserDataMethods<'lua, T> for NonStaticUserDataRegistry<'lua, T> { where M: FnMut(&'lua Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); let method = NonStaticMethod::MethodMut(Box::new(move |lua, ud, nargs| unsafe { @@ -885,7 +885,7 @@ impl<'lua, T> UserDataMethods<'lua, T> for NonStaticUserDataRegistry<'lua, T> { M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti<'lua>, MR: Future> + 's, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { // The panic should never happen as async non-static code wouldn't compile // Non-static lifetime must be bounded to 'lua lifetime @@ -900,7 +900,7 @@ impl<'lua, T> UserDataMethods<'lua, T> for NonStaticUserDataRegistry<'lua, T> { M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti<'lua>, MR: Future> + 's, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { // The panic should never happen as async non-static code wouldn't compile // Non-static lifetime must be bounded to 'lua lifetime @@ -911,7 +911,7 @@ impl<'lua, T> UserDataMethods<'lua, T> for NonStaticUserDataRegistry<'lua, T> { where F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); let func = NonStaticMethod::Function(Box::new(move |lua, nargs| unsafe { @@ -925,7 +925,7 @@ impl<'lua, T> UserDataMethods<'lua, T> for NonStaticUserDataRegistry<'lua, T> { where F: FnMut(&'lua Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); let func = NonStaticMethod::FunctionMut(Box::new(move |lua, nargs| unsafe { @@ -941,7 +941,7 @@ impl<'lua, T> UserDataMethods<'lua, T> for NonStaticUserDataRegistry<'lua, T> { F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti<'lua>, FR: Future> + 'lua, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { // The panic should never happen as async non-static code wouldn't compile // Non-static lifetime must be bounded to 'lua lifetime diff --git a/src/table.rs b/src/table.rs index 43b942ec..9eca8d5b 100644 --- a/src/table.rs +++ b/src/table.rs @@ -79,7 +79,7 @@ impl<'lua> Table<'lua> { /// ``` /// /// [`raw_set`]: #method.raw_set - pub fn set, V: IntoLua<'lua>>(&self, key: K, value: V) -> Result<()> { + pub fn set(&self, key: K, value: V) -> Result<()> { // Fast track if !self.has_metatable() { return self.raw_set(key, value); @@ -122,7 +122,7 @@ impl<'lua> Table<'lua> { /// ``` /// /// [`raw_get`]: #method.raw_get - pub fn get, V: FromLua<'lua>>(&self, key: K) -> Result { + pub fn get>(&self, key: K) -> Result { // Fast track if !self.has_metatable() { return self.raw_get(key); @@ -145,14 +145,14 @@ impl<'lua> Table<'lua> { /// Checks whether the table contains a non-nil value for `key`. /// /// This might invoke the `__index` metamethod. - pub fn contains_key>(&self, key: K) -> Result { + pub fn contains_key(&self, key: K) -> Result { Ok(self.get::<_, Value>(key)? != Value::Nil) } /// Appends a value to the back of the table. /// /// This might invoke the `__len` and `__newindex` metamethods. - pub fn push>(&self, value: V) -> Result<()> { + pub fn push(&self, value: V) -> Result<()> { // Fast track if !self.has_metatable() { return self.raw_push(value); @@ -253,7 +253,7 @@ impl<'lua> Table<'lua> { } /// Sets a key-value pair without invoking metamethods. - pub fn raw_set, V: IntoLua<'lua>>(&self, key: K, value: V) -> Result<()> { + pub fn raw_set(&self, key: K, value: V) -> Result<()> { #[cfg(feature = "luau")] self.check_readonly_write()?; @@ -278,7 +278,7 @@ impl<'lua> Table<'lua> { } /// Gets the value associated to `key` without invoking metamethods. - pub fn raw_get, V: FromLua<'lua>>(&self, key: K) -> Result { + pub fn raw_get>(&self, key: K) -> Result { let lua = self.0.lua; let state = lua.state(); unsafe { @@ -295,7 +295,7 @@ impl<'lua> Table<'lua> { /// Inserts element value at position `idx` to the table, shifting up the elements from `table[idx]`. /// The worst case complexity is O(n), where n is the table length. - pub fn raw_insert>(&self, idx: Integer, value: V) -> Result<()> { + pub fn raw_insert(&self, idx: Integer, value: V) -> Result<()> { let size = self.raw_len() as Integer; if idx < 1 || idx > size + 1 { return Err(Error::runtime("index out of bounds")); @@ -321,7 +321,7 @@ impl<'lua> Table<'lua> { } /// Appends a value to the back of the table without invoking metamethods. - pub fn raw_push>(&self, value: V) -> Result<()> { + pub fn raw_push(&self, value: V) -> Result<()> { #[cfg(feature = "luau")] self.check_readonly_write()?; @@ -377,7 +377,7 @@ impl<'lua> Table<'lua> { /// where n is the table length. /// /// For other key types this is equivalent to setting `table[key] = nil`. - pub fn raw_remove>(&self, key: K) -> Result<()> { + pub fn raw_remove(&self, key: K) -> Result<()> { let lua = self.0.lua; let state = lua.state(); let key = key.into_lua(lua)?; @@ -751,7 +751,7 @@ impl<'lua> Table<'lua> { /// Sets element value at position `idx` without invoking metamethods. #[doc(hidden)] - pub fn raw_seti>(&self, idx: usize, value: V) -> Result<()> { + pub fn raw_seti(&self, idx: usize, value: V) -> Result<()> { #[cfg(feature = "luau")] self.check_readonly_write()?; @@ -852,7 +852,7 @@ impl<'lua> AsRef> for Table<'lua> { impl<'lua, T> PartialEq<[T]> for Table<'lua> where - T: IntoLua<'lua> + Clone, + T: IntoLua + Clone, { fn eq(&self, other: &[T]) -> bool { let lua = self.0.lua; @@ -882,7 +882,7 @@ where impl<'lua, T> PartialEq<&[T]> for Table<'lua> where - T: IntoLua<'lua> + Clone, + T: IntoLua + Clone, { #[inline] fn eq(&self, other: &&[T]) -> bool { @@ -892,7 +892,7 @@ where impl<'lua, T, const N: usize> PartialEq<[T; N]> for Table<'lua> where - T: IntoLua<'lua> + Clone, + T: IntoLua + Clone, { #[inline] fn eq(&self, other: &[T; N]) -> bool { @@ -907,7 +907,7 @@ pub trait TableExt<'lua>: Sealed { /// The metamethod is called with the table as its first argument, followed by the passed arguments. fn call(&self, args: A) -> Result where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua>; /// Asynchronously calls the table as function assuming it has `__call` metamethod. @@ -917,7 +917,7 @@ pub trait TableExt<'lua>: Sealed { #[cfg_attr(docsrs, doc(cfg(feature = "async")))] fn call_async(&self, args: A) -> LocalBoxFuture<'lua, Result> where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua> + 'lua; /// Gets the function associated to `key` from the table and executes it, @@ -929,7 +929,7 @@ pub trait TableExt<'lua>: Sealed { /// This might invoke the `__index` metamethod. fn call_method(&self, name: &str, args: A) -> Result where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua>; /// Gets the function associated to `key` from the table and executes it, @@ -941,7 +941,7 @@ pub trait TableExt<'lua>: Sealed { /// This might invoke the `__index` metamethod. fn call_function(&self, name: &str, args: A) -> Result where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua>; /// Gets the function associated to `key` from the table and asynchronously executes it, @@ -954,7 +954,7 @@ pub trait TableExt<'lua>: Sealed { #[cfg_attr(docsrs, doc(cfg(feature = "async")))] fn call_async_method(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result> where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua> + 'lua; /// Gets the function associated to `key` from the table and asynchronously executes it, @@ -967,14 +967,14 @@ pub trait TableExt<'lua>: Sealed { #[cfg_attr(docsrs, doc(cfg(feature = "async")))] fn call_async_function(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result> where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua> + 'lua; } impl<'lua> TableExt<'lua> for Table<'lua> { fn call(&self, args: A) -> Result where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua>, { // Convert table to a function and call via pcall that respects the `__call` metamethod. @@ -984,7 +984,7 @@ impl<'lua> TableExt<'lua> for Table<'lua> { #[cfg(feature = "async")] fn call_async(&self, args: A) -> LocalBoxFuture<'lua, Result> where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua> + 'lua, { let args = match args.into_lua_multi(self.0.lua) { @@ -997,7 +997,7 @@ impl<'lua> TableExt<'lua> for Table<'lua> { fn call_method(&self, name: &str, args: A) -> Result where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua>, { self.get::<_, Function>(name)?.call((self, args)) @@ -1005,7 +1005,7 @@ impl<'lua> TableExt<'lua> for Table<'lua> { fn call_function(&self, name: &str, args: A) -> Result where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua>, { self.get::<_, Function>(name)?.call(args) @@ -1014,7 +1014,7 @@ impl<'lua> TableExt<'lua> for Table<'lua> { #[cfg(feature = "async")] fn call_async_method(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result> where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua> + 'lua, { self.call_async_function(name, (self, args)) @@ -1023,7 +1023,7 @@ impl<'lua> TableExt<'lua> for Table<'lua> { #[cfg(feature = "async")] fn call_async_function(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result> where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua> + 'lua, { let lua = self.0.lua; diff --git a/src/thread.rs b/src/thread.rs index f90dabae..489416f8 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -139,7 +139,7 @@ impl<'lua> Thread<'lua> { /// ``` pub fn resume(&self, args: A) -> Result where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua>, { if self.status() != ThreadStatus::Resumable { @@ -164,7 +164,7 @@ impl<'lua> Thread<'lua> { /// Resumes execution of this thread. /// /// It's similar to `resume()` but leaves `nresults` values on the thread stack. - unsafe fn resume_inner>(&self, args: A) -> Result { + unsafe fn resume_inner(&self, args: A) -> Result { let lua = self.0.lua; let state = lua.state(); let thread_state = self.state(); @@ -325,7 +325,7 @@ impl<'lua> Thread<'lua> { #[cfg_attr(docsrs, doc(cfg(feature = "async")))] pub fn into_async(self, args: A) -> AsyncThread<'lua, R> where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua>, { let args = args.into_lua_multi(self.0.lua); @@ -415,7 +415,7 @@ impl OwnedThread { /// See [`Thread::resume()`] for more details. pub fn resume<'lua, A, R>(&'lua self, args: A) -> Result where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua>, { self.to_ref().resume(args) diff --git a/src/userdata.rs b/src/userdata.rs index d7858e10..408333bd 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -266,7 +266,7 @@ pub trait UserDataMethods<'lua, T> { where M: Fn(&'lua Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>; + R: IntoLuaMulti; /// Add a regular method which accepts a `&mut T` as the first parameter. /// @@ -277,7 +277,7 @@ pub trait UserDataMethods<'lua, T> { where M: FnMut(&'lua Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>; + R: IntoLuaMulti; /// Add an async method which accepts a `&T` as the first parameter and returns Future. /// @@ -295,7 +295,7 @@ pub trait UserDataMethods<'lua, T> { M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti<'lua>, MR: Future> + 's, - R: IntoLuaMulti<'lua>; + R: IntoLuaMulti; /// Add an async method which accepts a `&mut T` as the first parameter and returns Future. /// @@ -313,7 +313,7 @@ pub trait UserDataMethods<'lua, T> { M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti<'lua>, MR: Future> + 's, - R: IntoLuaMulti<'lua>; + R: IntoLuaMulti; /// Add a regular method as a function which accepts generic arguments, the first argument will /// be a [`AnyUserData`] of type `T` if the method is called with Lua method syntax: @@ -329,7 +329,7 @@ pub trait UserDataMethods<'lua, T> { where F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>; + R: IntoLuaMulti; /// Add a regular method as a mutable function which accepts generic arguments. /// @@ -340,7 +340,7 @@ pub trait UserDataMethods<'lua, T> { where F: FnMut(&'lua Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>; + R: IntoLuaMulti; /// Add a regular method as an async function which accepts generic arguments /// and returns Future. @@ -357,7 +357,7 @@ pub trait UserDataMethods<'lua, T> { F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti<'lua>, FR: Future> + 'lua, - R: IntoLuaMulti<'lua>; + R: IntoLuaMulti; /// Add a metamethod which accepts a `&T` as the first parameter. /// @@ -371,7 +371,7 @@ pub trait UserDataMethods<'lua, T> { where M: Fn(&'lua Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>; + R: IntoLuaMulti; /// Add a metamethod as a function which accepts a `&mut T` as the first parameter. /// @@ -385,7 +385,7 @@ pub trait UserDataMethods<'lua, T> { where M: FnMut(&'lua Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>; + R: IntoLuaMulti; /// Add an async metamethod which accepts a `&T` as the first parameter and returns Future. /// @@ -403,7 +403,7 @@ pub trait UserDataMethods<'lua, T> { M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti<'lua>, MR: Future> + 's, - R: IntoLuaMulti<'lua>; + R: IntoLuaMulti; /// Add an async metamethod which accepts a `&mut T` as the first parameter and returns Future. /// @@ -421,7 +421,7 @@ pub trait UserDataMethods<'lua, T> { M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti<'lua>, MR: Future> + 's, - R: IntoLuaMulti<'lua>; + R: IntoLuaMulti; /// Add a metamethod which accepts generic arguments. /// @@ -432,7 +432,7 @@ pub trait UserDataMethods<'lua, T> { where F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>; + R: IntoLuaMulti; /// Add a metamethod as a mutable function which accepts generic arguments. /// @@ -443,7 +443,7 @@ pub trait UserDataMethods<'lua, T> { where F: FnMut(&'lua Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>; + R: IntoLuaMulti; /// Add a metamethod which accepts generic arguments and returns Future. /// @@ -459,7 +459,7 @@ pub trait UserDataMethods<'lua, T> { F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti<'lua>, FR: Future> + 'lua, - R: IntoLuaMulti<'lua>; + R: IntoLuaMulti; // // Below are internal methods used in generated code @@ -484,7 +484,7 @@ pub trait UserDataFields<'lua, T> { /// be used as a fall-back if no regular field or method are found. fn add_field(&mut self, name: impl AsRef, value: V) where - V: IntoLua<'lua> + Clone + 'static; + V: IntoLua + Clone + 'static; /// Add a regular field getter as a method which accepts a `&T` as the parameter. /// @@ -496,7 +496,7 @@ pub trait UserDataFields<'lua, T> { fn add_field_method_get(&mut self, name: impl AsRef, method: M) where M: Fn(&'lua Lua, &T) -> Result + MaybeSend + 'static, - R: IntoLua<'lua>; + R: IntoLua; /// Add a regular field setter as a method which accepts a `&mut T` as the first parameter. /// @@ -520,7 +520,7 @@ pub trait UserDataFields<'lua, T> { fn add_field_function_get(&mut self, name: impl AsRef, function: F) where F: Fn(&'lua Lua, AnyUserData<'lua>) -> Result + MaybeSend + 'static, - R: IntoLua<'lua>; + R: IntoLua; /// Add a regular field setter as a function which accepts a generic [`AnyUserData`] of type `T` /// first argument. @@ -544,7 +544,7 @@ pub trait UserDataFields<'lua, T> { /// like `__gc` or `__metatable`. fn add_meta_field(&mut self, name: impl AsRef, value: V) where - V: IntoLua<'lua> + Clone + 'static; + V: IntoLua + Clone + 'static; /// Add a metatable field computed from `f`. /// @@ -557,7 +557,7 @@ pub trait UserDataFields<'lua, T> { fn add_meta_field_with(&mut self, name: impl AsRef, f: F) where F: Fn(&'lua Lua) -> Result + MaybeSend + 'static, - R: IntoLua<'lua>; + R: IntoLua; // // Below are internal methods used in generated code @@ -872,7 +872,7 @@ impl<'lua> AnyUserData<'lua> { /// [`user_value`]: #method.user_value /// [`set_nth_user_value`]: #method.set_nth_user_value #[inline] - pub fn set_user_value>(&self, v: V) -> Result<()> { + pub fn set_user_value(&self, v: V) -> Result<()> { self.set_nth_user_value(1, v) } @@ -903,7 +903,7 @@ impl<'lua> AnyUserData<'lua> { /// For other Lua versions this functionality is provided using a wrapping table. /// /// [`nth_user_value`]: #method.nth_user_value - pub fn set_nth_user_value>(&self, n: usize, v: V) -> Result<()> { + pub fn set_nth_user_value(&self, n: usize, v: V) -> Result<()> { if n < 1 || n > u16::MAX as usize { return Err(Error::runtime("user value index out of bounds")); } @@ -1002,7 +1002,7 @@ impl<'lua> AnyUserData<'lua> { /// The value can be retrieved with [`named_user_value`]. /// /// [`named_user_value`]: #method.named_user_value - pub fn set_named_user_value>(&self, name: &str, v: V) -> Result<()> { + pub fn set_named_user_value(&self, name: &str, v: V) -> Result<()> { let lua = self.0.lua; let state = lua.state(); unsafe { @@ -1276,7 +1276,7 @@ impl<'lua> UserDataMetatable<'lua> { /// Access to restricted metamethods such as `__gc` or `__metatable` will cause an error. /// Setting `__index` or `__newindex` metamethods is also restricted because their values are cached /// for `mlua` internal usage. - pub fn set>(&self, key: impl AsRef, value: V) -> Result<()> { + pub fn set(&self, key: impl AsRef, value: V) -> Result<()> { let key = MetaMethod::validate(key.as_ref())?; // `__index` and `__newindex` cannot be changed in runtime, because values are cached if key == MetaMethod::Index || key == MetaMethod::NewIndex { @@ -1422,16 +1422,16 @@ impl<'lua> AnyUserData<'lua> { /// Wraps any Rust type, returning an opaque type that implements [`IntoLua`] trait. /// /// This function uses [`Lua::create_any_userdata()`] under the hood. - pub fn wrap(data: T) -> impl IntoLua<'lua> { + pub fn wrap(data: T) -> impl IntoLua { WrappedUserdata(move |lua| lua.create_any_userdata(data)) } } -impl<'lua, F> IntoLua<'lua> for WrappedUserdata +impl IntoLua for WrappedUserdata where F: for<'l> FnOnce(&'l Lua) -> Result>, { - fn into_lua(self, lua: &'lua Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result> { (self.0)(lua).map(Value::UserData) } } diff --git a/src/userdata_ext.rs b/src/userdata_ext.rs index 71c36f96..268324db 100644 --- a/src/userdata_ext.rs +++ b/src/userdata_ext.rs @@ -9,17 +9,17 @@ use futures_util::future::{self, LocalBoxFuture}; /// An extension trait for [`AnyUserData`] that provides a variety of convenient functionality. pub trait AnyUserDataExt<'lua>: Sealed { /// Gets the value associated to `key` from the userdata, assuming it has `__index` metamethod. - fn get, V: FromLua<'lua>>(&self, key: K) -> Result; + fn get>(&self, key: K) -> Result; /// Sets the value associated to `key` in the userdata, assuming it has `__newindex` metamethod. - fn set, V: IntoLua<'lua>>(&self, key: K, value: V) -> Result<()>; + fn set(&self, key: K, value: V) -> Result<()>; /// Calls the userdata as a function assuming it has `__call` metamethod. /// /// The metamethod is called with the userdata as its first argument, followed by the passed arguments. fn call(&self, args: A) -> Result where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua>; /// Asynchronously calls the userdata as a function assuming it has `__call` metamethod. @@ -29,14 +29,14 @@ pub trait AnyUserDataExt<'lua>: Sealed { #[cfg_attr(docsrs, doc(cfg(feature = "async")))] fn call_async(&self, args: A) -> LocalBoxFuture<'lua, Result> where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua> + 'lua; /// Calls the userdata method, assuming it has `__index` metamethod /// and a function associated to `name`. fn call_method(&self, name: &str, args: A) -> Result where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua>; /// Gets the function associated to `key` from the table and asynchronously executes it, @@ -49,7 +49,7 @@ pub trait AnyUserDataExt<'lua>: Sealed { #[cfg_attr(docsrs, doc(cfg(feature = "async")))] fn call_async_method(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result> where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua> + 'lua; /// Gets the function associated to `key` from the table and executes it, @@ -61,7 +61,7 @@ pub trait AnyUserDataExt<'lua>: Sealed { /// This might invoke the `__index` metamethod. fn call_function(&self, name: &str, args: A) -> Result where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua>; /// Gets the function associated to `key` from the table and asynchronously executes it, @@ -74,12 +74,12 @@ pub trait AnyUserDataExt<'lua>: Sealed { #[cfg_attr(docsrs, doc(cfg(feature = "async")))] fn call_async_function(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result> where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua> + 'lua; } impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { - fn get, V: FromLua<'lua>>(&self, key: K) -> Result { + fn get>(&self, key: K) -> Result { let metatable = self.get_metatable()?; match metatable.get::(MetaMethod::Index)? { Value::Table(table) => table.raw_get(key), @@ -88,7 +88,7 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { } } - fn set, V: IntoLua<'lua>>(&self, key: K, value: V) -> Result<()> { + fn set(&self, key: K, value: V) -> Result<()> { let metatable = self.get_metatable()?; match metatable.get::(MetaMethod::NewIndex)? { Value::Table(table) => table.raw_set(key, value), @@ -99,7 +99,7 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { fn call(&self, args: A) -> Result where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua>, { let metatable = self.get_metatable()?; @@ -112,7 +112,7 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { #[cfg(feature = "async")] fn call_async(&self, args: A) -> LocalBoxFuture<'lua, Result> where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua> + 'lua, { let metatable = match self.get_metatable() { @@ -136,7 +136,7 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { fn call_method(&self, name: &str, args: A) -> Result where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua>, { self.call_function(name, (self, args)) @@ -145,7 +145,7 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { #[cfg(feature = "async")] fn call_async_method(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result> where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua> + 'lua, { self.call_async_function(name, (self, args)) @@ -153,7 +153,7 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { fn call_function(&self, name: &str, args: A) -> Result where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua>, { match self.get(name)? { @@ -168,7 +168,7 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { #[cfg(feature = "async")] fn call_async_function(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result> where - A: IntoLuaMulti<'lua>, + A: IntoLuaMulti, R: FromLuaMulti<'lua> + 'lua, { match self.get(name) { diff --git a/src/userdata_impl.rs b/src/userdata_impl.rs index 97807586..b3acd48b 100644 --- a/src/userdata_impl.rs +++ b/src/userdata_impl.rs @@ -62,7 +62,7 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> { where M: Fn(&'lua Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = get_function_name::(name); macro_rules! try_self_arg { @@ -138,7 +138,7 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> { where M: FnMut(&'lua Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = get_function_name::(name); macro_rules! try_self_arg { @@ -216,7 +216,7 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> { M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti<'lua>, MR: Future> + 's, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = Arc::new(get_function_name::(name)); let method = Arc::new(method); @@ -310,7 +310,7 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> { M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti<'lua>, MR: Future> + 's, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = Arc::new(get_function_name::(name)); let method = Arc::new(method); @@ -398,7 +398,7 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> { where F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = get_function_name::(name); Box::new(move |lua, nargs| unsafe { @@ -411,7 +411,7 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> { where F: FnMut(&'lua Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = get_function_name::(name); let function = RefCell::new(function); @@ -430,7 +430,7 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> { F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti<'lua>, FR: Future> + 'lua, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = get_function_name::(name); Box::new(move |lua, args| unsafe { @@ -445,7 +445,7 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> { pub(crate) fn check_meta_field(lua: &'lua Lua, name: &str, value: V) -> Result> where - V: IntoLua<'lua>, + V: IntoLua, { let value = value.into_lua(lua)?; if name == MetaMethod::Index || name == MetaMethod::NewIndex { @@ -472,7 +472,7 @@ fn get_function_name(name: &str) -> StdString { impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistry<'lua, T> { fn add_field(&mut self, name: impl AsRef, value: V) where - V: IntoLua<'lua> + Clone + 'static, + V: IntoLua + Clone + 'static, { let name = name.as_ref().to_string(); self.fields.push(( @@ -484,7 +484,7 @@ impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistry<'lua, T> { fn add_field_method_get(&mut self, name: impl AsRef, method: M) where M: Fn(&'lua Lua, &T) -> Result + MaybeSend + 'static, - R: IntoLua<'lua>, + R: IntoLua, { let name = name.as_ref(); let method = Self::box_method(name, move |lua, data, ()| method(lua, data)); @@ -504,7 +504,7 @@ impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistry<'lua, T> { fn add_field_function_get(&mut self, name: impl AsRef, function: F) where F: Fn(&'lua Lua, AnyUserData<'lua>) -> Result + MaybeSend + 'static, - R: IntoLua<'lua>, + R: IntoLua, { let name = name.as_ref(); let func = Self::box_function(name, function); @@ -523,7 +523,7 @@ impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistry<'lua, T> { fn add_meta_field(&mut self, name: impl AsRef, value: V) where - V: IntoLua<'lua> + Clone + 'static, + V: IntoLua + Clone + 'static, { let name = name.as_ref().to_string(); let name2 = name.clone(); @@ -538,7 +538,7 @@ impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistry<'lua, T> { fn add_meta_field_with(&mut self, name: impl AsRef, f: F) where F: Fn(&'lua Lua) -> Result + MaybeSend + 'static, - R: IntoLua<'lua>, + R: IntoLua, { let name = name.as_ref().to_string(); let name2 = name.clone(); @@ -565,7 +565,7 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistry<'lua, T> { where M: Fn(&'lua Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = name.as_ref(); self.methods @@ -576,7 +576,7 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistry<'lua, T> { where M: FnMut(&'lua Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = name.as_ref(); self.methods @@ -591,7 +591,7 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistry<'lua, T> { M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti<'lua>, MR: Future> + 's, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = name.as_ref(); self.async_methods @@ -606,7 +606,7 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistry<'lua, T> { M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti<'lua>, MR: Future> + 's, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = name.as_ref(); self.async_methods @@ -617,7 +617,7 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistry<'lua, T> { where F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = name.as_ref(); self.methods @@ -628,7 +628,7 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistry<'lua, T> { where F: FnMut(&'lua Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = name.as_ref(); self.methods @@ -641,7 +641,7 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistry<'lua, T> { F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti<'lua>, FR: Future> + 'lua, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = name.as_ref(); self.async_methods @@ -652,7 +652,7 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistry<'lua, T> { where M: Fn(&'lua Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = name.as_ref(); self.meta_methods @@ -663,7 +663,7 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistry<'lua, T> { where M: FnMut(&'lua Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = name.as_ref(); self.meta_methods @@ -678,7 +678,7 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistry<'lua, T> { M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti<'lua>, MR: Future> + 's, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = name.as_ref(); self.async_meta_methods @@ -693,7 +693,7 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistry<'lua, T> { M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti<'lua>, MR: Future> + 's, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = name.as_ref(); self.async_meta_methods @@ -704,7 +704,7 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistry<'lua, T> { where F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = name.as_ref(); self.meta_methods @@ -715,7 +715,7 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistry<'lua, T> { where F: FnMut(&'lua Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = name.as_ref(); self.meta_methods @@ -728,7 +728,7 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistry<'lua, T> { F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti<'lua>, FR: Future> + 'lua, - R: IntoLuaMulti<'lua>, + R: IntoLuaMulti, { let name = name.as_ref(); self.async_meta_methods diff --git a/src/value.rs b/src/value.rs index 8ebfc42b..2e5f2d9d 100644 --- a/src/value.rs +++ b/src/value.rs @@ -687,9 +687,9 @@ impl<'a, 'lua> Serialize for SerializableValue<'a, 'lua> { } /// Trait for types convertible to `Value`. -pub trait IntoLua<'lua>: Sized { +pub trait IntoLua: Sized { /// Performs the conversion. - fn into_lua(self, lua: &'lua Lua) -> Result>; + fn into_lua(self, lua: &Lua) -> Result>; /// Pushes the value into the Lua stack. /// @@ -697,7 +697,7 @@ pub trait IntoLua<'lua>: Sized { /// This method does not check Lua stack space. #[doc(hidden)] #[inline] - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { lua.push_value(&self.into_lua(lua)?) } } @@ -859,16 +859,16 @@ impl<'a, 'lua> IntoIterator for &'a MultiValue<'lua> { /// /// This is a generalization of `IntoLua`, allowing any number of resulting Lua values instead of just /// one. Any type that implements `IntoLua` will automatically implement this trait. -pub trait IntoLuaMulti<'lua>: Sized { +pub trait IntoLuaMulti: Sized { /// Performs the conversion. - fn into_lua_multi(self, lua: &'lua Lua) -> Result>; + fn into_lua_multi(self, lua: &Lua) -> Result>; /// Pushes the values into the Lua stack. /// /// Returns number of pushed values. #[doc(hidden)] #[inline] - unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result { + unsafe fn push_into_stack_multi(self, lua: &Lua) -> Result { let values = self.into_lua_multi(lua)?; let len: c_int = values.len().try_into().unwrap(); unsafe { From 722105168301dc37e7c8209c7fb18a19c2262820 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 2 Jun 2024 00:51:24 +0100 Subject: [PATCH 112/635] Refactor: - Remove the rest of `'lua` lifetimes - Remove Owned types --- Cargo.toml | 4 +- benches/benchmark.rs | 6 +- examples/guided_tour.rs | 7 +- mlua_derive/src/from_lua.rs | 4 +- mlua_derive/src/lib.rs | 17 +- src/chunk.rs | 66 +- src/conversion.rs | 447 +++------- src/function.rs | 158 ++-- src/hook.rs | 25 +- src/lib.rs | 18 +- src/lua.rs | 1451 +++++++++++++++++--------------- src/multi.rs | 79 +- src/prelude.rs | 7 - src/serde/de.rs | 59 +- src/serde/mod.rs | 13 +- src/serde/ser.rs | 155 ++-- src/string.rs | 91 +- src/table.rs | 262 +++--- src/thread.rs | 172 ++-- src/types.rs | 137 +-- src/userdata.rs | 550 +++--------- src/userdata_cell.rs | 410 +++++++++ src/userdata_ext.rs | 50 +- src/userdata_impl.rs | 784 +++++++++-------- src/value.rs | 169 ++-- tests/async.rs | 22 +- tests/conversion.rs | 116 --- tests/function.rs | 42 - tests/{scope.rs => scope.rs.1} | 0 tests/serde.rs | 86 +- tests/static.rs | 4 +- tests/string.rs | 19 - tests/table.rs | 14 - tests/thread.rs | 31 - tests/userdata.rs | 58 +- 35 files changed, 2557 insertions(+), 2976 deletions(-) create mode 100644 src/userdata_cell.rs rename tests/{scope.rs => scope.rs.1} (100%) diff --git a/Cargo.toml b/Cargo.toml index 0a6aee7f..2ab55409 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.9.9" # remember to update mlua_derive +version = "0.10.0" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.71" edition = "2021" @@ -53,7 +53,7 @@ futures-util = { version = "0.3", optional = true, default-features = false, fea serde = { version = "1.0", optional = true } erased-serde = { version = "0.4", optional = true } serde-value = { version = "0.7", optional = true } -parking_lot = { version = "0.12" } +parking_lot = { version = "0.12", features = ["arc_lock"] } ffi = { package = "mlua-sys", version = "0.6.1", path = "mlua-sys" } diff --git a/benches/benchmark.rs b/benches/benchmark.rs index f8352e0e..b0a0fbb4 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -305,7 +305,7 @@ fn userdata_create(c: &mut Criterion) { fn userdata_call_index(c: &mut Criterion) { struct UserData(#[allow(unused)] i64); impl LuaUserData for UserData { - fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods<'a, M: LuaUserDataMethods<'a, Self>>(methods: &mut M) { methods.add_meta_method(LuaMetaMethod::Index, move |_, _, key: LuaString| Ok(key)); } } @@ -331,7 +331,7 @@ fn userdata_call_index(c: &mut Criterion) { fn userdata_call_method(c: &mut Criterion) { struct UserData(i64); impl LuaUserData for UserData { - fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods<'a, M: LuaUserDataMethods<'a, Self>>(methods: &mut M) { methods.add_method("add", |_, this, i: i64| Ok(this.0 + i)); } } @@ -361,7 +361,7 @@ fn userdata_call_method(c: &mut Criterion) { fn userdata_async_call_method(c: &mut Criterion) { struct UserData(i64); impl LuaUserData for UserData { - fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods<'a, M: LuaUserDataMethods<'a, Self>>(methods: &mut M) { methods.add_async_method("add", |_, this, i: i64| async move { task::yield_now().await; Ok(this.0 + i) diff --git a/examples/guided_tour.rs b/examples/guided_tour.rs index 26b50adf..60ba1596 100644 --- a/examples/guided_tour.rs +++ b/examples/guided_tour.rs @@ -154,8 +154,8 @@ fn main() -> Result<()> { struct Vec2(f32, f32); // We can implement `FromLua` trait for our `Vec2` to return a copy - impl<'lua> FromLua<'lua> for Vec2 { - fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result { + impl FromLua for Vec2 { + fn from_lua(value: Value, _: &Lua) -> Result { match value { Value::UserData(ud) => Ok(*ud.borrow::()?), _ => unreachable!(), @@ -192,6 +192,8 @@ fn main() -> Result<()> { // requirement. You can call `Lua::scope` to create userdata and callbacks types that only live // for as long as the call to scope, but do not have to be `'static` (and `Send`). + // TODO: Re-enable this + /* { let mut rust_val = 0; @@ -213,6 +215,7 @@ fn main() -> Result<()> { assert_eq!(rust_val, 42); } + */ // We were able to run our 'sketchy' function inside the scope just fine. However, if we // try to run our 'sketchy' function outside of the scope, the function we created will have diff --git a/mlua_derive/src/from_lua.rs b/mlua_derive/src/from_lua.rs index 3d4e4edf..8d0b6836 100644 --- a/mlua_derive/src/from_lua.rs +++ b/mlua_derive/src/from_lua.rs @@ -15,9 +15,9 @@ pub fn from_lua(input: TokenStream) -> TokenStream { }; quote! { - impl #impl_generics ::mlua::FromLua<'_> for #ident #ty_generics #where_clause { + impl #impl_generics ::mlua::FromLua for #ident #ty_generics #where_clause { #[inline] - fn from_lua(value: ::mlua::Value<'_>, _: &'_ ::mlua::Lua) -> ::mlua::Result { + fn from_lua(value: ::mlua::Value, _: &::mlua::Lua) -> ::mlua::Result { match value { ::mlua::Value::UserData(ud) => Ok(ud.borrow::()?.clone()), _ => Err(::mlua::Error::FromLuaConversionError { diff --git a/mlua_derive/src/lib.rs b/mlua_derive/src/lib.rs index 74605cb9..07209be4 100644 --- a/mlua_derive/src/lib.rs +++ b/mlua_derive/src/lib.rs @@ -99,15 +99,14 @@ pub fn chunk(input: TokenStream) -> TokenStream { use ::std::borrow::Cow; use ::std::cell::Cell; use ::std::io::Result as IoResult; - use ::std::marker::PhantomData; - struct InnerChunk<'lua, F: FnOnce(&'lua Lua) -> Result>>(Cell>, PhantomData<&'lua ()>); + struct InnerChunk Result
>(Cell>); - impl<'lua, F> AsChunk<'lua, 'static> for InnerChunk<'lua, F> + impl AsChunk<'static> for InnerChunk where - F: FnOnce(&'lua Lua) -> Result>, + F: FnOnce(&Lua) -> Result
, { - fn environment(&self, lua: &'lua Lua) -> Result>> { + fn environment(&self, lua: &Lua) -> Result> { if #caps_len > 0 { if let Some(make_env) = self.0.take() { return make_env(lua).map(Some); @@ -125,9 +124,7 @@ pub fn chunk(input: TokenStream) -> TokenStream { } } - fn annotate<'a, F: FnOnce(&'a Lua) -> Result>>(f: F) -> F { f } - - let make_env = annotate(move |lua: &Lua| -> Result
{ + let make_env = move |lua: &Lua| -> Result
{ let globals = lua.globals(); let env = lua.create_table()?; let meta = lua.create_table()?; @@ -139,9 +136,9 @@ pub fn chunk(input: TokenStream) -> TokenStream { env.set_metatable(Some(meta)); Ok(env) - }); + }; - InnerChunk(Cell::new(Some(make_env)), PhantomData) + InnerChunk(Cell::new(Some(make_env))) }}; wrapped_code.into() diff --git a/src/chunk.rs b/src/chunk.rs index 9de6f476..a0a48e18 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -7,7 +7,7 @@ use std::string::String as StdString; use crate::error::{Error, ErrorContext, Result}; use crate::function::Function; -use crate::lua::Lua; +use crate::lua::{Lua, WeakLua}; use crate::table::Table; use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti}; @@ -15,7 +15,7 @@ use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti}; /// /// [loadable by Lua]: https://www.lua.org/manual/5.4/manual.html#3.3.2 /// [`Chunk`]: crate::Chunk -pub trait AsChunk<'lua, 'a> { +pub trait AsChunk<'a> { /// Returns optional chunk name fn name(&self) -> Option { None @@ -24,7 +24,7 @@ pub trait AsChunk<'lua, 'a> { /// Returns optional chunk [environment] /// /// [environment]: https://www.lua.org/manual/5.4/manual.html#2.2 - fn environment(&self, lua: &'lua Lua) -> Result>> { + fn environment(&self, lua: &Lua) -> Result> { let _lua = lua; // suppress warning Ok(None) } @@ -38,43 +38,43 @@ pub trait AsChunk<'lua, 'a> { fn source(self) -> IoResult>; } -impl<'a> AsChunk<'_, 'a> for &'a str { +impl<'a> AsChunk<'a> for &'a str { fn source(self) -> IoResult> { Ok(Cow::Borrowed(self.as_ref())) } } -impl AsChunk<'_, 'static> for StdString { +impl AsChunk<'static> for StdString { fn source(self) -> IoResult> { Ok(Cow::Owned(self.into_bytes())) } } -impl<'a> AsChunk<'_, 'a> for &'a StdString { +impl<'a> AsChunk<'a> for &'a StdString { fn source(self) -> IoResult> { Ok(Cow::Borrowed(self.as_bytes())) } } -impl<'a> AsChunk<'_, 'a> for &'a [u8] { +impl<'a> AsChunk<'a> for &'a [u8] { fn source(self) -> IoResult> { Ok(Cow::Borrowed(self)) } } -impl AsChunk<'_, 'static> for Vec { +impl AsChunk<'static> for Vec { fn source(self) -> IoResult> { Ok(Cow::Owned(self)) } } -impl<'a> AsChunk<'_, 'a> for &'a Vec { +impl<'a> AsChunk<'a> for &'a Vec { fn source(self) -> IoResult> { Ok(Cow::Borrowed(self.as_ref())) } } -impl AsChunk<'_, 'static> for &Path { +impl AsChunk<'static> for &Path { fn name(&self) -> Option { Some(format!("@{}", self.display())) } @@ -84,7 +84,7 @@ impl AsChunk<'_, 'static> for &Path { } } -impl AsChunk<'_, 'static> for PathBuf { +impl AsChunk<'static> for PathBuf { fn name(&self) -> Option { Some(format!("@{}", self.display())) } @@ -98,10 +98,10 @@ impl AsChunk<'_, 'static> for PathBuf { /// /// [`Lua::load`]: crate::Lua::load #[must_use = "`Chunk`s do nothing unless one of `exec`, `eval`, `call`, or `into_function` are called on them"] -pub struct Chunk<'lua, 'a> { - pub(crate) lua: &'lua Lua, +pub struct Chunk<'a> { + pub(crate) lua: WeakLua, pub(crate) name: StdString, - pub(crate) env: Result>>, + pub(crate) env: Result>, pub(crate) mode: Option, pub(crate) source: IoResult>, #[cfg(feature = "luau")] @@ -290,7 +290,7 @@ impl Compiler { } } -impl<'lua, 'a> Chunk<'lua, 'a> { +impl<'a> Chunk<'a> { /// Sets the name of this chunk, which results in more informative error traces. pub fn set_name(mut self, name: impl Into) -> Self { self.name = name.into(); @@ -309,9 +309,11 @@ impl<'lua, 'a> Chunk<'lua, 'a> { /// necessary to populate the environment in order for scripts using custom environments to be /// useful. pub fn set_environment(mut self, env: V) -> Self { + let lua = self.lua.lock(); + let lua = lua.lua(); self.env = env - .into_lua(self.lua) - .and_then(|val| self.lua.unpack(val)) + .into_lua(lua) + .and_then(|val| lua.unpack(val)) .context("bad environment value"); self } @@ -363,7 +365,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> { /// If the chunk can be parsed as an expression, this loads and executes the chunk and returns /// the value that it evaluates to. Otherwise, the chunk is interpreted as a block as normal, /// and this is equivalent to calling `exec`. - pub fn eval>(self) -> Result { + pub fn eval(self) -> Result { // Bytecode is always interpreted as a statement. // For source code, first try interpreting the lua as an expression by adding // "return", then as a statement. This is the same thing the @@ -388,7 +390,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> { #[cfg_attr(docsrs, doc(cfg(feature = "async")))] pub async fn eval_async(self) -> Result where - R: FromLuaMulti<'lua> + 'lua, + R: FromLuaMulti, { if self.detect_mode() == ChunkMode::Binary { self.call_async(()).await @@ -402,7 +404,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> { /// Load the chunk function and call it with the given arguments. /// /// This is equivalent to `into_function` and calling the resulting function. - pub fn call>(self, args: A) -> Result { + pub fn call(self, args: A) -> Result { self.into_function()?.call(args) } @@ -418,7 +420,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> { pub async fn call_async(self, args: A) -> Result where A: IntoLuaMulti, - R: FromLuaMulti<'lua> + 'lua, + R: FromLuaMulti, { self.into_function()?.call_async(args).await } @@ -427,7 +429,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> { /// /// This simply compiles the chunk without actually executing it. #[cfg_attr(not(feature = "luau"), allow(unused_mut))] - pub fn into_function(mut self) -> Result> { + pub fn into_function(mut self) -> Result { #[cfg(feature = "luau")] if self.compiler.is_some() { // We don't need to compile source if no compiler set @@ -436,6 +438,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> { let name = Self::convert_name(self.name)?; self.lua + .lock() .load_chunk(Some(&name), self.env?, self.mode, self.source?.as_ref()) } @@ -455,7 +458,11 @@ impl<'lua, 'a> Chunk<'lua, 'a> { self.mode = Some(ChunkMode::Binary); } #[cfg(not(feature = "luau"))] - if let Ok(func) = self.lua.load_chunk(None, None, None, source.as_ref()) { + if let Ok(func) = self + .lua + .lock() + .load_chunk(None, None, None, source.as_ref()) + { let data = func.dump(false); self.source = Ok(Cow::Owned(data)); self.mode = Some(ChunkMode::Binary); @@ -474,7 +481,8 @@ impl<'lua, 'a> Chunk<'lua, 'a> { let mut text_source = None; if let Ok(ref source) = self.source { if self.detect_mode() == ChunkMode::Text { - if let Some(cache) = self.lua.app_data_ref::() { + let lua = self.lua.lock(); + if let Some(cache) = lua.app_data_ref::() { if let Some(data) = cache.0.get(source.as_ref()) { self.source = Ok(Cow::Owned(data.clone())); self.mode = Some(ChunkMode::Binary); @@ -490,13 +498,14 @@ impl<'lua, 'a> Chunk<'lua, 'a> { self.compile(); if let Ok(ref binary_source) = self.source { if self.detect_mode() == ChunkMode::Binary { - if let Some(mut cache) = self.lua.app_data_mut::() { + let lua = self.lua.lock(); + if let Some(mut cache) = lua.app_data_mut::() { cache.0.insert(text_source, binary_source.as_ref().to_vec()); } else { let mut cache = ChunksCache(HashMap::new()); cache.0.insert(text_source, binary_source.as_ref().to_vec()); - let _ = self.lua.try_set_app_data(cache); - } + let _ = lua.try_set_app_data(cache); + }; } } } @@ -504,7 +513,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> { self } - fn to_expression(&self) -> Result> { + fn to_expression(&self) -> Result { // We assume that mode is Text let source = self.source.as_ref(); let source = source.map_err(Error::runtime)?; @@ -519,6 +528,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> { let name = Self::convert_name(self.name.clone())?; self.lua + .lock() .load_chunk(Some(&name), self.env.clone()?, None, &source) } diff --git a/src/conversion.rs b/src/conversion.rs index f5210977..1194c635 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -12,69 +12,63 @@ use num_traits::cast; use crate::error::{Error, Result}; use crate::function::Function; -use crate::lua::Lua; +use crate::lua::{Lua, LuaInner}; use crate::string::String; use crate::table::Table; use crate::thread::Thread; use crate::types::{LightUserData, MaybeSend, RegistryKey}; -use crate::userdata::{AnyUserData, UserData, UserDataRef, UserDataRefMut}; +use crate::userdata::{AnyUserData, UserData}; use crate::value::{FromLua, IntoLua, Nil, Value}; -#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] -use crate::{ - function::OwnedFunction, string::OwnedString, table::OwnedTable, thread::OwnedThread, - userdata::OwnedAnyUserData, -}; - -impl IntoLua for Value<'_> { +impl IntoLua for Value { #[inline] - fn into_lua(self, _: &Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result { unsafe { Ok(transmute(self)) } } } -impl IntoLua for &Value<'_> { +impl IntoLua for &Value { #[inline] - fn into_lua(self, _: &Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result { unsafe { Ok(transmute(self.clone())) } } #[inline] - unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { lua.push_value(self) } } -impl<'lua> FromLua<'lua> for Value<'lua> { +impl FromLua for Value { #[inline] - fn from_lua(lua_value: Value<'lua>, _: &'lua Lua) -> Result { + fn from_lua(lua_value: Value, _: &Lua) -> Result { Ok(lua_value) } } -impl IntoLua for String<'_> { +impl IntoLua for String { #[inline] - fn into_lua(self, _: &Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result { unsafe { Ok(Value::String(transmute(self))) } } } -impl IntoLua for &String<'_> { +impl IntoLua for &String { #[inline] - fn into_lua(self, _: &Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result { unsafe { Ok(Value::String(transmute(self.clone()))) } } #[inline] - unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { lua.push_ref(&self.0); Ok(()) } } -impl<'lua> FromLua<'lua> for String<'lua> { +impl FromLua for String { #[inline] - fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result> { + fn from_lua(value: Value, lua: &Lua) -> Result { let ty = value.type_name(); lua.coerce_string(value)? .ok_or_else(|| Error::FromLuaConversionError { @@ -85,62 +79,29 @@ impl<'lua> FromLua<'lua> for String<'lua> { } } -#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] -#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl IntoLua for OwnedString { - #[inline] - fn into_lua(self, lua: &'_ Lua) -> Result> { - Ok(Value::String(String(lua.adopt_owned_ref(self.0)))) - } -} - -#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] -#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl IntoLua for &OwnedString { - #[inline] - fn into_lua(self, lua: &Lua) -> Result> { - OwnedString::into_lua(self.clone(), lua) - } - +impl IntoLua for Table { #[inline] - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { - lua.push_owned_ref(&self.0); - Ok(()) - } -} - -#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] -#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl<'lua> FromLua<'lua> for OwnedString { - #[inline] - fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { - String::from_lua(value, lua).map(|s| s.into_owned()) - } -} - -impl IntoLua for Table<'_> { - #[inline] - fn into_lua(self, _: &Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result { unsafe { Ok(Value::Table(transmute(self))) } } } -impl IntoLua for &Table<'_> { +impl IntoLua for &Table { #[inline] - fn into_lua(self, _: &'_ Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result { unsafe { Ok(Value::Table(transmute(self.clone()))) } } #[inline] - unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { lua.push_ref(&self.0); Ok(()) } } -impl<'lua> FromLua<'lua> for Table<'lua> { +impl FromLua for Table { #[inline] - fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result> { + fn from_lua(value: Value, _: &Lua) -> Result
{ match value { Value::Table(table) => Ok(table), _ => Err(Error::FromLuaConversionError { @@ -152,62 +113,29 @@ impl<'lua> FromLua<'lua> for Table<'lua> { } } -#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] -#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl IntoLua for OwnedTable { +impl IntoLua for Function { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { - Ok(Value::Table(Table(lua.adopt_owned_ref(self.0)))) - } -} - -#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] -#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl IntoLua for &OwnedTable { - #[inline] - fn into_lua(self, lua: &Lua) -> Result> { - OwnedTable::into_lua(self.clone(), lua) - } - - #[inline] - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { - lua.push_owned_ref(&self.0); - Ok(()) - } -} - -#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] -#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl<'lua> FromLua<'lua> for OwnedTable { - #[inline] - fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { - Table::from_lua(value, lua).map(|s| s.into_owned()) - } -} - -impl IntoLua for Function<'_> { - #[inline] - fn into_lua(self, _: &Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result { unsafe { Ok(Value::Function(transmute(self))) } } } -impl IntoLua for &Function<'_> { +impl IntoLua for &Function { #[inline] - fn into_lua(self, _: &Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result { unsafe { Ok(Value::Function(transmute(self.clone()))) } } #[inline] - unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { lua.push_ref(&self.0); Ok(()) } } -impl<'lua> FromLua<'lua> for Function<'lua> { +impl FromLua for Function { #[inline] - fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result> { + fn from_lua(value: Value, _: &Lua) -> Result { match value { Value::Function(table) => Ok(table), _ => Err(Error::FromLuaConversionError { @@ -219,62 +147,29 @@ impl<'lua> FromLua<'lua> for Function<'lua> { } } -#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] -#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl IntoLua for OwnedFunction { - #[inline] - fn into_lua(self, lua: &Lua) -> Result> { - Ok(Value::Function(Function(lua.adopt_owned_ref(self.0)))) - } -} - -#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] -#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl IntoLua for &OwnedFunction { - #[inline] - fn into_lua(self, lua: &Lua) -> Result> { - OwnedFunction::into_lua(self.clone(), lua) - } - - #[inline] - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { - lua.push_owned_ref(&self.0); - Ok(()) - } -} - -#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] -#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl<'lua> FromLua<'lua> for OwnedFunction { +impl IntoLua for Thread { #[inline] - fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { - Function::from_lua(value, lua).map(|s| s.into_owned()) - } -} - -impl IntoLua for Thread<'_> { - #[inline] - fn into_lua(self, _: &Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result { unsafe { Ok(Value::Thread(transmute(self))) } } } -impl IntoLua for &Thread<'_> { +impl IntoLua for &Thread { #[inline] - fn into_lua(self, _: &Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result { unsafe { Ok(Value::Thread(transmute(self.clone()))) } } #[inline] - unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { lua.push_ref(&self.0); Ok(()) } } -impl<'lua> FromLua<'lua> for Thread<'lua> { +impl FromLua for Thread { #[inline] - fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result> { + fn from_lua(value: Value, _: &Lua) -> Result { match value { Value::Thread(t) => Ok(t), _ => Err(Error::FromLuaConversionError { @@ -286,62 +181,29 @@ impl<'lua> FromLua<'lua> for Thread<'lua> { } } -#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] -#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl IntoLua for OwnedThread { - #[inline] - fn into_lua(self, lua: &Lua) -> Result> { - Ok(Value::Thread(Thread(lua.adopt_owned_ref(self.0), self.1))) - } -} - -#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] -#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl IntoLua for &OwnedThread { - #[inline] - fn into_lua(self, lua: &Lua) -> Result> { - OwnedThread::into_lua(self.clone(), lua) - } - +impl IntoLua for AnyUserData { #[inline] - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { - lua.push_owned_ref(&self.0); - Ok(()) - } -} - -#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] -#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl<'lua> FromLua<'lua> for OwnedThread { - #[inline] - fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { - Thread::from_lua(value, lua).map(|s| s.into_owned()) - } -} - -impl IntoLua for AnyUserData<'_> { - #[inline] - fn into_lua(self, _: &Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result { unsafe { Ok(Value::UserData(transmute(self))) } } } -impl IntoLua for &AnyUserData<'_> { +impl IntoLua for &AnyUserData { #[inline] - fn into_lua(self, _: &Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result { unsafe { Ok(Value::UserData(transmute(self.clone()))) } } #[inline] - unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { lua.push_ref(&self.0); Ok(()) } } -impl<'lua> FromLua<'lua> for AnyUserData<'lua> { +impl FromLua for AnyUserData { #[inline] - fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result> { + fn from_lua(value: Value, _: &Lua) -> Result { match value { Value::UserData(ud) => Ok(ud), _ => Err(Error::FromLuaConversionError { @@ -353,73 +215,23 @@ impl<'lua> FromLua<'lua> for AnyUserData<'lua> { } } -#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] -#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl IntoLua for OwnedAnyUserData { - #[inline] - fn into_lua(self, lua: &Lua) -> Result> { - Ok(Value::UserData(AnyUserData( - lua.adopt_owned_ref(self.0), - self.1, - ))) - } -} - -#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] -#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl IntoLua for &OwnedAnyUserData { - #[inline] - fn into_lua(self, lua: &Lua) -> Result> { - OwnedAnyUserData::into_lua(self.clone(), lua) - } - - #[inline] - unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> { - lua.push_owned_ref(&self.0); - Ok(()) - } -} - -#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] -#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] -impl<'lua> FromLua<'lua> for OwnedAnyUserData { - #[inline] - fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { - AnyUserData::from_lua(value, lua).map(|s| s.into_owned()) - } -} - impl IntoLua for T { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { Ok(Value::UserData(lua.create_userdata(self)?)) } } -impl<'lua, T: 'static> FromLua<'lua> for UserDataRef<'lua, T> { - #[inline] - fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result { - Self::from_value(value) - } -} - -impl<'lua, T: 'static> FromLua<'lua> for UserDataRefMut<'lua, T> { - #[inline] - fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result { - Self::from_value(value) - } -} - impl IntoLua for Error { #[inline] - fn into_lua(self, _: &Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result { Ok(Value::Error(Box::new(self))) } } -impl<'lua> FromLua<'lua> for Error { +impl FromLua for Error { #[inline] - fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { + fn from_lua(value: Value, lua: &Lua) -> Result { match value { Value::Error(err) => Ok(*err), val => Ok(Error::runtime( @@ -433,23 +245,23 @@ impl<'lua> FromLua<'lua> for Error { impl IntoLua for RegistryKey { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { lua.registry_value(&self) } #[inline] - unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { <&RegistryKey>::push_into_stack(&self, lua) } } impl IntoLua for &RegistryKey { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { lua.registry_value(self) } - unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { if !lua.owns_registry_value(self) { return Err(Error::MismatchedRegistryKey); } @@ -464,29 +276,29 @@ impl IntoLua for &RegistryKey { } } -impl<'lua> FromLua<'lua> for RegistryKey { +impl FromLua for RegistryKey { #[inline] - fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { + fn from_lua(value: Value, lua: &Lua) -> Result { lua.create_registry_value(value) } } impl IntoLua for bool { #[inline] - fn into_lua(self, _: &Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result { Ok(Value::Boolean(self)) } #[inline] - unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { ffi::lua_pushboolean(lua.state(), self as c_int); Ok(()) } } -impl<'lua> FromLua<'lua> for bool { +impl FromLua for bool { #[inline] - fn from_lua(v: Value<'lua>, _: &'lua Lua) -> Result { + fn from_lua(v: Value, _: &Lua) -> Result { match v { Value::Nil => Ok(false), Value::Boolean(b) => Ok(b), @@ -495,21 +307,21 @@ impl<'lua> FromLua<'lua> for bool { } #[inline] - unsafe fn from_stack(idx: c_int, lua: &'lua Lua) -> Result { + unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { Ok(ffi::lua_toboolean(lua.state(), idx) != 0) } } impl IntoLua for LightUserData { #[inline] - fn into_lua(self, _: &Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result { Ok(Value::LightUserData(self)) } } -impl<'lua> FromLua<'lua> for LightUserData { +impl FromLua for LightUserData { #[inline] - fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result { + fn from_lua(value: Value, _: &Lua) -> Result { match value { Value::LightUserData(ud) => Ok(ud), _ => Err(Error::FromLuaConversionError { @@ -524,15 +336,15 @@ impl<'lua> FromLua<'lua> for LightUserData { #[cfg(feature = "luau")] impl IntoLua for crate::types::Vector { #[inline] - fn into_lua(self, _: &Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result { Ok(Value::Vector(self)) } } #[cfg(feature = "luau")] -impl<'lua> FromLua<'lua> for crate::types::Vector { +impl FromLua for crate::types::Vector { #[inline] - fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result { + fn from_lua(value: Value, _: &Lua) -> Result { match value { Value::Vector(v) => Ok(v), _ => Err(Error::FromLuaConversionError { @@ -546,19 +358,19 @@ impl<'lua> FromLua<'lua> for crate::types::Vector { impl IntoLua for StdString { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { Ok(Value::String(lua.create_string(&self)?)) } #[inline] - unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { push_bytes_into_stack(self, lua) } } -impl<'lua> FromLua<'lua> for StdString { +impl FromLua for StdString { #[inline] - fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { + fn from_lua(value: Value, lua: &Lua) -> Result { let ty = value.type_name(); Ok(lua .coerce_string(value)? @@ -572,7 +384,7 @@ impl<'lua> FromLua<'lua> for StdString { } #[inline] - unsafe fn from_stack(idx: c_int, lua: &'lua Lua) -> Result { + unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { let state = lua.state(); if ffi::lua_type(state, idx) == ffi::LUA_TSTRING { let mut size = 0; @@ -587,39 +399,39 @@ impl<'lua> FromLua<'lua> for StdString { }); } // Fallback to default - Self::from_lua(lua.stack_value(idx), lua) + Self::from_lua(lua.stack_value(idx), lua.lua()) } } impl IntoLua for &str { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { Ok(Value::String(lua.create_string(self)?)) } #[inline] - unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { push_bytes_into_stack(self, lua) } } impl IntoLua for Cow<'_, str> { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { Ok(Value::String(lua.create_string(self.as_bytes())?)) } } impl IntoLua for Box { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { Ok(Value::String(lua.create_string(&*self)?)) } } -impl<'lua> FromLua<'lua> for Box { +impl FromLua for Box { #[inline] - fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { + fn from_lua(value: Value, lua: &Lua) -> Result { let ty = value.type_name(); Ok(lua .coerce_string(value)? @@ -636,14 +448,14 @@ impl<'lua> FromLua<'lua> for Box { impl IntoLua for CString { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { Ok(Value::String(lua.create_string(self.as_bytes())?)) } } -impl<'lua> FromLua<'lua> for CString { +impl FromLua for CString { #[inline] - fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { + fn from_lua(value: Value, lua: &Lua) -> Result { let ty = value.type_name(); let string = lua .coerce_string(value)? @@ -666,34 +478,35 @@ impl<'lua> FromLua<'lua> for CString { impl IntoLua for &CStr { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { Ok(Value::String(lua.create_string(self.to_bytes())?)) } } impl IntoLua for Cow<'_, CStr> { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { Ok(Value::String(lua.create_string(self.to_bytes())?)) } } impl IntoLua for BString { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { Ok(Value::String(lua.create_string(&self)?)) } } -impl<'lua> FromLua<'lua> for BString { - fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { +impl FromLua for BString { + fn from_lua(value: Value, lua: &Lua) -> Result { let ty = value.type_name(); match value { Value::String(s) => Ok(s.as_bytes().into()), #[cfg(feature = "luau")] Value::UserData(ud) if ud.1 == crate::types::SubtypeId::Buffer => unsafe { + let lua = ud.0.lua.lock(); let mut size = 0usize; - let buf = ffi::lua_tobuffer(ud.0.lua.ref_thread(), ud.0.index, &mut size); + let buf = ffi::lua_tobuffer(lua.ref_thread(), ud.0.index, &mut size); mlua_assert!(!buf.is_null(), "invalid Luau buffer"); Ok(slice::from_raw_parts(buf as *const u8, size).into()) }, @@ -709,7 +522,7 @@ impl<'lua> FromLua<'lua> for BString { } } - unsafe fn from_stack(idx: c_int, lua: &'lua Lua) -> Result { + unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { let state = lua.state(); match ffi::lua_type(state, idx) { ffi::LUA_TSTRING => { @@ -726,7 +539,7 @@ impl<'lua> FromLua<'lua> for BString { } _ => { // Fallback to default - Self::from_lua(lua.stack_value(idx), lua) + Self::from_lua(lua.stack_value(idx), lua.lua()) } } } @@ -734,13 +547,13 @@ impl<'lua> FromLua<'lua> for BString { impl IntoLua for &BStr { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { Ok(Value::String(lua.create_string(self)?)) } } #[inline] -unsafe fn push_bytes_into_stack<'lua, T>(this: T, lua: &'lua Lua) -> Result<()> +unsafe fn push_bytes_into_stack(this: T, lua: &LuaInner) -> Result<()> where T: IntoLua + AsRef<[u8]>, { @@ -751,14 +564,14 @@ where return Ok(()); } // Fallback to default - lua.push_value(&T::into_lua(this, lua)?) + lua.push_value(&T::into_lua(this, lua.lua())?) } macro_rules! lua_convert_int { ($x:ty) => { impl IntoLua for $x { #[inline] - fn into_lua(self, _: &Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result { cast(self) .map(Value::Integer) .or_else(|| cast(self).map(Value::Number)) @@ -771,7 +584,7 @@ macro_rules! lua_convert_int { } #[inline] - unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { match cast(self) { Some(i) => ffi::lua_pushinteger(lua.state(), i), None => ffi::lua_pushnumber(lua.state(), self as ffi::lua_Number), @@ -780,9 +593,9 @@ macro_rules! lua_convert_int { } } - impl<'lua> FromLua<'lua> for $x { + impl FromLua for $x { #[inline] - fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { + fn from_lua(value: Value, lua: &Lua) -> Result { let ty = value.type_name(); (match value { Value::Integer(i) => cast(i), @@ -830,7 +643,7 @@ macro_rules! lua_convert_float { ($x:ty) => { impl IntoLua for $x { #[inline] - fn into_lua(self, _: &Lua) -> Result> { + fn into_lua(self, _: &Lua) -> Result { cast(self) .ok_or_else(|| Error::ToLuaConversionError { from: stringify!($x), @@ -841,9 +654,9 @@ macro_rules! lua_convert_float { } } - impl<'lua> FromLua<'lua> for $x { + impl FromLua for $x { #[inline] - fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { + fn from_lua(value: Value, lua: &Lua) -> Result { let ty = value.type_name(); lua.coerce_number(value)? .ok_or_else(|| Error::FromLuaConversionError { @@ -871,7 +684,7 @@ where T: IntoLua + Clone, { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { Ok(Value::Table( lua.create_sequence_from(self.iter().cloned())?, )) @@ -883,17 +696,17 @@ where T: IntoLua, { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { Ok(Value::Table(lua.create_sequence_from(self)?)) } } -impl<'lua, T, const N: usize> FromLua<'lua> for [T; N] +impl FromLua for [T; N] where - T: FromLua<'lua>, + T: FromLua, { #[inline] - fn from_lua(value: Value<'lua>, _lua: &'lua Lua) -> Result { + fn from_lua(value: Value, _lua: &Lua) -> Result { match value { #[cfg(feature = "luau")] #[rustfmt::skip] @@ -927,28 +740,28 @@ where impl IntoLua for Box<[T]> { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { Ok(Value::Table(lua.create_sequence_from(self.into_vec())?)) } } -impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Box<[T]> { +impl FromLua for Box<[T]> { #[inline] - fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { + fn from_lua(value: Value, lua: &Lua) -> Result { Ok(Vec::::from_lua(value, lua)?.into_boxed_slice()) } } impl IntoLua for Vec { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { Ok(Value::Table(lua.create_sequence_from(self)?)) } } -impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Vec { +impl FromLua for Vec { #[inline] - fn from_lua(value: Value<'lua>, _lua: &'lua Lua) -> Result { + fn from_lua(value: Value, _lua: &Lua) -> Result { match value { Value::Table(table) => table.sequence_values().collect(), _ => Err(Error::FromLuaConversionError { @@ -962,16 +775,14 @@ impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Vec { impl IntoLua for HashMap { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { Ok(Value::Table(lua.create_table_from(self)?)) } } -impl<'lua, K: Eq + Hash + FromLua<'lua>, V: FromLua<'lua>, S: BuildHasher + Default> FromLua<'lua> - for HashMap -{ +impl FromLua for HashMap { #[inline] - fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result { + fn from_lua(value: Value, _: &Lua) -> Result { if let Value::Table(table) = value { table.pairs().collect() } else { @@ -986,14 +797,14 @@ impl<'lua, K: Eq + Hash + FromLua<'lua>, V: FromLua<'lua>, S: BuildHasher + Defa impl IntoLua for BTreeMap { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { Ok(Value::Table(lua.create_table_from(self)?)) } } -impl<'lua, K: Ord + FromLua<'lua>, V: FromLua<'lua>> FromLua<'lua> for BTreeMap { +impl FromLua for BTreeMap { #[inline] - fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result { + fn from_lua(value: Value, _: &Lua) -> Result { if let Value::Table(table) = value { table.pairs().collect() } else { @@ -1008,20 +819,20 @@ impl<'lua, K: Ord + FromLua<'lua>, V: FromLua<'lua>> FromLua<'lua> for BTreeMap< impl IntoLua for HashSet { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { Ok(Value::Table(lua.create_table_from( self.into_iter().map(|val| (val, true)), )?)) } } -impl<'lua, T: Eq + Hash + FromLua<'lua>, S: BuildHasher + Default> FromLua<'lua> for HashSet { +impl FromLua for HashSet { #[inline] - fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result { + fn from_lua(value: Value, _: &Lua) -> Result { match value { Value::Table(table) if table.raw_len() > 0 => table.sequence_values().collect(), Value::Table(table) => table - .pairs::>() + .pairs::() .map(|res| res.map(|(k, _)| k)) .collect(), _ => Err(Error::FromLuaConversionError { @@ -1035,20 +846,20 @@ impl<'lua, T: Eq + Hash + FromLua<'lua>, S: BuildHasher + Default> FromLua<'lua> impl IntoLua for BTreeSet { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { Ok(Value::Table(lua.create_table_from( self.into_iter().map(|val| (val, true)), )?)) } } -impl<'lua, T: Ord + FromLua<'lua>> FromLua<'lua> for BTreeSet { +impl FromLua for BTreeSet { #[inline] - fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result { + fn from_lua(value: Value, _: &Lua) -> Result { match value { Value::Table(table) if table.raw_len() > 0 => table.sequence_values().collect(), Value::Table(table) => table - .pairs::>() + .pairs::() .map(|res| res.map(|(k, _)| k)) .collect(), _ => Err(Error::FromLuaConversionError { @@ -1062,7 +873,7 @@ impl<'lua, T: Ord + FromLua<'lua>> FromLua<'lua> for BTreeSet { impl IntoLua for Option { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { match self { Some(val) => val.into_lua(lua), None => Ok(Nil), @@ -1070,7 +881,7 @@ impl IntoLua for Option { } #[inline] - unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { + unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { match self { Some(val) => val.push_into_stack(lua)?, None => ffi::lua_pushnil(lua.state()), @@ -1079,9 +890,9 @@ impl IntoLua for Option { } } -impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Option { +impl FromLua for Option { #[inline] - fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result { + fn from_lua(value: Value, lua: &Lua) -> Result { match value { Nil => Ok(None), value => Ok(Some(T::from_lua(value, lua)?)), @@ -1089,7 +900,7 @@ impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Option { } #[inline] - unsafe fn from_stack(idx: c_int, lua: &'lua Lua) -> Result { + unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { if ffi::lua_isnil(lua.state(), idx) != 0 { Ok(None) } else { diff --git a/src/function.rs b/src/function.rs index 81a7cdac..a27069ed 100644 --- a/src/function.rs +++ b/src/function.rs @@ -22,28 +22,7 @@ use { /// Handle to an internal Lua function. #[derive(Clone, Debug)] -pub struct Function<'lua>(pub(crate) ValueRef<'lua>); - -/// Owned handle to an internal Lua function. -/// -/// The owned handle holds a *strong* reference to the current Lua instance. -/// Be warned, if you place it into a Lua type (eg. [`UserData`] or a Rust callback), it is *very easy* -/// to accidentally cause reference cycles that would prevent destroying Lua instance. -/// -/// [`UserData`]: crate::UserData -#[cfg(feature = "unstable")] -#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] -#[derive(Clone, Debug)] -pub struct OwnedFunction(pub(crate) crate::types::OwnedValueRef); - -#[cfg(feature = "unstable")] -impl OwnedFunction { - /// Get borrowed handle to the underlying Lua function. - #[cfg_attr(feature = "send", allow(unused))] - pub const fn to_ref(&self) -> Function { - Function(self.0.to_ref()) - } -} +pub struct Function(pub(crate) ValueRef); /// Contains information about a function. /// @@ -81,7 +60,7 @@ pub struct CoverageInfo { pub hits: Vec, } -impl<'lua> Function<'lua> { +impl Function { /// Calls the function, passing `args` as function arguments. /// /// The function's return values are converted to the generic type `R`. @@ -122,8 +101,8 @@ impl<'lua> Function<'lua> { /// # Ok(()) /// # } /// ``` - pub fn call>(&self, args: A) -> Result { - let lua = self.0.lua; + pub fn call(&self, args: A) -> Result { + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -134,7 +113,7 @@ impl<'lua> Function<'lua> { let stack_start = ffi::lua_gettop(state); // Push function and the arguments lua.push_ref(&self.0); - let nargs = args.push_into_stack_multi(lua)?; + let nargs = args.push_into_stack_multi(&lua)?; // Call the function let ret = ffi::lua_pcall(state, nargs, ffi::LUA_MULTRET, stack_start); if ret != ffi::LUA_OK { @@ -142,7 +121,7 @@ impl<'lua> Function<'lua> { } // Get the results let nresults = ffi::lua_gettop(state) - stack_start; - R::from_stack_multi(nresults, lua) + R::from_stack_multi(nresults, &lua) } } @@ -176,12 +155,12 @@ impl<'lua> Function<'lua> { /// [`AsyncThread`]: crate::AsyncThread #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub fn call_async(&self, args: A) -> impl Future> + 'lua + pub fn call_async(&self, args: A) -> impl Future> where A: IntoLuaMulti, - R: FromLuaMulti<'lua> + 'lua, + R: FromLuaMulti, { - let lua = self.0.lua; + let lua = self.0.lua.lock(); let thread_res = lua.create_recycled_thread(self).map(|th| { let mut th = th.into_async(args); th.set_recyclable(true); @@ -217,7 +196,7 @@ impl<'lua> Function<'lua> { /// # Ok(()) /// # } /// ``` - pub fn bind(&self, args: A) -> Result> { + pub fn bind(&self, args: A) -> Result { unsafe extern "C-unwind" fn args_wrapper_impl(state: *mut ffi::lua_State) -> c_int { let nargs = ffi::lua_gettop(state); let nbinds = ffi::lua_tointeger(state, ffi::lua_upvalueindex(1)) as c_int; @@ -233,10 +212,10 @@ impl<'lua> Function<'lua> { nargs + nbinds } - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); - let args = args.into_lua_multi(lua)?; + let args = args.into_lua_multi(lua.lua())?; let nargs = args.len() as c_int; if nargs == 0 { @@ -262,6 +241,7 @@ impl<'lua> Function<'lua> { Function(lua.pop_ref()) }; + let lua = lua.lua(); lua.load( r#" local func, args_wrapper = ... @@ -281,7 +261,7 @@ impl<'lua> Function<'lua> { /// /// This function always returns `None` for Rust/C functions. pub fn environment(&self) -> Option
{ - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -318,7 +298,7 @@ impl<'lua> Function<'lua> { /// /// This function does nothing for Rust/C functions. pub fn set_environment(&self, env: Table) -> Result { - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -342,6 +322,7 @@ impl<'lua> Function<'lua> { ffi::lua_pop(state, 1); // Create an anonymous function with the new environment let f_with_env = lua + .lua() .load("return _ENV") .set_environment(env) .try_cache() @@ -364,7 +345,7 @@ impl<'lua> Function<'lua> { /// /// [`lua_getinfo`]: https://www.lua.org/manual/5.4/manual.html#lua_getinfo pub fn info(&self) -> FunctionInfo { - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -425,7 +406,7 @@ impl<'lua> Function<'lua> { 0 } - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); let mut data: Vec = Vec::new(); unsafe { @@ -482,7 +463,7 @@ impl<'lua> Function<'lua> { }); } - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -514,80 +495,42 @@ impl<'lua> Function<'lua> { #[cfg(feature = "luau")] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn deep_clone(&self) -> Self { - let ref_thread = self.0.lua.ref_thread(); + let lua = self.0.lua.lock(); + let ref_thread = lua.ref_thread(); unsafe { if ffi::lua_iscfunction(ref_thread, self.0.index) != 0 { return self.clone(); } ffi::lua_clonefunction(ref_thread, self.0.index); - Function(self.0.lua.pop_ref_thread()) + Function(lua.pop_ref_thread()) } } - - /// Convert this handle to owned version. - #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] - #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] - #[inline] - pub fn into_owned(self) -> OwnedFunction { - OwnedFunction(self.0.into_owned()) - } } -impl<'lua> PartialEq for Function<'lua> { +impl PartialEq for Function { fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } -// Additional shortcuts -#[cfg(feature = "unstable")] -impl OwnedFunction { - /// Calls the function, passing `args` as function arguments. - /// - /// This is a shortcut for [`Function::call()`]. - #[inline] - pub fn call<'lua, A, R>(&'lua self, args: A) -> Result - where - A: IntoLuaMulti, - R: FromLuaMulti<'lua>, - { - self.to_ref().call(args) - } - - /// Returns a future that, when polled, calls `self`, passing `args` as function arguments, - /// and drives the execution. - /// - /// This is a shortcut for [`Function::call_async()`]. - #[cfg(feature = "async")] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - #[inline] - pub async fn call_async<'lua, A, R>(&'lua self, args: A) -> Result - where - A: IntoLuaMulti, - R: FromLuaMulti<'lua> + 'lua, - { - self.to_ref().call_async(args).await - } -} - -pub(crate) struct WrappedFunction<'lua>(pub(crate) Callback<'lua, 'static>); +pub(crate) struct WrappedFunction(pub(crate) Callback<'static>); #[cfg(feature = "async")] -pub(crate) struct WrappedAsyncFunction<'lua>(pub(crate) AsyncCallback<'lua, 'static>); +pub(crate) struct WrappedAsyncFunction(pub(crate) AsyncCallback<'static>); -impl<'lua> Function<'lua> { +impl Function { /// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`] trait. #[inline] pub fn wrap(func: F) -> impl IntoLua where - A: FromLuaMulti<'lua>, + A: FromLuaMulti, R: IntoLuaMulti, - F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, + F: Fn(&Lua, A) -> Result + MaybeSend + 'static, { WrappedFunction(Box::new(move |lua, nargs| unsafe { let args = A::from_stack_args(nargs, 1, None, lua)?; - func(lua, args)?.push_into_stack_multi(lua) + func(lua.lua(), args)?.push_into_stack_multi(lua) })) } @@ -595,9 +538,9 @@ impl<'lua> Function<'lua> { #[inline] pub fn wrap_mut(func: F) -> impl IntoLua where - A: FromLuaMulti<'lua>, + A: FromLuaMulti, R: IntoLuaMulti, - F: FnMut(&'lua Lua, A) -> Result + MaybeSend + 'static, + F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, { let func = RefCell::new(func); WrappedFunction(Box::new(move |lua, nargs| unsafe { @@ -605,7 +548,7 @@ impl<'lua> Function<'lua> { .try_borrow_mut() .map_err(|_| Error::RecursiveMutCallback)?; let args = A::from_stack_args(nargs, 1, None, lua)?; - func(lua, args)?.push_into_stack_multi(lua) + func(lua.lua(), args)?.push_into_stack_multi(lua) })) } @@ -614,45 +557,44 @@ impl<'lua> Function<'lua> { #[cfg_attr(docsrs, doc(cfg(feature = "async")))] pub fn wrap_async(func: F) -> impl IntoLua where - A: FromLuaMulti<'lua>, + A: FromLuaMulti, R: IntoLuaMulti, - F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static, + F: Fn(&Lua, A) -> FR + MaybeSend + 'static, FR: Future> + 'static, { - WrappedAsyncFunction(Box::new(move |lua, args| unsafe { + WrappedAsyncFunction(Box::new(move |rawlua, args| unsafe { + let lua = rawlua.lua(); let args = match A::from_lua_args(args, 1, None, lua) { Ok(args) => args, Err(e) => return Box::pin(future::err(e)), }; let fut = func(lua, args); - Box::pin(async move { fut.await?.push_into_stack_multi(lua) }) + let weak = rawlua.weak().clone(); + Box::pin(async move { fut.await?.push_into_stack_multi(&weak.lock()) }) })) } } -impl IntoLua for WrappedFunction<'_> { +impl IntoLua for WrappedFunction { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { - lua.create_callback(unsafe { mem::transmute(self.0) }) - .map(Value::Function) + fn into_lua(self, lua: &Lua) -> Result { + lua.lock().create_callback(self.0).map(Value::Function) } } #[cfg(feature = "async")] -impl IntoLua for WrappedAsyncFunction<'_> { +impl IntoLua for WrappedAsyncFunction { #[inline] - fn into_lua(self, lua: &Lua) -> Result> { - lua.create_async_callback(unsafe { mem::transmute(self.0) }) + fn into_lua(self, lua: &Lua) -> Result { + lua.lock() + .create_async_callback(self.0) .map(Value::Function) } } -#[cfg(test)] -mod assertions { - use super::*; +// #[cfg(test)] +// mod assertions { +// use super::*; - static_assertions::assert_not_impl_any!(Function: Send); - - #[cfg(all(feature = "unstable", not(feature = "send")))] - static_assertions::assert_not_impl_any!(OwnedFunction: Send); -} +// static_assertions::assert_not_impl_any!(Function: Send); +// } diff --git a/src/hook.rs b/src/hook.rs index 950bc796..72f8b88b 100644 --- a/src/hook.rs +++ b/src/hook.rs @@ -1,12 +1,14 @@ use std::borrow::Cow; use std::cell::UnsafeCell; +use std::mem::ManuallyDrop; #[cfg(not(feature = "luau"))] use std::ops::{BitOr, BitOrAssign}; use std::os::raw::c_int; use ffi::lua_Debug; +use parking_lot::ReentrantMutexGuard; -use crate::lua::Lua; +use crate::lua::{Lua, LuaInner}; use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str}; /// Contains information about currently executing Lua code. @@ -19,24 +21,37 @@ use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str}; /// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#lua_Debug /// [`Lua::set_hook`]: crate::Lua::set_hook pub struct Debug<'lua> { - lua: &'lua Lua, + lua: ManuallyDrop>, ar: ActivationRecord, #[cfg(feature = "luau")] level: c_int, } +impl<'lua> Drop for Debug<'lua> { + fn drop(&mut self) { + if let ActivationRecord::Owned(_) = self.ar { + unsafe { ManuallyDrop::drop(&mut self.lua) } + } + } +} + impl<'lua> Debug<'lua> { + // We assume the lock is held when this function is called. #[cfg(not(feature = "luau"))] pub(crate) fn new(lua: &'lua Lua, ar: *mut lua_Debug) -> Self { Debug { - lua, + lua: unsafe { lua.guard_unchecked() }, ar: ActivationRecord::Borrowed(ar), } } - pub(crate) fn new_owned(lua: &'lua Lua, _level: c_int, ar: lua_Debug) -> Self { + pub(crate) fn new_owned( + guard: ReentrantMutexGuard<'lua, LuaInner>, + _level: c_int, + ar: lua_Debug, + ) -> Self { Debug { - lua, + lua: ManuallyDrop::new(guard), ar: ActivationRecord::Owned(UnsafeCell::new(ar)), #[cfg(feature = "luau")] level: _level, diff --git a/src/lib.rs b/src/lib.rs index 9345f5f9..d1faffdf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,13 +89,14 @@ mod lua; mod luau; mod memory; mod multi; -mod scope; +// mod scope; mod stdlib; mod string; mod table; mod thread; mod types; mod userdata; +mod userdata_cell; mod userdata_ext; mod userdata_impl; mod util; @@ -111,7 +112,7 @@ pub use crate::function::{Function, FunctionInfo}; pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack}; pub use crate::lua::{GCMode, Lua, LuaOptions}; pub use crate::multi::Variadic; -pub use crate::scope::Scope; +// pub use crate::scope::Scope; pub use crate::stdlib::StdLib; pub use crate::string::String; pub use crate::table::{Table, TableExt, TablePairs, TableSequence}; @@ -119,8 +120,8 @@ pub use crate::thread::{Thread, ThreadStatus}; pub use crate::types::{AppDataRef, AppDataRefMut, Integer, LightUserData, Number, RegistryKey}; pub use crate::userdata::{ AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, - UserDataRef, UserDataRefMut, }; +pub use crate::userdata_cell::{UserDataRef, UserDataRefMut}; pub use crate::userdata_ext::AnyUserDataExt; pub use crate::userdata_impl::UserDataRegistry; pub use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value}; @@ -154,13 +155,6 @@ pub mod serde; #[macro_use] extern crate mlua_derive; -// Unstable features -#[cfg(feature = "unstable")] -pub use crate::{ - function::OwnedFunction, string::OwnedString, table::OwnedTable, thread::OwnedThread, - userdata::OwnedAnyUserData, -}; - /// Create a type that implements [`AsChunk`] and can capture Rust variables. /// /// This macro allows to write Lua code directly in Rust code. @@ -279,6 +273,6 @@ pub(crate) mod private { impl Sealed for Error {} impl Sealed for std::result::Result {} impl Sealed for Lua {} - impl Sealed for Table<'_> {} - impl Sealed for AnyUserData<'_> {} + impl Sealed for Table {} + impl Sealed for AnyUserData {} } diff --git a/src/lua.rs b/src/lua.rs index df34c252..44472d5a 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1,17 +1,18 @@ use std::any::TypeId; use std::cell::{Cell, RefCell, UnsafeCell}; -use std::collections::VecDeque; +// use std::collections::VecDeque; use std::ffi::{CStr, CString}; use std::fmt; use std::marker::PhantomData; -use std::mem::{self, MaybeUninit}; +use std::mem::{self, ManuallyDrop, MaybeUninit}; use std::ops::Deref; use std::os::raw::{c_char, c_int, c_void}; use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe, Location}; use std::ptr; use std::result::Result as StdResult; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Weak}; +use parking_lot::{Mutex, ReentrantMutex, ReentrantMutexGuard}; use rustc_hash::FxHashMap; use crate::chunk::{AsChunk, Chunk, ChunkMode}; @@ -19,16 +20,18 @@ use crate::error::{Error, Result}; use crate::function::Function; use crate::hook::Debug; use crate::memory::{MemoryState, ALLOCATOR}; -use crate::scope::Scope; +// use crate::scope::Scope; use crate::stdlib::StdLib; use crate::string::String; use crate::table::Table; use crate::thread::Thread; use crate::types::{ - AppData, AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, - LightUserData, MaybeSend, Number, RegistryKey, SubtypeId, ValueRef, + AppData, AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Callback, CallbackUpvalue, + DestructedUserdata, Integer, LightUserData, MaybeSend, Number, RegistryKey, SubtypeId, + ValueRef, }; -use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataCell}; +use crate::userdata::{AnyUserData, MetaMethod, UserData}; +use crate::userdata_cell::{UserDataRef, UserDataVariant}; use crate::userdata_impl::{UserDataProxy, UserDataRegistry}; use crate::util::{ self, assert_stack, check_stack, error_traceback, get_destructed_userdata_metatable, @@ -66,8 +69,15 @@ use { use serde::Serialize; /// Top level Lua struct which represents an instance of Lua VM. +#[derive(Clone)] #[repr(transparent)] -pub struct Lua(Arc); +pub struct Lua(Arc>); + +#[derive(Clone)] +#[repr(transparent)] +pub(crate) struct WeakLua(Weak>); + +pub(crate) struct LuaGuard(ArcReentrantMutexGuard); /// An inner Lua struct which holds a raw Lua state. pub struct LuaInner { @@ -80,7 +90,8 @@ pub struct LuaInner { // Data associated with the Lua. pub(crate) struct ExtraData { // Same layout as `Lua` - inner: MaybeUninit>, + inner: MaybeUninit>>, + weak: MaybeUninit>>, registered_userdata: FxHashMap, registered_userdata_mt: FxHashMap<*const c_void, Option>, @@ -106,7 +117,7 @@ pub(crate) struct ExtraData { // Pool of `WrappedFailure` enums in the ref thread (as userdata) wrapped_failure_pool: Vec, // Pool of `MultiValue` containers - multivalue_pool: Vec>>, + // multivalue_pool: Vec>, // Pool of `Thread`s (coroutines) for async execution #[cfg(feature = "async")] thread_pool: Vec, @@ -225,7 +236,7 @@ pub(crate) static ASYNC_POLL_PENDING: u8 = 0; pub(crate) static EXTRA_REGISTRY_KEY: u8 = 0; const WRAPPED_FAILURE_POOL_SIZE: usize = 64; -const MULTIVALUE_POOL_SIZE: usize = 64; +// const MULTIVALUE_POOL_SIZE: usize = 64; const REF_STACK_RESERVE: c_int = 1; /// Requires `feature = "send"` @@ -262,23 +273,14 @@ impl Drop for ExtraData { unsafe { self.inner.assume_init_drop(); } - - *mlua_expect!(self.registry_unref_list.lock(), "unref list poisoned") = None; + unsafe { self.weak.assume_init_drop() }; + *self.registry_unref_list.lock() = None; } } impl fmt::Debug for Lua { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Lua({:p})", self.state()) - } -} - -impl Deref for Lua { - type Target = LuaInner; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 + write!(f, "Lua({:p})", self.lock().state()) } } @@ -344,7 +346,10 @@ impl Lua { if libs.contains(StdLib::PACKAGE) { mlua_expect!(lua.disable_c_modules(), "Error during disabling C modules"); } - unsafe { (*lua.extra.get()).safe = true }; + unsafe { + let rawlua = lua.lock(); + (*rawlua.extra.get()).safe = true; + } Ok(lua) } @@ -400,7 +405,7 @@ impl Lua { } let lua = Lua::init_from_ptr(state); - let extra = lua.extra.get(); + let extra = lua.lock().extra.get(); mlua_expect!( load_from_std_lib(state, libs), @@ -510,6 +515,7 @@ impl Lua { // Create ExtraData let extra = Arc::new(UnsafeCell::new(ExtraData { inner: MaybeUninit::uninit(), + weak: MaybeUninit::uninit(), registered_userdata: FxHashMap::default(), registered_userdata_mt: FxHashMap::default(), last_checked_userdata_mt: (ptr::null(), None), @@ -525,7 +531,7 @@ impl Lua { ref_stack_top: ffi::lua_gettop(ref_thread), ref_free: Vec::new(), wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_SIZE), - multivalue_pool: Vec::with_capacity(MULTIVALUE_POOL_SIZE), + // multivalue_pool: Vec::with_capacity(MULTIVALUE_POOL_SIZE), #[cfg(feature = "async")] thread_pool: Vec::new(), wrapped_failure_mt_ptr, @@ -568,15 +574,16 @@ impl Lua { ); assert_stack(main_state, ffi::LUA_MINSTACK); - let inner = Arc::new(LuaInner { + let inner = Arc::new(ReentrantMutex::new(LuaInner { state: Cell::new(state), main_state, extra: Arc::clone(&extra), - }); + })); (*extra.get()).inner.write(Arc::clone(&inner)); #[cfg(not(feature = "module"))] Arc::decrement_strong_count(Arc::as_ptr(&inner)); + (*extra.get()).weak.write(Arc::downgrade(&inner)); Lua(inner) } @@ -587,7 +594,8 @@ impl Lua { /// /// [`StdLib`]: crate::StdLib pub fn load_from_std_lib(&self, libs: StdLib) -> Result<()> { - let is_safe = unsafe { (*self.extra.get()).safe }; + let lua = self.lock(); + let is_safe = unsafe { (*lua.extra.get()).safe }; #[cfg(not(feature = "luau"))] if is_safe && libs.contains(StdLib::DEBUG) { @@ -602,14 +610,14 @@ impl Lua { )); } - let res = unsafe { load_from_std_lib(self.main_state, libs) }; + let res = unsafe { load_from_std_lib(lua.main_state, libs) }; // If `package` library loaded into a safe lua state then disable C modules - let curr_libs = unsafe { (*self.extra.get()).libs }; + let curr_libs = unsafe { (*lua.extra.get()).libs }; if is_safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) { mlua_expect!(self.disable_c_modules(), "Error during disabling C modules"); } - unsafe { (*self.extra.get()).libs |= libs }; + unsafe { (*lua.extra.get()).libs |= libs }; res } @@ -629,18 +637,19 @@ impl Lua { /// Behavior is similar to Lua's [`require`] function. /// /// [`require`]: https://www.lua.org/manual/5.4/manual.html#pdf-require - pub fn load_from_function<'lua, T>(&'lua self, modname: &str, func: Function<'lua>) -> Result + pub fn load_from_function(&self, modname: &str, func: Function) -> Result where - T: FromLua<'lua>, + T: FromLua, { - let state = self.state(); + let lua = self.lock(); + let state = lua.state(); let loaded = unsafe { let _sg = StackGuard::new(state); check_stack(state, 2)?; protect_lua!(state, 0, 1, fn(state) { ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); })?; - Table(self.pop_ref()) + Table(lua.pop_ref()) }; let modname = self.create_string(modname)?; @@ -666,14 +675,15 @@ impl Lua { /// /// [`package.loaded`]: https://www.lua.org/manual/5.4/manual.html#pdf-package.loaded pub fn unload(&self, modname: &str) -> Result<()> { - let state = self.state(); + let lua = self.lock(); + let state = lua.state(); let loaded = unsafe { let _sg = StackGuard::new(state); check_stack(state, 2)?; protect_lua!(state, 0, 1, fn(state) { ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); })?; - Table(self.pop_ref()) + Table(lua.pop_ref()) }; let modname = self.create_string(modname)?; @@ -709,22 +719,23 @@ impl Lua { // The returned value then pushed onto the stack. #[doc(hidden)] #[cfg(not(tarpaulin_include))] - pub unsafe fn entrypoint<'lua, A, R, F>(self, state: *mut ffi::lua_State, func: F) -> c_int + pub unsafe fn entrypoint(self, state: *mut ffi::lua_State, func: F) -> c_int where - A: FromLuaMulti<'lua>, + A: FromLuaMulti, R: IntoLua, - F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, + F: Fn(&Lua, A) -> Result + MaybeSend + 'static, { - let extra = self.extra.get(); + let extra = self.lock().extra.get(); // `self` is no longer needed and must be dropped at this point to avoid possible memory leak // in case of possible longjmp (lua_error) below drop(self); callback_error_ext(state, extra, move |nargs| { - let lua: &Lua = mem::transmute((*extra).inner.assume_init_ref()); - let _guard = StateGuard::new(&lua.0, state); - let args = A::from_stack_args(nargs, 1, None, lua)?; - func(lua, args)?.push_into_stack(lua)?; + let lua = (*extra).lua(); + let rawlua = lua.lock(); + let _guard = StateGuard::new(&rawlua, state); + let args = A::from_stack_args(nargs, 1, None, &rawlua)?; + func(lua, args)?.push_into_stack(&rawlua)?; Ok(1) }) } @@ -732,10 +743,10 @@ impl Lua { // A simple module entrypoint without arguments #[doc(hidden)] #[cfg(not(tarpaulin_include))] - pub unsafe fn entrypoint1<'lua, R, F>(self, state: *mut ffi::lua_State, func: F) -> c_int + pub unsafe fn entrypoint1(self, state: *mut ffi::lua_State, func: F) -> c_int where R: IntoLua, - F: Fn(&'lua Lua) -> Result + MaybeSend + 'static, + F: Fn(&Lua) -> Result + MaybeSend + 'static, { self.entrypoint(state, move |lua, _: ()| func(lua)) } @@ -778,9 +789,10 @@ impl Lua { #[cfg(any(feature = "luau", docsrs))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn sandbox(&self, enabled: bool) -> Result<()> { + let lua = self.lock(); unsafe { - if (*self.extra.get()).sandboxed != enabled { - let state = self.main_state; + if (*lua.extra.get()).sandboxed != enabled { + let state = lua.main_state; check_stack(state, 3)?; protect_lua!(state, 0, 0, |state| { if enabled { @@ -788,12 +800,12 @@ impl Lua { ffi::luaL_sandboxthread(state); } else { // Restore original `LUA_GLOBALSINDEX` - ffi::lua_xpush(self.ref_thread(), state, ffi::LUA_GLOBALSINDEX); + ffi::lua_xpush(lua.ref_thread(), state, ffi::LUA_GLOBALSINDEX); ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX); ffi::luaL_sandbox(state, 0); } })?; - (*self.extra.get()).sandboxed = enabled; + (*lua.extra.get()).sandboxed = enabled; } Ok(()) } @@ -843,42 +855,8 @@ impl Lua { where F: Fn(&Lua, Debug) -> Result<()> + MaybeSend + 'static, { - unsafe { self.set_thread_hook(self.state(), triggers, callback) }; - } - - /// Sets a 'hook' function for a thread (coroutine). - #[cfg(not(feature = "luau"))] - pub(crate) unsafe fn set_thread_hook( - &self, - state: *mut ffi::lua_State, - triggers: HookTriggers, - callback: F, - ) where - F: Fn(&Lua, Debug) -> Result<()> + MaybeSend + 'static, - { - unsafe extern "C-unwind" fn hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) { - let extra = extra_data(state); - if (*extra).hook_thread != state { - // Hook was destined for a different thread, ignore - ffi::lua_sethook(state, None, 0, 0); - return; - } - callback_error_ext(state, extra, move |_| { - let hook_cb = (*extra).hook_callback.clone(); - let hook_cb = mlua_expect!(hook_cb, "no hook callback set in hook_proc"); - if Arc::strong_count(&hook_cb) > 2 { - return Ok(()); // Don't allow recursion - } - let lua: &Lua = mem::transmute((*extra).inner.assume_init_ref()); - let _guard = StateGuard::new(&lua.0, state); - let debug = Debug::new(lua, ar); - hook_cb(lua, debug) - }) - } - - (*self.extra.get()).hook_callback = Some(Arc::new(callback)); - (*self.extra.get()).hook_thread = state; // Mark for what thread the hook is set - ffi::lua_sethook(state, Some(hook_proc), triggers.mask(), triggers.count()); + let lua = self.lock(); + unsafe { lua.set_thread_hook(lua.state(), triggers, callback) }; } /// Removes any hook previously set by [`Lua::set_hook()`] or [`Thread::set_hook()`]. @@ -887,18 +865,19 @@ impl Lua { #[cfg(not(feature = "luau"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub fn remove_hook(&self) { + let lua = self.lock(); unsafe { - let state = self.state(); + let state = lua.state(); ffi::lua_sethook(state, None, 0, 0); - match get_main_state(self.main_state) { + match get_main_state(lua.main_state) { Some(main_state) if !ptr::eq(state, main_state) => { // If main_state is different from state, remove hook from it too ffi::lua_sethook(main_state, None, 0, 0); } _ => {} }; - (*self.extra.get()).hook_callback = None; - (*self.extra.get()).hook_thread = ptr::null_mut(); + (*lua.extra.get()).hook_callback = None; + (*lua.extra.get()).hook_thread = ptr::null_mut(); } } @@ -963,8 +942,9 @@ impl Lua { if Arc::strong_count(&interrupt_cb) > 2 { return Ok(VmState::Continue); // Don't allow recursion } - let lua: &Lua = mem::transmute((*extra).inner.assume_init_ref()); - let _guard = StateGuard::new(&lua.0, state); + let lua = (*extra).lua(); + let rawlua = lua.lock(); + let _guard = StateGuard::new(&rawlua, state); interrupt_cb(lua) }); match result { @@ -975,9 +955,10 @@ impl Lua { } } + let lua = self.lock(); unsafe { - (*self.extra.get()).interrupt_callback = Some(Arc::new(callback)); - (*ffi::lua_callbacks(self.main_state)).interrupt = Some(interrupt_proc); + (*lua.extra.get()).interrupt_callback = Some(Arc::new(callback)); + (*ffi::lua_callbacks(lua.main_state)).interrupt = Some(interrupt_proc); } } @@ -987,9 +968,10 @@ impl Lua { #[cfg(any(feature = "luau", docsrs))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn remove_interrupt(&self) { + let lua = self.lock(); unsafe { - (*self.extra.get()).interrupt_callback = None; - (*ffi::lua_callbacks(self.main_state)).interrupt = None; + (*lua.extra.get()).interrupt_callback = None; + (*ffi::lua_callbacks(lua.main_state)).interrupt = None; } } @@ -1004,8 +986,9 @@ impl Lua { { unsafe extern "C-unwind" fn warn_proc(ud: *mut c_void, msg: *const c_char, tocont: c_int) { let extra = ud as *mut ExtraData; - let lua: &Lua = mem::transmute((*extra).inner.assume_init_ref()); - callback_error_ext(lua.state(), extra, |_| { + let lua = (*extra).lua(); + let rawlua = lua.lock(); + callback_error_ext(rawlua.state(), extra, |_| { let cb = mlua_expect!( (*extra).warn_callback.as_ref(), "no warning callback set in warn_proc" @@ -1015,10 +998,11 @@ impl Lua { }); } - let state = self.main_state; + let lua = self.lock(); + let state = lua.main_state; unsafe { - (*self.extra.get()).warn_callback = Some(Box::new(callback)); - ffi::lua_setwarnf(state, Some(warn_proc), self.extra.get() as *mut c_void); + (*lua.extra.get()).warn_callback = Some(Box::new(callback)); + ffi::lua_setwarnf(state, Some(warn_proc), lua.extra.get() as *mut c_void); } } @@ -1030,9 +1014,10 @@ impl Lua { #[cfg(feature = "lua54")] #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] pub fn remove_warning_function(&self) { + let lua = self.lock(); unsafe { - (*self.extra.get()).warn_callback = None; - ffi::lua_setwarnf(self.main_state, None, ptr::null_mut()); + (*lua.extra.get()).warn_callback = None; + ffi::lua_setwarnf(lua.main_state, None, ptr::null_mut()); } } @@ -1050,9 +1035,10 @@ impl Lua { bytes[..msg.len()].copy_from_slice(msg.as_bytes()); let real_len = bytes.iter().position(|&c| c == 0).unwrap(); bytes.truncate(real_len); + let lua = self.lock(); unsafe { ffi::lua_warning( - self.state(), + lua.state(), bytes.as_ptr() as *const c_char, incomplete as c_int, ); @@ -1067,30 +1053,32 @@ impl Lua { /// /// [`Debug`]: crate::hook::Debug pub fn inspect_stack(&self, level: usize) -> Option { + let lua = self.lock(); unsafe { let mut ar: ffi::lua_Debug = mem::zeroed(); let level = level as c_int; #[cfg(not(feature = "luau"))] - if ffi::lua_getstack(self.state(), level, &mut ar) == 0 { + if ffi::lua_getstack(lua.state(), level, &mut ar) == 0 { return None; } #[cfg(feature = "luau")] - if ffi::lua_getinfo(self.state(), level, cstr!(""), &mut ar) == 0 { + if ffi::lua_getinfo(lua.state(), level, cstr!(""), &mut ar) == 0 { return None; } - Some(Debug::new_owned(self, level, ar)) + Some(Debug::new_owned(lua, level, ar)) } } /// Returns the amount of memory (in bytes) currently used inside this Lua state. pub fn used_memory(&self) -> usize { + let lua = self.lock(); unsafe { - match MemoryState::get(self.main_state) { + match MemoryState::get(lua.main_state) { mem_state if !mem_state.is_null() => (*mem_state).used_memory(), _ => { // Get data from the Lua GC - let used_kbytes = ffi::lua_gc(self.main_state, ffi::LUA_GCCOUNT, 0); - let used_kbytes_rem = ffi::lua_gc(self.main_state, ffi::LUA_GCCOUNTB, 0); + let used_kbytes = ffi::lua_gc(lua.main_state, ffi::LUA_GCCOUNT, 0); + let used_kbytes_rem = ffi::lua_gc(lua.main_state, ffi::LUA_GCCOUNTB, 0); (used_kbytes as usize) * 1024 + (used_kbytes_rem as usize) } } @@ -1105,8 +1093,9 @@ impl Lua { /// /// Does not work in module mode where Lua state is managed externally. pub fn set_memory_limit(&self, limit: usize) -> Result { + let lua = self.lock(); unsafe { - match MemoryState::get(self.main_state) { + match MemoryState::get(lua.main_state) { mem_state if !mem_state.is_null() => Ok((*mem_state).set_memory_limit(limit)), _ => Err(Error::MemoryLimitNotAvailable), } @@ -1123,17 +1112,20 @@ impl Lua { feature = "luau" ))] pub fn gc_is_running(&self) -> bool { - unsafe { ffi::lua_gc(self.main_state, ffi::LUA_GCISRUNNING, 0) != 0 } + let lua = self.lock(); + unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCISRUNNING, 0) != 0 } } /// Stop the Lua GC from running pub fn gc_stop(&self) { - unsafe { ffi::lua_gc(self.main_state, ffi::LUA_GCSTOP, 0) }; + let lua = self.lock(); + unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCSTOP, 0) }; } /// Restarts the Lua GC if it is not running pub fn gc_restart(&self) { - unsafe { ffi::lua_gc(self.main_state, ffi::LUA_GCRESTART, 0) }; + let lua = self.lock(); + unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCRESTART, 0) }; } /// Perform a full garbage-collection cycle. @@ -1141,9 +1133,10 @@ impl Lua { /// It may be necessary to call this function twice to collect all currently unreachable /// objects. Once to finish the current gc cycle, and once to start and finish the next cycle. pub fn gc_collect(&self) -> Result<()> { + let lua = self.lock(); unsafe { - check_stack(self.main_state, 2)?; - protect_lua!(self.main_state, 0, 0, fn(state) ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0)) + check_stack(lua.main_state, 2)?; + protect_lua!(lua.main_state, 0, 0, fn(state) ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0)) } } @@ -1159,9 +1152,10 @@ impl Lua { /// if `kbytes` is 0, then this is the same as calling `gc_step`. Returns true if this step has /// finished a collection cycle. pub fn gc_step_kbytes(&self, kbytes: c_int) -> Result { + let lua = self.lock(); unsafe { - check_stack(self.main_state, 3)?; - protect_lua!(self.main_state, 0, 0, |state| { + check_stack(lua.main_state, 3)?; + protect_lua!(lua.main_state, 0, 0, |state| { ffi::lua_gc(state, ffi::LUA_GCSTEP, kbytes) != 0 }) } @@ -1176,11 +1170,12 @@ impl Lua { /// /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 pub fn gc_set_pause(&self, pause: c_int) -> c_int { + let lua = self.lock(); unsafe { #[cfg(not(feature = "luau"))] - return ffi::lua_gc(self.main_state, ffi::LUA_GCSETPAUSE, pause); + return ffi::lua_gc(lua.main_state, ffi::LUA_GCSETPAUSE, pause); #[cfg(feature = "luau")] - return ffi::lua_gc(self.main_state, ffi::LUA_GCSETGOAL, pause); + return ffi::lua_gc(lua.main_state, ffi::LUA_GCSETGOAL, pause); } } @@ -1191,7 +1186,8 @@ impl Lua { /// /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 pub fn gc_set_step_multiplier(&self, step_multiplier: c_int) -> c_int { - unsafe { ffi::lua_gc(self.main_state, ffi::LUA_GCSETSTEPMUL, step_multiplier) } + let lua = self.lock(); + unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCSETSTEPMUL, step_multiplier) } } /// Changes the collector to incremental mode with the given parameters. @@ -1201,7 +1197,8 @@ impl Lua { /// /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5.1 pub fn gc_inc(&self, pause: c_int, step_multiplier: c_int, step_size: c_int) -> GCMode { - let state = self.main_state; + let lua = self.lock(); + let state = lua.main_state; #[cfg(any( feature = "lua53", @@ -1254,7 +1251,8 @@ impl Lua { #[cfg(feature = "lua54")] #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] pub fn gc_gen(&self, minor_multiplier: c_int, major_multiplier: c_int) -> GCMode { - let state = self.main_state; + let lua = self.lock(); + let state = lua.main_state; let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCGEN, minor_multiplier, major_multiplier) }; match prev_mode { @@ -1275,7 +1273,8 @@ impl Lua { #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn set_compiler(&self, compiler: Compiler) { - unsafe { (*self.extra.get()).compiler = Some(compiler) }; + let lua = self.lock(); + unsafe { (*lua.extra.get()).compiler = Some(compiler) }; } /// Toggles JIT compilation mode for new chunks of code. @@ -1311,62 +1310,16 @@ impl Lua { /// /// [`Chunk::exec`]: crate::Chunk::exec #[track_caller] - pub fn load<'lua, 'a>(&'lua self, chunk: impl AsChunk<'lua, 'a>) -> Chunk<'lua, 'a> { + pub fn load<'a>(&self, chunk: impl AsChunk<'a>) -> Chunk<'a> { let caller = Location::caller(); Chunk { - lua: self, + lua: self.weak(), name: chunk.name().unwrap_or_else(|| caller.to_string()), env: chunk.environment(self), mode: chunk.mode(), source: chunk.source(), #[cfg(feature = "luau")] - compiler: unsafe { (*self.extra.get()).compiler.clone() }, - } - } - - pub(crate) fn load_chunk<'lua>( - &'lua self, - name: Option<&CStr>, - env: Option
, - mode: Option, - source: &[u8], - ) -> Result> { - let state = self.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 1)?; - - let mode_str = match mode { - Some(ChunkMode::Binary) => cstr!("b"), - Some(ChunkMode::Text) => cstr!("t"), - None => cstr!("bt"), - }; - - match ffi::luaL_loadbufferx( - state, - source.as_ptr() as *const c_char, - source.len(), - name.map(|n| n.as_ptr()).unwrap_or_else(ptr::null), - mode_str, - ) { - ffi::LUA_OK => { - if let Some(env) = env { - self.push_ref(&env.0); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - ffi::lua_setupvalue(state, -2, 1); - #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] - ffi::lua_setfenv(state, -2); - } - - #[cfg(feature = "luau-jit")] - if (*self.extra.get()).enable_jit && ffi::luau_codegen_supported() != 0 { - ffi::luau_codegen_compile(state, -1); - } - - Ok(Function(self.pop_ref())) - } - err => Err(pop_error(state, err)), - } + compiler: unsafe { (*self.lock().extra.get()).compiler.clone() }, } } @@ -1374,17 +1327,18 @@ impl Lua { /// embedded nulls, so in addition to `&str` and `&String`, you can also pass plain `&[u8]` /// here. pub fn create_string(&self, s: impl AsRef<[u8]>) -> Result { - let state = self.state(); + let lua = self.lock(); + let state = lua.state(); unsafe { - if self.unlikely_memory_error() { - push_string(self.ref_thread(), s.as_ref(), false)?; - return Ok(String(self.pop_ref_thread())); + if lua.unlikely_memory_error() { + push_string(lua.ref_thread(), s.as_ref(), false)?; + return Ok(String(lua.pop_ref_thread())); } let _sg = StackGuard::new(state); check_stack(state, 3)?; push_string(state, s.as_ref(), true)?; - Ok(String(self.pop_ref())) + Ok(String(lua.pop_ref())) } } @@ -1395,17 +1349,18 @@ impl Lua { /// [buffer]: https://luau-lang.org/library#buffer-library #[cfg(feature = "luau")] pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result { - let state = self.state(); + let lua = self.lock(); + let state = lua.state(); unsafe { - if self.unlikely_memory_error() { - crate::util::push_buffer(self.ref_thread(), buf.as_ref(), false)?; - return Ok(AnyUserData(self.pop_ref_thread(), SubtypeId::Buffer)); + if lua.unlikely_memory_error() { + crate::util::push_buffer(lua.ref_thread(), buf.as_ref(), false)?; + return Ok(AnyUserData(lua.pop_ref_thread(), SubtypeId::Buffer)); } let _sg = StackGuard::new(state); check_stack(state, 4)?; crate::util::push_buffer(state, buf.as_ref(), true)?; - Ok(AnyUserData(self.pop_ref(), SubtypeId::Buffer)) + Ok(AnyUserData(lua.pop_ref(), SubtypeId::Buffer)) } } @@ -1419,39 +1374,41 @@ impl Lua { /// `nrec` is a hint for how many other elements the table will have. /// Lua may use these hints to preallocate memory for the new table. pub fn create_table_with_capacity(&self, narr: usize, nrec: usize) -> Result
{ - let state = self.state(); + let lua = self.lock(); + let state = lua.state(); unsafe { - if self.unlikely_memory_error() { - push_table(self.ref_thread(), narr, nrec, false)?; - return Ok(Table(self.pop_ref_thread())); + if lua.unlikely_memory_error() { + push_table(lua.ref_thread(), narr, nrec, false)?; + return Ok(Table(lua.pop_ref_thread())); } let _sg = StackGuard::new(state); check_stack(state, 3)?; push_table(state, narr, nrec, true)?; - Ok(Table(self.pop_ref())) + Ok(Table(lua.pop_ref())) } } /// Creates a table and fills it with values from an iterator. - pub fn create_table_from<'lua, K, V, I>(&'lua self, iter: I) -> Result> + pub fn create_table_from(&self, iter: I) -> Result
where K: IntoLua, V: IntoLua, I: IntoIterator, { - let state = self.state(); + let lua = self.lock(); + let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 6)?; let iter = iter.into_iter(); let lower_bound = iter.size_hint().0; - let protect = !self.unlikely_memory_error(); + let protect = !lua.unlikely_memory_error(); push_table(state, 0, lower_bound, protect)?; for (k, v) in iter { - self.push(k)?; - self.push(v)?; + lua.push(k)?; + lua.push(v)?; if protect { protect_lua!(state, 3, 1, fn(state) ffi::lua_rawset(state, -3))?; } else { @@ -1459,27 +1416,28 @@ impl Lua { } } - Ok(Table(self.pop_ref())) + Ok(Table(lua.pop_ref())) } } /// Creates a table from an iterator of values, using `1..` as the keys. - pub fn create_sequence_from<'lua, T, I>(&'lua self, iter: I) -> Result> + pub fn create_sequence_from(&self, iter: I) -> Result
where T: IntoLua, I: IntoIterator, { - let state = self.state(); + let lua = self.lock(); + let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 5)?; let iter = iter.into_iter(); let lower_bound = iter.size_hint().0; - let protect = !self.unlikely_memory_error(); + let protect = !lua.unlikely_memory_error(); push_table(state, lower_bound, 0, protect)?; for (i, v) in iter.enumerate() { - self.push(v)?; + lua.push(v)?; if protect { protect_lua!(state, 2, 1, |state| { ffi::lua_rawseti(state, -2, (i + 1) as Integer); @@ -1489,7 +1447,7 @@ impl Lua { } } - Ok(Table(self.pop_ref())) + Ok(Table(lua.pop_ref())) } } @@ -1538,15 +1496,16 @@ impl Lua { /// /// [`IntoLua`]: crate::IntoLua /// [`IntoLuaMulti`]: crate::IntoLuaMulti - pub fn create_function<'lua, A, R, F>(&'lua self, func: F) -> Result> + pub fn create_function(&self, func: F) -> Result where - A: FromLuaMulti<'lua>, + A: FromLuaMulti, R: IntoLuaMulti, - F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, + F: Fn(&Lua, A) -> Result + MaybeSend + 'static, { - self.create_callback(Box::new(move |lua, nargs| unsafe { + let lua = self.lock(); + lua.create_callback(Box::new(move |lua, nargs| unsafe { let args = A::from_stack_args(nargs, 1, None, lua)?; - func(lua, args)?.push_into_stack_multi(lua) + func(lua.lua(), args)?.push_into_stack_multi(lua) })) } @@ -1556,11 +1515,11 @@ impl Lua { /// [`create_function`] for more information about the implementation. /// /// [`create_function`]: #method.create_function - pub fn create_function_mut<'lua, A, R, F>(&'lua self, func: F) -> Result> + pub fn create_function_mut(&self, func: F) -> Result where - A: FromLuaMulti<'lua>, + A: FromLuaMulti, R: IntoLuaMulti, - F: FnMut(&'lua Lua, A) -> Result + MaybeSend + 'static, + F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, { let func = RefCell::new(func); self.create_function(move |lua, args| { @@ -1575,10 +1534,11 @@ impl Lua { /// # Safety /// This function is unsafe because provides a way to execute unsafe C function. pub unsafe fn create_c_function(&self, func: ffi::lua_CFunction) -> Result { - let state = self.state(); + let lua = self.lock(); + let state = lua.state(); check_stack(state, 1)?; ffi::lua_pushcfunction(state, func); - Ok(Function(self.pop_ref())) + Ok(Function(lua.pop_ref())) } /// Wraps a Rust async function or closure, creating a callable Lua function handle to it. @@ -1622,104 +1582,32 @@ impl Lua { /// [`AsyncThread`]: crate::AsyncThread #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub fn create_async_function<'lua, A, R, F, FR>(&'lua self, func: F) -> Result> + pub fn create_async_function<'lua, 'a, F, A, FR, R>(&'lua self, func: F) -> Result where - A: FromLuaMulti<'lua>, + 'lua: 'a, + F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, + A: FromLuaMulti, + FR: Future> + 'a, R: IntoLuaMulti, - F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static, - FR: Future> + 'lua, { - self.create_async_callback(Box::new(move |lua, args| unsafe { + let lua = self.lock(); + lua.create_async_callback(Box::new(move |rawlua, args| unsafe { + // let rawlua = mem::transmute::<&LuaInner, &LuaInner>(rawlua); + let lua = rawlua.lua(); let args = match A::from_lua_args(args, 1, None, lua) { Ok(args) => args, Err(e) => return Box::pin(future::err(e)), }; let fut = func(lua, args); - Box::pin(async move { fut.await?.push_into_stack_multi(lua) }) + Box::pin(async move { fut.await?.push_into_stack_multi(rawlua) }) })) } /// Wraps a Lua function into a new thread (or coroutine). /// /// Equivalent to `coroutine.create`. - pub fn create_thread<'lua>(&'lua self, func: Function) -> Result> { - self.create_thread_inner(&func) - } - - /// Wraps a Lua function into a new thread (or coroutine). - /// - /// Takes function by reference. - fn create_thread_inner<'lua>(&'lua self, func: &Function) -> Result> { - let state = self.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 3)?; - - let thread_state = if self.unlikely_memory_error() { - ffi::lua_newthread(state) - } else { - protect_lua!(state, 0, 1, |state| ffi::lua_newthread(state))? - }; - self.push_ref(&func.0); - ffi::lua_xmove(state, thread_state, 1); - - Ok(Thread::new(self.pop_ref())) - } - } - - /// Wraps a Lua function into a new or recycled thread (coroutine). - #[cfg(feature = "async")] - pub(crate) fn create_recycled_thread<'lua>( - &'lua self, - func: &Function, - ) -> Result> { - #[cfg(any(feature = "lua54", feature = "luau"))] - unsafe { - let state = self.state(); - let _sg = StackGuard::new(state); - check_stack(state, 1)?; - - if let Some(index) = (*self.extra.get()).thread_pool.pop() { - let thread_state = ffi::lua_tothread(self.ref_thread(), index); - self.push_ref(&func.0); - ffi::lua_xmove(state, thread_state, 1); - - #[cfg(feature = "luau")] - { - // Inherit `LUA_GLOBALSINDEX` from the caller - ffi::lua_xpush(state, thread_state, ffi::LUA_GLOBALSINDEX); - ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX); - } - - return Ok(Thread::new(ValueRef::new(self, index))); - } - }; - self.create_thread_inner(func) - } - - /// Resets thread (coroutine) and returns to the pool for later use. - #[cfg(feature = "async")] - #[cfg(any(feature = "lua54", feature = "luau"))] - pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) -> bool { - let extra = &mut *self.extra.get(); - if extra.thread_pool.len() < extra.thread_pool.capacity() { - let thread_state = ffi::lua_tothread(extra.ref_thread, thread.0.index); - #[cfg(all(feature = "lua54", not(feature = "vendored")))] - let status = ffi::lua_resetthread(thread_state); - #[cfg(all(feature = "lua54", feature = "vendored"))] - let status = ffi::lua_closethread(thread_state, self.state()); - #[cfg(feature = "lua54")] - if status != ffi::LUA_OK { - // Error object is on top, drop it - ffi::lua_settop(thread_state, 0); - } - #[cfg(feature = "luau")] - ffi::lua_resetthread(thread_state); - extra.thread_pool.push(thread.0.index); - thread.0.drop = false; - return true; - } - false + pub fn create_thread(&self, func: Function) -> Result { + self.lock().create_thread_inner(&func) } /// Creates a Lua userdata object from a custom userdata type. @@ -1730,7 +1618,8 @@ impl Lua { where T: UserData + MaybeSend + 'static, { - unsafe { self.make_userdata(UserDataCell::new(data)) } + let lua = self.lock(); + unsafe { lua.make_userdata(UserDataVariant::new(data)) } } /// Creates a Lua userdata object from a custom serializable userdata type. @@ -1743,7 +1632,8 @@ impl Lua { where T: UserData + Serialize + MaybeSend + 'static, { - unsafe { self.make_userdata(UserDataCell::new_ser(data)) } + let lua = self.lock(); + unsafe { lua.make_userdata(UserDataVariant::new_ser(data)) } } /// Creates a Lua userdata object from a custom Rust type. @@ -1758,7 +1648,8 @@ impl Lua { where T: MaybeSend + 'static, { - unsafe { self.make_any_userdata(UserDataCell::new(data)) } + let lua = self.lock(); + unsafe { lua.make_any_userdata(UserDataVariant::new(data)) } } /// Creates a Lua userdata object from a custom serializable Rust type. @@ -1773,7 +1664,8 @@ impl Lua { where T: Serialize + MaybeSend + 'static, { - unsafe { self.make_any_userdata(UserDataCell::new_ser(data)) } + let lua = self.lock(); + unsafe { lua.make_any_userdata(UserDataVariant::new_ser(data)) } } /// Registers a custom Rust type in Lua to use in userdata objects. @@ -1786,17 +1678,18 @@ impl Lua { let mut registry = UserDataRegistry::new(); f(&mut registry); + let lua = self.lock(); unsafe { // Deregister the type if it already registered let type_id = TypeId::of::(); - if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) { - ffi::luaL_unref(self.state(), ffi::LUA_REGISTRYINDEX, table_id); + if let Some(&table_id) = (*lua.extra.get()).registered_userdata.get(&type_id) { + ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, table_id); } // Register the type - self.register_userdata_metatable(registry)?; - Ok(()) + lua.register_userdata_metatable(registry)?; } + Ok(()) } /// Create a Lua userdata "proxy" object from a custom userdata type. @@ -1816,11 +1709,11 @@ impl Lua { /// struct MyUserData(i32); /// /// impl UserData for MyUserData { - /// fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { + /// fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { /// fields.add_field_method_get("val", |_, this| Ok(this.0)); /// } /// - /// fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + /// fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { /// methods.add_function("new", |_, value: i32| Ok(MyUserData(value))); /// } /// } @@ -1836,7 +1729,8 @@ impl Lua { where T: UserData + 'static, { - unsafe { self.make_userdata(UserDataCell::new(UserDataProxy::(PhantomData))) } + let lua = self.lock(); + unsafe { lua.make_userdata(UserDataVariant::new(UserDataProxy::(PhantomData))) } } /// Sets the metatable for a Luau builtin vector type. @@ -1862,7 +1756,8 @@ impl Lua { /// Returns a handle to the global environment. pub fn globals(&self) -> Table { - let state = self.state(); + let lua = self.lock(); + let state = lua.state(); unsafe { let _sg = StackGuard::new(state); assert_stack(state, 1); @@ -1870,19 +1765,20 @@ impl Lua { ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] ffi::lua_pushvalue(state, ffi::LUA_GLOBALSINDEX); - Table(self.pop_ref()) + Table(lua.pop_ref()) } } /// Returns a handle to the active `Thread`. For calls to `Lua` this will be the main Lua thread, /// for parameters given to a callback, this will be whatever Lua thread called the callback. pub fn current_thread(&self) -> Thread { - let state = self.state(); + let lua = self.lock(); + let state = lua.state(); unsafe { let _sg = StackGuard::new(state); assert_stack(state, 1); ffi::lua_pushthread(state); - Thread::new(self.pop_ref()) + Thread::new(&lua, lua.pop_ref()) } } @@ -1906,31 +1802,32 @@ impl Lua { /// dropped. `Function` types will error when called, and `AnyUserData` will be typeless. It /// would be impossible to prevent handles to scoped values from escaping anyway, since you /// would always be able to smuggle them through Lua state. - pub fn scope<'lua, 'scope, R>( - &'lua self, - f: impl FnOnce(&Scope<'lua, 'scope>) -> Result, - ) -> Result - where - 'lua: 'scope, - { - f(&Scope::new(self)) - } + // pub fn scope<'lua, 'scope, R>( + // &'lua self, + // f: impl FnOnce(&Scope<'lua, 'scope>) -> Result, + // ) -> Result + // where + // 'lua: 'scope, + // { + // f(&Scope::new(self)) + // } /// Attempts to coerce a Lua value into a String in a manner consistent with Lua's internal /// behavior. /// /// To succeed, the value must be a string (in which case this is a no-op), an integer, or a /// number. - pub fn coerce_string<'lua>(&'lua self, v: Value<'lua>) -> Result>> { + pub fn coerce_string(&self, v: Value) -> Result> { Ok(match v { Value::String(s) => Some(s), v => unsafe { - let state = self.state(); + let lua = self.lock(); + let state = lua.state(); let _sg = StackGuard::new(state); check_stack(state, 4)?; - self.push_value(&v)?; - let res = if self.unlikely_memory_error() { + lua.push_value(&v)?; + let res = if lua.unlikely_memory_error() { ffi::lua_tolstring(state, -1, ptr::null_mut()) } else { protect_lua!(state, 1, 1, |state| { @@ -1938,7 +1835,7 @@ impl Lua { })? }; if !res.is_null() { - Some(String(self.pop_ref())) + Some(String(lua.pop_ref())) } else { None } @@ -1956,11 +1853,12 @@ impl Lua { Ok(match v { Value::Integer(i) => Some(i), v => unsafe { - let state = self.state(); + let lua = self.lock(); + let state = lua.state(); let _sg = StackGuard::new(state); check_stack(state, 2)?; - self.push_value(&v)?; + lua.push_value(&v)?; let mut isint = 0; let i = ffi::lua_tointegerx(state, -1, &mut isint); if isint == 0 { @@ -1981,11 +1879,12 @@ impl Lua { Ok(match v { Value::Number(n) => Some(n), v => unsafe { - let state = self.state(); + let lua = self.lock(); + let state = lua.state(); let _sg = StackGuard::new(state); check_stack(state, 2)?; - self.push_value(&v)?; + lua.push_value(&v)?; let mut isnum = 0; let n = ffi::lua_tonumberx(state, -1, &mut isnum); if isnum == 0 { @@ -1998,25 +1897,22 @@ impl Lua { } /// Converts a value that implements `IntoLua` into a `Value` instance. - pub fn pack<'lua, T: IntoLua>(&'lua self, t: T) -> Result> { + pub fn pack(&self, t: T) -> Result { t.into_lua(self) } /// Converts a `Value` instance into a value that implements `FromLua`. - pub fn unpack<'lua, T: FromLua<'lua>>(&'lua self, value: Value<'lua>) -> Result { + pub fn unpack(&self, value: Value) -> Result { T::from_lua(value, self) } /// Converts a value that implements `IntoLuaMulti` into a `MultiValue` instance. - pub fn pack_multi<'lua, T: IntoLuaMulti>(&'lua self, t: T) -> Result> { + pub fn pack_multi(&self, t: T) -> Result { t.into_lua_multi(self) } /// Converts a `MultiValue` instance into a value that implements `FromLuaMulti`. - pub fn unpack_multi<'lua, T: FromLuaMulti<'lua>>( - &'lua self, - value: MultiValue<'lua>, - ) -> Result { + pub fn unpack_multi(&self, value: MultiValue) -> Result { T::from_lua_multi(value, self) } @@ -2024,16 +1920,17 @@ impl Lua { /// /// This value will be available to rust from all `Lua` instances which share the same main /// state. - pub fn set_named_registry_value<'lua, T>(&'lua self, name: &str, t: T) -> Result<()> + pub fn set_named_registry_value(&self, name: &str, t: T) -> Result<()> where T: IntoLua, { - let state = self.state(); + let lua = self.lock(); + let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 5)?; - self.push(t)?; + lua.push(t)?; rawset_field(state, ffi::LUA_REGISTRYINDEX, name) } } @@ -2044,20 +1941,21 @@ impl Lua { /// get a value previously set by [`set_named_registry_value`]. /// /// [`set_named_registry_value`]: #method.set_named_registry_value - pub fn named_registry_value<'lua, T>(&'lua self, name: &str) -> Result + pub fn named_registry_value(&self, name: &str) -> Result where - T: FromLua<'lua>, + T: FromLua, { - let state = self.state(); + let lua = self.lock(); + let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 3)?; - let protect = !self.unlikely_memory_error(); + let protect = !lua.unlikely_memory_error(); push_string(state, name.as_bytes(), protect)?; ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX); - T::from_stack(-1, self) + T::from_stack(-1, &lua) } } @@ -2081,14 +1979,15 @@ impl Lua { /// /// [`RegistryKey`]: crate::RegistryKey pub fn create_registry_value(&self, t: T) -> Result { - let state = self.state(); + let lua = self.lock(); + let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 4)?; - self.push(t)?; + lua.push(t)?; - let unref_list = (*self.extra.get()).registry_unref_list.clone(); + let unref_list = (*lua.extra.get()).registry_unref_list.clone(); // Check if the value is nil (no need to store it in the registry) if ffi::lua_isnil(state, -1) != 0 { @@ -2096,9 +1995,7 @@ impl Lua { } // Try to reuse previously allocated slot - let free_registry_id = mlua_expect!(unref_list.lock(), "unref list poisoned") - .as_mut() - .and_then(|x| x.pop()); + let free_registry_id = unref_list.lock().as_mut().and_then(|x| x.pop()); if let Some(registry_id) = free_registry_id { // It must be safe to replace the value without triggering memory error ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); @@ -2106,7 +2003,7 @@ impl Lua { } // Allocate a new RegistryKey slot - let registry_id = if self.unlikely_memory_error() { + let registry_id = if lua.unlikely_memory_error() { ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) } else { protect_lua!(state, 1, 0, |state| { @@ -2123,12 +2020,13 @@ impl Lua { /// previously placed by [`create_registry_value`]. /// /// [`create_registry_value`]: #method.create_registry_value - pub fn registry_value<'lua, T: FromLua<'lua>>(&'lua self, key: &RegistryKey) -> Result { - if !self.owns_registry_value(key) { + pub fn registry_value(&self, key: &RegistryKey) -> Result { + let lua = self.lock(); + if !lua.owns_registry_value(key) { return Err(Error::MismatchedRegistryKey); } - let state = self.state(); + let state = lua.state(); match key.id() { ffi::LUA_REFNIL => T::from_lua(Value::Nil, self), registry_id => unsafe { @@ -2136,7 +2034,7 @@ impl Lua { check_stack(state, 1)?; ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); - T::from_stack(-1, self) + T::from_stack(-1, &lua) }, } } @@ -2151,12 +2049,13 @@ impl Lua { /// [`create_registry_value`]: #method.create_registry_value /// [`expire_registry_values`]: #method.expire_registry_values pub fn remove_registry_value(&self, key: RegistryKey) -> Result<()> { - if !self.owns_registry_value(&key) { + let lua = self.lock(); + if !lua.owns_registry_value(&key) { return Err(Error::MismatchedRegistryKey); } unsafe { - ffi::luaL_unref(self.state(), ffi::LUA_REGISTRYINDEX, key.take()); + ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, key.take()); } Ok(()) } @@ -2166,18 +2065,15 @@ impl Lua { /// See [`create_registry_value`] for more details. /// /// [`create_registry_value`]: #method.create_registry_value - pub fn replace_registry_value<'lua, T: IntoLua>( - &'lua self, - key: &RegistryKey, - t: T, - ) -> Result<()> { - if !self.owns_registry_value(key) { + pub fn replace_registry_value(&self, key: &RegistryKey, t: T) -> Result<()> { + let lua = self.lock(); + if !lua.owns_registry_value(key) { return Err(Error::MismatchedRegistryKey); } let t = t.into_lua(self)?; - let state = self.state(); + let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 2)?; @@ -2198,7 +2094,7 @@ impl Lua { } (value, registry_id) => { // It must be safe to replace the value without triggering memory error - self.push_value(&value)?; + lua.push_value(&value)?; ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); } } @@ -2213,8 +2109,7 @@ impl Lua { /// `Error::MismatchedRegistryKey` if passed a `RegistryKey` that was not created with a /// matching `Lua` state. pub fn owns_registry_value(&self, key: &RegistryKey) -> bool { - let registry_unref_list = unsafe { &(*self.extra.get()).registry_unref_list }; - Arc::ptr_eq(&key.unref_list, registry_unref_list) + self.lock().owns_registry_value(key) } /// Remove any registry values whose `RegistryKey`s have all been dropped. @@ -2223,12 +2118,10 @@ impl Lua { /// but you can call this method to remove any unreachable registry values not manually removed /// by `Lua::remove_registry_value`. pub fn expire_registry_values(&self) { - let state = self.state(); + let lua = self.lock(); + let state = lua.state(); unsafe { - let mut unref_list = mlua_expect!( - (*self.extra.get()).registry_unref_list.lock(), - "unref list poisoned" - ); + let mut unref_list = (*lua.extra.get()).registry_unref_list.lock(); let unref_list = mem::replace(&mut *unref_list, Some(Vec::new())); for id in mlua_expect!(unref_list, "unref list not set") { ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, id); @@ -2268,7 +2161,8 @@ impl Lua { /// ``` #[track_caller] pub fn set_app_data(&self, data: T) -> Option { - let extra = unsafe { &*self.extra.get() }; + let lua = self.lock(); + let extra = unsafe { &*lua.extra.get() }; extra.app_data.insert(data) } @@ -2281,7 +2175,8 @@ impl Lua { /// /// See [`Lua::set_app_data()`] for examples. pub fn try_set_app_data(&self, data: T) -> StdResult, T> { - let extra = unsafe { &*self.extra.get() }; + let lua = self.lock(); + let extra = unsafe { &*lua.extra.get() }; extra.app_data.try_insert(data) } @@ -2293,8 +2188,9 @@ impl Lua { /// can be taken out at the same time. #[track_caller] pub fn app_data_ref(&self) -> Option> { - let extra = unsafe { &*self.extra.get() }; - extra.app_data.borrow() + let guard = self.lock_arc(); + let extra = unsafe { &*guard.extra.get() }; + extra.app_data.borrow(Some(guard)) } /// Gets a mutable reference to an application data object stored by [`Lua::set_app_data()`] of type `T`. @@ -2304,8 +2200,9 @@ impl Lua { /// Panics if the data object of type `T` is currently borrowed. #[track_caller] pub fn app_data_mut(&self) -> Option> { - let extra = unsafe { &*self.extra.get() }; - extra.app_data.borrow_mut() + let guard = self.lock_arc(); + let extra = unsafe { &*guard.extra.get() }; + extra.app_data.borrow_mut(Some(guard)) } /// Removes an application data of type `T`. @@ -2315,66 +2212,372 @@ impl Lua { /// Panics if the app data container is currently borrowed. #[track_caller] pub fn remove_app_data(&self) -> Option { - let extra = unsafe { &*self.extra.get() }; + let lua = self.lock(); + let extra = unsafe { &*lua.extra.get() }; extra.app_data.remove() } - /// Pushes a value that implements `IntoLua` onto the Lua stack. - /// - /// Uses 2 stack spaces, does not call checkstack. - #[doc(hidden)] - #[inline(always)] - pub unsafe fn push<'lua>(&'lua self, value: impl IntoLua) -> Result<()> { - value.push_into_stack(self) - } + // FIXME + // /// Pushes a value that implements `IntoLua` onto the Lua stack. + // /// + // /// Uses 2 stack spaces, does not call checkstack. + // #[doc(hidden)] + // #[inline(always)] + // pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> { + // // value.push_into_stack(self) + // } - /// Pushes a `Value` (by reference) onto the Lua stack. - /// - /// Uses 2 stack spaces, does not call `checkstack`. - pub(crate) unsafe fn push_value(&self, value: &Value) -> Result<()> { - let state = self.state(); - match value { - Value::Nil => ffi::lua_pushnil(state), - Value::Boolean(b) => ffi::lua_pushboolean(state, *b as c_int), - Value::LightUserData(ud) => ffi::lua_pushlightuserdata(state, ud.0), - Value::Integer(i) => ffi::lua_pushinteger(state, *i), - Value::Number(n) => ffi::lua_pushnumber(state, *n), - #[cfg(feature = "luau")] - Value::Vector(v) => { - #[cfg(not(feature = "luau-vector4"))] - ffi::lua_pushvector(state, v.x(), v.y(), v.z()); - #[cfg(feature = "luau-vector4")] - ffi::lua_pushvector(state, v.x(), v.y(), v.z(), v.w()); - } - Value::String(s) => self.push_ref(&s.0), - Value::Table(t) => self.push_ref(&t.0), - Value::Function(f) => self.push_ref(&f.0), - Value::Thread(t) => self.push_ref(&t.0), - Value::UserData(ud) => self.push_ref(&ud.0), - Value::Error(err) => { - let protect = !self.unlikely_memory_error(); - push_gc_userdata(state, WrappedFailure::Error(*err.clone()), protect)?; - } - } - Ok(()) + /// Returns internal `Poll::Pending` constant used for executing async callbacks. + #[cfg(feature = "async")] + #[doc(hidden)] + #[inline] + pub fn poll_pending() -> LightUserData { + LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut c_void) } - /// Pops a value from the Lua stack. - /// - /// Uses 2 stack spaces, does not call checkstack. - #[doc(hidden)] - pub unsafe fn pop_value(&self) -> Value { - let state = self.state(); - match ffi::lua_type(state, -1) { - ffi::LUA_TNIL => { - ffi::lua_pop(state, 1); - Nil - } + // Luau version located in `luau/mod.rs` + #[cfg(not(feature = "luau"))] + fn disable_c_modules(&self) -> Result<()> { + let package: Table = self.globals().get("package")?; - ffi::LUA_TBOOLEAN => { - let b = Value::Boolean(ffi::lua_toboolean(state, -1) != 0); - ffi::lua_pop(state, 1); - b + package.set( + "loadlib", + self.create_function(|_, ()| -> Result<()> { + Err(Error::SafetyError( + "package.loadlib is disabled in safe mode".to_string(), + )) + })?, + )?; + + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + let searchers: Table = package.get("searchers")?; + #[cfg(any(feature = "lua51", feature = "luajit"))] + let searchers: Table = package.get("loaders")?; + + let loader = self.create_function(|_, ()| Ok("\n\tcan't load C modules in safe mode"))?; + + // The third and fourth searchers looks for a loader as a C library + searchers.raw_set(3, loader)?; + searchers.raw_remove(4)?; + + Ok(()) + } + + pub(crate) unsafe fn try_from_ptr(state: *mut ffi::lua_State) -> Option { + let extra = extra_data(state); + if extra.is_null() { + return None; + } + Some(Lua(Arc::clone((*extra).inner.assume_init_ref()))) + } + + #[inline(always)] + pub(crate) fn lock(&self) -> ReentrantMutexGuard { + self.0.lock() + } + + #[inline(always)] + pub(crate) fn lock_arc(&self) -> LuaGuard { + LuaGuard(self.0.lock_arc()) + } + + #[inline(always)] + pub(crate) unsafe fn guard_unchecked(&self) -> ManuallyDrop> { + ManuallyDrop::new(self.0.make_guard_unchecked()) + } + + #[inline(always)] + pub(crate) fn weak(&self) -> WeakLua { + WeakLua(Arc::downgrade(&self.0)) + } +} + +impl LuaInner { + #[inline(always)] + pub(crate) fn lua(&self) -> &Lua { + unsafe { (*self.extra.get()).lua() } + } + + #[inline(always)] + pub(crate) fn weak(&self) -> &WeakLua { + unsafe { (*self.extra.get()).weak() } + } + + #[inline(always)] + pub(crate) fn state(&self) -> *mut ffi::lua_State { + self.state.get() + } + + #[cfg(feature = "luau")] + #[inline(always)] + pub(crate) fn main_state(&self) -> *mut ffi::lua_State { + self.main_state + } + + #[inline(always)] + pub(crate) fn ref_thread(&self) -> *mut ffi::lua_State { + unsafe { (*self.extra.get()).ref_thread } + } + + /// See [`Lua::try_set_app_data`] + pub(crate) fn try_set_app_data( + &self, + data: T, + ) -> StdResult, T> { + let extra = unsafe { &*self.extra.get() }; + extra.app_data.try_insert(data) + } + + /// See [`Lua::app_data_ref`] + #[track_caller] + pub(crate) fn app_data_ref(&self) -> Option> { + let extra = unsafe { &*self.extra.get() }; + extra.app_data.borrow(None) + } + + /// See [`Lua::app_data_mut`] + #[track_caller] + pub(crate) fn app_data_mut(&self) -> Option> { + let extra = unsafe { &*self.extra.get() }; + extra.app_data.borrow_mut(None) + } + + /// See [`Lua::create_registry_value`] + pub(crate) fn owns_registry_value(&self, key: &RegistryKey) -> bool { + let registry_unref_list = unsafe { &(*self.extra.get()).registry_unref_list }; + Arc::ptr_eq(&key.unref_list, registry_unref_list) + } + + pub(crate) fn load_chunk( + &self, + name: Option<&CStr>, + env: Option
, + mode: Option, + source: &[u8], + ) -> Result { + let state = self.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 1)?; + + let mode_str = match mode { + Some(ChunkMode::Binary) => cstr!("b"), + Some(ChunkMode::Text) => cstr!("t"), + None => cstr!("bt"), + }; + + match ffi::luaL_loadbufferx( + state, + source.as_ptr() as *const c_char, + source.len(), + name.map(|n| n.as_ptr()).unwrap_or_else(ptr::null), + mode_str, + ) { + ffi::LUA_OK => { + if let Some(env) = env { + self.push_ref(&env.0); + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + ffi::lua_setupvalue(state, -2, 1); + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + ffi::lua_setfenv(state, -2); + } + + #[cfg(feature = "luau-jit")] + if (*self.extra.get()).enable_jit && ffi::luau_codegen_supported() != 0 { + ffi::luau_codegen_compile(state, -1); + } + + Ok(Function(self.pop_ref())) + } + err => Err(pop_error(state, err)), + } + } + } + + /// Sets a 'hook' function for a thread (coroutine). + #[cfg(not(feature = "luau"))] + pub(crate) unsafe fn set_thread_hook( + &self, + state: *mut ffi::lua_State, + triggers: HookTriggers, + callback: F, + ) where + F: Fn(&Lua, Debug) -> Result<()> + MaybeSend + 'static, + { + unsafe extern "C-unwind" fn hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) { + let extra = extra_data(state); + if (*extra).hook_thread != state { + // Hook was destined for a different thread, ignore + ffi::lua_sethook(state, None, 0, 0); + return; + } + callback_error_ext(state, extra, move |_| { + let hook_cb = (*extra).hook_callback.clone(); + let hook_cb = mlua_expect!(hook_cb, "no hook callback set in hook_proc"); + if Arc::strong_count(&hook_cb) > 2 { + return Ok(()); // Don't allow recursion + } + let lua = (*extra).lua(); + let rawlua = lua.lock(); + let _guard = StateGuard::new(&rawlua, state); + let debug = Debug::new(lua, ar); + hook_cb(lua, debug) + }) + } + + (*self.extra.get()).hook_callback = Some(Arc::new(callback)); + (*self.extra.get()).hook_thread = state; // Mark for what thread the hook is set + ffi::lua_sethook(state, Some(hook_proc), triggers.mask(), triggers.count()); + } + + /// Wraps a Lua function into a new thread (or coroutine). + /// + /// Takes function by reference. + fn create_thread_inner(&self, func: &Function) -> Result { + let state = self.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 3)?; + + let thread_state = if self.unlikely_memory_error() { + ffi::lua_newthread(state) + } else { + protect_lua!(state, 0, 1, |state| ffi::lua_newthread(state))? + }; + self.push_ref(&func.0); + ffi::lua_xmove(state, thread_state, 1); + + Ok(Thread::new(&self, self.pop_ref())) + } + } + + /// Wraps a Lua function into a new or recycled thread (coroutine). + #[cfg(feature = "async")] + pub(crate) fn create_recycled_thread(&self, func: &Function) -> Result { + #[cfg(any(feature = "lua54", feature = "luau"))] + unsafe { + let state = self.state(); + let _sg = StackGuard::new(state); + check_stack(state, 1)?; + + if let Some(index) = (*self.extra.get()).thread_pool.pop() { + let thread_state = ffi::lua_tothread(self.ref_thread(), index); + self.push_ref(&func.0); + ffi::lua_xmove(state, thread_state, 1); + + #[cfg(feature = "luau")] + { + // Inherit `LUA_GLOBALSINDEX` from the caller + ffi::lua_xpush(state, thread_state, ffi::LUA_GLOBALSINDEX); + ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX); + } + + return Ok(Thread::new(self, ValueRef::new(self, index))); + } + }; + self.create_thread_inner(func) + } + + /// Resets thread (coroutine) and returns to the pool for later use. + #[cfg(feature = "async")] + #[cfg(any(feature = "lua54", feature = "luau"))] + pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) -> bool { + let extra = &mut *self.extra.get(); + if extra.thread_pool.len() < extra.thread_pool.capacity() { + let thread_state = ffi::lua_tothread(extra.ref_thread, thread.0.index); + #[cfg(all(feature = "lua54", not(feature = "vendored")))] + let status = ffi::lua_resetthread(thread_state); + #[cfg(all(feature = "lua54", feature = "vendored"))] + let status = ffi::lua_closethread(thread_state, self.state()); + #[cfg(feature = "lua54")] + if status != ffi::LUA_OK { + // Error object is on top, drop it + ffi::lua_settop(thread_state, 0); + } + #[cfg(feature = "luau")] + ffi::lua_resetthread(thread_state); + extra.thread_pool.push(thread.0.index); + thread.0.drop = false; + return true; + } + false + } + + // FIXME + // #[inline] + // pub(crate) fn pop_multivalue_from_pool(&self) -> Option> { + // let extra = unsafe { &mut *self.extra.get() }; + // extra.multivalue_pool.pop() + // } + + // FIXME + // #[inline] + // pub(crate) fn push_multivalue_to_pool(&self, mut multivalue: VecDeque) { + // let extra = unsafe { &mut *self.extra.get() }; + // if extra.multivalue_pool.len() < MULTIVALUE_POOL_SIZE { + // multivalue.clear(); + // extra + // .multivalue_pool + // .push(unsafe { mem::transmute(multivalue) }); + // } + // } + + /// Pushes a value that implements `IntoLua` onto the Lua stack. + /// + /// Uses 2 stack spaces, does not call checkstack. + #[doc(hidden)] + #[inline(always)] + pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> { + value.push_into_stack(self) + } + + /// Pushes a `Value` (by reference) onto the Lua stack. + /// + /// Uses 2 stack spaces, does not call `checkstack`. + pub(crate) unsafe fn push_value(&self, value: &Value) -> Result<()> { + let state = self.state(); + match value { + Value::Nil => ffi::lua_pushnil(state), + Value::Boolean(b) => ffi::lua_pushboolean(state, *b as c_int), + Value::LightUserData(ud) => ffi::lua_pushlightuserdata(state, ud.0), + Value::Integer(i) => ffi::lua_pushinteger(state, *i), + Value::Number(n) => ffi::lua_pushnumber(state, *n), + #[cfg(feature = "luau")] + Value::Vector(v) => { + #[cfg(not(feature = "luau-vector4"))] + ffi::lua_pushvector(state, v.x(), v.y(), v.z()); + #[cfg(feature = "luau-vector4")] + ffi::lua_pushvector(state, v.x(), v.y(), v.z(), v.w()); + } + Value::String(s) => self.push_ref(&s.0), + Value::Table(t) => self.push_ref(&t.0), + Value::Function(f) => self.push_ref(&f.0), + Value::Thread(t) => self.push_ref(&t.0), + Value::UserData(ud) => self.push_ref(&ud.0), + Value::Error(err) => { + let protect = !self.unlikely_memory_error(); + push_gc_userdata(state, WrappedFailure::Error(*err.clone()), protect)?; + } + } + Ok(()) + } + + /// Pops a value from the Lua stack. + /// + /// Uses 2 stack spaces, does not call checkstack. + #[doc(hidden)] + pub(crate) unsafe fn pop_value(&self) -> Value { + let state = self.state(); + match ffi::lua_type(state, -1) { + ffi::LUA_TNIL => { + ffi::lua_pop(state, 1); + Nil + } + + ffi::LUA_TBOOLEAN => { + let b = Value::Boolean(ffi::lua_toboolean(state, -1) != 0); + ffi::lua_pop(state, 1); + b } ffi::LUA_TLIGHTUSERDATA => { @@ -2451,7 +2654,7 @@ impl Lua { } } - ffi::LUA_TTHREAD => Value::Thread(Thread::new(self.pop_ref())), + ffi::LUA_TTHREAD => Value::Thread(Thread::new(self, self.pop_ref())), #[cfg(feature = "luau")] ffi::LUA_TBUFFER => { @@ -2554,7 +2757,7 @@ impl Lua { ffi::LUA_TTHREAD => { ffi::lua_xpush(state, self.ref_thread(), idx); - Value::Thread(Thread::new(self.pop_ref_thread())) + Value::Thread(Thread::new(self, self.pop_ref_thread())) } #[cfg(feature = "luau")] @@ -2576,21 +2779,12 @@ impl Lua { } // Pushes a ValueRef value onto the stack, uses 1 stack space, does not call checkstack - pub(crate) unsafe fn push_ref(&self, vref: &ValueRef) { - assert!( - Arc::ptr_eq(&vref.lua.0, &self.0), - "Lua instance passed Value created from a different main Lua state" - ); - ffi::lua_xpush(self.ref_thread(), self.state(), vref.index); - } - - #[cfg(all(feature = "unstable", not(feature = "send")))] - pub(crate) unsafe fn push_owned_ref(&self, vref: &crate::types::OwnedValueRef) { + pub(crate) fn push_ref(&self, vref: &ValueRef) { assert!( - Arc::ptr_eq(&vref.inner, &self.0), + self.weak() == &vref.lua, "Lua instance passed Value created from a different main Lua state" ); - ffi::lua_xpush(self.ref_thread(), self.state(), vref.index); + unsafe { ffi::lua_xpush(self.ref_thread(), self.state(), vref.index) }; } // Pops the topmost element of the stack and stores a reference to it. This pins the object, @@ -2622,27 +2816,13 @@ impl Lua { } } - pub(crate) fn drop_ref_index(&self, index: c_int) { + pub(crate) fn drop_ref(&self, vref: &ValueRef) { unsafe { let ref_thread = self.ref_thread(); ffi::lua_pushnil(ref_thread); - ffi::lua_replace(ref_thread, index); - (*self.extra.get()).ref_free.push(index); - } - } - - #[cfg(all(feature = "unstable", not(feature = "send")))] - pub(crate) fn adopt_owned_ref(&self, vref: crate::types::OwnedValueRef) -> ValueRef { - assert!( - Arc::ptr_eq(&vref.inner, &self.0), - "Lua instance passed Value created from a different main Lua state" - ); - let index = vref.index; - unsafe { - ptr::read(&vref.inner); - mem::forget(vref); + ffi::lua_replace(ref_thread, vref.index); + (*self.extra.get()).ref_free.push(vref.index); } - ValueRef::new(self, index) } #[inline] @@ -2655,9 +2835,95 @@ impl Lua { ffi::lua_pushcfunction(state, error_traceback); } - unsafe fn register_userdata_metatable<'lua, T: 'static>( - &'lua self, - mut registry: UserDataRegistry<'lua, T>, + #[inline] + pub(crate) unsafe fn unlikely_memory_error(&self) -> bool { + // MemoryInfo is empty in module mode so we cannot predict memory limits + match MemoryState::get(self.main_state) { + mem_state if !mem_state.is_null() => (*mem_state).memory_limit() == 0, + #[cfg(feature = "module")] + _ => (*self.extra.get()).skip_memory_check, // Check the special flag (only for module mode) + #[cfg(not(feature = "module"))] + _ => false, + } + } + + pub(crate) unsafe fn make_userdata(&self, data: UserDataVariant) -> Result + where + T: UserData + 'static, + { + self.make_userdata_with_metatable(data, || { + // Check if userdata/metatable is already registered + let type_id = TypeId::of::(); + if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) { + return Ok(table_id as Integer); + } + + // Create new metatable from UserData definition + let mut registry = UserDataRegistry::new(); + T::register(&mut registry); + + self.register_userdata_metatable(registry) + }) + } + + pub(crate) unsafe fn make_any_userdata( + &self, + data: UserDataVariant, + ) -> Result + where + T: 'static, + { + self.make_userdata_with_metatable(data, || { + // Check if userdata/metatable is already registered + let type_id = TypeId::of::(); + if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) { + return Ok(table_id as Integer); + } + + // Create empty metatable + let registry = UserDataRegistry::new(); + self.register_userdata_metatable::(registry) + }) + } + + unsafe fn make_userdata_with_metatable( + &self, + data: UserDataVariant, + get_metatable_id: impl FnOnce() -> Result, + ) -> Result { + let state = self.state(); + let _sg = StackGuard::new(state); + check_stack(state, 3)?; + + // We push metatable first to ensure having correct metatable with `__gc` method + ffi::lua_pushnil(state); + ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, get_metatable_id()?); + let protect = !self.unlikely_memory_error(); + #[cfg(not(feature = "lua54"))] + push_userdata(state, data, protect)?; + #[cfg(feature = "lua54")] + push_userdata_uv(state, data, USER_VALUE_MAXSLOT as c_int, protect)?; + ffi::lua_replace(state, -3); + ffi::lua_setmetatable(state, -2); + + // Set empty environment for Lua 5.1 + #[cfg(any(feature = "lua51", feature = "luajit"))] + if protect { + protect_lua!(state, 1, 1, fn(state) { + ffi::lua_newtable(state); + ffi::lua_setuservalue(state, -2); + })?; + } else { + ffi::lua_newtable(state); + ffi::lua_setuservalue(state, -2); + } + + Ok(AnyUserData(self.pop_ref(), SubtypeId::None)) + } + + unsafe fn register_userdata_metatable( + &self, + mut registry: UserDataRegistry, ) -> Result { let state = self.state(); let _sg = StackGuard::new(state); @@ -2680,7 +2946,8 @@ impl Lua { let mut has_name = false; for (k, f) in registry.meta_fields { has_name = has_name || k == MetaMethod::Type; - mlua_assert!(f(self, 0)? == 1, "field function must return one value"); + let inner = mem::transmute::<&LuaInner, &LuaInner>(self); + mlua_assert!(f(inner, 0)? == 1, "field function must return one value"); rawset_field(state, -2, MetaMethod::validate(&k)?)?; } // Set `__name/__type` if not provided @@ -2705,7 +2972,8 @@ impl Lua { push_table(state, 0, fields_nrec, true)?; } for (k, f) in registry.fields { - mlua_assert!(f(self, 0)? == 1, "field function must return one value"); + let inner = mem::transmute::<&LuaInner, &LuaInner>(self); + mlua_assert!(f(inner, 0)? == 1, "field function must return one value"); rawset_field(state, -2, &k)?; } rawset_field(state, metatable_index, "__index")?; @@ -2786,7 +3054,7 @@ impl Lua { let extra_init = None; #[cfg(not(feature = "luau"))] let extra_init: Option Result<()>> = Some(|state| { - ffi::lua_pushcfunction(state, util::userdata_destructor::>); + ffi::lua_pushcfunction(state, util::userdata_destructor::>); rawset_field(state, -2, "__gc") }); @@ -2835,6 +3103,12 @@ impl Lua { } } + #[inline(always)] + pub(crate) unsafe fn get_userdata_ref(&self, idx: c_int) -> Result> { + let guard = self.lua().lock_arc(); + (*get_userdata::>(self.state(), idx)).try_make_ref(guard) + } + // Returns `TypeId` for the userdata ref, checking that it's registered and not destructed. // // Returns `None` if the userdata is registered but non-static. @@ -2887,18 +3161,8 @@ impl Lua { Ok(type_id) } - // Creates a Function out of a Callback containing a 'static Fn. This is safe ONLY because the - // Fn is 'static, otherwise it could capture 'lua arguments improperly. Without ATCs, we - // cannot easily deal with the "correct" callback type of: - // - // Box Fn(&'lua Lua, MultiValue<'lua>) -> Result>)> - // - // So we instead use a caller provided lifetime, which without the 'static requirement would be - // unsafe. - pub(crate) fn create_callback<'lua>( - &'lua self, - func: Callback<'lua, 'static>, - ) -> Result> { + // Creates a Function out of a Callback containing a 'static Fn. + pub(crate) fn create_callback(&self, func: Callback) -> Result { unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int { // Normal functions can be scoped and therefore destroyed, // so we need to check that the first upvalue is valid @@ -2915,11 +3179,11 @@ impl Lua { return Err(Error::CallbackDestructed); } - let lua: &Lua = mem::transmute((*extra).inner.assume_init_ref()); - let _guard = StateGuard::new(&lua.0, state); + let lua = (*extra).lua().lock(); + let _guard = StateGuard::new(&lua, state); let func = &*(*upvalue).data; - func(lua, nargs) + func(mem::transmute::<&LuaInner, &LuaInner>(&lua), nargs) }) } @@ -2945,10 +3209,7 @@ impl Lua { } #[cfg(feature = "async")] - pub(crate) fn create_async_callback<'lua>( - &'lua self, - func: AsyncCallback<'lua, 'static>, - ) -> Result> { + pub(crate) fn create_async_callback(&self, func: AsyncCallback) -> Result { #[cfg(any( feature = "lua54", feature = "lua53", @@ -2969,14 +3230,17 @@ impl Lua { let extra = (*upvalue).extra.get(); callback_error_ext(state, extra, |nargs| { // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) - let lua: &Lua = mem::transmute((*extra).inner.assume_init_ref()); - let _guard = StateGuard::new(&lua.0, state); + let lua = (*extra).lua(); + // The lock must be already held as the callback is executed + let rawlua = lua.guard_unchecked(); + let rawlua = mem::transmute::<&LuaInner, &LuaInner>(&rawlua); + let _guard = StateGuard::new(rawlua, state); - let args = MultiValue::from_stack_multi(nargs, lua)?; + let args = MultiValue::from_stack_multi(nargs, rawlua)?; let func = &*(*upvalue).data; - let fut = func(lua, args); + let fut = func(rawlua, args); let extra = Arc::clone(&(*upvalue).extra); - let protect = !lua.unlikely_memory_error(); + let protect = !rawlua.unlikely_memory_error(); push_gc_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?; if protect { protect_lua!(state, 1, 1, fn(state) { @@ -2995,11 +3259,13 @@ impl Lua { let extra = (*upvalue).extra.get(); callback_error_ext(state, extra, |_| { // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) - let lua: &Lua = mem::transmute((*extra).inner.assume_init_ref()); - let _guard = StateGuard::new(&lua.0, state); + let lua = (*extra).lua(); + // The lock must be already held as the future is polled + let rawlua = lua.guard_unchecked(); + let _guard = StateGuard::new(&rawlua, state); let fut = &mut (*upvalue).data; - let mut ctx = Context::from_waker(lua.waker()); + let mut ctx = Context::from_waker(rawlua.waker()); match fut.as_mut().poll(&mut ctx) { Poll::Pending => { ffi::lua_pushnil(state); @@ -3017,9 +3283,9 @@ impl Lua { Ok(nresults + 1) } nresults => { - let results = MultiValue::from_stack_multi(nresults, lua)?; + let results = MultiValue::from_stack_multi(nresults, &rawlua)?; ffi::lua_pushinteger(state, nresults as _); - lua.push(lua.create_sequence_from(results)?)?; + rawlua.push(lua.create_sequence_from(results)?)?; Ok(2) } } @@ -3058,17 +3324,18 @@ impl Lua { len as c_int } - let coroutine = self.globals().get::<_, Table>("coroutine")?; + let lua = self.lua(); + let coroutine = lua.globals().get::<_, Table>("coroutine")?; - let env = self.create_table_with_capacity(0, 3)?; + let env = lua.create_table_with_capacity(0, 3)?; env.set("get_poll", get_poll)?; // Cache `yield` function env.set("yield", coroutine.get::<_, Function>("yield")?)?; unsafe { - env.set("unpack", self.create_c_function(unpack)?)?; + env.set("unpack", lua.create_c_function(unpack)?)?; } - self.load( + lua.load( r#" local poll = get_poll(...) while true do @@ -3105,183 +3372,53 @@ impl Lua { pub(crate) unsafe fn set_waker(&self, waker: NonNull) -> NonNull { mem::replace(&mut (*self.extra.get()).waker, waker) } +} - /// Returns internal `Poll::Pending` constant used for executing async callbacks. - #[cfg(feature = "async")] - #[doc(hidden)] - #[inline] - pub fn poll_pending() -> LightUserData { - LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut c_void) - } - - pub(crate) unsafe fn make_userdata(&self, data: UserDataCell) -> Result - where - T: UserData + 'static, - { - self.make_userdata_with_metatable(data, || { - // Check if userdata/metatable is already registered - let type_id = TypeId::of::(); - if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) { - return Ok(table_id as Integer); - } - - // Create new metatable from UserData definition - let mut registry = UserDataRegistry::new(); - T::add_fields(&mut registry); - T::add_methods(&mut registry); - - self.register_userdata_metatable(registry) - }) - } - - pub(crate) unsafe fn make_any_userdata(&self, data: UserDataCell) -> Result - where - T: 'static, - { - self.make_userdata_with_metatable(data, || { - // Check if userdata/metatable is already registered - let type_id = TypeId::of::(); - if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) { - return Ok(table_id as Integer); - } - - // Create empty metatable - let registry = UserDataRegistry::new(); - self.register_userdata_metatable::(registry) - }) +impl WeakLua { + #[inline(always)] + pub(crate) fn lock(&self) -> LuaGuard { + LuaGuard::new(self.0.upgrade().unwrap()) } +} - unsafe fn make_userdata_with_metatable( - &self, - data: UserDataCell, - get_metatable_id: impl FnOnce() -> Result, - ) -> Result { - let state = self.state(); - let _sg = StackGuard::new(state); - check_stack(state, 3)?; - - // We push metatable first to ensure having correct metatable with `__gc` method - ffi::lua_pushnil(state); - ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, get_metatable_id()?); - let protect = !self.unlikely_memory_error(); - #[cfg(not(feature = "lua54"))] - push_userdata(state, data, protect)?; - #[cfg(feature = "lua54")] - push_userdata_uv(state, data, USER_VALUE_MAXSLOT as c_int, protect)?; - ffi::lua_replace(state, -3); - ffi::lua_setmetatable(state, -2); - - // Set empty environment for Lua 5.1 - #[cfg(any(feature = "lua51", feature = "luajit"))] - if protect { - protect_lua!(state, 1, 1, fn(state) { - ffi::lua_newtable(state); - ffi::lua_setuservalue(state, -2); - })?; - } else { - ffi::lua_newtable(state); - ffi::lua_setuservalue(state, -2); - } - - Ok(AnyUserData(self.pop_ref(), SubtypeId::None)) +impl PartialEq for WeakLua { + fn eq(&self, other: &Self) -> bool { + Weak::ptr_eq(&self.0, &other.0) } +} - // Luau version located in `luau/mod.rs` - #[cfg(not(feature = "luau"))] - fn disable_c_modules(&self) -> Result<()> { - let package: Table = self.globals().get("package")?; - - package.set( - "loadlib", - self.create_function(|_, ()| -> Result<()> { - Err(Error::SafetyError( - "package.loadlib is disabled in safe mode".to_string(), - )) - })?, - )?; - - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - let searchers: Table = package.get("searchers")?; - #[cfg(any(feature = "lua51", feature = "luajit"))] - let searchers: Table = package.get("loaders")?; - - let loader = self.create_function(|_, ()| Ok("\n\tcan't load C modules in safe mode"))?; - - // The third and fourth searchers looks for a loader as a C library - searchers.raw_set(3, loader)?; - searchers.raw_remove(4)?; - - Ok(()) - } +impl Eq for WeakLua {} - pub(crate) unsafe fn try_from_ptr(state: *mut ffi::lua_State) -> Option { - let extra = extra_data(state); - if extra.is_null() { - return None; - } - Some(Lua(Arc::clone((*extra).inner.assume_init_ref()))) +impl LuaGuard { + pub(crate) fn new(handle: Arc>) -> Self { + Self(handle.lock_arc()) } +} - #[inline] - pub(crate) unsafe fn unlikely_memory_error(&self) -> bool { - // MemoryInfo is empty in module mode so we cannot predict memory limits - match MemoryState::get(self.main_state) { - mem_state if !mem_state.is_null() => (*mem_state).memory_limit() == 0, - #[cfg(feature = "module")] - _ => (*self.extra.get()).skip_memory_check, // Check the special flag (only for module mode) - #[cfg(not(feature = "module"))] - _ => false, - } - } +impl Deref for LuaGuard { + type Target = LuaInner; - #[cfg(feature = "unstable")] - #[inline] - pub(crate) fn clone(&self) -> Arc { - Arc::clone(&self.0) + fn deref(&self) -> &Self::Target { + &*self.0 } } -impl LuaInner { - #[inline(always)] - pub(crate) fn state(&self) -> *mut ffi::lua_State { - self.state.get() - } +impl ExtraData { + // Index of `error_traceback` function in auxiliary thread stack + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + const ERROR_TRACEBACK_IDX: c_int = 1; - #[cfg(feature = "luau")] #[inline(always)] - pub(crate) fn main_state(&self) -> *mut ffi::lua_State { - self.main_state + const fn lua(&self) -> &Lua { + unsafe { mem::transmute(self.inner.assume_init_ref()) } } #[inline(always)] - pub(crate) fn ref_thread(&self) -> *mut ffi::lua_State { - unsafe { (*self.extra.get()).ref_thread } - } - - #[inline] - pub(crate) fn pop_multivalue_from_pool(&self) -> Option> { - let extra = unsafe { &mut *self.extra.get() }; - extra.multivalue_pool.pop() - } - - #[inline] - pub(crate) fn push_multivalue_to_pool(&self, mut multivalue: VecDeque) { - let extra = unsafe { &mut *self.extra.get() }; - if extra.multivalue_pool.len() < MULTIVALUE_POOL_SIZE { - multivalue.clear(); - extra - .multivalue_pool - .push(unsafe { mem::transmute(multivalue) }); - } + const fn weak(&self) -> &WeakLua { + unsafe { mem::transmute(self.weak.assume_init_ref()) } } } -impl ExtraData { - // Index of `error_traceback` function in auxiliary thread stack - #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] - const ERROR_TRACEBACK_IDX: c_int = 1; -} - struct StateGuard<'a>(&'a LuaInner, *mut ffi::lua_State); impl<'a> StateGuard<'a> { diff --git a/src/multi.rs b/src/multi.rs index 00f9e627..e56aeb9e 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -5,7 +5,7 @@ use std::os::raw::c_int; use std::result::Result as StdResult; use crate::error::Result; -use crate::lua::Lua; +use crate::lua::{Lua, LuaInner}; use crate::util::check_stack; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil}; @@ -13,7 +13,7 @@ use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil /// on success, or in the case of an error, returning `nil` and an error message. impl IntoLuaMulti for StdResult { #[inline] - fn into_lua_multi(self, lua: &Lua) -> Result> { + fn into_lua_multi(self, lua: &Lua) -> Result { match self { Ok(val) => (val,).into_lua_multi(lua), Err(err) => (Nil, err).into_lua_multi(lua), @@ -21,7 +21,7 @@ impl IntoLuaMulti for StdResult { } #[inline] - unsafe fn push_into_stack_multi(self, lua: &Lua) -> Result { + unsafe fn push_into_stack_multi(self, lua: &LuaInner) -> Result { match self { Ok(val) => (val,).push_into_stack_multi(lua), Err(err) => (Nil, err).push_into_stack_multi(lua), @@ -31,7 +31,7 @@ impl IntoLuaMulti for StdResult { impl IntoLuaMulti for StdResult<(), E> { #[inline] - fn into_lua_multi(self, lua: &Lua) -> Result> { + fn into_lua_multi(self, lua: &Lua) -> Result { match self { Ok(_) => Ok(MultiValue::new()), Err(err) => (Nil, err).into_lua_multi(lua), @@ -39,7 +39,7 @@ impl IntoLuaMulti for StdResult<(), E> { } #[inline] - unsafe fn push_into_stack_multi(self, lua: &Lua) -> Result { + unsafe fn push_into_stack_multi(self, lua: &LuaInner) -> Result { match self { Ok(_) => Ok(0), Err(err) => (Nil, err).push_into_stack_multi(lua), @@ -49,39 +49,34 @@ impl IntoLuaMulti for StdResult<(), E> { impl IntoLuaMulti for T { #[inline] - fn into_lua_multi(self, lua: &Lua) -> Result> { + fn into_lua_multi(self, lua: &Lua) -> Result { let mut v = MultiValue::with_lua_and_capacity(lua, 1); v.push_back(self.into_lua(lua)?); Ok(v) } #[inline] - unsafe fn push_into_stack_multi(self, lua: &Lua) -> Result { + unsafe fn push_into_stack_multi(self, lua: &LuaInner) -> Result { self.push_into_stack(lua)?; Ok(1) } } -impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for T { +impl FromLuaMulti for T { #[inline] - fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result { + fn from_lua_multi(mut values: MultiValue, lua: &Lua) -> Result { T::from_lua(values.pop_front().unwrap_or(Nil), lua) } #[inline] - fn from_lua_args( - mut args: MultiValue<'lua>, - i: usize, - to: Option<&str>, - lua: &'lua Lua, - ) -> Result { + fn from_lua_args(mut args: MultiValue, i: usize, to: Option<&str>, lua: &Lua) -> Result { T::from_lua_arg(args.pop_front().unwrap_or(Nil), i, to, lua) } #[inline] - unsafe fn from_stack_multi(nvals: c_int, lua: &'lua Lua) -> Result { + unsafe fn from_stack_multi(nvals: c_int, lua: &LuaInner) -> Result { if nvals == 0 { - return T::from_lua(Nil, lua); + return T::from_lua(Nil, lua.lua()); } T::from_stack(-nvals, lua) } @@ -91,25 +86,25 @@ impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for T { nargs: c_int, i: usize, to: Option<&str>, - lua: &'lua Lua, + lua: &LuaInner, ) -> Result { if nargs == 0 { - return T::from_lua_arg(Nil, i, to, lua); + return T::from_lua_arg(Nil, i, to, lua.lua()); } T::from_stack_arg(-nargs, i, to, lua) } } -impl IntoLuaMulti for MultiValue<'_> { +impl IntoLuaMulti for MultiValue { #[inline] - fn into_lua_multi(self, _: &Lua) -> Result> { + fn into_lua_multi(self, _: &Lua) -> Result { unsafe { Ok(transmute(self)) } } } -impl<'lua> FromLuaMulti<'lua> for MultiValue<'lua> { +impl FromLuaMulti for MultiValue { #[inline] - fn from_lua_multi(values: MultiValue<'lua>, _: &'lua Lua) -> Result { + fn from_lua_multi(values: MultiValue, _: &Lua) -> Result { Ok(values) } } @@ -187,16 +182,16 @@ impl DerefMut for Variadic { impl IntoLuaMulti for Variadic { #[inline] - fn into_lua_multi(self, lua: &Lua) -> Result> { + fn into_lua_multi(self, lua: &Lua) -> Result { let mut values = MultiValue::with_lua_and_capacity(lua, self.0.len()); values.extend_from_values(self.0.into_iter().map(|val| val.into_lua(lua)))?; Ok(values) } } -impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for Variadic { +impl FromLuaMulti for Variadic { #[inline] - fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result { + fn from_lua_multi(mut values: MultiValue, lua: &Lua) -> Result { values .drain(..) .map(|val| T::from_lua(val, lua)) @@ -209,24 +204,24 @@ macro_rules! impl_tuple { () => ( impl IntoLuaMulti for () { #[inline] - fn into_lua_multi(self, lua: &Lua) -> Result> { + fn into_lua_multi(self, lua: &Lua) -> Result { Ok(MultiValue::with_lua_and_capacity(lua, 0)) } #[inline] - unsafe fn push_into_stack_multi(self, _lua: &Lua) -> Result { + unsafe fn push_into_stack_multi(self, _lua: &LuaInner) -> Result { Ok(0) } } - impl<'lua> FromLuaMulti<'lua> for () { + impl FromLuaMulti for () { #[inline] - fn from_lua_multi(_values: MultiValue<'lua>, _lua: &'lua Lua) -> Result { + fn from_lua_multi(_values: MultiValue, _lua: &Lua) -> Result { Ok(()) } #[inline] - unsafe fn from_stack_multi(nvals: c_int, lua: &'lua Lua) -> Result { + unsafe fn from_stack_multi(nvals: c_int, lua: &LuaInner) -> Result { if nvals > 0 { ffi::lua_pop(lua.state(), nvals); } @@ -242,7 +237,7 @@ macro_rules! impl_tuple { { #[allow(unused_mut, non_snake_case)] #[inline] - fn into_lua_multi(self, lua: &Lua) -> Result> { + fn into_lua_multi(self, lua: &Lua) -> Result { let ($($name,)* $last,) = self; let mut results = $last.into_lua_multi(lua)?; @@ -252,7 +247,7 @@ macro_rules! impl_tuple { #[allow(non_snake_case)] #[inline] - unsafe fn push_into_stack_multi(self, lua: &Lua) -> Result { + unsafe fn push_into_stack_multi(self, lua: &LuaInner) -> Result { let ($($name,)* $last,) = self; let mut nresults = 0; $( @@ -268,13 +263,13 @@ macro_rules! impl_tuple { } } - impl<'lua, $($name,)* $last> FromLuaMulti<'lua> for ($($name,)* $last,) - where $($name: FromLua<'lua>,)* - $last: FromLuaMulti<'lua> + impl<$($name,)* $last> FromLuaMulti for ($($name,)* $last,) + where $($name: FromLua,)* + $last: FromLuaMulti { #[allow(unused_mut, non_snake_case)] #[inline] - fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result { + fn from_lua_multi(mut values: MultiValue, lua: &Lua) -> Result { $(let $name = FromLua::from_lua(values.pop_front().unwrap_or(Nil), lua)?;)* let $last = FromLuaMulti::from_lua_multi(values, lua)?; Ok(($($name,)* $last,)) @@ -282,7 +277,7 @@ macro_rules! impl_tuple { #[allow(unused_mut, non_snake_case)] #[inline] - fn from_lua_args(mut args: MultiValue<'lua>, mut i: usize, to: Option<&str>, lua: &'lua Lua) -> Result { + fn from_lua_args(mut args: MultiValue, mut i: usize, to: Option<&str>, lua: &Lua) -> Result { $( let $name = FromLua::from_lua_arg(args.pop_front().unwrap_or(Nil), i, to, lua)?; i += 1; @@ -293,13 +288,13 @@ macro_rules! impl_tuple { #[allow(unused_mut, non_snake_case)] #[inline] - unsafe fn from_stack_multi(mut nvals: c_int, lua: &'lua Lua) -> Result { + unsafe fn from_stack_multi(mut nvals: c_int, lua: &LuaInner) -> Result { $( let $name = if nvals > 0 { nvals -= 1; FromLua::from_stack(-(nvals + 1), lua) } else { - FromLua::from_lua(Nil, lua) + FromLua::from_lua(Nil, lua.lua()) }?; )* let $last = FromLuaMulti::from_stack_multi(nvals, lua)?; @@ -308,13 +303,13 @@ macro_rules! impl_tuple { #[allow(unused_mut, non_snake_case)] #[inline] - unsafe fn from_stack_args(mut nargs: c_int, mut i: usize, to: Option<&str>, lua: &'lua Lua) -> Result { + unsafe fn from_stack_args(mut nargs: c_int, mut i: usize, to: Option<&str>, lua: &LuaInner) -> Result { $( let $name = if nargs > 0 { nargs -= 1; FromLua::from_stack_arg(-(nargs + 1), i, to, lua) } else { - FromLua::from_lua_arg(Nil, i, to, lua) + FromLua::from_lua_arg(Nil, i, to, lua.lua()) }?; i += 1; )* diff --git a/src/prelude.rs b/src/prelude.rs index e4459c21..c2929f79 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -35,10 +35,3 @@ pub use crate::{ DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, SerializeOptions as LuaSerializeOptions, }; - -#[cfg(feature = "unstable")] -#[doc(no_inline)] -pub use crate::{ - OwnedAnyUserData as LuaOwnedAnyUserData, OwnedFunction as LuaOwnedFunction, - OwnedString as LuaOwnedString, OwnedTable as LuaOwnedTable, OwnedThread as LuaOwnedThread, -}; diff --git a/src/serde/de.rs b/src/serde/de.rs index 3cc182d0..542180f8 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -14,8 +14,8 @@ use crate::value::Value; /// A struct for deserializing Lua values into Rust values. #[derive(Debug)] -pub struct Deserializer<'lua> { - value: Value<'lua>, +pub struct Deserializer { + value: Value, options: Options, visited: Rc>>, } @@ -93,14 +93,14 @@ impl Options { } } -impl<'lua> Deserializer<'lua> { +impl Deserializer { /// Creates a new Lua Deserializer for the `Value`. - pub fn new(value: Value<'lua>) -> Self { + pub fn new(value: Value) -> Self { Self::new_with_options(value, Options::default()) } /// Creates a new Lua Deserializer for the `Value` with custom options. - pub fn new_with_options(value: Value<'lua>, options: Options) -> Self { + pub fn new_with_options(value: Value, options: Options) -> Self { Deserializer { value, options, @@ -109,7 +109,7 @@ impl<'lua> Deserializer<'lua> { } fn from_parts( - value: Value<'lua>, + value: Value, options: Options, visited: Rc>>, ) -> Self { @@ -121,7 +121,7 @@ impl<'lua> Deserializer<'lua> { } } -impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> { +impl<'de> serde::Deserializer<'de> for Deserializer { type Error = Error; #[inline] @@ -150,8 +150,9 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> { } #[cfg(feature = "luau")] Value::UserData(ud) if ud.1 == crate::types::SubtypeId::Buffer => unsafe { + let lua = ud.0.lua.lock(); let mut size = 0usize; - let buf = ffi::lua_tobuffer(ud.0.lua.ref_thread(), ud.0.index, &mut size); + let buf = ffi::lua_tobuffer(lua.ref_thread(), ud.0.index, &mut size); mlua_assert!(!buf.is_null(), "invalid Luau buffer"); let buf = std::slice::from_raw_parts(buf as *const u8, size); visitor.visit_bytes(buf) @@ -394,13 +395,13 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> { } } -struct SeqDeserializer<'lua> { - seq: TableSequence<'lua, Value<'lua>>, +struct SeqDeserializer { + seq: TableSequence, options: Options, visited: Rc>>, } -impl<'lua, 'de> de::SeqAccess<'de> for SeqDeserializer<'lua> { +impl<'de> de::SeqAccess<'de> for SeqDeserializer { type Error = Error; fn next_element_seed(&mut self, seed: T) -> Result> @@ -466,13 +467,13 @@ impl<'de> de::SeqAccess<'de> for VecDeserializer { } } -pub(crate) enum MapPairs<'lua> { - Iter(TablePairs<'lua, Value<'lua>, Value<'lua>>), - Vec(Vec<(Value<'lua>, Value<'lua>)>), +pub(crate) enum MapPairs { + Iter(TablePairs), + Vec(Vec<(Value, Value)>), } -impl<'lua> MapPairs<'lua> { - pub(crate) fn new(t: Table<'lua>, sort_keys: bool) -> Result { +impl MapPairs { + pub(crate) fn new(t: Table, sort_keys: bool) -> Result { if sort_keys { let mut pairs = t.pairs::().collect::>>()?; pairs.sort_by(|(a, _), (b, _)| b.cmp(a)); // reverse order as we pop values from the end @@ -497,8 +498,8 @@ impl<'lua> MapPairs<'lua> { } } -impl<'lua> Iterator for MapPairs<'lua> { - type Item = Result<(Value<'lua>, Value<'lua>)>; +impl Iterator for MapPairs { + type Item = Result<(Value, Value)>; fn next(&mut self) -> Option { match self { @@ -508,15 +509,15 @@ impl<'lua> Iterator for MapPairs<'lua> { } } -struct MapDeserializer<'lua> { - pairs: MapPairs<'lua>, - value: Option>, +struct MapDeserializer { + pairs: MapPairs, + value: Option, options: Options, visited: Rc>>, processed: usize, } -impl<'lua, 'de> de::MapAccess<'de> for MapDeserializer<'lua> { +impl<'de> de::MapAccess<'de> for MapDeserializer { type Error = Error; fn next_key_seed(&mut self, seed: T) -> Result> @@ -566,16 +567,16 @@ impl<'lua, 'de> de::MapAccess<'de> for MapDeserializer<'lua> { } } -struct EnumDeserializer<'lua> { +struct EnumDeserializer { variant: StdString, - value: Option>, + value: Option, options: Options, visited: Rc>>, } -impl<'lua, 'de> de::EnumAccess<'de> for EnumDeserializer<'lua> { +impl<'de> de::EnumAccess<'de> for EnumDeserializer { type Error = Error; - type Variant = VariantDeserializer<'lua>; + type Variant = VariantDeserializer; fn variant_seed(self, seed: T) -> Result<(T::Value, Self::Variant)> where @@ -591,13 +592,13 @@ impl<'lua, 'de> de::EnumAccess<'de> for EnumDeserializer<'lua> { } } -struct VariantDeserializer<'lua> { - value: Option>, +struct VariantDeserializer { + value: Option, options: Options, visited: Rc>>, } -impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> { +impl<'de> de::VariantAccess<'de> for VariantDeserializer { type Error = Error; fn unit_variant(self) -> Result<()> { diff --git a/src/serde/mod.rs b/src/serde/mod.rs index 877b8896..94952810 100644 --- a/src/serde/mod.rs +++ b/src/serde/mod.rs @@ -99,7 +99,7 @@ pub trait LuaSerdeExt: Sealed { /// "#).exec() /// } /// ``` - fn to_value<'lua, T: Serialize + ?Sized>(&'lua self, t: &T) -> Result>; + fn to_value(&self, t: &T) -> Result; /// Converts `T` into a [`Value`] instance with options. /// @@ -124,7 +124,7 @@ pub trait LuaSerdeExt: Sealed { /// "#).exec() /// } /// ``` - fn to_value_with<'lua, T>(&'lua self, t: &T, options: ser::Options) -> Result> + fn to_value_with(&self, t: &T, options: ser::Options) -> Result where T: Serialize + ?Sized; @@ -199,20 +199,21 @@ impl LuaSerdeExt for Lua { } fn array_metatable(&self) -> Table { + let lua = self.lock(); unsafe { - push_array_metatable(self.ref_thread()); - Table(self.pop_ref_thread()) + push_array_metatable(lua.ref_thread()); + Table(lua.pop_ref_thread()) } } - fn to_value<'lua, T>(&'lua self, t: &T) -> Result> + fn to_value(&self, t: &T) -> Result where T: Serialize + ?Sized, { t.serialize(ser::Serializer::new(self)) } - fn to_value_with<'lua, T>(&'lua self, t: &T, options: ser::Options) -> Result> + fn to_value_with(&self, t: &T, options: ser::Options) -> Result where T: Serialize + ?Sized, { diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 6ee57a1f..df3a16ce 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -8,8 +8,8 @@ use crate::value::{IntoLua, Value}; /// A struct for serializing Rust values into Lua values. #[derive(Debug)] -pub struct Serializer<'lua> { - lua: &'lua Lua, +pub struct Serializer<'a> { + lua: &'a Lua, options: Options, } @@ -109,14 +109,14 @@ impl Options { } } -impl<'lua> Serializer<'lua> { +impl<'a> Serializer<'a> { /// Creates a new Lua Serializer with default options. - pub fn new(lua: &'lua Lua) -> Self { + pub fn new(lua: &'a Lua) -> Self { Self::new_with_options(lua, Options::default()) } /// Creates a new Lua Serializer with custom options. - pub fn new_with_options(lua: &'lua Lua, options: Options) -> Self { + pub fn new_with_options(lua: &'a Lua, options: Options) -> Self { Serializer { lua, options } } } @@ -124,28 +124,28 @@ impl<'lua> Serializer<'lua> { macro_rules! lua_serialize_number { ($name:ident, $t:ty) => { #[inline] - fn $name(self, value: $t) -> Result> { + fn $name(self, value: $t) -> Result { value.into_lua(self.lua) } }; } -impl<'lua> ser::Serializer for Serializer<'lua> { - type Ok = Value<'lua>; +impl<'a> ser::Serializer for Serializer<'a> { + type Ok = Value; type Error = Error; // Associated types for keeping track of additional state while serializing // compound data structures like sequences and maps. - type SerializeSeq = SerializeSeq<'lua>; - type SerializeTuple = SerializeSeq<'lua>; - type SerializeTupleStruct = SerializeSeq<'lua>; - type SerializeTupleVariant = SerializeTupleVariant<'lua>; - type SerializeMap = SerializeMap<'lua>; - type SerializeStruct = SerializeStruct<'lua>; - type SerializeStructVariant = SerializeStructVariant<'lua>; + type SerializeSeq = SerializeSeq<'a>; + type SerializeTuple = SerializeSeq<'a>; + type SerializeTupleStruct = SerializeSeq<'a>; + type SerializeTupleVariant = SerializeTupleVariant<'a>; + type SerializeMap = SerializeMap<'a>; + type SerializeStruct = SerializeStruct<'a>; + type SerializeStructVariant = SerializeStructVariant<'a>; #[inline] - fn serialize_bool(self, value: bool) -> Result> { + fn serialize_bool(self, value: bool) -> Result { Ok(Value::Boolean(value)) } @@ -164,22 +164,22 @@ impl<'lua> ser::Serializer for Serializer<'lua> { lua_serialize_number!(serialize_f64, f64); #[inline] - fn serialize_char(self, value: char) -> Result> { + fn serialize_char(self, value: char) -> Result { self.serialize_str(&value.to_string()) } #[inline] - fn serialize_str(self, value: &str) -> Result> { + fn serialize_str(self, value: &str) -> Result { self.lua.create_string(value).map(Value::String) } #[inline] - fn serialize_bytes(self, value: &[u8]) -> Result> { + fn serialize_bytes(self, value: &[u8]) -> Result { self.lua.create_string(value).map(Value::String) } #[inline] - fn serialize_none(self) -> Result> { + fn serialize_none(self) -> Result { if self.options.serialize_none_to_null { Ok(self.lua.null()) } else { @@ -188,7 +188,7 @@ impl<'lua> ser::Serializer for Serializer<'lua> { } #[inline] - fn serialize_some(self, value: &T) -> Result> + fn serialize_some(self, value: &T) -> Result where T: Serialize + ?Sized, { @@ -196,7 +196,7 @@ impl<'lua> ser::Serializer for Serializer<'lua> { } #[inline] - fn serialize_unit(self) -> Result> { + fn serialize_unit(self) -> Result { if self.options.serialize_unit_to_null { Ok(self.lua.null()) } else { @@ -205,7 +205,7 @@ impl<'lua> ser::Serializer for Serializer<'lua> { } #[inline] - fn serialize_unit_struct(self, _name: &'static str) -> Result> { + fn serialize_unit_struct(self, _name: &'static str) -> Result { if self.options.serialize_unit_to_null { Ok(self.lua.null()) } else { @@ -219,12 +219,12 @@ impl<'lua> ser::Serializer for Serializer<'lua> { _name: &'static str, _variant_index: u32, variant: &'static str, - ) -> Result> { + ) -> Result { self.serialize_str(variant) } #[inline] - fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result> + fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result where T: Serialize + ?Sized, { @@ -238,7 +238,7 @@ impl<'lua> ser::Serializer for Serializer<'lua> { _variant_index: u32, variant: &'static str, value: &T, - ) -> Result> + ) -> Result where T: Serialize + ?Sized, { @@ -255,7 +255,7 @@ impl<'lua> ser::Serializer for Serializer<'lua> { if self.options.set_array_metatable { table.set_metatable(Some(self.lua.array_metatable())); } - Ok(SerializeSeq::new(table, self.options)) + Ok(SerializeSeq::new(self.lua, table, self.options)) } #[inline] @@ -286,6 +286,7 @@ impl<'lua> ser::Serializer for Serializer<'lua> { _len: usize, ) -> Result { Ok(SerializeTupleVariant { + lua: self.lua, variant, table: self.lua.create_table()?, options: self.options, @@ -295,6 +296,7 @@ impl<'lua> ser::Serializer for Serializer<'lua> { #[inline] fn serialize_map(self, len: Option) -> Result { Ok(SerializeMap { + lua: self.lua, key: None, table: self.lua.create_table_with_capacity(0, len.unwrap_or(0))?, options: self.options, @@ -330,6 +332,7 @@ impl<'lua> ser::Serializer for Serializer<'lua> { len: usize, ) -> Result { Ok(SerializeStructVariant { + lua: self.lua, variant, table: self.lua.create_table_with_capacity(0, len)?, options: self.options, @@ -338,19 +341,19 @@ impl<'lua> ser::Serializer for Serializer<'lua> { } #[doc(hidden)] -pub struct SerializeSeq<'lua> { - lua: &'lua Lua, +pub struct SerializeSeq<'a> { + lua: &'a Lua, #[cfg(feature = "luau")] vector: Option, - table: Option>, + table: Option
, next: usize, options: Options, } -impl<'lua> SerializeSeq<'lua> { - const fn new(table: Table<'lua>, options: Options) -> Self { +impl<'a> SerializeSeq<'a> { + fn new(lua: &'a Lua, table: Table, options: Options) -> Self { Self { - lua: table.0.lua, + lua, #[cfg(feature = "luau")] vector: None, table: Some(table), @@ -360,7 +363,7 @@ impl<'lua> SerializeSeq<'lua> { } #[cfg(feature = "luau")] - const fn new_vector(lua: &'lua Lua, options: Options) -> Self { + const fn new_vector(lua: &'a Lua, options: Options) -> Self { Self { lua, vector: Some(crate::types::Vector::zero()), @@ -371,8 +374,8 @@ impl<'lua> SerializeSeq<'lua> { } } -impl<'lua> ser::SerializeSeq for SerializeSeq<'lua> { - type Ok = Value<'lua>; +impl ser::SerializeSeq for SerializeSeq<'_> { + type Ok = Value; type Error = Error; fn serialize_element(&mut self, value: &T) -> Result<()> @@ -386,13 +389,13 @@ impl<'lua> ser::SerializeSeq for SerializeSeq<'lua> { Ok(()) } - fn end(self) -> Result> { + fn end(self) -> Result { Ok(Value::Table(self.table.unwrap())) } } -impl<'lua> ser::SerializeTuple for SerializeSeq<'lua> { - type Ok = Value<'lua>; +impl ser::SerializeTuple for SerializeSeq<'_> { + type Ok = Value; type Error = Error; fn serialize_element(&mut self, value: &T) -> Result<()> @@ -402,13 +405,13 @@ impl<'lua> ser::SerializeTuple for SerializeSeq<'lua> { ser::SerializeSeq::serialize_element(self, value) } - fn end(self) -> Result> { + fn end(self) -> Result { ser::SerializeSeq::end(self) } } -impl<'lua> ser::SerializeTupleStruct for SerializeSeq<'lua> { - type Ok = Value<'lua>; +impl ser::SerializeTupleStruct for SerializeSeq<'_> { + type Ok = Value; type Error = Error; fn serialize_field(&mut self, value: &T) -> Result<()> @@ -426,7 +429,7 @@ impl<'lua> ser::SerializeTupleStruct for SerializeSeq<'lua> { ser::SerializeSeq::serialize_element(self, value) } - fn end(self) -> Result> { + fn end(self) -> Result { #[cfg(feature = "luau")] if let Some(vector) = self.vector { return Ok(Value::Vector(vector)); @@ -436,49 +439,49 @@ impl<'lua> ser::SerializeTupleStruct for SerializeSeq<'lua> { } #[doc(hidden)] -pub struct SerializeTupleVariant<'lua> { +pub struct SerializeTupleVariant<'a> { + lua: &'a Lua, variant: &'static str, - table: Table<'lua>, + table: Table, options: Options, } -impl<'lua> ser::SerializeTupleVariant for SerializeTupleVariant<'lua> { - type Ok = Value<'lua>; +impl ser::SerializeTupleVariant for SerializeTupleVariant<'_> { + type Ok = Value; type Error = Error; fn serialize_field(&mut self, value: &T) -> Result<()> where T: Serialize + ?Sized, { - let lua = self.table.0.lua; - self.table.raw_push(lua.to_value_with(value, self.options)?) + self.table + .raw_push(self.lua.to_value_with(value, self.options)?) } - fn end(self) -> Result> { - let lua = self.table.0.lua; - let table = lua.create_table()?; + fn end(self) -> Result { + let table = self.lua.create_table()?; table.raw_set(self.variant, self.table)?; Ok(Value::Table(table)) } } #[doc(hidden)] -pub struct SerializeMap<'lua> { - table: Table<'lua>, - key: Option>, +pub struct SerializeMap<'a> { + lua: &'a Lua, + table: Table, + key: Option, options: Options, } -impl<'lua> ser::SerializeMap for SerializeMap<'lua> { - type Ok = Value<'lua>; +impl ser::SerializeMap for SerializeMap<'_> { + type Ok = Value; type Error = Error; fn serialize_key(&mut self, key: &T) -> Result<()> where T: Serialize + ?Sized, { - let lua = self.table.0.lua; - self.key = Some(lua.to_value_with(key, self.options)?); + self.key = Some(self.lua.to_value_with(key, self.options)?); Ok(()) } @@ -486,29 +489,28 @@ impl<'lua> ser::SerializeMap for SerializeMap<'lua> { where T: Serialize + ?Sized, { - let lua = self.table.0.lua; let key = mlua_expect!( self.key.take(), "serialize_value called before serialize_key" ); - let value = lua.to_value_with(value, self.options)?; + let value = self.lua.to_value_with(value, self.options)?; self.table.raw_set(key, value) } - fn end(self) -> Result> { + fn end(self) -> Result { Ok(Value::Table(self.table)) } } #[doc(hidden)] -pub struct SerializeStruct<'lua> { - lua: &'lua Lua, - inner: Option>, +pub struct SerializeStruct<'a> { + lua: &'a Lua, + inner: Option, options: Options, } -impl<'lua> ser::SerializeStruct for SerializeStruct<'lua> { - type Ok = Value<'lua>; +impl ser::SerializeStruct for SerializeStruct<'_> { + type Ok = Value; type Error = Error; fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> @@ -529,7 +531,7 @@ impl<'lua> ser::SerializeStruct for SerializeStruct<'lua> { Ok(()) } - fn end(self) -> Result> { + fn end(self) -> Result { match self.inner { Some(table @ Value::Table(_)) => Ok(table), Some(value) if self.options.detect_serde_json_arbitrary_precision => { @@ -551,29 +553,28 @@ impl<'lua> ser::SerializeStruct for SerializeStruct<'lua> { } #[doc(hidden)] -pub struct SerializeStructVariant<'lua> { +pub struct SerializeStructVariant<'a> { + lua: &'a Lua, variant: &'static str, - table: Table<'lua>, + table: Table, options: Options, } -impl<'lua> ser::SerializeStructVariant for SerializeStructVariant<'lua> { - type Ok = Value<'lua>; +impl ser::SerializeStructVariant for SerializeStructVariant<'_> { + type Ok = Value; type Error = Error; fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> where T: Serialize + ?Sized, { - let lua = self.table.0.lua; self.table - .raw_set(key, lua.to_value_with(value, self.options)?)?; + .raw_set(key, self.lua.to_value_with(value, self.options)?)?; Ok(()) } - fn end(self) -> Result> { - let lua = self.table.0.lua; - let table = lua.create_table_with_capacity(0, 1)?; + fn end(self) -> Result { + let table = self.lua.create_table_with_capacity(0, 1)?; table.raw_set(self.variant, self.table)?; Ok(Value::Table(table)) } diff --git a/src/string.rs b/src/string.rs index add8bdcd..cde30eef 100644 --- a/src/string.rs +++ b/src/string.rs @@ -17,30 +17,9 @@ use crate::types::ValueRef; /// /// Unlike Rust strings, Lua strings may not be valid UTF-8. #[derive(Clone)] -pub struct String<'lua>(pub(crate) ValueRef<'lua>); +pub struct String(pub(crate) ValueRef); -/// Owned handle to an internal Lua string. -/// -/// The owned handle holds a *strong* reference to the current Lua instance. -/// Be warned, if you place it into a Lua type (eg. [`UserData`] or a Rust callback), it is *very easy* -/// to accidentally cause reference cycles that would prevent destroying Lua instance. -/// -/// [`UserData`]: crate::UserData -#[cfg(feature = "unstable")] -#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] -#[derive(Clone)] -pub struct OwnedString(pub(crate) crate::types::OwnedValueRef); - -#[cfg(feature = "unstable")] -impl OwnedString { - /// Get borrowed handle to the underlying Lua string. - #[cfg_attr(feature = "send", allow(unused))] - pub const fn to_ref(&self) -> String { - String(self.0.to_ref()) - } -} - -impl<'lua> String<'lua> { +impl String { /// Get a `&str` slice if the Lua string is valid UTF-8. /// /// # Examples @@ -116,7 +95,8 @@ impl<'lua> String<'lua> { /// Get the bytes that make up this string, including the trailing nul byte. pub fn as_bytes_with_nul(&self) -> &[u8] { - let ref_thread = self.0.lua.ref_thread(); + let lua = self.0.lua.lock(); + let ref_thread = lua.ref_thread(); unsafe { mlua_debug_assert!( ffi::lua_type(ref_thread, self.0.index) == ffi::LUA_TSTRING, @@ -141,17 +121,9 @@ impl<'lua> String<'lua> { pub fn to_pointer(&self) -> *const c_void { self.0.to_pointer() } - - /// Convert this handle to owned version. - #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] - #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] - #[inline] - pub fn into_owned(self) -> OwnedString { - OwnedString(self.0.into_owned()) - } } -impl<'lua> fmt::Debug for String<'lua> { +impl fmt::Debug for String { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let bytes = self.as_bytes(); // Check if the string is valid utf8 @@ -180,13 +152,13 @@ impl<'lua> fmt::Debug for String<'lua> { } } -impl<'lua> AsRef<[u8]> for String<'lua> { +impl AsRef<[u8]> for String { fn as_ref(&self) -> &[u8] { self.as_bytes() } } -impl<'lua> Borrow<[u8]> for String<'lua> { +impl Borrow<[u8]> for String { fn borrow(&self) -> &[u8] { self.as_bytes() } @@ -200,7 +172,7 @@ impl<'lua> Borrow<[u8]> for String<'lua> { // The only downside is that this disallows a comparison with `Cow`, as that only implements // `AsRef`, which collides with this impl. Requiring `AsRef` would fix that, but limit us // in other ways. -impl<'lua, T> PartialEq for String<'lua> +impl PartialEq for String where T: AsRef<[u8]> + ?Sized, { @@ -209,16 +181,16 @@ where } } -impl<'lua> Eq for String<'lua> {} +impl Eq for String {} -impl<'lua> Hash for String<'lua> { +impl Hash for String { fn hash(&self, state: &mut H) { self.as_bytes().hash(state); } } #[cfg(feature = "serialize")] -impl<'lua> Serialize for String<'lua> { +impl Serialize for String { fn serialize(&self, serializer: S) -> StdResult where S: Serializer, @@ -230,40 +202,9 @@ impl<'lua> Serialize for String<'lua> { } } -// Additional shortcuts -#[cfg(feature = "unstable")] -impl OwnedString { - /// Get a `&str` slice if the Lua string is valid UTF-8. - /// - /// This is a shortcut for [`String::to_str()`]. - #[inline] - pub fn to_str(&self) -> Result<&str> { - let s = self.to_ref(); - // Reattach lifetime to &self - unsafe { std::mem::transmute(s.to_str()) } - } - - /// Get the bytes that make up this string. - /// - /// This is a shortcut for [`String::as_bytes()`]. - #[inline] - pub fn as_bytes(&self) -> &[u8] { - let s = self.to_ref(); - // Reattach lifetime to &self - unsafe { std::mem::transmute(s.as_bytes()) } - } -} - -#[cfg(feature = "unstable")] -impl fmt::Debug for OwnedString { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.to_ref().fmt(f) - } -} - -#[cfg(test)] -mod assertions { - use super::*; +// #[cfg(test)] +// mod assertions { +// use super::*; - static_assertions::assert_not_impl_any!(String: Send); -} +// static_assertions::assert_not_impl_any!(String: Send); +// } diff --git a/src/table.rs b/src/table.rs index 9eca8d5b..3e0edb59 100644 --- a/src/table.rs +++ b/src/table.rs @@ -22,30 +22,9 @@ use futures_util::future::{self, LocalBoxFuture}; /// Handle to an internal Lua table. #[derive(Clone)] -pub struct Table<'lua>(pub(crate) ValueRef<'lua>); +pub struct Table(pub(crate) ValueRef); -/// Owned handle to an internal Lua table. -/// -/// The owned handle holds a *strong* reference to the current Lua instance. -/// Be warned, if you place it into a Lua type (eg. [`UserData`] or a Rust callback), it is *very easy* -/// to accidentally cause reference cycles that would prevent destroying Lua instance. -/// -/// [`UserData`]: crate::UserData -#[cfg(feature = "unstable")] -#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] -#[derive(Clone, Debug)] -pub struct OwnedTable(pub(crate) crate::types::OwnedValueRef); - -#[cfg(feature = "unstable")] -impl OwnedTable { - /// Get borrowed handle to the underlying Lua table. - #[cfg_attr(feature = "send", allow(unused))] - pub const fn to_ref(&self) -> Table { - Table(self.0.to_ref()) - } -} - -impl<'lua> Table<'lua> { +impl Table { /// Sets a key-value pair in the table. /// /// If the value is `nil`, this will effectively remove the pair. @@ -85,15 +64,15 @@ impl<'lua> Table<'lua> { return self.raw_set(key, value); } - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 5)?; lua.push_ref(&self.0); - key.push_into_stack(lua)?; - value.push_into_stack(lua)?; + key.push_into_stack(&lua)?; + value.push_into_stack(&lua)?; protect_lua!(state, 3, 0, fn(state) ffi::lua_settable(state, -3)) } } @@ -122,23 +101,23 @@ impl<'lua> Table<'lua> { /// ``` /// /// [`raw_get`]: #method.raw_get - pub fn get>(&self, key: K) -> Result { + pub fn get(&self, key: K) -> Result { // Fast track if !self.has_metatable() { return self.raw_get(key); } - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 4)?; lua.push_ref(&self.0); - key.push_into_stack(lua)?; + key.push_into_stack(&lua)?; protect_lua!(state, 2, 1, fn(state) ffi::lua_gettable(state, -2))?; - V::from_stack(-1, lua) + V::from_stack(-1, &lua) } } @@ -158,14 +137,14 @@ impl<'lua> Table<'lua> { return self.raw_push(value); } - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 4)?; lua.push_ref(&self.0); - value.push_into_stack(lua)?; + value.push_into_stack(&lua)?; protect_lua!(state, 2, 0, fn(state) { let len = ffi::luaL_len(state, -2) as Integer; ffi::lua_seti(state, -2, len + 1); @@ -177,13 +156,13 @@ impl<'lua> Table<'lua> { /// Removes the last element from the table and returns it. /// /// This might invoke the `__len` and `__newindex` metamethods. - pub fn pop>(&self) -> Result { + pub fn pop(&self) -> Result { // Fast track if !self.has_metatable() { return self.raw_pop(); } - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -196,7 +175,7 @@ impl<'lua> Table<'lua> { ffi::lua_pushnil(state); ffi::lua_seti(state, -3, len); })?; - V::from_stack(-1, lua) + V::from_stack(-1, &lua) } } @@ -257,15 +236,15 @@ impl<'lua> Table<'lua> { #[cfg(feature = "luau")] self.check_readonly_write()?; - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 5)?; lua.push_ref(&self.0); - key.push_into_stack(lua)?; - value.push_into_stack(lua)?; + key.push_into_stack(&lua)?; + value.push_into_stack(&lua)?; if lua.unlikely_memory_error() { ffi::lua_rawset(state, -3); @@ -278,18 +257,18 @@ impl<'lua> Table<'lua> { } /// Gets the value associated to `key` without invoking metamethods. - pub fn raw_get>(&self, key: K) -> Result { - let lua = self.0.lua; + pub fn raw_get(&self, key: K) -> Result { + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 3)?; lua.push_ref(&self.0); - key.push_into_stack(lua)?; + key.push_into_stack(&lua)?; ffi::lua_rawget(state, -2); - V::from_stack(-1, lua) + V::from_stack(-1, &lua) } } @@ -301,14 +280,14 @@ impl<'lua> Table<'lua> { return Err(Error::runtime("index out of bounds")); } - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 5)?; lua.push_ref(&self.0); - value.push_into_stack(lua)?; + value.push_into_stack(&lua)?; protect_lua!(state, 2, 0, |state| { for i in (idx..=size).rev() { // table[i+1] = table[i] @@ -325,14 +304,14 @@ impl<'lua> Table<'lua> { #[cfg(feature = "luau")] self.check_readonly_write()?; - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 4)?; lua.push_ref(&self.0); - value.push_into_stack(lua)?; + value.push_into_stack(&lua)?; unsafe fn callback(state: *mut ffi::lua_State) { let len = ffi::lua_rawlen(state, -2) as Integer; @@ -349,11 +328,11 @@ impl<'lua> Table<'lua> { } /// Removes the last element from the table and returns it, without invoking metamethods. - pub fn raw_pop>(&self) -> Result { + pub fn raw_pop(&self) -> Result { #[cfg(feature = "luau")] self.check_readonly_write()?; - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -366,7 +345,7 @@ impl<'lua> Table<'lua> { ffi::lua_pushnil(state); ffi::lua_rawseti(state, -3, len); - V::from_stack(-1, lua) + V::from_stack(-1, &lua) } } @@ -378,9 +357,9 @@ impl<'lua> Table<'lua> { /// /// For other key types this is equivalent to setting `table[key] = nil`. pub fn raw_remove(&self, key: K) -> Result<()> { - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); - let key = key.into_lua(lua)?; + let key = key.into_lua(lua.lua())?; match key { Value::Integer(idx) => { let size = self.raw_len() as Integer; @@ -414,7 +393,7 @@ impl<'lua> Table<'lua> { #[cfg(feature = "luau")] self.check_readonly_write()?; - let lua = self.0.lua; + let lua = self.0.lua.lock(); unsafe { #[cfg(feature = "luau")] ffi::lua_cleartable(lua.ref_thread(), self.0.index); @@ -458,7 +437,7 @@ impl<'lua> Table<'lua> { return Ok(self.raw_len() as Integer); } - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -471,8 +450,8 @@ impl<'lua> Table<'lua> { /// Returns the result of the Lua `#` operator, without invoking the `__len` metamethod. pub fn raw_len(&self) -> usize { - let ref_thread = self.0.lua.ref_thread(); - unsafe { ffi::lua_rawlen(ref_thread, self.0.index) } + let lua = self.0.lua.lock(); + unsafe { ffi::lua_rawlen(lua.ref_thread(), self.0.index) } } /// Returns `true` if the table is empty, without invoking metamethods. @@ -485,7 +464,7 @@ impl<'lua> Table<'lua> { } // Check hash part - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -504,8 +483,8 @@ impl<'lua> Table<'lua> { /// Returns a reference to the metatable of this table, or `None` if no metatable is set. /// /// Unlike the `getmetatable` Lua function, this method ignores the `__metatable` field. - pub fn get_metatable(&self) -> Option> { - let lua = self.0.lua; + pub fn get_metatable(&self) -> Option
{ + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -524,14 +503,14 @@ impl<'lua> Table<'lua> { /// /// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does /// nothing). - pub fn set_metatable(&self, metatable: Option>) { + pub fn set_metatable(&self, metatable: Option
) { // Workaround to throw readonly error without returning Result #[cfg(feature = "luau")] if self.is_readonly() { panic!("attempt to modify a readonly table"); } - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -551,7 +530,8 @@ impl<'lua> Table<'lua> { #[doc(hidden)] #[inline] pub fn has_metatable(&self) -> bool { - let ref_thread = self.0.lua.ref_thread(); + let lua = self.0.lua.lock(); + let ref_thread = lua.ref_thread(); unsafe { if ffi::lua_getmetatable(ref_thread, self.0.index) != 0 { ffi::lua_pop(ref_thread, 1); @@ -567,7 +547,8 @@ impl<'lua> Table<'lua> { #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn set_readonly(&self, enabled: bool) { - let ref_thread = self.0.lua.ref_thread(); + let lua = self.0.lua.lock(); + let ref_thread = lua.ref_thread(); unsafe { ffi::lua_setreadonly(ref_thread, self.0.index, enabled as _); if !enabled { @@ -583,7 +564,8 @@ impl<'lua> Table<'lua> { #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn is_readonly(&self) -> bool { - let ref_thread = self.0.lua.ref_thread(); + let lua = self.0.lua.lock(); + let ref_thread = lua.ref_thread(); unsafe { ffi::lua_getreadonly(ref_thread, self.0.index) != 0 } } @@ -598,14 +580,6 @@ impl<'lua> Table<'lua> { self.0.to_pointer() } - /// Convert this handle to owned version. - #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] - #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] - #[inline] - pub fn into_owned(self) -> OwnedTable { - OwnedTable(self.0.into_owned()) - } - /// Consume this table and return an iterator over the pairs of the table. /// /// This works like the Lua `pairs` function, but does not invoke the `__pairs` metamethod. @@ -639,7 +613,7 @@ impl<'lua> Table<'lua> { /// /// [`Result`]: crate::Result /// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next - pub fn pairs, V: FromLua<'lua>>(self) -> TablePairs<'lua, K, V> { + pub fn pairs(self) -> TablePairs { TablePairs { table: self.0, key: Some(Nil), @@ -653,10 +627,10 @@ impl<'lua> Table<'lua> { /// It does not invoke the `__pairs` metamethod. pub fn for_each(&self, mut f: impl FnMut(K, V) -> Result<()>) -> Result<()> where - K: FromLua<'lua>, - V: FromLua<'lua>, + K: FromLua, + V: FromLua, { - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -665,8 +639,8 @@ impl<'lua> Table<'lua> { lua.push_ref(&self.0); ffi::lua_pushnil(state); while ffi::lua_next(state, -2) != 0 { - let k = K::from_stack(-2, lua)?; - let v = V::from_stack(-1, lua)?; + let k = K::from_stack(-2, &lua)?; + let v = V::from_stack(-1, &lua)?; f(k, v)?; // Keep key for next iteration ffi::lua_pop(state, 1); @@ -713,7 +687,7 @@ impl<'lua> Table<'lua> { /// [`pairs`]: #method.pairs /// [`Result`]: crate::Result /// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next - pub fn sequence_values>(self) -> TableSequence<'lua, V> { + pub fn sequence_values(self) -> TableSequence { TableSequence { table: self.0, index: 1, @@ -723,16 +697,16 @@ impl<'lua> Table<'lua> { #[doc(hidden)] #[deprecated(since = "0.9.0", note = "use `sequence_values` instead")] - pub fn raw_sequence_values>(self) -> TableSequence<'lua, V> { + pub fn raw_sequence_values(self) -> TableSequence { self.sequence_values() } #[cfg(feature = "serialize")] pub(crate) fn for_each_value(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()> where - V: FromLua<'lua>, + V: FromLua, { - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -742,7 +716,7 @@ impl<'lua> Table<'lua> { let len = ffi::lua_rawlen(state, -1); for i in 1..=len { ffi::lua_rawgeti(state, -1, i as _); - f(V::from_stack(-1, lua)?)?; + f(V::from_stack(-1, &lua)?)?; ffi::lua_pop(state, 1); } } @@ -755,14 +729,14 @@ impl<'lua> Table<'lua> { #[cfg(feature = "luau")] self.check_readonly_write()?; - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 5)?; lua.push_ref(&self.0); - value.push_into_stack(lua)?; + value.push_into_stack(&lua)?; let idx = idx.try_into().unwrap(); if lua.unlikely_memory_error() { @@ -776,7 +750,7 @@ impl<'lua> Table<'lua> { #[cfg(feature = "serialize")] pub(crate) fn is_array(&self) -> bool { - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -828,7 +802,7 @@ impl<'lua> Table<'lua> { } } -impl fmt::Debug for Table<'_> { +impl fmt::Debug for Table { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { if fmt.alternate() { return self.fmt_pretty(fmt, 0, &mut HashSet::new()); @@ -837,25 +811,25 @@ impl fmt::Debug for Table<'_> { } } -impl<'lua> PartialEq for Table<'lua> { +impl PartialEq for Table { fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } -impl<'lua> AsRef> for Table<'lua> { +impl AsRef
for Table { #[inline] fn as_ref(&self) -> &Self { self } } -impl<'lua, T> PartialEq<[T]> for Table<'lua> +impl PartialEq<[T]> for Table where T: IntoLua + Clone, { fn eq(&self, other: &[T]) -> bool { - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -870,7 +844,7 @@ where if val == Nil { return i == other.len(); } - match other.get(i).map(|v| v.clone().into_lua(lua)) { + match other.get(i).map(|v| v.clone().into_lua(lua.lua())) { Some(Ok(other_val)) if val == other_val => continue, _ => return false, } @@ -880,7 +854,7 @@ where } } -impl<'lua, T> PartialEq<&[T]> for Table<'lua> +impl PartialEq<&[T]> for Table where T: IntoLua + Clone, { @@ -890,7 +864,7 @@ where } } -impl<'lua, T, const N: usize> PartialEq<[T; N]> for Table<'lua> +impl PartialEq<[T; N]> for Table where T: IntoLua + Clone, { @@ -901,24 +875,24 @@ where } /// An extension trait for `Table`s that provides a variety of convenient functionality. -pub trait TableExt<'lua>: Sealed { +pub trait TableExt: Sealed { /// Calls the table as function assuming it has `__call` metamethod. /// /// The metamethod is called with the table as its first argument, followed by the passed arguments. fn call(&self, args: A) -> Result where A: IntoLuaMulti, - R: FromLuaMulti<'lua>; + R: FromLuaMulti; /// Asynchronously calls the table as function assuming it has `__call` metamethod. /// /// The metamethod is called with the table as its first argument, followed by the passed arguments. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async(&self, args: A) -> LocalBoxFuture<'lua, Result> + fn call_async(&self, args: A) -> LocalBoxFuture<'static, Result> where A: IntoLuaMulti, - R: FromLuaMulti<'lua> + 'lua; + R: FromLuaMulti + 'static; /// Gets the function associated to `key` from the table and executes it, /// passing the table itself along with `args` as function arguments. @@ -930,7 +904,7 @@ pub trait TableExt<'lua>: Sealed { fn call_method(&self, name: &str, args: A) -> Result where A: IntoLuaMulti, - R: FromLuaMulti<'lua>; + R: FromLuaMulti; /// Gets the function associated to `key` from the table and executes it, /// passing `args` as function arguments. @@ -942,7 +916,7 @@ pub trait TableExt<'lua>: Sealed { fn call_function(&self, name: &str, args: A) -> Result where A: IntoLuaMulti, - R: FromLuaMulti<'lua>; + R: FromLuaMulti; /// Gets the function associated to `key` from the table and asynchronously executes it, /// passing the table itself along with `args` as function arguments and returning Future. @@ -952,10 +926,10 @@ pub trait TableExt<'lua>: Sealed { /// This might invoke the `__index` metamethod. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_method(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result> + fn call_async_method(&self, name: &str, args: A) -> LocalBoxFuture<'static, Result> where A: IntoLuaMulti, - R: FromLuaMulti<'lua> + 'lua; + R: FromLuaMulti + 'static; /// Gets the function associated to `key` from the table and asynchronously executes it, /// passing `args` as function arguments and returning Future. @@ -965,29 +939,30 @@ pub trait TableExt<'lua>: Sealed { /// This might invoke the `__index` metamethod. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_function(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result> + fn call_async_function(&self, name: &str, args: A) -> LocalBoxFuture<'static, Result> where A: IntoLuaMulti, - R: FromLuaMulti<'lua> + 'lua; + R: FromLuaMulti + 'static; } -impl<'lua> TableExt<'lua> for Table<'lua> { +impl TableExt for Table { fn call(&self, args: A) -> Result where A: IntoLuaMulti, - R: FromLuaMulti<'lua>, + R: FromLuaMulti, { // Convert table to a function and call via pcall that respects the `__call` metamethod. Function(self.0.clone()).call(args) } #[cfg(feature = "async")] - fn call_async(&self, args: A) -> LocalBoxFuture<'lua, Result> + fn call_async(&self, args: A) -> LocalBoxFuture<'static, Result> where A: IntoLuaMulti, - R: FromLuaMulti<'lua> + 'lua, + R: FromLuaMulti + 'static, { - let args = match args.into_lua_multi(self.0.lua) { + let lua = self.0.lua.lock(); + let args = match args.into_lua_multi(lua.lua()) { Ok(args) => args, Err(e) => return Box::pin(future::err(e)), }; @@ -998,7 +973,7 @@ impl<'lua> TableExt<'lua> for Table<'lua> { fn call_method(&self, name: &str, args: A) -> Result where A: IntoLuaMulti, - R: FromLuaMulti<'lua>, + R: FromLuaMulti, { self.get::<_, Function>(name)?.call((self, args)) } @@ -1006,30 +981,30 @@ impl<'lua> TableExt<'lua> for Table<'lua> { fn call_function(&self, name: &str, args: A) -> Result where A: IntoLuaMulti, - R: FromLuaMulti<'lua>, + R: FromLuaMulti, { self.get::<_, Function>(name)?.call(args) } #[cfg(feature = "async")] - fn call_async_method(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result> + fn call_async_method(&self, name: &str, args: A) -> LocalBoxFuture<'static, Result> where A: IntoLuaMulti, - R: FromLuaMulti<'lua> + 'lua, + R: FromLuaMulti + 'static, { self.call_async_function(name, (self, args)) } #[cfg(feature = "async")] - fn call_async_function(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result> + fn call_async_function(&self, name: &str, args: A) -> LocalBoxFuture<'static, Result> where A: IntoLuaMulti, - R: FromLuaMulti<'lua> + 'lua, + R: FromLuaMulti + 'static, { - let lua = self.0.lua; + let lua = self.0.lua.lock(); match self.get::<_, Function>(name) { Ok(func) => { - let args = match args.into_lua_multi(lua) { + let args = match args.into_lua_multi(lua.lua()) { Ok(args) => args, Err(e) => return Box::pin(future::err(e)), }; @@ -1042,14 +1017,14 @@ impl<'lua> TableExt<'lua> for Table<'lua> { /// A wrapped [`Table`] with customized serialization behavior. #[cfg(feature = "serialize")] -pub(crate) struct SerializableTable<'a, 'lua> { - table: &'a Table<'lua>, +pub(crate) struct SerializableTable<'a> { + table: &'a Table, options: crate::serde::de::Options, visited: Rc>>, } #[cfg(feature = "serialize")] -impl<'lua> Serialize for Table<'lua> { +impl Serialize for Table { #[inline] fn serialize(&self, serializer: S) -> StdResult { SerializableTable::new(self, Default::default(), Default::default()).serialize(serializer) @@ -1057,10 +1032,10 @@ impl<'lua> Serialize for Table<'lua> { } #[cfg(feature = "serialize")] -impl<'a, 'lua> SerializableTable<'a, 'lua> { +impl<'a> SerializableTable<'a> { #[inline] pub(crate) fn new( - table: &'a Table<'lua>, + table: &'a Table, options: crate::serde::de::Options, visited: Rc>>, ) -> Self { @@ -1073,7 +1048,7 @@ impl<'a, 'lua> SerializableTable<'a, 'lua> { } #[cfg(feature = "serialize")] -impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> { +impl<'a> Serialize for SerializableTable<'a> { fn serialize(&self, serializer: S) -> StdResult where S: Serializer, @@ -1157,22 +1132,22 @@ impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> { /// This struct is created by the [`Table::pairs`] method. /// /// [`Table::pairs`]: crate::Table::pairs -pub struct TablePairs<'lua, K, V> { - table: ValueRef<'lua>, - key: Option>, +pub struct TablePairs { + table: ValueRef, + key: Option, _phantom: PhantomData<(K, V)>, } -impl<'lua, K, V> Iterator for TablePairs<'lua, K, V> +impl Iterator for TablePairs where - K: FromLua<'lua>, - V: FromLua<'lua>, + K: FromLua, + V: FromLua, { type Item = Result<(K, V)>; fn next(&mut self) -> Option { if let Some(prev_key) = self.key.take() { - let lua = self.table.lua; + let lua = self.table.lua.lock(); let state = lua.state(); let res = (|| unsafe { @@ -1189,8 +1164,8 @@ where let key = lua.stack_value(-2); Ok(Some(( key.clone(), - K::from_lua(key, lua)?, - V::from_stack(-1, lua)?, + K::from_lua(key, lua.lua())?, + V::from_stack(-1, &lua)?, ))) } else { Ok(None) @@ -1216,21 +1191,21 @@ where /// This struct is created by the [`Table::sequence_values`] method. /// /// [`Table::sequence_values`]: crate::Table::sequence_values -pub struct TableSequence<'lua, V> { +pub struct TableSequence { // TODO: Use `&Table` - table: ValueRef<'lua>, + table: ValueRef, index: Integer, _phantom: PhantomData, } -impl<'lua, V> Iterator for TableSequence<'lua, V> +impl Iterator for TableSequence where - V: FromLua<'lua>, + V: FromLua, { type Item = Result; fn next(&mut self) -> Option { - let lua = self.table.lua; + let lua = self.table.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -1243,19 +1218,16 @@ where ffi::LUA_TNIL => None, _ => { self.index += 1; - Some(V::from_stack(-1, lua)) + Some(V::from_stack(-1, &lua)) } } } } } -#[cfg(test)] -mod assertions { - use super::*; +// #[cfg(test)] +// mod assertions { +// use super::*; - static_assertions::assert_not_impl_any!(Table: Send); - - #[cfg(feature = "unstable")] - static_assertions::assert_not_impl_any!(OwnedTable: Send); -} +// static_assertions::assert_not_impl_any!(Table: Send); +// } diff --git a/src/thread.rs b/src/thread.rs index 489416f8..b6a0e867 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -3,6 +3,7 @@ use std::os::raw::{c_int, c_void}; use crate::error::{Error, Result}; #[allow(unused)] use crate::lua::Lua; +use crate::lua::LuaInner; use crate::types::ValueRef; use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard}; use crate::value::{FromLuaMulti, IntoLuaMulti}; @@ -43,31 +44,7 @@ pub enum ThreadStatus { /// Handle to an internal Lua thread (coroutine). #[derive(Clone, Debug)] -pub struct Thread<'lua>(pub(crate) ValueRef<'lua>, pub(crate) *mut ffi::lua_State); - -/// Owned handle to an internal Lua thread (coroutine). -/// -/// The owned handle holds a *strong* reference to the current Lua instance. -/// Be warned, if you place it into a Lua type (eg. [`UserData`] or a Rust callback), it is *very easy* -/// to accidentally cause reference cycles that would prevent destroying Lua instance. -/// -/// [`UserData`]: crate::UserData -#[cfg(feature = "unstable")] -#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] -#[derive(Clone, Debug)] -pub struct OwnedThread( - pub(crate) crate::types::OwnedValueRef, - pub(crate) *mut ffi::lua_State, -); - -#[cfg(feature = "unstable")] -impl OwnedThread { - /// Get borrowed handle to the underlying Lua table. - #[cfg_attr(feature = "send", allow(unused))] - pub const fn to_ref(&self) -> Thread { - Thread(self.0.to_ref(), self.1) - } -} +pub struct Thread(pub(crate) ValueRef, pub(crate) *mut ffi::lua_State); /// Thread (coroutine) representation as an async [`Future`] or [`Stream`]. /// @@ -78,17 +55,17 @@ impl OwnedThread { #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct AsyncThread<'lua, R> { - thread: Thread<'lua>, - init_args: Option>>, +pub struct AsyncThread { + thread: Thread, + init_args: Option>, ret: PhantomData, recycle: bool, } -impl<'lua> Thread<'lua> { +impl Thread { #[inline(always)] - pub(crate) fn new(r#ref: ValueRef<'lua>) -> Self { - let state = unsafe { ffi::lua_tothread(r#ref.lua.ref_thread(), r#ref.index) }; + pub(crate) fn new(lua: &LuaInner, r#ref: ValueRef) -> Self { + let state = unsafe { ffi::lua_tothread(lua.ref_thread(), r#ref.index) }; Thread(r#ref, state) } @@ -140,13 +117,14 @@ impl<'lua> Thread<'lua> { pub fn resume(&self, args: A) -> Result where A: IntoLuaMulti, - R: FromLuaMulti<'lua>, + R: FromLuaMulti, { - if self.status() != ThreadStatus::Resumable { + let lua = self.0.lua.lock(); + + if unsafe { self.status_unprotected() } != ThreadStatus::Resumable { return Err(Error::CoroutineInactive); } - let lua = self.0.lua; let state = lua.state(); let thread_state = self.state(); unsafe { @@ -157,7 +135,7 @@ impl<'lua> Thread<'lua> { check_stack(state, nresults + 1)?; ffi::lua_xmove(thread_state, state, nresults); - R::from_stack_multi(nresults, lua) + R::from_stack_multi(nresults, &lua) } } @@ -165,11 +143,11 @@ impl<'lua> Thread<'lua> { /// /// It's similar to `resume()` but leaves `nresults` values on the thread stack. unsafe fn resume_inner(&self, args: A) -> Result { - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); let thread_state = self.state(); - let nargs = args.push_into_stack_multi(lua)?; + let nargs = args.push_into_stack_multi(&lua)?; if nargs > 0 { check_stack(thread_state, nargs)?; ffi::lua_xmove(state, thread_state, nargs); @@ -195,20 +173,25 @@ impl<'lua> Thread<'lua> { /// Gets the status of the thread. pub fn status(&self) -> ThreadStatus { + let _guard = self.0.lua.lock(); + unsafe { self.status_unprotected() } + } + + /// Gets the status of the thread without locking the Lua state. + pub(crate) unsafe fn status_unprotected(&self) -> ThreadStatus { let thread_state = self.state(); - if thread_state == self.0.lua.state() { + // FIXME: skip double lock + if thread_state == self.0.lua.lock().state() { // The coroutine is currently running return ThreadStatus::Unresumable; } - unsafe { - let status = ffi::lua_status(thread_state); - if status != ffi::LUA_OK && status != ffi::LUA_YIELD { - ThreadStatus::Error - } else if status == ffi::LUA_YIELD || ffi::lua_gettop(thread_state) > 0 { - ThreadStatus::Resumable - } else { - ThreadStatus::Unresumable - } + let status = ffi::lua_status(thread_state); + if status != ffi::LUA_OK && status != ffi::LUA_YIELD { + ThreadStatus::Error + } else if status == ffi::LUA_YIELD || ffi::lua_gettop(thread_state) > 0 { + ThreadStatus::Resumable + } else { + ThreadStatus::Unresumable } } @@ -222,7 +205,7 @@ impl<'lua> Thread<'lua> { where F: Fn(&Lua, Debug) -> Result<()> + MaybeSend + 'static, { - let lua = self.0.lua; + let lua = self.0.lua.lock(); unsafe { lua.set_thread_hook(self.state(), triggers, callback); } @@ -244,8 +227,8 @@ impl<'lua> Thread<'lua> { /// [Lua 5.4]: https://www.lua.org/manual/5.4/manual.html#lua_closethread #[cfg(any(feature = "lua54", feature = "luau"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "luau"))))] - pub fn reset(&self, func: crate::function::Function<'lua>) -> Result<()> { - let lua = self.0.lua; + pub fn reset(&self, func: crate::function::Function) -> Result<()> { + let lua = self.0.lua.lock(); let thread_state = self.state(); if thread_state == lua.state() { return Err(Error::runtime("cannot reset a running thread")); @@ -323,12 +306,13 @@ impl<'lua> Thread<'lua> { /// ``` #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub fn into_async(self, args: A) -> AsyncThread<'lua, R> + pub fn into_async(self, args: A) -> AsyncThread where A: IntoLuaMulti, - R: FromLuaMulti<'lua>, + R: FromLuaMulti, { - let args = args.into_lua_multi(self.0.lua); + let lua = self.0.lua.lock(); + let args = args.into_lua_multi(lua.lua()); AsyncThread { thread: self, init_args: Some(args), @@ -372,7 +356,7 @@ impl<'lua> Thread<'lua> { #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[doc(hidden)] pub fn sandbox(&self) -> Result<()> { - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); let thread_state = self.state(); unsafe { @@ -391,44 +375,16 @@ impl<'lua> Thread<'lua> { pub fn to_pointer(&self) -> *const c_void { self.0.to_pointer() } - - /// Convert this handle to owned version. - #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] - #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] - #[inline] - pub fn into_owned(self) -> OwnedThread { - OwnedThread(self.0.into_owned(), self.1) - } } -impl<'lua> PartialEq for Thread<'lua> { +impl PartialEq for Thread { fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } -// Additional shortcuts -#[cfg(feature = "unstable")] -impl OwnedThread { - /// Resumes execution of this thread. - /// - /// See [`Thread::resume()`] for more details. - pub fn resume<'lua, A, R>(&'lua self, args: A) -> Result - where - A: IntoLuaMulti, - R: FromLuaMulti<'lua>, - { - self.to_ref().resume(args) - } - - /// Gets the status of the thread. - pub fn status(&self) -> ThreadStatus { - self.to_ref().status() - } -} - #[cfg(feature = "async")] -impl<'lua, R> AsyncThread<'lua, R> { +impl AsyncThread { #[inline] pub(crate) fn set_recyclable(&mut self, recyclable: bool) { self.recycle = recyclable; @@ -437,15 +393,15 @@ impl<'lua, R> AsyncThread<'lua, R> { #[cfg(feature = "async")] #[cfg(any(feature = "lua54", feature = "luau"))] -impl<'lua, R> Drop for AsyncThread<'lua, R> { +impl Drop for AsyncThread { fn drop(&mut self) { if self.recycle { unsafe { - let lua = self.thread.0.lua; + let lua = self.thread.0.lua.lock(); // For Lua 5.4 this also closes all pending to-be-closed variables if !lua.recycle_thread(&mut self.thread) { #[cfg(feature = "lua54")] - if self.thread.status() == ThreadStatus::Error { + if self.thread.status_unprotected() == ThreadStatus::Error { #[cfg(not(feature = "vendored"))] ffi::lua_resetthread(self.thread.state()); #[cfg(feature = "vendored")] @@ -458,24 +414,21 @@ impl<'lua, R> Drop for AsyncThread<'lua, R> { } #[cfg(feature = "async")] -impl<'lua, R> Stream for AsyncThread<'lua, R> -where - R: FromLuaMulti<'lua>, -{ +impl Stream for AsyncThread { type Item = Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - if self.thread.status() != ThreadStatus::Resumable { - return Poll::Ready(None); - } - - let lua = self.thread.0.lua; + let lua = self.thread.0.lua.lock(); let state = lua.state(); let thread_state = self.thread.state(); unsafe { + if self.thread.status_unprotected() != ThreadStatus::Resumable { + return Poll::Ready(None); + } + let _sg = StackGuard::new(state); let _thread_sg = StackGuard::with_top(thread_state, 0); - let _wg = WakerGuard::new(lua, cx.waker()); + let _wg = WakerGuard::new(&lua, cx.waker()); // This is safe as we are not moving the whole struct let this = self.get_unchecked_mut(); @@ -493,30 +446,27 @@ where ffi::lua_xmove(thread_state, state, nresults); cx.waker().wake_by_ref(); - Poll::Ready(Some(R::from_stack_multi(nresults, lua))) + Poll::Ready(Some(R::from_stack_multi(nresults, &lua))) } } } #[cfg(feature = "async")] -impl<'lua, R> Future for AsyncThread<'lua, R> -where - R: FromLuaMulti<'lua>, -{ +impl Future for AsyncThread { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if self.thread.status() != ThreadStatus::Resumable { - return Poll::Ready(Err(Error::CoroutineInactive)); - } - - let lua = self.thread.0.lua; + let lua = self.thread.0.lua.lock(); let state = lua.state(); let thread_state = self.thread.state(); unsafe { + if self.thread.status_unprotected() != ThreadStatus::Resumable { + return Poll::Ready(Err(Error::CoroutineInactive)); + } + let _sg = StackGuard::new(state); let _thread_sg = StackGuard::with_top(thread_state, 0); - let _wg = WakerGuard::new(lua, cx.waker()); + let _wg = WakerGuard::new(&lua, cx.waker()); // This is safe as we are not moving the whole struct let this = self.get_unchecked_mut(); @@ -539,7 +489,7 @@ where check_stack(state, nresults + 1)?; ffi::lua_xmove(thread_state, state, nresults); - Poll::Ready(R::from_stack_multi(nresults, lua)) + Poll::Ready(R::from_stack_multi(nresults, &lua)) } } } @@ -552,7 +502,7 @@ unsafe fn is_poll_pending(state: *mut ffi::lua_State) -> bool { #[cfg(feature = "async")] struct WakerGuard<'lua, 'a> { - lua: &'lua Lua, + lua: &'lua LuaInner, prev: NonNull, _phantom: PhantomData<&'a ()>, } @@ -560,7 +510,7 @@ struct WakerGuard<'lua, 'a> { #[cfg(feature = "async")] impl<'lua, 'a> WakerGuard<'lua, 'a> { #[inline] - pub fn new(lua: &'lua Lua, waker: &'a Waker) -> Result> { + pub fn new(lua: &'lua LuaInner, waker: &'a Waker) -> Result> { let prev = unsafe { lua.set_waker(NonNull::from(waker)) }; Ok(WakerGuard { lua, diff --git a/src/types.rs b/src/types.rs index aa8ccad4..f15f79e8 100644 --- a/src/types.rs +++ b/src/types.rs @@ -5,22 +5,20 @@ use std::ops::{Deref, DerefMut}; use std::os::raw::{c_int, c_void}; use std::result::Result as StdResult; use std::sync::atomic::{AtomicI32, Ordering}; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use std::{fmt, mem, ptr}; +use parking_lot::{Mutex, RawMutex, RawThreadId}; use rustc_hash::FxHashMap; use crate::error::Result; #[cfg(not(feature = "luau"))] use crate::hook::Debug; -use crate::lua::{ExtraData, Lua}; +use crate::lua::{ExtraData, Lua, LuaGuard, LuaInner, WeakLua}; #[cfg(feature = "async")] use {crate::value::MultiValue, futures_util::future::LocalBoxFuture}; -#[cfg(feature = "unstable")] -use {crate::lua::LuaInner, std::marker::PhantomData}; - #[cfg(all(feature = "luau", feature = "serialize"))] use serde::ser::{Serialize, SerializeTupleStruct, Serializer}; @@ -43,21 +41,21 @@ pub(crate) enum SubtypeId { #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct LightUserData(pub *mut c_void); -pub(crate) type Callback<'lua, 'a> = Box Result + 'a>; +pub(crate) type Callback<'a> = Box Result + 'static>; pub(crate) struct Upvalue { pub(crate) data: T, pub(crate) extra: Arc>, } -pub(crate) type CallbackUpvalue = Upvalue>; +pub(crate) type CallbackUpvalue = Upvalue>; #[cfg(feature = "async")] -pub(crate) type AsyncCallback<'lua, 'a> = - Box) -> LocalBoxFuture<'lua, Result> + 'a>; +pub(crate) type AsyncCallback<'a> = + Box LocalBoxFuture<'a, Result> + 'static>; #[cfg(feature = "async")] -pub(crate) type AsyncCallbackUpvalue = Upvalue>; +pub(crate) type AsyncCallbackUpvalue = Upvalue>; #[cfg(feature = "async")] pub(crate) type AsyncPollUpvalue = Upvalue>>; @@ -184,6 +182,9 @@ impl PartialEq<[f32; Self::SIZE]> for Vector { } } +pub(crate) type ArcReentrantMutexGuard = + parking_lot::lock_api::ArcReentrantMutexGuard; + pub(crate) struct DestructedUserdata; /// An auto generated key into the Lua registry. @@ -233,7 +234,7 @@ impl Drop for RegistryKey { let registry_id = self.id(); // We don't need to collect nil slot if registry_id > ffi::LUA_REFNIL { - let mut unref_list = mlua_expect!(self.unref_list.lock(), "unref list poisoned"); + let mut unref_list = self.unref_list.lock(); if let Some(list) = unref_list.as_mut() { list.push(registry_id); } @@ -273,16 +274,16 @@ impl RegistryKey { } } -pub(crate) struct ValueRef<'lua> { - pub(crate) lua: &'lua Lua, +pub(crate) struct ValueRef { + pub(crate) lua: WeakLua, pub(crate) index: c_int, pub(crate) drop: bool, } -impl<'lua> ValueRef<'lua> { - pub(crate) const fn new(lua: &'lua Lua, index: c_int) -> Self { +impl ValueRef { + pub(crate) fn new(lua: &LuaInner, index: c_int) -> Self { ValueRef { - lua, + lua: lua.weak().clone(), index, drop: true, } @@ -290,95 +291,39 @@ impl<'lua> ValueRef<'lua> { #[inline] pub(crate) fn to_pointer(&self) -> *const c_void { - unsafe { ffi::lua_topointer(self.lua.ref_thread(), self.index) } - } - - #[cfg(feature = "unstable")] - #[inline] - pub(crate) fn into_owned(self) -> OwnedValueRef { - assert!(self.drop, "Cannot turn non-drop reference into owned"); - let owned_ref = OwnedValueRef::new(self.lua.clone(), self.index); - mem::forget(self); - owned_ref + let lua = self.lua.lock(); + unsafe { ffi::lua_topointer(lua.ref_thread(), self.index) } } } -impl<'lua> fmt::Debug for ValueRef<'lua> { +impl fmt::Debug for ValueRef { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Ref({:p})", self.to_pointer()) } } -impl<'lua> Clone for ValueRef<'lua> { +impl Clone for ValueRef { fn clone(&self) -> Self { - self.lua.clone_ref(self) + self.lua.lock().clone_ref(self) } } -impl<'lua> Drop for ValueRef<'lua> { +impl Drop for ValueRef { fn drop(&mut self) { if self.drop { - self.lua.drop_ref_index(self.index); + self.lua.lock().drop_ref(self); } } } -impl<'lua> PartialEq for ValueRef<'lua> { +impl PartialEq for ValueRef { fn eq(&self, other: &Self) -> bool { - let ref_thread = self.lua.ref_thread(); assert!( - ref_thread == other.lua.ref_thread(), + self.lua == other.lua, "Lua instance passed Value created from a different main Lua state" ); - unsafe { ffi::lua_rawequal(ref_thread, self.index, other.index) == 1 } - } -} - -#[cfg(feature = "unstable")] -pub(crate) struct OwnedValueRef { - pub(crate) inner: Arc, - pub(crate) index: c_int, - _non_send: PhantomData<*const ()>, -} - -#[cfg(feature = "unstable")] -impl fmt::Debug for OwnedValueRef { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "OwnedRef({:p})", self.to_ref().to_pointer()) - } -} - -#[cfg(feature = "unstable")] -impl Clone for OwnedValueRef { - fn clone(&self) -> Self { - self.to_ref().clone().into_owned() - } -} - -#[cfg(feature = "unstable")] -impl Drop for OwnedValueRef { - fn drop(&mut self) { - let lua: &Lua = unsafe { mem::transmute(&self.inner) }; - lua.drop_ref_index(self.index); - } -} - -#[cfg(feature = "unstable")] -impl OwnedValueRef { - pub(crate) const fn new(inner: Arc, index: c_int) -> Self { - OwnedValueRef { - inner, - index, - _non_send: PhantomData, - } - } - - pub(crate) const fn to_ref(&self) -> ValueRef { - ValueRef { - lua: unsafe { mem::transmute(&self.inner) }, - index: self.index, - drop: false, - } + let lua = self.lua.lock(); + unsafe { ffi::lua_rawequal(lua.ref_thread(), self.index, other.index) == 1 } } } @@ -411,7 +356,7 @@ impl AppData { } #[track_caller] - pub(crate) fn borrow(&self) -> Option> { + pub(crate) fn borrow(&self, guard: Option) -> Option> { let data = unsafe { &*self.container.get() } .get(&TypeId::of::())? .borrow(); @@ -419,11 +364,15 @@ impl AppData { Some(AppDataRef { data: Ref::filter_map(data, |data| data.downcast_ref()).ok()?, borrow: &self.borrow, + _guard: guard, }) } #[track_caller] - pub(crate) fn borrow_mut(&self) -> Option> { + pub(crate) fn borrow_mut( + &self, + guard: Option, + ) -> Option> { let data = unsafe { &*self.container.get() } .get(&TypeId::of::())? .borrow_mut(); @@ -431,6 +380,7 @@ impl AppData { Some(AppDataRefMut { data: RefMut::filter_map(data, |data| data.downcast_mut()).ok()?, borrow: &self.borrow, + _guard: guard, }) } @@ -455,6 +405,7 @@ impl AppData { pub struct AppDataRef<'a, T: ?Sized + 'a> { data: Ref<'a, T>, borrow: &'a Cell, + _guard: Option, } impl Drop for AppDataRef<'_, T> { @@ -490,6 +441,7 @@ impl fmt::Debug for AppDataRef<'_, T> { pub struct AppDataRefMut<'a, T: ?Sized + 'a> { data: RefMut<'a, T>, borrow: &'a Cell, + _guard: Option, } impl Drop for AppDataRefMut<'_, T> { @@ -526,13 +478,10 @@ impl fmt::Debug for AppDataRefMut<'_, T> { } } -#[cfg(test)] -mod assertions { - use super::*; - - static_assertions::assert_impl_all!(RegistryKey: Send, Sync); - static_assertions::assert_not_impl_any!(ValueRef: Send); +// #[cfg(test)] +// mod assertions { +// use super::*; - #[cfg(feature = "unstable")] - static_assertions::assert_not_impl_any!(OwnedValueRef: Send); -} +// static_assertions::assert_impl_all!(RegistryKey: Send, Sync); +// static_assertions::assert_not_impl_any!(ValueRef: Send); +// } diff --git a/src/userdata.rs b/src/userdata.rs index 408333bd..b21000cd 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -1,10 +1,7 @@ -use std::any::{type_name, TypeId}; -use std::cell::{Ref, RefCell, RefMut}; +use std::any::TypeId; use std::ffi::CStr; use std::fmt; use std::hash::Hash; -use std::mem; -use std::ops::{Deref, DerefMut}; use std::os::raw::{c_char, c_int, c_void}; use std::string::String as StdString; @@ -19,10 +16,11 @@ use { use crate::error::{Error, Result}; use crate::function::Function; -use crate::lua::Lua; +use crate::lua::{Lua, LuaGuard}; use crate::string::String; use crate::table::{Table, TablePairs}; use crate::types::{MaybeSend, SubtypeId, ValueRef}; +use crate::userdata_cell::{UserDataRef, UserDataRefMut, UserDataVariant}; use crate::util::{check_stack, get_userdata, take_userdata, StackGuard}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; use crate::UserDataRegistry; @@ -254,7 +252,7 @@ impl AsRef for MetaMethod { /// Method registry for [`UserData`] implementors. /// /// [`UserData`]: crate::UserData -pub trait UserDataMethods<'lua, T> { +pub trait UserDataMethods<'a, T> { /// Add a regular method which accepts a `&T` as the first parameter. /// /// Regular methods are implemented by overriding the `__index` metamethod and returning the @@ -262,10 +260,10 @@ pub trait UserDataMethods<'lua, T> { /// /// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will /// be used as a fall-back if no regular method is found. - fn add_method(&mut self, name: impl AsRef, method: M) + fn add_method(&mut self, name: impl ToString, method: M) where - M: Fn(&'lua Lua, &T, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + M: Fn(&'a Lua, &T, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti; /// Add a regular method which accepts a `&mut T` as the first parameter. @@ -273,10 +271,10 @@ pub trait UserDataMethods<'lua, T> { /// Refer to [`add_method`] for more information about the implementation. /// /// [`add_method`]: #method.add_method - fn add_method_mut(&mut self, name: impl AsRef, method: M) + fn add_method_mut(&mut self, name: impl ToString, method: M) where - M: FnMut(&'lua Lua, &mut T, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + M: FnMut(&'a Lua, &mut T, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti; /// Add an async method which accepts a `&T` as the first parameter and returns Future. @@ -288,13 +286,12 @@ pub trait UserDataMethods<'lua, T> { /// [`add_method`]: #method.add_method #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn add_async_method<'s, M, A, MR, R>(&mut self, name: impl AsRef, method: M) + fn add_async_method(&mut self, name: impl ToString, method: M) where - 'lua: 's, T: 'static, - M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - MR: Future> + 's, + M: Fn(&'a Lua, &'a T, A) -> MR + MaybeSend + 'static, + A: FromLuaMulti, + MR: Future> + 'a, R: IntoLuaMulti; /// Add an async method which accepts a `&mut T` as the first parameter and returns Future. @@ -306,13 +303,12 @@ pub trait UserDataMethods<'lua, T> { /// [`add_method`]: #method.add_method #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn add_async_method_mut<'s, M, A, MR, R>(&mut self, name: impl AsRef, method: M) + fn add_async_method_mut(&mut self, name: impl ToString, method: M) where - 'lua: 's, T: 'static, - M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - MR: Future> + 's, + M: Fn(&'a Lua, &'a mut T, A) -> MR + MaybeSend + 'static, + A: FromLuaMulti, + MR: Future> + 'a, R: IntoLuaMulti; /// Add a regular method as a function which accepts generic arguments, the first argument will @@ -325,10 +321,10 @@ pub trait UserDataMethods<'lua, T> { /// [`AnyUserData`]: crate::AnyUserData /// [`add_method`]: #method.add_method /// [`add_method_mut`]: #method.add_method_mut - fn add_function(&mut self, name: impl AsRef, function: F) + fn add_function(&mut self, name: impl ToString, function: F) where - F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + F: Fn(&'a Lua, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti; /// Add a regular method as a mutable function which accepts generic arguments. @@ -336,10 +332,10 @@ pub trait UserDataMethods<'lua, T> { /// This is a version of [`add_function`] that accepts a FnMut argument. /// /// [`add_function`]: #method.add_function - fn add_function_mut(&mut self, name: impl AsRef, function: F) + fn add_function_mut(&mut self, name: impl ToString, function: F) where - F: FnMut(&'lua Lua, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + F: FnMut(&'a Lua, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti; /// Add a regular method as an async function which accepts generic arguments @@ -352,11 +348,11 @@ pub trait UserDataMethods<'lua, T> { /// [`add_function`]: #method.add_function #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn add_async_function(&mut self, name: impl AsRef, function: F) + fn add_async_function(&mut self, name: impl ToString, function: F) where - F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - FR: Future> + 'lua, + F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, + A: FromLuaMulti, + FR: Future> + 'a, R: IntoLuaMulti; /// Add a metamethod which accepts a `&T` as the first parameter. @@ -367,10 +363,10 @@ pub trait UserDataMethods<'lua, T> { /// side has a metatable. To prevent this, use [`add_meta_function`]. /// /// [`add_meta_function`]: #method.add_meta_function - fn add_meta_method(&mut self, name: impl AsRef, method: M) + fn add_meta_method(&mut self, name: impl ToString, method: M) where - M: Fn(&'lua Lua, &T, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + M: Fn(&'a Lua, &T, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti; /// Add a metamethod as a function which accepts a `&mut T` as the first parameter. @@ -381,10 +377,10 @@ pub trait UserDataMethods<'lua, T> { /// side has a metatable. To prevent this, use [`add_meta_function`]. /// /// [`add_meta_function`]: #method.add_meta_function - fn add_meta_method_mut(&mut self, name: impl AsRef, method: M) + fn add_meta_method_mut(&mut self, name: impl ToString, method: M) where - M: FnMut(&'lua Lua, &mut T, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + M: FnMut(&'a Lua, &mut T, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti; /// Add an async metamethod which accepts a `&T` as the first parameter and returns Future. @@ -396,13 +392,12 @@ pub trait UserDataMethods<'lua, T> { /// [`add_meta_method`]: #method.add_meta_method #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn add_async_meta_method<'s, M, A, MR, R>(&mut self, name: impl AsRef, method: M) + fn add_async_meta_method(&mut self, name: impl ToString, method: M) where - 'lua: 's, T: 'static, - M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - MR: Future> + 's, + M: Fn(&'a Lua, &'a T, A) -> MR + MaybeSend + 'static, + A: FromLuaMulti, + MR: Future> + 'a, R: IntoLuaMulti; /// Add an async metamethod which accepts a `&mut T` as the first parameter and returns Future. @@ -414,13 +409,12 @@ pub trait UserDataMethods<'lua, T> { /// [`add_meta_method_mut`]: #method.add_meta_method_mut #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn add_async_meta_method_mut<'s, M, A, MR, R>(&mut self, name: impl AsRef, method: M) + fn add_async_meta_method_mut(&mut self, name: impl ToString, method: M) where - 'lua: 's, T: 'static, - M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - MR: Future> + 's, + M: Fn(&'a Lua, &'a mut T, A) -> MR + MaybeSend + 'static, + A: FromLuaMulti, + MR: Future> + 'a, R: IntoLuaMulti; /// Add a metamethod which accepts generic arguments. @@ -428,10 +422,10 @@ pub trait UserDataMethods<'lua, T> { /// Metamethods for binary operators can be triggered if either the left or right argument to /// the binary operator has a metatable, so the first argument here is not necessarily a /// userdata of type `T`. - fn add_meta_function(&mut self, name: impl AsRef, function: F) + fn add_meta_function(&mut self, name: impl ToString, function: F) where - F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + F: Fn(&'a Lua, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti; /// Add a metamethod as a mutable function which accepts generic arguments. @@ -439,10 +433,10 @@ pub trait UserDataMethods<'lua, T> { /// This is a version of [`add_meta_function`] that accepts a FnMut argument. /// /// [`add_meta_function`]: #method.add_meta_function - fn add_meta_function_mut(&mut self, name: impl AsRef, function: F) + fn add_meta_function_mut(&mut self, name: impl ToString, function: F) where - F: FnMut(&'lua Lua, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + F: FnMut(&'a Lua, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti; /// Add a metamethod which accepts generic arguments and returns Future. @@ -454,25 +448,18 @@ pub trait UserDataMethods<'lua, T> { /// [`add_meta_function`]: #method.add_meta_function #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn add_async_meta_function(&mut self, name: impl AsRef, function: F) + fn add_async_meta_function(&mut self, name: impl ToString, function: F) where - F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - FR: Future> + 'lua, + F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, + A: FromLuaMulti, + FR: Future> + 'a, R: IntoLuaMulti; - - // - // Below are internal methods used in generated code - // - - #[doc(hidden)] - fn append_methods_from(&mut self, _other: UserDataRegistry<'lua, S>) {} } /// Field registry for [`UserData`] implementors. /// /// [`UserData`]: crate::UserData -pub trait UserDataFields<'lua, T> { +pub trait UserDataFields<'a, T> { /// Add a static field to the `UserData`. /// /// Static fields are implemented by updating the `__index` metamethod and returning the @@ -482,7 +469,7 @@ pub trait UserDataFields<'lua, T> { /// /// If `add_meta_method` is used to set the `__index` metamethod, it will /// be used as a fall-back if no regular field or method are found. - fn add_field(&mut self, name: impl AsRef, value: V) + fn add_field(&mut self, name: impl ToString, value: V) where V: IntoLua + Clone + 'static; @@ -493,9 +480,9 @@ pub trait UserDataFields<'lua, T> { /// /// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will /// be used as a fall-back if no regular field or method are found. - fn add_field_method_get(&mut self, name: impl AsRef, method: M) + fn add_field_method_get(&mut self, name: impl ToString, method: M) where - M: Fn(&'lua Lua, &T) -> Result + MaybeSend + 'static, + M: Fn(&'a Lua, &T) -> Result + MaybeSend + 'static, R: IntoLua; /// Add a regular field setter as a method which accepts a `&mut T` as the first parameter. @@ -505,10 +492,10 @@ pub trait UserDataFields<'lua, T> { /// /// If `add_meta_method` is used to set the `__newindex` metamethod, the `__newindex` metamethod will /// be used as a fall-back if no regular field is found. - fn add_field_method_set(&mut self, name: impl AsRef, method: M) + fn add_field_method_set(&mut self, name: impl ToString, method: M) where - M: FnMut(&'lua Lua, &mut T, A) -> Result<()> + MaybeSend + 'static, - A: FromLua<'lua>; + M: FnMut(&'a Lua, &mut T, A) -> Result<()> + MaybeSend + 'static, + A: FromLua; /// Add a regular field getter as a function which accepts a generic [`AnyUserData`] of type `T` /// argument. @@ -517,9 +504,9 @@ pub trait UserDataFields<'lua, T> { /// /// [`AnyUserData`]: crate::AnyUserData /// [`add_field_method_get`]: #method.add_field_method_get - fn add_field_function_get(&mut self, name: impl AsRef, function: F) + fn add_field_function_get(&mut self, name: impl ToString, function: F) where - F: Fn(&'lua Lua, AnyUserData<'lua>) -> Result + MaybeSend + 'static, + F: Fn(&'a Lua, AnyUserData) -> Result + MaybeSend + 'static, R: IntoLua; /// Add a regular field setter as a function which accepts a generic [`AnyUserData`] of type `T` @@ -529,10 +516,10 @@ pub trait UserDataFields<'lua, T> { /// /// [`AnyUserData`]: crate::AnyUserData /// [`add_field_method_set`]: #method.add_field_method_set - fn add_field_function_set(&mut self, name: impl AsRef, function: F) + fn add_field_function_set(&mut self, name: impl ToString, function: F) where - F: FnMut(&'lua Lua, AnyUserData<'lua>, A) -> Result<()> + MaybeSend + 'static, - A: FromLua<'lua>; + F: FnMut(&'a Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static, + A: FromLua; /// Add a metatable field. /// @@ -542,7 +529,7 @@ pub trait UserDataFields<'lua, T> { /// /// `mlua` will trigger an error on an attempt to define a protected metamethod, /// like `__gc` or `__metatable`. - fn add_meta_field(&mut self, name: impl AsRef, value: V) + fn add_meta_field(&mut self, name: impl ToString, value: V) where V: IntoLua + Clone + 'static; @@ -554,17 +541,10 @@ pub trait UserDataFields<'lua, T> { /// /// `mlua` will trigger an error on an attempt to define a protected metamethod, /// like `__gc` or `__metatable`. - fn add_meta_field_with(&mut self, name: impl AsRef, f: F) + fn add_meta_field_with(&mut self, name: impl ToString, f: F) where - F: Fn(&'lua Lua) -> Result + MaybeSend + 'static, + F: Fn(&'a Lua) -> Result + MaybeSend + 'static, R: IntoLua; - - // - // Below are internal methods used in generated code - // - - #[doc(hidden)] - fn append_fields_from(&mut self, _other: UserDataRegistry<'lua, S>) {} } /// Trait for custom userdata types. @@ -602,11 +582,11 @@ pub trait UserDataFields<'lua, T> { /// struct MyUserData(i32); /// /// impl UserData for MyUserData { -/// fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { +/// fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { /// fields.add_field_method_get("val", |_, this| Ok(this.0)); /// } /// -/// fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { +/// fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { /// methods.add_method_mut("add", |_, this, value: i32| { /// this.0 += value; /// Ok(()) @@ -637,140 +617,19 @@ pub trait UserDataFields<'lua, T> { pub trait UserData: Sized { /// Adds custom fields specific to this userdata. #[allow(unused_variables)] - fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {} + fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) {} /// Adds custom methods and operators specific to this userdata. #[allow(unused_variables)] - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {} -} - -// Wraps UserData in a way to always implement `serde::Serialize` trait. -pub(crate) struct UserDataCell(RefCell>); - -impl UserDataCell { - #[inline] - pub(crate) fn new(data: T) -> Self { - UserDataCell(RefCell::new(UserDataVariant::new(data))) - } - - #[inline] - pub(crate) fn new_ref(data: &T) -> Self { - UserDataCell(RefCell::new(UserDataVariant::new_ref(data))) - } - - #[inline] - pub(crate) fn new_ref_mut(data: &mut T) -> Self { - UserDataCell(RefCell::new(UserDataVariant::new_ref_mut(data))) - } - - #[cfg(feature = "serialize")] - #[inline] - pub(crate) fn new_ser(data: T) -> Self - where - T: Serialize + 'static, - { - UserDataCell(RefCell::new(UserDataVariant::new_ser(data))) - } - - // Immutably borrows the wrapped value. - #[inline] - pub(crate) fn try_borrow(&self) -> Result> { - self.0 - .try_borrow() - .map(|r| Ref::map(r, |r| r.deref())) - .map_err(|_| Error::UserDataBorrowError) - } - - // Mutably borrows the wrapped value. - #[inline] - pub(crate) fn try_borrow_mut(&self) -> Result> { - self.0 - .try_borrow_mut() - .map_err(|_| Error::UserDataBorrowMutError) - .and_then(|r| { - RefMut::filter_map(r, |r| r.try_deref_mut().ok()) - .map_err(|_| Error::UserDataBorrowMutError) - }) - } - - // Consumes this `UserDataCell`, returning the wrapped value. - #[inline] - fn into_inner(self) -> Result { - self.0.into_inner().into_inner() - } -} - -pub(crate) enum UserDataVariant { - Default(Box), - Ref(*const T), - RefMut(*mut T), - #[cfg(feature = "serialize")] - Serializable(Box), -} - -impl UserDataVariant { - #[inline] - fn new(data: T) -> Self { - UserDataVariant::Default(Box::new(data)) - } - - #[inline] - fn new_ref(data: &T) -> Self { - UserDataVariant::Ref(data) - } - - #[inline] - fn new_ref_mut(data: &mut T) -> Self { - UserDataVariant::RefMut(data) - } - - #[cfg(feature = "serialize")] - #[inline] - fn new_ser(data: T) -> Self - where - T: Serialize + 'static, - { - UserDataVariant::Serializable(Box::new(data)) - } - - #[inline] - fn try_deref_mut(&mut self) -> Result<&mut T> { - match self { - Self::Default(data) => Ok(data.deref_mut()), - Self::Ref(_) => Err(Error::UserDataBorrowMutError), - Self::RefMut(data) => unsafe { Ok(&mut **data) }, - #[cfg(feature = "serialize")] - Self::Serializable(data) => unsafe { Ok(&mut *(data.as_mut() as *mut _ as *mut T)) }, - } - } - - #[inline] - fn into_inner(self) -> Result { - match self { - Self::Default(data) => Ok(*data), - Self::Ref(_) | Self::RefMut(_) => Err(Error::UserDataTypeMismatch), - #[cfg(feature = "serialize")] - Self::Serializable(data) => unsafe { - Ok(*Box::from_raw(Box::into_raw(data) as *mut T)) - }, - } - } -} + fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) {} -impl Deref for UserDataVariant { - type Target = T; - - #[inline] - fn deref(&self) -> &Self::Target { - match self { - Self::Default(data) => data, - Self::Ref(data) => unsafe { &**data }, - Self::RefMut(data) => unsafe { &**data }, - #[cfg(feature = "serialize")] - Self::Serializable(data) => unsafe { - &*(data.as_ref() as *const _ as *const Self::Target) - }, - } + /// Registers this type for use in Lua. + /// + /// This method is responsible for calling `add_fields` and `add_methods` on the provided + /// [`UserDataRegistry`]. + fn register(registry: &mut UserDataRegistry) { + Self::add_fields(registry); + Self::add_methods(registry); } } @@ -791,31 +650,12 @@ impl Deref for UserDataVariant { /// [`is`]: crate::AnyUserData::is /// [`borrow`]: crate::AnyUserData::borrow #[derive(Clone, Debug)] -pub struct AnyUserData<'lua>(pub(crate) ValueRef<'lua>, pub(crate) SubtypeId); +pub struct AnyUserData(pub(crate) ValueRef, pub(crate) SubtypeId); -/// Owned handle to an internal Lua userdata. -/// -/// The owned handle holds a *strong* reference to the current Lua instance. -/// Be warned, if you place it into a Lua type (eg. [`UserData`] or a Rust callback), it is *very easy* -/// to accidentally cause reference cycles that would prevent destroying Lua instance. -#[cfg(feature = "unstable")] -#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] -#[derive(Clone, Debug)] -pub struct OwnedAnyUserData(pub(crate) crate::types::OwnedValueRef, pub(crate) SubtypeId); - -#[cfg(feature = "unstable")] -impl OwnedAnyUserData { - /// Get borrowed handle to the underlying Lua userdata. - #[cfg_attr(feature = "send", allow(unused))] - pub const fn to_ref(&self) -> AnyUserData { - AnyUserData(self.0.to_ref(), self.1) - } -} - -impl<'lua> AnyUserData<'lua> { +impl AnyUserData { /// Checks whether the type of this userdata is `T`. pub fn is(&self) -> bool { - self.inspect(|_: &UserDataCell| Ok(())).is_ok() + self.inspect::(|_, _| Ok(())).is_ok() } /// Borrow this userdata immutably if it is of type `T`. @@ -825,8 +665,8 @@ impl<'lua> AnyUserData<'lua> { /// Returns a `UserDataBorrowError` if the userdata is already mutably borrowed. Returns a /// `UserDataTypeMismatch` if the userdata is not of type `T`. #[inline] - pub fn borrow(&self) -> Result> { - self.inspect(|cell| cell.try_borrow()) + pub fn borrow(&self) -> Result> { + self.inspect(|variant, guard| variant.try_make_ref(guard)) } /// Borrow this userdata mutably if it is of type `T`. @@ -836,8 +676,8 @@ impl<'lua> AnyUserData<'lua> { /// Returns a `UserDataBorrowMutError` if the userdata cannot be mutably borrowed. /// Returns a `UserDataTypeMismatch` if the userdata is not of type `T`. #[inline] - pub fn borrow_mut(&self) -> Result> { - self.inspect(|cell| cell.try_borrow_mut()) + pub fn borrow_mut(&self) -> Result> { + self.inspect(|variant, guard| variant.try_make_mut_ref(guard)) } /// Takes the value out of this userdata. @@ -845,7 +685,7 @@ impl<'lua> AnyUserData<'lua> { /// /// Keeps associated user values unchanged (they will be collected by Lua's GC). pub fn take(&self) -> Result { - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -855,8 +695,8 @@ impl<'lua> AnyUserData<'lua> { match type_id { Some(type_id) if type_id == TypeId::of::() => { // Try to borrow userdata exclusively - let _ = (*get_userdata::>(state, -1)).try_borrow_mut()?; - take_userdata::>(state).into_inner() + let _ = (*get_userdata::>(state, -1)).try_borrow_mut()?; + take_userdata::>(state).into_inner() } _ => Err(Error::UserDataTypeMismatch), } @@ -883,13 +723,13 @@ impl<'lua> AnyUserData<'lua> { /// [`set_user_value`]: #method.set_user_value /// [`nth_user_value`]: #method.nth_user_value #[inline] - pub fn user_value>(&self) -> Result { + pub fn user_value(&self) -> Result { self.nth_user_value(1) } #[doc(hidden)] #[deprecated(since = "0.9.0", note = "please use `user_value` instead")] - pub fn get_user_value>(&self) -> Result { + pub fn get_user_value(&self) -> Result { self.nth_user_value(1) } @@ -908,7 +748,7 @@ impl<'lua> AnyUserData<'lua> { return Err(Error::runtime("user value index out of bounds")); } - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -956,12 +796,12 @@ impl<'lua> AnyUserData<'lua> { /// For other Lua versions this functionality is provided using a wrapping table. /// /// [`set_nth_user_value`]: #method.set_nth_user_value - pub fn nth_user_value>(&self, n: usize) -> Result { + pub fn nth_user_value(&self, n: usize) -> Result { if n < 1 || n > u16::MAX as usize { return Err(Error::runtime("user value index out of bounds")); } - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -972,7 +812,7 @@ impl<'lua> AnyUserData<'lua> { #[cfg(feature = "lua54")] if n < USER_VALUE_MAXSLOT { ffi::lua_getiuservalue(state, -1, n as c_int); - return V::from_lua(lua.pop_value(), lua); + return V::from_lua(lua.pop_value(), lua.lua()); } // Multiple (extra) user values are emulated by storing them in a table @@ -987,13 +827,13 @@ impl<'lua> AnyUserData<'lua> { ffi::lua_rawgeti(state, -1, n as ffi::lua_Integer); })?; - V::from_lua(lua.pop_value(), lua) + V::from_lua(lua.pop_value(), lua.lua()) } } #[doc(hidden)] #[deprecated(since = "0.9.0", note = "please use `nth_user_value` instead")] - pub fn get_nth_user_value>(&self, n: usize) -> Result { + pub fn get_nth_user_value(&self, n: usize) -> Result { self.nth_user_value(n) } @@ -1003,7 +843,7 @@ impl<'lua> AnyUserData<'lua> { /// /// [`named_user_value`]: #method.named_user_value pub fn set_named_user_value(&self, name: &str, v: V) -> Result<()> { - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -1037,8 +877,8 @@ impl<'lua> AnyUserData<'lua> { /// Returns an associated value by name set by [`set_named_user_value`]. /// /// [`set_named_user_value`]: #method.set_named_user_value - pub fn named_user_value>(&self, name: &str) -> Result { - let lua = self.0.lua; + pub fn named_user_value(&self, name: &str) -> Result { + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -1056,13 +896,13 @@ impl<'lua> AnyUserData<'lua> { ffi::lua_rawget(state, -2); })?; - V::from_lua(lua.pop_value(), lua) + V::from_lua(lua.pop_value(), lua.lua()) } } #[doc(hidden)] #[deprecated(since = "0.9.0", note = "please use `named_user_value` instead")] - pub fn get_named_user_value>(&self, name: &str) -> Result { + pub fn get_named_user_value(&self, name: &str) -> Result { self.named_user_value(name) } @@ -1075,12 +915,12 @@ impl<'lua> AnyUserData<'lua> { /// /// [`UserDataMetatable`]: crate::UserDataMetatable #[inline] - pub fn get_metatable(&self) -> Result> { + pub fn get_metatable(&self) -> Result { self.get_raw_metatable().map(UserDataMetatable) } - fn get_raw_metatable(&self) -> Result> { - let lua = self.0.lua; + fn get_raw_metatable(&self) -> Result
{ + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -1102,18 +942,11 @@ impl<'lua> AnyUserData<'lua> { self.0.to_pointer() } - /// Convert this handle to owned version. - #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] - #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] - #[inline] - pub fn into_owned(self) -> OwnedAnyUserData { - OwnedAnyUserData(self.0.into_owned(), self.1) - } - #[cfg(feature = "async")] #[inline] pub(crate) fn type_id(&self) -> Result> { - unsafe { self.0.lua.get_userdata_ref_type_id(&self.0) } + let lua = self.0.lua.lock(); + unsafe { lua.get_userdata_ref_type_id(&self.0) } } /// Returns a type name of this `UserData` (from a metatable field). @@ -1126,7 +959,7 @@ impl<'lua> AnyUserData<'lua> { SubtypeId::CData => return Ok(Some("cdata".to_owned())), } - let lua = self.0.lua; + let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -1170,32 +1003,33 @@ impl<'lua> AnyUserData<'lua> { /// Returns `true` if this `AnyUserData` is serializable (eg. was created using `create_ser_userdata`). #[cfg(feature = "serialize")] pub(crate) fn is_serializable(&self) -> bool { - let lua = self.0.lua; + let lua = self.0.lua.lock(); let is_serializable = || unsafe { - // Userdata can be unregistered or destructed + // Userdata must be registered and not destructed let _ = lua.get_userdata_ref_type_id(&self.0)?; - let ud = &*get_userdata::>(lua.ref_thread(), self.0.index); - match &*ud.0.try_borrow().map_err(|_| Error::UserDataBorrowError)? { - UserDataVariant::Serializable(_) => Result::Ok(true), + let ud = &*get_userdata::>(lua.ref_thread(), self.0.index); + match ud { + UserDataVariant::Serializable(..) => Result::Ok(true), _ => Result::Ok(false), } }; is_serializable().unwrap_or(false) } - fn inspect<'a, T, F, R>(&'a self, func: F) -> Result + pub(crate) fn inspect<'a, T, F, R>(&'a self, func: F) -> Result where T: 'static, - F: FnOnce(&'a UserDataCell) -> Result, + F: FnOnce(&'a UserDataVariant, LuaGuard) -> Result, { - let lua = self.0.lua; + let lua = self.0.lua.lock(); unsafe { let type_id = lua.get_userdata_ref_type_id(&self.0)?; match type_id { Some(type_id) if type_id == TypeId::of::() => { let ref_thread = lua.ref_thread(); - func(&*get_userdata::>(ref_thread, self.0.index)) + let ud = get_userdata::>(ref_thread, self.0.index); + func(&*ud, lua) } _ => Err(Error::UserDataTypeMismatch), } @@ -1203,13 +1037,13 @@ impl<'lua> AnyUserData<'lua> { } } -impl<'lua> PartialEq for AnyUserData<'lua> { +impl PartialEq for AnyUserData { fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } -impl<'lua> AsRef> for AnyUserData<'lua> { +impl AsRef for AnyUserData { #[inline] fn as_ref(&self) -> &Self { self @@ -1223,50 +1057,16 @@ unsafe fn getuservalue_table(state: *mut ffi::lua_State, idx: c_int) -> c_int { return ffi::lua_getuservalue(state, idx); } -// Additional shortcuts -#[cfg(feature = "unstable")] -impl OwnedAnyUserData { - /// Borrow this userdata immutably if it is of type `T`. - /// - /// This is a shortcut for [`AnyUserData::borrow()`] - #[inline] - pub fn borrow(&self) -> Result> { - let ud = self.to_ref(); - let t = ud.borrow::()?; - // Reattach lifetime to &self - Ok(unsafe { mem::transmute::, Ref>(t) }) - } - - /// Borrow this userdata mutably if it is of type `T`. - /// - /// This is a shortcut for [`AnyUserData::borrow_mut()`] - #[inline] - pub fn borrow_mut(&self) -> Result> { - let ud = self.to_ref(); - let t = ud.borrow_mut::()?; - // Reattach lifetime to &self - Ok(unsafe { mem::transmute::, RefMut>(t) }) - } - - /// Takes the value out of this userdata. - /// - /// This is a shortcut for [`AnyUserData::take()`] - #[inline] - pub fn take(&self) -> Result { - self.to_ref().take() - } -} - /// Handle to a `UserData` metatable. #[derive(Clone, Debug)] -pub struct UserDataMetatable<'lua>(pub(crate) Table<'lua>); +pub struct UserDataMetatable(pub(crate) Table); -impl<'lua> UserDataMetatable<'lua> { +impl UserDataMetatable { /// Gets the value associated to `key` from the metatable. /// /// If no value is associated to `key`, returns the `Nil` value. /// Access to restricted metamethods such as `__gc` or `__metatable` will cause an error. - pub fn get>(&self, key: impl AsRef) -> Result { + pub fn get(&self, key: impl AsRef) -> Result { self.0.raw_get(MetaMethod::validate(key.as_ref())?) } @@ -1295,7 +1095,7 @@ impl<'lua> UserDataMetatable<'lua> { /// The pairs are wrapped in a [`Result`], since they are lazily converted to `V` type. /// /// [`Result`]: crate::Result - pub fn pairs>(self) -> UserDataMetatablePairs<'lua, V> { + pub fn pairs(self) -> UserDataMetatablePairs { UserDataMetatablePairs(self.0.pairs()) } } @@ -1308,11 +1108,11 @@ impl<'lua> UserDataMetatable<'lua> { /// /// [`UserData`]: crate::UserData /// [`UserDataMetatable::pairs`]: crate::UserDataMetatable::method.pairs -pub struct UserDataMetatablePairs<'lua, V>(TablePairs<'lua, StdString, V>); +pub struct UserDataMetatablePairs(TablePairs); -impl<'lua, V> Iterator for UserDataMetatablePairs<'lua, V> +impl Iterator for UserDataMetatablePairs where - V: FromLua<'lua>, + V: FromLua, { type Item = Result<(StdString, V)>; @@ -1332,12 +1132,12 @@ where } #[cfg(feature = "serialize")] -impl<'lua> Serialize for AnyUserData<'lua> { +impl Serialize for AnyUserData { fn serialize(&self, serializer: S) -> StdResult where S: Serializer, { - let lua = self.0.lua; + let lua = self.0.lua.lock(); // Special case for Luau buffer type #[cfg(feature = "luau")] @@ -1351,74 +1151,19 @@ impl<'lua> Serialize for AnyUserData<'lua> { return serializer.serialize_bytes(buf); } - let data = unsafe { + unsafe { let _ = lua .get_userdata_ref_type_id(&self.0) .map_err(ser::Error::custom)?; - let ud = &*get_userdata::>(lua.ref_thread(), self.0.index); - ud.0.try_borrow() - .map_err(|_| ser::Error::custom(Error::UserDataBorrowError))? - }; - match &*data { - UserDataVariant::Serializable(ser) => ser.serialize(serializer), - _ => Err(ser::Error::custom("cannot serialize ")), + let ud = &*get_userdata::>(lua.ref_thread(), self.0.index); + ud.serialize(serializer) } } } -/// A wrapper type for an immutably borrowed value from a `AnyUserData`. -/// -/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua. -pub struct UserDataRef<'lua, T: 'static>(#[allow(unused)] AnyUserData<'lua>, Ref<'lua, T>); - -impl<'lua, T: 'static> Deref for UserDataRef<'lua, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.1 - } -} - -impl<'lua, T: 'static> UserDataRef<'lua, T> { - pub(crate) fn from_value(value: Value<'lua>) -> Result { - let ud = try_value_to_userdata::(value)?; - // It's safe to lift lifetime of `Ref` to `'lua` as long as we hold AnyUserData to it. - let this = unsafe { mem::transmute(ud.borrow::()?) }; - Ok(UserDataRef(ud, this)) - } -} - -/// A wrapper type for a mutably borrowed value from a `AnyUserData`. -/// -/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua. -pub struct UserDataRefMut<'lua, T: 'static>(#[allow(unused)] AnyUserData<'lua>, RefMut<'lua, T>); - -impl<'lua, T: 'static> Deref for UserDataRefMut<'lua, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.1 - } -} - -impl<'lua, T: 'static> DerefMut for UserDataRefMut<'lua, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.1 - } -} +pub(crate) struct WrappedUserdata Result>(F); -impl<'lua, T: 'static> UserDataRefMut<'lua, T> { - pub(crate) fn from_value(value: Value<'lua>) -> Result { - let ud = try_value_to_userdata::(value)?; - // It's safe to lift lifetime of `RefMut` to `'lua` as long as we hold AnyUserData to it. - let this = unsafe { mem::transmute(ud.borrow_mut::()?) }; - Ok(UserDataRefMut(ud, this)) - } -} - -pub(crate) struct WrappedUserdata FnOnce(&'lua Lua) -> Result>>(F); - -impl<'lua> AnyUserData<'lua> { +impl AnyUserData { /// Wraps any Rust type, returning an opaque type that implements [`IntoLua`] trait. /// /// This function uses [`Lua::create_any_userdata()`] under the hood. @@ -1429,31 +1174,16 @@ impl<'lua> AnyUserData<'lua> { impl IntoLua for WrappedUserdata where - F: for<'l> FnOnce(&'l Lua) -> Result>, + F: for<'l> FnOnce(&'l Lua) -> Result, { - fn into_lua(self, lua: &Lua) -> Result> { + fn into_lua(self, lua: &Lua) -> Result { (self.0)(lua).map(Value::UserData) } } -#[inline] -fn try_value_to_userdata(value: Value) -> Result { - match value { - Value::UserData(ud) => Ok(ud), - _ => Err(Error::FromLuaConversionError { - from: value.type_name(), - to: "userdata", - message: Some(format!("expected userdata of type {}", type_name::())), - }), - } -} +// #[cfg(test)] +// mod assertions { +// use super::*; -#[cfg(test)] -mod assertions { - use super::*; - - static_assertions::assert_not_impl_any!(AnyUserData: Send); - - #[cfg(all(feature = "unstable", not(feature = "send")))] - static_assertions::assert_not_impl_any!(OwnedAnyUserData: Send); -} +// static_assertions::assert_not_impl_any!(AnyUserData: Send); +// } diff --git a/src/userdata_cell.rs b/src/userdata_cell.rs new file mode 100644 index 00000000..59b4503b --- /dev/null +++ b/src/userdata_cell.rs @@ -0,0 +1,410 @@ +use std::any::{type_name, TypeId}; +use std::cell::{Cell, UnsafeCell}; +use std::fmt; +use std::ops::{Deref, DerefMut}; +use std::os::raw::c_int; +use std::rc::Rc; + +#[cfg(feature = "serialize")] +use serde::ser::{Serialize, Serializer}; + +use crate::error::{Error, Result}; +use crate::lua::{Lua, LuaGuard, LuaInner}; +use crate::userdata::AnyUserData; +use crate::util::get_userdata; +use crate::value::{FromLua, Value}; + +// A enum for storing userdata values. +// It's stored inside a Lua VM and protected by the outer `ReentrantMutex`. +pub(crate) enum UserDataVariant { + Default(Rc>), + #[cfg(feature = "serialize")] + Serializable(Rc>>), +} + +impl Clone for UserDataVariant { + #[inline] + fn clone(&self) -> Self { + match self { + Self::Default(inner) => Self::Default(Rc::clone(inner)), + #[cfg(feature = "serialize")] + Self::Serializable(inner) => UserDataVariant::Serializable(Rc::clone(inner)), + } + } +} + +impl UserDataVariant { + #[inline(always)] + pub(crate) fn new(data: T) -> Self { + Self::Default(Rc::new(InnerRefCell::new(data))) + } + + // Immutably borrows the wrapped value in-place. + #[inline(always)] + pub(crate) unsafe fn try_borrow(&self) -> Result> { + UserDataBorrowRef::try_from(self) + } + + // Immutably borrows the wrapped value and returns an owned reference. + #[inline(always)] + pub(crate) fn try_make_ref(&self, guard: LuaGuard) -> Result> { + UserDataRef::try_from(self.clone(), guard) + } + + // Mutably borrows the wrapped value in-place. + #[inline(always)] + pub(crate) unsafe fn try_borrow_mut(&self) -> Result> { + UserDataBorrowMut::try_from(self) + } + + // Mutably borrows the wrapped value and returns an owned reference. + #[inline(always)] + pub(crate) fn try_make_mut_ref(&self, guard: LuaGuard) -> Result> { + UserDataRefMut::try_from(self.clone(), guard) + } + + // Returns the wrapped value. + // + // This method checks that we have exclusive access to the value. + pub(crate) fn into_inner(self) -> Result { + set_writing(self.flag())?; + Ok(match self { + Self::Default(inner) => Rc::into_inner(inner).unwrap().value.into_inner(), + #[cfg(feature = "serialize")] + Self::Serializable(inner) => unsafe { + let raw = Box::into_raw(Rc::into_inner(inner).unwrap().value.into_inner()); + *Box::from_raw(raw as *mut T) + }, + }) + } + + #[inline(always)] + fn flag(&self) -> &Cell { + match self { + Self::Default(inner) => &inner.borrow, + #[cfg(feature = "serialize")] + Self::Serializable(inner) => &inner.borrow, + } + } + + #[inline(always)] + unsafe fn get_ref(&self) -> &T { + match self { + Self::Default(inner) => &*inner.value.get(), + #[cfg(feature = "serialize")] + Self::Serializable(inner) => &*(inner.value.get() as *mut Box), + } + } + + #[inline(always)] + unsafe fn get_mut(&self) -> &mut T { + match self { + Self::Default(inner) => &mut *inner.value.get(), + #[cfg(feature = "serialize")] + Self::Serializable(inner) => &mut *(inner.value.get() as *mut Box), + } + } +} + +#[cfg(feature = "serialize")] +impl UserDataVariant { + #[inline(always)] + pub(crate) fn new_ser(data: T) -> Self { + let data = Box::new(data) as Box; + Self::Serializable(Rc::new(InnerRefCell::new(data))) + } +} + +#[cfg(feature = "serialize")] +impl Serialize for UserDataVariant<()> { + fn serialize(&self, serializer: S) -> std::result::Result { + match self { + UserDataVariant::Default(_) => { + Err(serde::ser::Error::custom("cannot serialize ")) + } + UserDataVariant::Serializable(inner) => unsafe { + let _ = self.try_borrow().map_err(serde::ser::Error::custom)?; + (*inner.value.get()).serialize(serializer) + }, + } + } +} + +// +// Inspired by `std::cell::RefCell`` implementation +// + +pub(crate) struct InnerRefCell { + borrow: Cell, + value: UnsafeCell, +} + +impl InnerRefCell { + #[inline(always)] + pub fn new(value: T) -> Self { + InnerRefCell { + borrow: Cell::new(UNUSED), + value: UnsafeCell::new(value), + } + } +} + +/// A wrapper type for a [`UserData`] value that provides read access. +/// +/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua. +pub struct UserDataRef { + variant: UserDataVariant, + #[allow(unused)] + guard: LuaGuard, +} + +impl Deref for UserDataRef { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + unsafe { self.variant.get_ref() } + } +} + +impl Drop for UserDataRef { + #[inline] + fn drop(&mut self) { + unset_reading(self.variant.flag()); + } +} + +impl fmt::Debug for UserDataRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +impl fmt::Display for UserDataRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +impl UserDataRef { + #[inline] + fn try_from(variant: UserDataVariant, guard: LuaGuard) -> Result { + set_reading(variant.flag())?; + Ok(UserDataRef { variant, guard }) + } +} + +impl FromLua for UserDataRef { + fn from_lua(value: Value, _: &Lua) -> Result { + try_value_to_userdata::(value)?.borrow() + } + + unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { + let type_id = lua.get_userdata_type_id(idx)?; + match type_id { + Some(type_id) if type_id == TypeId::of::() => lua.get_userdata_ref(idx), + _ => Err(Error::UserDataTypeMismatch), + } + } +} + +/// A wrapper type for a mutably borrowed value from a `AnyUserData`. +/// +/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua. +pub struct UserDataRefMut { + variant: UserDataVariant, + #[allow(unused)] + guard: LuaGuard, +} + +impl Deref for UserDataRefMut { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + unsafe { self.variant.get_ref() } + } +} + +impl DerefMut for UserDataRefMut { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { self.variant.get_mut() } + } +} + +impl Drop for UserDataRefMut { + #[inline] + fn drop(&mut self) { + unset_writing(self.variant.flag()); + } +} + +impl fmt::Debug for UserDataRefMut { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +impl fmt::Display for UserDataRefMut { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +impl UserDataRefMut { + fn try_from(variant: UserDataVariant, guard: LuaGuard) -> Result { + // There must currently be no existing references + set_writing(variant.flag())?; + Ok(UserDataRefMut { variant, guard }) + } +} + +impl FromLua for UserDataRefMut { + fn from_lua(value: Value, _: &Lua) -> Result { + try_value_to_userdata::(value)?.borrow_mut() + } + + unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { + let type_id = lua.get_userdata_type_id(idx)?; + match type_id { + Some(type_id) if type_id == TypeId::of::() => { + let guard = lua.lua().lock_arc(); + (*get_userdata::>(lua.state(), idx)).try_make_mut_ref(guard) + } + _ => Err(Error::UserDataTypeMismatch), + } + } +} + +// Positive values represent the number of `Ref` active. Negative values +// represent the number of `RefMut` active. Multiple `RefMut`s can only be +// active at a time if they refer to distinct, nonoverlapping components of a +// `RefCell` (e.g., different ranges of a slice). +type BorrowFlag = isize; +const UNUSED: BorrowFlag = 0; + +#[inline(always)] +fn is_writing(x: BorrowFlag) -> bool { + x < UNUSED +} + +#[inline(always)] +fn is_reading(x: BorrowFlag) -> bool { + x > UNUSED +} + +#[inline(always)] +fn set_writing(borrow: &Cell) -> Result<()> { + let flag = borrow.get(); + if flag != UNUSED { + return Err(Error::UserDataBorrowMutError); + } + borrow.set(UNUSED - 1); + Ok(()) +} + +#[inline(always)] +fn set_reading(borrow: &Cell) -> Result<()> { + let flag = borrow.get().wrapping_add(1); + if !is_reading(flag) { + return Err(Error::UserDataBorrowError); + } + borrow.set(flag); + Ok(()) +} + +#[inline(always)] +#[track_caller] +fn unset_writing(borrow: &Cell) { + let flag = borrow.get(); + debug_assert!(is_writing(flag)); + borrow.set(flag + 1); +} + +#[inline(always)] +#[track_caller] +fn unset_reading(borrow: &Cell) { + let flag = borrow.get(); + debug_assert!(is_reading(flag)); + borrow.set(flag - 1); +} + +pub(crate) struct UserDataBorrowRef<'a, T>(&'a UserDataVariant); + +impl<'a, T> Drop for UserDataBorrowRef<'a, T> { + #[inline] + fn drop(&mut self) { + unset_reading(self.0.flag()); + } +} + +impl<'a, T> Deref for UserDataBorrowRef<'a, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + unsafe { self.0.get_ref() } + } +} + +impl<'a, T> UserDataBorrowRef<'a, T> { + #[inline(always)] + pub(crate) fn try_from(variant: &'a UserDataVariant) -> Result { + set_reading(variant.flag())?; + Ok(UserDataBorrowRef(&variant)) + } +} + +pub(crate) struct UserDataBorrowMut<'a, T>(&'a UserDataVariant); + +impl<'a, T> Drop for UserDataBorrowMut<'a, T> { + #[inline] + fn drop(&mut self) { + unset_writing(self.0.flag()); + } +} + +impl<'a, T> Deref for UserDataBorrowMut<'a, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + unsafe { self.0.get_ref() } + } +} + +impl<'a, T> DerefMut for UserDataBorrowMut<'a, T> { + #[inline] + fn deref_mut(&mut self) -> &mut T { + unsafe { self.0.get_mut() } + } +} + +impl<'a, T> UserDataBorrowMut<'a, T> { + #[inline(always)] + pub(crate) fn try_from(variant: &'a UserDataVariant) -> Result { + set_writing(variant.flag())?; + Ok(UserDataBorrowMut(&variant)) + } +} + +#[inline] +fn try_value_to_userdata(value: Value) -> Result { + match value { + Value::UserData(ud) => Ok(ud), + _ => Err(Error::FromLuaConversionError { + from: value.type_name(), + to: "userdata", + message: Some(format!("expected userdata of type {}", type_name::())), + }), + } +} + +#[cfg(test)] +mod assertions { + use super::*; + + static_assertions::assert_not_impl_all!(UserDataRef<()>: Sync, Send); + static_assertions::assert_not_impl_all!(UserDataRefMut<()>: Sync, Send); +} diff --git a/src/userdata_ext.rs b/src/userdata_ext.rs index 268324db..0863d9b6 100644 --- a/src/userdata_ext.rs +++ b/src/userdata_ext.rs @@ -7,9 +7,9 @@ use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; use futures_util::future::{self, LocalBoxFuture}; /// An extension trait for [`AnyUserData`] that provides a variety of convenient functionality. -pub trait AnyUserDataExt<'lua>: Sealed { +pub trait AnyUserDataExt: Sealed { /// Gets the value associated to `key` from the userdata, assuming it has `__index` metamethod. - fn get>(&self, key: K) -> Result; + fn get(&self, key: K) -> Result; /// Sets the value associated to `key` in the userdata, assuming it has `__newindex` metamethod. fn set(&self, key: K, value: V) -> Result<()>; @@ -20,24 +20,24 @@ pub trait AnyUserDataExt<'lua>: Sealed { fn call(&self, args: A) -> Result where A: IntoLuaMulti, - R: FromLuaMulti<'lua>; + R: FromLuaMulti; /// Asynchronously calls the userdata as a function assuming it has `__call` metamethod. /// /// The metamethod is called with the userdata as its first argument, followed by the passed arguments. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async(&self, args: A) -> LocalBoxFuture<'lua, Result> + fn call_async(&self, args: A) -> LocalBoxFuture<'static, Result> where A: IntoLuaMulti, - R: FromLuaMulti<'lua> + 'lua; + R: FromLuaMulti + 'static; /// Calls the userdata method, assuming it has `__index` metamethod /// and a function associated to `name`. fn call_method(&self, name: &str, args: A) -> Result where A: IntoLuaMulti, - R: FromLuaMulti<'lua>; + R: FromLuaMulti; /// Gets the function associated to `key` from the table and asynchronously executes it, /// passing the table itself along with `args` as function arguments and returning Future. @@ -47,10 +47,10 @@ pub trait AnyUserDataExt<'lua>: Sealed { /// This might invoke the `__index` metamethod. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_method(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result> + fn call_async_method(&self, name: &str, args: A) -> LocalBoxFuture<'static, Result> where A: IntoLuaMulti, - R: FromLuaMulti<'lua> + 'lua; + R: FromLuaMulti + 'static; /// Gets the function associated to `key` from the table and executes it, /// passing `args` as function arguments. @@ -62,7 +62,7 @@ pub trait AnyUserDataExt<'lua>: Sealed { fn call_function(&self, name: &str, args: A) -> Result where A: IntoLuaMulti, - R: FromLuaMulti<'lua>; + R: FromLuaMulti; /// Gets the function associated to `key` from the table and asynchronously executes it, /// passing `args` as function arguments and returning Future. @@ -72,14 +72,14 @@ pub trait AnyUserDataExt<'lua>: Sealed { /// This might invoke the `__index` metamethod. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_function(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result> + fn call_async_function(&self, name: &str, args: A) -> LocalBoxFuture<'static, Result> where A: IntoLuaMulti, - R: FromLuaMulti<'lua> + 'lua; + R: FromLuaMulti + 'static; } -impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { - fn get>(&self, key: K) -> Result { +impl AnyUserDataExt for AnyUserData { + fn get(&self, key: K) -> Result { let metatable = self.get_metatable()?; match metatable.get::(MetaMethod::Index)? { Value::Table(table) => table.raw_get(key), @@ -100,7 +100,7 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { fn call(&self, args: A) -> Result where A: IntoLuaMulti, - R: FromLuaMulti<'lua>, + R: FromLuaMulti, { let metatable = self.get_metatable()?; match metatable.get::(MetaMethod::Call)? { @@ -110,10 +110,10 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { } #[cfg(feature = "async")] - fn call_async(&self, args: A) -> LocalBoxFuture<'lua, Result> + fn call_async(&self, args: A) -> LocalBoxFuture<'static, Result> where A: IntoLuaMulti, - R: FromLuaMulti<'lua> + 'lua, + R: FromLuaMulti + 'static, { let metatable = match self.get_metatable() { Ok(metatable) => metatable, @@ -121,7 +121,8 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { }; match metatable.get::(MetaMethod::Call) { Ok(Value::Function(func)) => { - let args = match (self, args).into_lua_multi(self.0.lua) { + let lua = self.0.lua.lock(); + let args = match (self, args).into_lua_multi(lua.lua()) { Ok(args) => args, Err(e) => return Box::pin(future::err(e)), }; @@ -137,16 +138,16 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { fn call_method(&self, name: &str, args: A) -> Result where A: IntoLuaMulti, - R: FromLuaMulti<'lua>, + R: FromLuaMulti, { self.call_function(name, (self, args)) } #[cfg(feature = "async")] - fn call_async_method(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result> + fn call_async_method(&self, name: &str, args: A) -> LocalBoxFuture<'static, Result> where A: IntoLuaMulti, - R: FromLuaMulti<'lua> + 'lua, + R: FromLuaMulti + 'static, { self.call_async_function(name, (self, args)) } @@ -154,7 +155,7 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { fn call_function(&self, name: &str, args: A) -> Result where A: IntoLuaMulti, - R: FromLuaMulti<'lua>, + R: FromLuaMulti, { match self.get(name)? { Value::Function(func) => func.call(args), @@ -166,14 +167,15 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> { } #[cfg(feature = "async")] - fn call_async_function(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result> + fn call_async_function(&self, name: &str, args: A) -> LocalBoxFuture<'static, Result> where A: IntoLuaMulti, - R: FromLuaMulti<'lua> + 'lua, + R: FromLuaMulti + 'static, { match self.get(name) { Ok(Value::Function(func)) => { - let args = match args.into_lua_multi(self.0.lua) { + let lua = self.0.lua.lock(); + let args = match args.into_lua_multi(lua.lua()) { Ok(args) => args, Err(e) => return Box::pin(future::err(e)), }; diff --git a/src/userdata_impl.rs b/src/userdata_impl.rs index b3acd48b..b5527784 100644 --- a/src/userdata_impl.rs +++ b/src/userdata_impl.rs @@ -1,7 +1,7 @@ #![allow(clippy::await_holding_refcell_ref, clippy::await_holding_lock)] use std::any::TypeId; -use std::cell::{Ref, RefCell, RefMut}; +use std::cell::RefCell; use std::marker::PhantomData; use std::os::raw::c_int; use std::string::String as StdString; @@ -10,9 +10,8 @@ use std::sync::{Arc, Mutex, RwLock}; use crate::error::{Error, Result}; use crate::lua::Lua; use crate::types::{Callback, MaybeSend}; -use crate::userdata::{ - AnyUserData, MetaMethod, UserData, UserDataCell, UserDataFields, UserDataMethods, -}; +use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods}; +use crate::userdata_cell::{UserDataBorrowMut, UserDataBorrowRef, UserDataVariant}; use crate::util::{get_userdata, short_type_name}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; @@ -23,25 +22,25 @@ use std::rc::Rc; use {crate::types::AsyncCallback, futures_util::future, std::future::Future}; /// Handle to registry for userdata methods and metamethods. -pub struct UserDataRegistry<'lua, T: 'static> { +pub struct UserDataRegistry<'a, T: 'static> { // Fields - pub(crate) fields: Vec<(String, Callback<'lua, 'static>)>, - pub(crate) field_getters: Vec<(String, Callback<'lua, 'static>)>, - pub(crate) field_setters: Vec<(String, Callback<'lua, 'static>)>, - pub(crate) meta_fields: Vec<(String, Callback<'lua, 'static>)>, + pub(crate) fields: Vec<(String, Callback<'a>)>, + pub(crate) field_getters: Vec<(String, Callback<'a>)>, + pub(crate) field_setters: Vec<(String, Callback<'a>)>, + pub(crate) meta_fields: Vec<(String, Callback<'a>)>, // Methods - pub(crate) methods: Vec<(String, Callback<'lua, 'static>)>, + pub(crate) methods: Vec<(String, Callback<'a>)>, #[cfg(feature = "async")] - pub(crate) async_methods: Vec<(String, AsyncCallback<'lua, 'static>)>, - pub(crate) meta_methods: Vec<(String, Callback<'lua, 'static>)>, + pub(crate) async_methods: Vec<(String, AsyncCallback<'a>)>, + pub(crate) meta_methods: Vec<(String, Callback<'a>)>, #[cfg(feature = "async")] - pub(crate) async_meta_methods: Vec<(String, AsyncCallback<'lua, 'static>)>, + pub(crate) async_meta_methods: Vec<(String, AsyncCallback<'a>)>, _type: PhantomData, } -impl<'lua, T: 'static> UserDataRegistry<'lua, T> { +impl<'a, T: 'static> UserDataRegistry<'a, T> { pub(crate) const fn new() -> Self { UserDataRegistry { fields: Vec::new(), @@ -58,10 +57,10 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> { } } - fn box_method(name: &str, method: M) -> Callback<'lua, 'static> + fn box_method(name: &str, method: M) -> Callback<'a> where - M: Fn(&'lua Lua, &T, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + M: Fn(&'a Lua, &T, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti, { let name = get_function_name::(name); @@ -87,57 +86,57 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> { match try_self_arg!(lua.get_userdata_type_id(index)) { Some(id) if id == TypeId::of::() => { - let ud = try_self_arg!(get_userdata_ref::(state, index)); - method(lua, &ud, args?)?.push_into_stack_multi(lua) - } - #[cfg(not(feature = "send"))] - Some(id) if id == TypeId::of::>() => { - let ud = try_self_arg!(get_userdata_ref::>(state, index)); - method(lua, &ud, args?)?.push_into_stack_multi(lua) - } - #[cfg(not(feature = "send"))] - Some(id) if id == TypeId::of::>>() => { - let ud = try_self_arg!(get_userdata_ref::>>(state, index)); - let ud = try_self_arg!(ud.try_borrow(), Error::UserDataBorrowError); - method(lua, &ud, args?)?.push_into_stack_multi(lua) - } - Some(id) if id == TypeId::of::>() => { - let ud = try_self_arg!(get_userdata_ref::>(state, index)); - method(lua, &ud, args?)?.push_into_stack_multi(lua) - } - Some(id) if id == TypeId::of::>>() => { - let ud = try_self_arg!(get_userdata_ref::>>(state, index)); - let ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowError); - method(lua, &ud, args?)?.push_into_stack_multi(lua) - } - #[cfg(feature = "parking_lot")] - Some(id) if id == TypeId::of::>>() => { - let ud = get_userdata_ref::>>(state, index); - let ud = try_self_arg!(ud); - let ud = try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowError)); - method(lua, &ud, args?)?.push_into_stack_multi(lua) - } - Some(id) if id == TypeId::of::>>() => { - let ud = try_self_arg!(get_userdata_ref::>>(state, index)); - let ud = try_self_arg!(ud.try_read(), Error::UserDataBorrowError); - method(lua, &ud, args?)?.push_into_stack_multi(lua) - } - #[cfg(feature = "parking_lot")] - Some(id) if id == TypeId::of::>>() => { - let ud = get_userdata_ref::>>(state, index); - let ud = try_self_arg!(ud); - let ud = try_self_arg!(ud.try_read().ok_or(Error::UserDataBorrowError)); - method(lua, &ud, args?)?.push_into_stack_multi(lua) + let ud = try_self_arg!(borrow_userdata_ref::(state, index)); + method(lua.lua(), &ud, args?)?.push_into_stack_multi(lua) } + // #[cfg(not(feature = "send"))] + // Some(id) if id == TypeId::of::>() => { + // let ud = try_self_arg!(get_userdata_ref::>(state, index)); + // method(lua.lua(), ud, args?)?.push_into_stack_multi(lua) + // } + // #[cfg(not(feature = "send"))] + // Some(id) if id == TypeId::of::>>() => { + // let ud = try_self_arg!(get_userdata_ref::>>(state, index)); + // let ud = try_self_arg!(ud.try_borrow(), Error::UserDataBorrowError); + // method(lua.lua(), &ud, args?)?.push_into_stack_multi(lua) + // } + // Some(id) if id == TypeId::of::>() => { + // let ud = try_self_arg!(get_userdata_ref::>(state, index)); + // method(lua.lua(), &ud, args?)?.push_into_stack_multi(lua) + // } + // Some(id) if id == TypeId::of::>>() => { + // let ud = try_self_arg!(get_userdata_ref::>>(state, index)); + // let ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowError); + // method(lua.lua(), &ud, args?)?.push_into_stack_multi(lua) + // } + // #[cfg(feature = "parking_lot")] + // Some(id) if id == TypeId::of::>>() => { + // let ud = get_userdata_ref::>>(state, index); + // let ud = try_self_arg!(ud); + // let ud = try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowError)); + // method(lua.lua(), &ud, args?)?.push_into_stack_multi(lua) + // } + // Some(id) if id == TypeId::of::>>() => { + // let ud = try_self_arg!(get_userdata_ref::>>(state, index)); + // let ud = try_self_arg!(ud.try_read(), Error::UserDataBorrowError); + // method(lua.lua(), &ud, args?)?.push_into_stack_multi(lua) + // } + // #[cfg(feature = "parking_lot")] + // Some(id) if id == TypeId::of::>>() => { + // let ud = get_userdata_ref::>>(state, index); + // let ud = try_self_arg!(ud); + // let ud = try_self_arg!(ud.try_read().ok_or(Error::UserDataBorrowError)); + // method(lua.lua(), &ud, args?)?.push_into_stack_multi(lua) + // } _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), } }) } - fn box_method_mut(name: &str, method: M) -> Callback<'lua, 'static> + fn box_method_mut(name: &str, method: M) -> Callback<'a> where - M: FnMut(&'lua Lua, &mut T, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + M: FnMut(&'a Lua, &mut T, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti, { let name = get_function_name::(name); @@ -167,61 +166,59 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> { match try_self_arg!(lua.get_userdata_type_id(index)) { Some(id) if id == TypeId::of::() => { - let mut ud = try_self_arg!(get_userdata_mut::(state, index)); - method(lua, &mut ud, args?)?.push_into_stack_multi(lua) - } - #[cfg(not(feature = "send"))] - Some(id) if id == TypeId::of::>() => Err(Error::UserDataBorrowMutError), - #[cfg(not(feature = "send"))] - Some(id) if id == TypeId::of::>>() => { - let ud = try_self_arg!(get_userdata_mut::>>(state, index)); - let mut ud = try_self_arg!(ud.try_borrow_mut(), Error::UserDataBorrowMutError); - method(lua, &mut ud, args?)?.push_into_stack_multi(lua) - } - Some(id) if id == TypeId::of::>() => Err(Error::UserDataBorrowMutError), - Some(id) if id == TypeId::of::>>() => { - let ud = try_self_arg!(get_userdata_mut::>>(state, index)); - let mut ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowMutError); - method(lua, &mut ud, args?)?.push_into_stack_multi(lua) - } - #[cfg(feature = "parking_lot")] - Some(id) if id == TypeId::of::>>() => { - let ud = get_userdata_mut::>>(state, index); - let ud = try_self_arg!(ud); - let mut ud = try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowMutError)); - method(lua, &mut ud, args?)?.push_into_stack_multi(lua) - } - Some(id) if id == TypeId::of::>>() => { - let ud = try_self_arg!(get_userdata_mut::>>(state, index)); - let mut ud = try_self_arg!(ud.try_write(), Error::UserDataBorrowMutError); - method(lua, &mut ud, args?)?.push_into_stack_multi(lua) - } - #[cfg(feature = "parking_lot")] - Some(id) if id == TypeId::of::>>() => { - let ud = get_userdata_mut::>>(state, index); - let ud = try_self_arg!(ud); - let mut ud = try_self_arg!(ud.try_write().ok_or(Error::UserDataBorrowMutError)); - method(lua, &mut ud, args?)?.push_into_stack_multi(lua) + let mut ud = try_self_arg!(borrow_userdata_mut::(state, index)); + method(lua.lua(), &mut ud, args?)?.push_into_stack_multi(lua) } + // #[cfg(not(feature = "send"))] + // Some(id) if id == TypeId::of::>() => Err(Error::UserDataBorrowMutError), + // #[cfg(not(feature = "send"))] + // Some(id) if id == TypeId::of::>>() => { + // let ud = try_self_arg!(get_userdata_mut::>>(state, index)); + // let mut ud = try_self_arg!(ud.try_borrow_mut(), Error::UserDataBorrowMutError); + // method(lua.lua(), &mut ud, args?)?.push_into_stack_multi(lua) + // } + // Some(id) if id == TypeId::of::>() => Err(Error::UserDataBorrowMutError), + // Some(id) if id == TypeId::of::>>() => { + // let ud = try_self_arg!(get_userdata_mut::>>(state, index)); + // let mut ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowMutError); + // method(lua.lua(), &mut ud, args?)?.push_into_stack_multi(lua) + // } + // #[cfg(feature = "parking_lot")] + // Some(id) if id == TypeId::of::>>() => { + // let ud = get_userdata_mut::>>(state, index); + // let ud = try_self_arg!(ud); + // let mut ud = try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowMutError)); + // method(lua.lua(), &mut ud, args?)?.push_into_stack_multi(lua) + // } + // Some(id) if id == TypeId::of::>>() => { + // let ud = try_self_arg!(get_userdata_mut::>>(state, index)); + // let mut ud = try_self_arg!(ud.try_write(), Error::UserDataBorrowMutError); + // method(lua.lua(), &mut ud, args?)?.push_into_stack_multi(lua) + // } + // #[cfg(feature = "parking_lot")] + // Some(id) if id == TypeId::of::>>() => { + // let ud = get_userdata_mut::>>(state, index); + // let ud = try_self_arg!(ud); + // let mut ud = try_self_arg!(ud.try_write().ok_or(Error::UserDataBorrowMutError)); + // method(lua.lua(), &mut ud, args?)?.push_into_stack_multi(lua) + // } _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), } }) } #[cfg(feature = "async")] - fn box_async_method<'s, M, A, MR, R>(name: &str, method: M) -> AsyncCallback<'lua, 'static> + fn box_async_method(name: String, method: M) -> AsyncCallback<'a> where - 'lua: 's, - T: 'static, - M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - MR: Future> + 's, + M: Fn(&'a Lua, &'a T, A) -> MR + MaybeSend + 'static, + A: FromLuaMulti, + MR: Future> + 'a, R: IntoLuaMulti, { - let name = Arc::new(get_function_name::(name)); + let name = Arc::new(get_function_name::(&name)); let method = Arc::new(method); - Box::new(move |lua, mut args| unsafe { + Box::new(move |rawlua, mut args| unsafe { let name = name.clone(); let method = method.clone(); macro_rules! try_self_arg { @@ -237,65 +234,63 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> { let this = args.pop_front().ok_or_else(|| { Error::from_lua_conversion("missing argument", "userdata", None) }); + let lua = rawlua.lua(); let this = try_self_arg!(AnyUserData::from_lua(try_self_arg!(this), lua)); let args = A::from_lua_args(args, 2, Some(&name), lua); - let (ref_thread, index) = (lua.ref_thread(), this.0.index); + let (ref_thread, index) = (rawlua.ref_thread(), this.0.index); match try_self_arg!(this.type_id()) { Some(id) if id == TypeId::of::() => { - let ud = try_self_arg!(get_userdata_ref::(ref_thread, index)); - let ud = std::mem::transmute::<&T, &T>(&ud); - method(lua, ud, args?).await?.push_into_stack_multi(lua) - } - #[cfg(not(feature = "send"))] - Some(id) if id == TypeId::of::>() => { - let ud = try_self_arg!(get_userdata_ref::>(ref_thread, index)); - let ud = std::mem::transmute::<&T, &T>(&ud); - method(lua, ud, args?).await?.push_into_stack_multi(lua) - } - #[cfg(not(feature = "send"))] - Some(id) if id == TypeId::of::>>() => { - let ud = - try_self_arg!(get_userdata_ref::>>(ref_thread, index)); - let ud = try_self_arg!(ud.try_borrow(), Error::UserDataBorrowError); - let ud = std::mem::transmute::<&T, &T>(&ud); - method(lua, ud, args?).await?.push_into_stack_multi(lua) - } - Some(id) if id == TypeId::of::>() => { - let ud = try_self_arg!(get_userdata_ref::>(ref_thread, index)); - let ud = std::mem::transmute::<&T, &T>(&ud); - method(lua, ud, args?).await?.push_into_stack_multi(lua) - } - Some(id) if id == TypeId::of::>>() => { - let ud = - try_self_arg!(get_userdata_ref::>>(ref_thread, index)); - let ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowError); - let ud = std::mem::transmute::<&T, &T>(&ud); - method(lua, ud, args?).await?.push_into_stack_multi(lua) - } - #[cfg(feature = "parking_lot")] - Some(id) if id == TypeId::of::>>() => { - let ud = get_userdata_ref::>>(ref_thread, index); - let ud = try_self_arg!(ud); - let ud = try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowError)); - let ud = std::mem::transmute::<&T, &T>(&ud); - method(lua, ud, args?).await?.push_into_stack_multi(lua) - } - Some(id) if id == TypeId::of::>>() => { - let ud = - try_self_arg!(get_userdata_ref::>>(ref_thread, index)); - let ud = try_self_arg!(ud.try_read(), Error::UserDataBorrowError); - let ud = std::mem::transmute::<&T, &T>(&ud); - method(lua, ud, args?).await?.push_into_stack_multi(lua) - } - #[cfg(feature = "parking_lot")] - Some(id) if id == TypeId::of::>>() => { - let ud = get_userdata_ref::>>(ref_thread, index); - let ud = try_self_arg!(ud); - let ud = try_self_arg!(ud.try_read().ok_or(Error::UserDataBorrowError)); + let ud = try_self_arg!(borrow_userdata_ref::(ref_thread, index)); let ud = std::mem::transmute::<&T, &T>(&ud); - method(lua, ud, args?).await?.push_into_stack_multi(lua) + method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) } + // #[cfg(not(feature = "send"))] + // Some(id) if id == TypeId::of::>() => { + // let ud = try_self_arg!(rawlua.get_userdata_ref::>(&this)); + // let ud = std::mem::transmute::<&T, &T>(&ud); + // method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) + // } + // #[cfg(not(feature = "send"))] + // Some(id) if id == TypeId::of::>>() => { + // let ud = try_self_arg!(rawlua.get_userdata_ref::>>(&this)); + // let ud = try_self_arg!(ud.try_borrow(), Error::UserDataBorrowError); + // let ud = std::mem::transmute::<&T, &T>(&ud); + // method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) + // } + // Some(id) if id == TypeId::of::>() => { + // let ud = try_self_arg!(rawlua.get_userdata_ref::>(&this)); + // let ud = std::mem::transmute::<&T, &T>(&ud); + // method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) + // } + // Some(id) if id == TypeId::of::>>() => { + // let ud = try_self_arg!(rawlua.get_userdata_ref::>>(&this)); + // let ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowError); + // let ud = std::mem::transmute::<&T, &T>(&ud); + // method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) + // } + // #[cfg(feature = "parking_lot")] + // Some(id) if id == TypeId::of::>>() => { + // let ud = rawlua.get_userdata_ref::>>(&this); + // let ud = try_self_arg!(ud); + // let ud = try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowError)); + // let ud = std::mem::transmute::<&T, &T>(&ud); + // method(lua, ud, args?).await?.push_into_stack_multi(lua) + // } + // Some(id) if id == TypeId::of::>>() => { + // let ud = try_self_arg!(rawlua.get_userdata_ref::>>(&this)); + // let ud = try_self_arg!(ud.try_read(), Error::UserDataBorrowError); + // let ud = std::mem::transmute::<&T, &T>(&ud); + // method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) + // } + // #[cfg(feature = "parking_lot")] + // Some(id) if id == TypeId::of::>>() => { + // let ud = get_userdata_ref::>>(ref_thread, index); + // let ud = try_self_arg!(ud); + // let ud = try_self_arg!(ud.try_read().ok_or(Error::UserDataBorrowError)); + // let ud = std::mem::transmute::<&T, &T>(&ud); + // method(lua, ud, args?).await?.push_into_stack_multi(lua) + // } _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), } }) @@ -303,19 +298,17 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> { } #[cfg(feature = "async")] - fn box_async_method_mut<'s, M, A, MR, R>(name: &str, method: M) -> AsyncCallback<'lua, 'static> + fn box_async_method_mut(name: String, method: M) -> AsyncCallback<'a> where - 'lua: 's, - T: 'static, - M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - MR: Future> + 's, + M: Fn(&'a Lua, &'a mut T, A) -> MR + MaybeSend + 'static, + A: FromLuaMulti, + MR: Future> + 'a, R: IntoLuaMulti, { - let name = Arc::new(get_function_name::(name)); + let name = Arc::new(get_function_name::(&name)); let method = Arc::new(method); - Box::new(move |lua, mut args| unsafe { + Box::new(move |rawlua, mut args| unsafe { let name = name.clone(); let method = method.clone(); macro_rules! try_self_arg { @@ -331,86 +324,87 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> { let this = args.pop_front().ok_or_else(|| { Error::from_lua_conversion("missing argument", "userdata", None) }); + let lua = rawlua.lua(); let this = try_self_arg!(AnyUserData::from_lua(try_self_arg!(this), lua)); let args = A::from_lua_args(args, 2, Some(&name), lua); - let (ref_thread, index) = (lua.ref_thread(), this.0.index); + let (ref_thread, index) = (rawlua.ref_thread(), this.0.index); match try_self_arg!(this.type_id()) { Some(id) if id == TypeId::of::() => { - let mut ud = try_self_arg!(get_userdata_mut::(ref_thread, index)); + let mut ud = try_self_arg!(borrow_userdata_mut::(ref_thread, index)); let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud); - method(lua, ud, args?).await?.push_into_stack_multi(lua) - } - #[cfg(not(feature = "send"))] - Some(id) if id == TypeId::of::>>() => { - Err(Error::UserDataBorrowMutError) - } - #[cfg(not(feature = "send"))] - Some(id) if id == TypeId::of::>>() => { - let ud = - try_self_arg!(get_userdata_mut::>>(ref_thread, index)); - let mut ud = - try_self_arg!(ud.try_borrow_mut(), Error::UserDataBorrowMutError); - let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud); - method(lua, ud, args?).await?.push_into_stack_multi(lua) - } - #[cfg(not(feature = "send"))] - Some(id) if id == TypeId::of::>() => Err(Error::UserDataBorrowMutError), - Some(id) if id == TypeId::of::>>() => { - let ud = - try_self_arg!(get_userdata_mut::>>(ref_thread, index)); - let mut ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowMutError); - let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud); - method(lua, ud, args?).await?.push_into_stack_multi(lua) - } - #[cfg(feature = "parking_lot")] - Some(id) if id == TypeId::of::>>() => { - let ud = get_userdata_mut::>>(ref_thread, index); - let ud = try_self_arg!(ud); - let mut ud = - try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowMutError)); - let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud); - method(lua, ud, args?).await?.push_into_stack_multi(lua) - } - Some(id) if id == TypeId::of::>>() => { - let ud = - try_self_arg!(get_userdata_mut::>>(ref_thread, index)); - let mut ud = try_self_arg!(ud.try_write(), Error::UserDataBorrowMutError); - let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud); - method(lua, ud, args?).await?.push_into_stack_multi(lua) - } - #[cfg(feature = "parking_lot")] - Some(id) if id == TypeId::of::>>() => { - let ud = get_userdata_mut::>>(ref_thread, index); - let ud = try_self_arg!(ud); - let mut ud = - try_self_arg!(ud.try_write().ok_or(Error::UserDataBorrowMutError)); - let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud); - method(lua, ud, args?).await?.push_into_stack_multi(lua) + method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) } + // #[cfg(not(feature = "send"))] + // Some(id) if id == TypeId::of::>>() => { + // Err(Error::UserDataBorrowMutError) + // } + // #[cfg(not(feature = "send"))] + // Some(id) if id == TypeId::of::>>() => { + // let ud = + // try_self_arg!(get_userdata_mut::>>(ref_thread, index)); + // let mut ud = + // try_self_arg!(ud.try_borrow_mut(), Error::UserDataBorrowMutError); + // let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud); + // method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) + // } + // #[cfg(not(feature = "send"))] + // Some(id) if id == TypeId::of::>() => Err(Error::UserDataBorrowMutError), + // Some(id) if id == TypeId::of::>>() => { + // let ud = + // try_self_arg!(get_userdata_mut::>>(ref_thread, index)); + // let mut ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowMutError); + // let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud); + // method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) + // } + // #[cfg(feature = "parking_lot")] + // Some(id) if id == TypeId::of::>>() => { + // let ud = get_userdata_mut::>>(ref_thread, index); + // let ud = try_self_arg!(ud); + // let mut ud = + // try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowMutError)); + // let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud); + // method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) + // } + // Some(id) if id == TypeId::of::>>() => { + // let ud = + // try_self_arg!(get_userdata_mut::>>(ref_thread, index)); + // let mut ud = try_self_arg!(ud.try_write(), Error::UserDataBorrowMutError); + // let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud); + // method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) + // } + // #[cfg(feature = "parking_lot")] + // Some(id) if id == TypeId::of::>>() => { + // let ud = get_userdata_mut::>>(ref_thread, index); + // let ud = try_self_arg!(ud); + // let mut ud = + // try_self_arg!(ud.try_write().ok_or(Error::UserDataBorrowMutError)); + // let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud); + // method(lua, ud, args?).await?.push_into_stack_multi(lua) + // } _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), } }) }) } - fn box_function(name: &str, function: F) -> Callback<'lua, 'static> + fn box_function(name: &str, function: F) -> Callback<'a> where - F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + F: Fn(&'a Lua, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti, { let name = get_function_name::(name); Box::new(move |lua, nargs| unsafe { let args = A::from_stack_args(nargs, 1, Some(&name), lua)?; - function(lua, args)?.push_into_stack_multi(lua) + function(lua.lua(), args)?.push_into_stack_multi(lua) }) } - fn box_function_mut(name: &str, function: F) -> Callback<'lua, 'static> + fn box_function_mut(name: &str, function: F) -> Callback<'a> where - F: FnMut(&'lua Lua, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + F: FnMut(&'a Lua, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti, { let name = get_function_name::(name); @@ -420,30 +414,32 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> { .try_borrow_mut() .map_err(|_| Error::RecursiveMutCallback)?; let args = A::from_stack_args(nargs, 1, Some(&name), lua)?; - function(lua, args)?.push_into_stack_multi(lua) + function(lua.lua(), args)?.push_into_stack_multi(lua) }) } #[cfg(feature = "async")] - fn box_async_function(name: &str, function: F) -> AsyncCallback<'lua, 'static> + fn box_async_function(name: String, function: F) -> AsyncCallback<'a> where - F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - FR: Future> + 'lua, + F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, + A: FromLuaMulti, + FR: Future> + 'a, R: IntoLuaMulti, { - let name = get_function_name::(name); - Box::new(move |lua, args| unsafe { + let name = get_function_name::(&name); + Box::new(move |rawlua, args| unsafe { + let lua = rawlua.lua(); let args = match A::from_lua_args(args, 1, Some(&name), lua) { Ok(args) => args, Err(e) => return Box::pin(future::err(e)), }; let fut = function(lua, args); - Box::pin(async move { fut.await?.push_into_stack_multi(lua) }) + let weak = rawlua.weak().clone(); + Box::pin(async move { fut.await?.push_into_stack_multi(&weak.lock()) }) }) } - pub(crate) fn check_meta_field(lua: &'lua Lua, name: &str, value: V) -> Result> + pub(crate) fn check_meta_field(lua: &Lua, name: &str, value: V) -> Result where V: IntoLua, { @@ -469,310 +465,296 @@ fn get_function_name(name: &str) -> StdString { format!("{}.{name}", short_type_name::()) } -impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistry<'lua, T> { - fn add_field(&mut self, name: impl AsRef, value: V) +impl<'a, T: 'static> UserDataFields<'a, T> for UserDataRegistry<'a, T> { + fn add_field(&mut self, name: impl ToString, value: V) where V: IntoLua + Clone + 'static, { - let name = name.as_ref().to_string(); - self.fields.push(( - name, - Box::new(move |lua, _| unsafe { value.clone().push_into_stack_multi(lua) }), - )); + let name = name.to_string(); + let callback = Box::new(move |lua, _| unsafe { value.clone().push_into_stack_multi(lua) }); + self.fields.push((name, callback)); } - fn add_field_method_get(&mut self, name: impl AsRef, method: M) + fn add_field_method_get(&mut self, name: impl ToString, method: M) where - M: Fn(&'lua Lua, &T) -> Result + MaybeSend + 'static, + M: Fn(&'a Lua, &T) -> Result + MaybeSend + 'static, R: IntoLua, { - let name = name.as_ref(); - let method = Self::box_method(name, move |lua, data, ()| method(lua, data)); - self.field_getters.push((name.into(), method)); + let name = name.to_string(); + let callback = Self::box_method(&name, move |lua, data, ()| method(lua, &data)); + self.field_getters.push((name.into(), callback)); } - fn add_field_method_set(&mut self, name: impl AsRef, method: M) + fn add_field_method_set(&mut self, name: impl ToString, method: M) where - M: FnMut(&'lua Lua, &mut T, A) -> Result<()> + MaybeSend + 'static, - A: FromLua<'lua>, + M: FnMut(&'a Lua, &mut T, A) -> Result<()> + MaybeSend + 'static, + A: FromLua, { - let name = name.as_ref(); - let method = Self::box_method_mut(name, method); - self.field_setters.push((name.into(), method)); + let name = name.to_string(); + let callback = Self::box_method_mut(&name, method); + self.field_setters.push((name, callback)); } - fn add_field_function_get(&mut self, name: impl AsRef, function: F) + fn add_field_function_get(&mut self, name: impl ToString, function: F) where - F: Fn(&'lua Lua, AnyUserData<'lua>) -> Result + MaybeSend + 'static, + F: Fn(&'a Lua, AnyUserData) -> Result + MaybeSend + 'static, R: IntoLua, { - let name = name.as_ref(); - let func = Self::box_function(name, function); - self.field_getters.push((name.into(), func)); + let name = name.to_string(); + let callback = Self::box_function(&name, function); + self.field_getters.push((name, callback)); } - fn add_field_function_set(&mut self, name: impl AsRef, mut function: F) + fn add_field_function_set(&mut self, name: impl ToString, mut function: F) where - F: FnMut(&'lua Lua, AnyUserData<'lua>, A) -> Result<()> + MaybeSend + 'static, - A: FromLua<'lua>, + F: FnMut(&'a Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static, + A: FromLua, { - let name = name.as_ref(); - let func = Self::box_function_mut(name, move |lua, (data, val)| function(lua, data, val)); - self.field_setters.push((name.into(), func)); + let name = name.to_string(); + let callback = + Self::box_function_mut(&name, move |lua, (data, val)| function(lua, data, val)); + self.field_setters.push((name, callback)); } - fn add_meta_field(&mut self, name: impl AsRef, value: V) + fn add_meta_field(&mut self, name: impl ToString, value: V) where V: IntoLua + Clone + 'static, { - let name = name.as_ref().to_string(); + let name = name.to_string(); let name2 = name.clone(); self.meta_fields.push(( name, Box::new(move |lua, _| unsafe { - Self::check_meta_field(lua, &name2, value.clone())?.push_into_stack_multi(lua) + Self::check_meta_field(lua.lua(), &name2, value.clone())?.push_into_stack_multi(lua) }), )); } - fn add_meta_field_with(&mut self, name: impl AsRef, f: F) + fn add_meta_field_with(&mut self, name: impl ToString, f: F) where - F: Fn(&'lua Lua) -> Result + MaybeSend + 'static, + F: Fn(&'a Lua) -> Result + MaybeSend + 'static, R: IntoLua, { - let name = name.as_ref().to_string(); + let name = name.to_string(); let name2 = name.clone(); self.meta_fields.push(( name, - Box::new(move |lua, _| unsafe { - Self::check_meta_field(lua, &name2, f(lua)?)?.push_into_stack_multi(lua) + Box::new(move |rawlua, _| unsafe { + let lua = rawlua.lua(); + Self::check_meta_field(lua, &name2, f(lua)?)?.push_into_stack_multi(rawlua) }), )); } - - // Below are internal methods - - fn append_fields_from(&mut self, other: UserDataRegistry<'lua, S>) { - self.fields.extend(other.fields); - self.field_getters.extend(other.field_getters); - self.field_setters.extend(other.field_setters); - self.meta_fields.extend(other.meta_fields); - } } -impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistry<'lua, T> { - fn add_method(&mut self, name: impl AsRef, method: M) +impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { + fn add_method(&mut self, name: impl ToString, method: M) where - M: Fn(&'lua Lua, &T, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + M: Fn(&'a Lua, &T, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti, { - let name = name.as_ref(); - self.methods - .push((name.into(), Self::box_method(name, method))); + let name = name.to_string(); + let callback = Self::box_method(&name, method); + self.methods.push((name, callback)); } - fn add_method_mut(&mut self, name: impl AsRef, method: M) + fn add_method_mut(&mut self, name: impl ToString, method: M) where - M: FnMut(&'lua Lua, &mut T, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + M: FnMut(&'a Lua, &mut T, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti, { - let name = name.as_ref(); - self.methods - .push((name.into(), Self::box_method_mut(name, method))); + let name = name.to_string(); + let callback = Self::box_method_mut(&name, method); + self.methods.push((name, callback)); } #[cfg(feature = "async")] - fn add_async_method<'s, M, A, MR, R>(&mut self, name: impl AsRef, method: M) + fn add_async_method(&mut self, name: impl ToString, method: M) where - 'lua: 's, - T: 'static, - M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - MR: Future> + 's, + M: Fn(&'a Lua, &'a T, A) -> MR + MaybeSend + 'static, + A: FromLuaMulti, + MR: Future> + 'a, R: IntoLuaMulti, { - let name = name.as_ref(); - self.async_methods - .push((name.into(), Self::box_async_method(name, method))); + let name = name.to_string(); + let callback = Self::box_async_method(name.clone(), method); + self.async_methods.push((name, callback)); } #[cfg(feature = "async")] - fn add_async_method_mut<'s, M, A, MR, R>(&mut self, name: impl AsRef, method: M) + fn add_async_method_mut(&mut self, name: impl ToString, method: M) where - 'lua: 's, - T: 'static, - M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - MR: Future> + 's, + M: Fn(&'a Lua, &'a mut T, A) -> MR + MaybeSend + 'static, + A: FromLuaMulti, + MR: Future> + 'a, R: IntoLuaMulti, { - let name = name.as_ref(); - self.async_methods - .push((name.into(), Self::box_async_method_mut(name, method))); + let name = name.to_string(); + let callback = Self::box_async_method_mut(name.clone(), method); + self.async_methods.push((name, callback)); } - fn add_function(&mut self, name: impl AsRef, function: F) + fn add_function(&mut self, name: impl ToString, function: F) where - F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + F: Fn(&'a Lua, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti, { - let name = name.as_ref(); - self.methods - .push((name.into(), Self::box_function(name, function))); + let name = name.to_string(); + let callback = Self::box_function(&name, function); + self.methods.push((name, callback)); } - fn add_function_mut(&mut self, name: impl AsRef, function: F) + fn add_function_mut(&mut self, name: impl ToString, function: F) where - F: FnMut(&'lua Lua, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + F: FnMut(&'a Lua, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti, { - let name = name.as_ref(); - self.methods - .push((name.into(), Self::box_function_mut(name, function))); + let name = name.to_string(); + let callback = Self::box_function_mut(&name, function); + self.methods.push((name, callback)); } #[cfg(feature = "async")] - fn add_async_function(&mut self, name: impl AsRef, function: F) + fn add_async_function(&mut self, name: impl ToString, function: F) where - F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - FR: Future> + 'lua, + F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, + A: FromLuaMulti, + FR: Future> + 'a, R: IntoLuaMulti, { - let name = name.as_ref(); - self.async_methods - .push((name.into(), Self::box_async_function(name, function))); + let name = name.to_string(); + let callback = Self::box_async_function(name.clone(), function); + self.async_methods.push((name, callback)); } - fn add_meta_method(&mut self, name: impl AsRef, method: M) + fn add_meta_method(&mut self, name: impl ToString, method: M) where - M: Fn(&'lua Lua, &T, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + M: Fn(&'a Lua, &T, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti, { - let name = name.as_ref(); - self.meta_methods - .push((name.into(), Self::box_method(name, method))); + let name = name.to_string(); + let callback = Self::box_method(&name, method); + self.meta_methods.push((name, callback)); } - fn add_meta_method_mut(&mut self, name: impl AsRef, method: M) + fn add_meta_method_mut(&mut self, name: impl ToString, method: M) where - M: FnMut(&'lua Lua, &mut T, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + M: FnMut(&'a Lua, &mut T, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti, { - let name = name.as_ref(); - self.meta_methods - .push((name.into(), Self::box_method_mut(name, method))); + let name = name.to_string(); + let callback = Self::box_method_mut(&name, method); + self.meta_methods.push((name, callback)); } #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] - fn add_async_meta_method<'s, M, A, MR, R>(&mut self, name: impl AsRef, method: M) + fn add_async_meta_method(&mut self, name: impl ToString, method: M) where - 'lua: 's, - T: 'static, - M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - MR: Future> + 's, + M: Fn(&'a Lua, &'a T, A) -> MR + MaybeSend + 'static, + A: FromLuaMulti, + MR: Future> + 'a, R: IntoLuaMulti, { - let name = name.as_ref(); - self.async_meta_methods - .push((name.into(), Self::box_async_method(name, method))); + let name = name.to_string(); + let callback = Self::box_async_method(name.clone(), method); + self.async_meta_methods.push((name, callback)); } #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] - fn add_async_meta_method_mut<'s, M, A, MR, R>(&mut self, name: impl AsRef, method: M) + fn add_async_meta_method_mut(&mut self, name: impl ToString, method: M) where - 'lua: 's, - T: 'static, - M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - MR: Future> + 's, + M: Fn(&'a Lua, &'a mut T, A) -> MR + MaybeSend + 'static, + A: FromLuaMulti, + MR: Future> + 'a, R: IntoLuaMulti, { - let name = name.as_ref(); - self.async_meta_methods - .push((name.into(), Self::box_async_method_mut(name, method))); + let name = name.to_string(); + let callback = Self::box_async_method_mut(name.clone(), method); + self.async_meta_methods.push((name, callback)); } - fn add_meta_function(&mut self, name: impl AsRef, function: F) + fn add_meta_function(&mut self, name: impl ToString, function: F) where - F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + F: Fn(&'a Lua, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti, { - let name = name.as_ref(); - self.meta_methods - .push((name.into(), Self::box_function(name, function))); + let name = name.to_string(); + let callback = Self::box_function(&name, function); + self.meta_methods.push((name, callback)); } - fn add_meta_function_mut(&mut self, name: impl AsRef, function: F) + fn add_meta_function_mut(&mut self, name: impl ToString, function: F) where - F: FnMut(&'lua Lua, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, + F: FnMut(&'a Lua, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, R: IntoLuaMulti, { - let name = name.as_ref(); - self.meta_methods - .push((name.into(), Self::box_function_mut(name, function))); + let name = name.to_string(); + let callback = Self::box_function_mut(&name, function); + self.meta_methods.push((name, callback)); } #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] - fn add_async_meta_function(&mut self, name: impl AsRef, function: F) + fn add_async_meta_function(&mut self, name: impl ToString, function: F) where - F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - FR: Future> + 'lua, + F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, + A: FromLuaMulti, + FR: Future> + 'a, R: IntoLuaMulti, { - let name = name.as_ref(); - self.async_meta_methods - .push((name.into(), Self::box_async_function(name, function))); - } - - // Below are internal methods used in generated code - - fn append_methods_from(&mut self, other: UserDataRegistry<'lua, S>) { - self.methods.extend(other.methods); - #[cfg(feature = "async")] - self.async_methods.extend(other.async_methods); - self.meta_methods.extend(other.meta_methods); - #[cfg(feature = "async")] - self.async_meta_methods.extend(other.async_meta_methods); + let name = name.to_string(); + let callback = Self::box_async_function(name.clone(), function); + self.async_meta_methods.push((name, callback)); } } +// Borrow the userdata in-place from the Lua stack #[inline] -unsafe fn get_userdata_ref<'a, T>(state: *mut ffi::lua_State, index: c_int) -> Result> { - (*get_userdata::>(state, index)).try_borrow() +unsafe fn borrow_userdata_ref<'a, T>( + state: *mut ffi::lua_State, + index: c_int, +) -> Result> { + let ud = get_userdata::>(state, index); + (*ud).try_borrow() } +// Borrow the userdata mutably in-place from the Lua stack #[inline] -unsafe fn get_userdata_mut<'a, T>( +unsafe fn borrow_userdata_mut<'a, T>( state: *mut ffi::lua_State, index: c_int, -) -> Result> { - (*get_userdata::>(state, index)).try_borrow_mut() +) -> Result> { + let ud = get_userdata::>(state, index); + (*ud).try_borrow_mut() } macro_rules! lua_userdata_impl { ($type:ty) => { impl UserData for $type { - fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { - let mut orig_fields = UserDataRegistry::new(); - T::add_fields(&mut orig_fields); - fields.append_fields_from(orig_fields); - } - - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { - let mut orig_methods = UserDataRegistry::new(); - T::add_methods(&mut orig_methods); - methods.append_methods_from(orig_methods); + fn register(registry: &mut UserDataRegistry) { + let mut orig_registry = UserDataRegistry::new(); + T::register(&mut orig_registry); + + // Copy all fields, methods, etc. from the original registry + registry.fields.extend(orig_registry.fields); + registry.field_getters.extend(orig_registry.field_getters); + registry.field_setters.extend(orig_registry.field_setters); + registry.meta_fields.extend(orig_registry.meta_fields); + registry.methods.extend(orig_registry.methods); + #[cfg(feature = "async")] + registry.async_methods.extend(orig_registry.async_methods); + registry.meta_methods.extend(orig_registry.meta_methods); + #[cfg(feature = "async")] + registry + .async_meta_methods + .extend(orig_registry.async_meta_methods); } } }; diff --git a/src/value.rs b/src/value.rs index 2e5f2d9d..e589d8c9 100644 --- a/src/value.rs +++ b/src/value.rs @@ -19,7 +19,7 @@ use { use crate::error::{Error, Result}; use crate::function::Function; -use crate::lua::Lua; +use crate::lua::{Lua, LuaInner}; use crate::string::String; use crate::table::Table; use crate::thread::Thread; @@ -31,7 +31,7 @@ use crate::util::{check_stack, StackGuard}; /// variants contain handle types into the internal Lua state. It is a logic error to mix handle /// types between separate `Lua` instances, and doing so will result in a panic. #[derive(Clone)] -pub enum Value<'lua> { +pub enum Value { /// The Lua value `nil`. Nil, /// The Lua value `true` or `false`. @@ -51,27 +51,27 @@ pub enum Value<'lua> { /// An interned string, managed by Lua. /// /// Unlike Rust strings, Lua strings may not be valid UTF-8. - String(String<'lua>), + String(String), /// Reference to a Lua table. - Table(Table<'lua>), + Table(Table), /// Reference to a Lua function (or closure). - Function(Function<'lua>), + Function(Function), /// Reference to a Lua thread (or coroutine). - Thread(Thread<'lua>), + Thread(Thread), /// Reference to a userdata object that holds a custom type which implements `UserData`. /// Special builtin userdata types will be represented as other `Value` variants. - UserData(AnyUserData<'lua>), + UserData(AnyUserData), /// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned. Error(Box), } pub use self::Value::Nil; -impl<'lua> Value<'lua> { +impl Value { /// A special value (lightuserdata) to represent null value. /// /// It can be used in Lua tables without downsides of `nil`. - pub const NULL: Value<'static> = Value::LightUserData(LightUserData(ptr::null_mut())); + pub const NULL: Value = Value::LightUserData(LightUserData(ptr::null_mut())); /// Returns type name of this value. pub const fn type_name(&self) -> &'static str { @@ -152,15 +152,16 @@ impl<'lua> Value<'lua> { | Value::Function(Function(r)) | Value::Thread(Thread(r, ..)) | Value::UserData(AnyUserData(r, ..)) => unsafe { - let state = r.lua.state(); + let lua = r.lua.lock(); + let state = lua.state(); let _guard = StackGuard::new(state); check_stack(state, 3)?; - r.lua.push_ref(r); + lua.push_ref(r); protect_lua!(state, 1, 1, fn(state) { ffi::luaL_tolstring(state, -1, ptr::null_mut()); })?; - Ok(String(r.lua.pop_ref()).to_str()?.to_string()) + Ok(String(lua.pop_ref()).to_str()?.to_string()) }, Value::Error(err) => Ok(err.to_string()), } @@ -440,7 +441,7 @@ impl<'lua> Value<'lua> { #[cfg(feature = "serialize")] #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] #[doc(hidden)] - pub fn to_serializable(&self) -> SerializableValue<'_, 'lua> { + pub fn to_serializable(&self) -> SerializableValue { SerializableValue::new(self, Default::default(), None) } @@ -522,7 +523,7 @@ impl<'lua> Value<'lua> { } } -impl fmt::Debug for Value<'_> { +impl fmt::Debug for Value { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { if fmt.alternate() { return self.fmt_pretty(fmt, true, 0, &mut HashSet::new()); @@ -545,7 +546,7 @@ impl fmt::Debug for Value<'_> { } } -impl<'lua> PartialEq for Value<'lua> { +impl PartialEq for Value { fn eq(&self, other: &Self) -> bool { match (self, other) { (Value::Nil, Value::Nil) => true, @@ -567,7 +568,7 @@ impl<'lua> PartialEq for Value<'lua> { } } -impl<'lua> AsRef> for Value<'lua> { +impl AsRef for Value { #[inline] fn as_ref(&self) -> &Self { self @@ -577,15 +578,15 @@ impl<'lua> AsRef> for Value<'lua> { /// A wrapped [`Value`] with customized serialization behavior. #[cfg(feature = "serialize")] #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] -pub struct SerializableValue<'a, 'lua> { - value: &'a Value<'lua>, +pub struct SerializableValue<'a> { + value: &'a Value, options: crate::serde::de::Options, // In many cases we don't need `visited` map, so don't allocate memory by default visited: Option>>>, } #[cfg(feature = "serialize")] -impl<'lua> Serialize for Value<'lua> { +impl Serialize for Value { #[inline] fn serialize(&self, serializer: S) -> StdResult { SerializableValue::new(self, Default::default(), None).serialize(serializer) @@ -593,10 +594,10 @@ impl<'lua> Serialize for Value<'lua> { } #[cfg(feature = "serialize")] -impl<'a, 'lua> SerializableValue<'a, 'lua> { +impl<'a> SerializableValue<'a> { #[inline] pub(crate) fn new( - value: &'a Value<'lua>, + value: &'a Value, options: crate::serde::de::Options, visited: Option<&Rc>>>, ) -> Self { @@ -648,7 +649,7 @@ impl<'a, 'lua> SerializableValue<'a, 'lua> { } #[cfg(feature = "serialize")] -impl<'a, 'lua> Serialize for SerializableValue<'a, 'lua> { +impl<'a> Serialize for SerializableValue<'a> { fn serialize(&self, serializer: S) -> StdResult where S: Serializer, @@ -689,7 +690,7 @@ impl<'a, 'lua> Serialize for SerializableValue<'a, 'lua> { /// Trait for types convertible to `Value`. pub trait IntoLua: Sized { /// Performs the conversion. - fn into_lua(self, lua: &Lua) -> Result>; + fn into_lua(self, lua: &Lua) -> Result; /// Pushes the value into the Lua stack. /// @@ -697,15 +698,15 @@ pub trait IntoLua: Sized { /// This method does not check Lua stack space. #[doc(hidden)] #[inline] - unsafe fn push_into_stack(self, lua: &Lua) -> Result<()> { - lua.push_value(&self.into_lua(lua)?) + unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + lua.push_value(&self.into_lua(lua.lua())?) } } /// Trait for types convertible from `Value`. -pub trait FromLua<'lua>: Sized { +pub trait FromLua: Sized { /// Performs the conversion. - fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result; + fn from_lua(value: Value, lua: &Lua) -> Result; /// Performs the conversion for an argument (eg. function argument). /// @@ -713,7 +714,7 @@ pub trait FromLua<'lua>: Sized { /// `to` is a function name that received the argument. #[doc(hidden)] #[inline] - fn from_lua_arg(arg: Value<'lua>, i: usize, to: Option<&str>, lua: &'lua Lua) -> Result { + fn from_lua_arg(arg: Value, i: usize, to: Option<&str>, lua: &Lua) -> Result { Self::from_lua(arg, lua).map_err(|err| Error::BadArgument { to: to.map(|s| s.to_string()), pos: i, @@ -725,8 +726,8 @@ pub trait FromLua<'lua>: Sized { /// Performs the conversion for a value in the Lua stack at index `idx`. #[doc(hidden)] #[inline] - unsafe fn from_stack(idx: c_int, lua: &'lua Lua) -> Result { - Self::from_lua(lua.stack_value(idx), lua) + unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { + Self::from_lua(lua.stack_value(idx), lua.lua()) } /// Same as `from_lua_arg` but for a value in the Lua stack at index `idx`. @@ -736,7 +737,7 @@ pub trait FromLua<'lua>: Sized { idx: c_int, i: usize, to: Option<&str>, - lua: &'lua Lua, + lua: &LuaInner, ) -> Result { Self::from_stack(idx, lua).map_err(|err| Error::BadArgument { to: to.map(|s| s.to_string()), @@ -749,29 +750,31 @@ pub trait FromLua<'lua>: Sized { /// Multiple Lua values used for both argument passing and also for multiple return values. #[derive(Debug, Clone)] -pub struct MultiValue<'lua> { - deque: VecDeque>, - lua: Option<&'lua Lua>, +pub struct MultiValue { + deque: VecDeque, + // FIXME + // lua: Option<&'static Lua>, } -impl Drop for MultiValue<'_> { +impl Drop for MultiValue { fn drop(&mut self) { - if let Some(lua) = self.lua { - let vec = mem::take(&mut self.deque); - lua.push_multivalue_to_pool(vec); - } + // FIXME + // if let Some(lua) = self.lua { + // let vec = mem::take(&mut self.deque); + // lua.push_multivalue_to_pool(vec); + // } } } -impl<'lua> Default for MultiValue<'lua> { +impl Default for MultiValue { #[inline] - fn default() -> MultiValue<'lua> { + fn default() -> MultiValue { MultiValue::new() } } -impl<'lua> Deref for MultiValue<'lua> { - type Target = VecDeque>; +impl Deref for MultiValue { + type Target = VecDeque; #[inline] fn deref(&self) -> &Self::Target { @@ -779,44 +782,46 @@ impl<'lua> Deref for MultiValue<'lua> { } } -impl<'lua> DerefMut for MultiValue<'lua> { +impl DerefMut for MultiValue { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.deque } } -impl<'lua> MultiValue<'lua> { +impl MultiValue { /// Creates an empty `MultiValue` containing no values. - pub const fn new() -> MultiValue<'lua> { + pub const fn new() -> MultiValue { MultiValue { deque: VecDeque::new(), - lua: None, + // lua: None, } } /// Similar to `new` but can reuse previously used container with allocated capacity. #[inline] - pub(crate) fn with_lua_and_capacity(lua: &'lua Lua, capacity: usize) -> MultiValue<'lua> { - let deque = lua - .pop_multivalue_from_pool() - .map(|mut deque| { - if capacity > 0 { - deque.reserve(capacity); - } - deque - }) - .unwrap_or_else(|| VecDeque::with_capacity(capacity)); + pub(crate) fn with_lua_and_capacity(_lua: &Lua, capacity: usize) -> MultiValue { + // FIXME + // let deque = lua + // .pop_multivalue_from_pool() + // .map(|mut deque| { + // if capacity > 0 { + // deque.reserve(capacity); + // } + // deque + // }) + // .unwrap_or_else(|| VecDeque::with_capacity(capacity)); + let deque = VecDeque::with_capacity(capacity); MultiValue { deque, - lua: Some(lua), + // lua: Some(lua), } } #[inline] pub(crate) fn extend_from_values( &mut self, - iter: impl IntoIterator>>, + iter: impl IntoIterator>, ) -> Result<()> { for value in iter { self.push_back(value?); @@ -825,17 +830,20 @@ impl<'lua> MultiValue<'lua> { } } -impl<'lua> FromIterator> for MultiValue<'lua> { +impl FromIterator for MultiValue { #[inline] - fn from_iter>>(iter: I) -> Self { + fn from_iter>(iter: I) -> Self { let deque = VecDeque::from_iter(iter); - MultiValue { deque, lua: None } + MultiValue { + deque, + // lua: None, + } } } -impl<'lua> IntoIterator for MultiValue<'lua> { - type Item = Value<'lua>; - type IntoIter = vec_deque::IntoIter>; +impl IntoIterator for MultiValue { + type Item = Value; + type IntoIter = vec_deque::IntoIter; #[inline] fn into_iter(mut self) -> Self::IntoIter { @@ -845,9 +853,9 @@ impl<'lua> IntoIterator for MultiValue<'lua> { } } -impl<'a, 'lua> IntoIterator for &'a MultiValue<'lua> { - type Item = &'a Value<'lua>; - type IntoIter = vec_deque::Iter<'a, Value<'lua>>; +impl<'a> IntoIterator for &'a MultiValue { + type Item = &'a Value; + type IntoIter = vec_deque::Iter<'a, Value>; #[inline] fn into_iter(self) -> Self::IntoIter { @@ -861,15 +869,15 @@ impl<'a, 'lua> IntoIterator for &'a MultiValue<'lua> { /// one. Any type that implements `IntoLua` will automatically implement this trait. pub trait IntoLuaMulti: Sized { /// Performs the conversion. - fn into_lua_multi(self, lua: &Lua) -> Result>; + fn into_lua_multi(self, lua: &Lua) -> Result; /// Pushes the values into the Lua stack. /// /// Returns number of pushed values. #[doc(hidden)] #[inline] - unsafe fn push_into_stack_multi(self, lua: &Lua) -> Result { - let values = self.into_lua_multi(lua)?; + unsafe fn push_into_stack_multi(self, lua: &LuaInner) -> Result { + let values = self.into_lua_multi(lua.lua())?; let len: c_int = values.len().try_into().unwrap(); unsafe { check_stack(lua.state(), len + 1)?; @@ -885,14 +893,14 @@ pub trait IntoLuaMulti: Sized { /// /// This is a generalization of `FromLua`, allowing an arbitrary number of Lua values to participate /// in the conversion. Any type that implements `FromLua` will automatically implement this trait. -pub trait FromLuaMulti<'lua>: Sized { +pub trait FromLuaMulti: Sized { /// Performs the conversion. /// /// In case `values` contains more values than needed to perform the conversion, the excess /// values should be ignored. This reflects the semantics of Lua when calling a function or /// assigning values. Similarly, if not enough values are given, conversions should assume that /// any missing values are nil. - fn from_lua_multi(values: MultiValue<'lua>, lua: &'lua Lua) -> Result; + fn from_lua_multi(values: MultiValue, lua: &Lua) -> Result; /// Performs the conversion for a list of arguments. /// @@ -900,12 +908,7 @@ pub trait FromLuaMulti<'lua>: Sized { /// `to` is a function name that received the arguments. #[doc(hidden)] #[inline] - fn from_lua_args( - args: MultiValue<'lua>, - i: usize, - to: Option<&str>, - lua: &'lua Lua, - ) -> Result { + fn from_lua_args(args: MultiValue, i: usize, to: Option<&str>, lua: &Lua) -> Result { let _ = (i, to); Self::from_lua_multi(args, lua) } @@ -913,8 +916,8 @@ pub trait FromLuaMulti<'lua>: Sized { /// Performs the conversion for a number of values in the Lua stack. #[doc(hidden)] #[inline] - unsafe fn from_stack_multi(nvals: c_int, lua: &'lua Lua) -> Result { - let mut values = MultiValue::with_lua_and_capacity(lua, nvals as usize); + unsafe fn from_stack_multi(nvals: c_int, lua: &LuaInner) -> Result { + let mut values = MultiValue::with_lua_and_capacity(lua.lua(), nvals as usize); for idx in 0..nvals { values.push_back(lua.stack_value(-nvals + idx)); } @@ -922,7 +925,7 @@ pub trait FromLuaMulti<'lua>: Sized { // It's safe to clear the stack as all references moved to ref thread ffi::lua_pop(lua.state(), nvals); } - Self::from_lua_multi(values, lua) + Self::from_lua_multi(values, lua.lua()) } /// Same as `from_lua_args` but for a number of values in the Lua stack. @@ -932,7 +935,7 @@ pub trait FromLuaMulti<'lua>: Sized { nargs: c_int, i: usize, to: Option<&str>, - lua: &'lua Lua, + lua: &LuaInner, ) -> Result { let _ = (i, to); Self::from_stack_multi(nargs, lua) diff --git a/tests/async.rs b/tests/async.rs index 9c4c8652..0fda52ff 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -386,7 +386,7 @@ async fn test_async_userdata() -> Result<()> { struct MyUserData(u64); impl UserData for MyUserData { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { methods.add_async_method("get_value", |_, data, ()| async move { sleep_ms(10).await; Ok(data.0) @@ -488,7 +488,7 @@ async fn test_async_thread_error() -> Result<()> { struct MyUserData; impl UserData for MyUserData { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { methods.add_meta_method("__tostring", |_, _this, ()| Ok("myuserdata error")) } } @@ -507,24 +507,6 @@ async fn test_async_thread_error() -> Result<()> { Ok(()) } -#[cfg(all(feature = "unstable", not(feature = "send")))] -#[tokio::test] -async fn test_owned_async_call() -> Result<()> { - let lua = Lua::new(); - - let hello = lua - .create_async_function(|_, name: String| async move { - sleep_ms(10).await; - Ok(format!("hello, {}!", name)) - })? - .into_owned(); - drop(lua); - - assert_eq!(hello.call_async::<_, String>("alex").await?, "hello, alex!"); - - Ok(()) -} - #[tokio::test] async fn test_async_terminate() -> Result<()> { let lua = Lua::new(); diff --git a/tests/conversion.rs b/tests/conversion.rs index 5897d75e..a0e0b383 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -43,35 +43,6 @@ fn test_string_into_lua() -> Result<()> { Ok(()) } -#[cfg(all(feature = "unstable", not(feature = "send")))] -#[test] -fn test_owned_string_into_lua() -> Result<()> { - let lua = Lua::new(); - - // Direct conversion - let s = lua.create_string("hello, world")?.into_owned(); - let s2 = (&s).into_lua(&lua)?; - assert_eq!(s.to_ref(), *s2.as_string().unwrap()); - - // Push into stack - let table = lua.create_table()?; - table.set("s", &s)?; - assert_eq!(s.to_ref(), table.get::<_, String>("s")?); - - Ok(()) -} - -#[cfg(all(feature = "unstable", not(feature = "send")))] -#[test] -fn test_owned_string_from_lua() -> Result<()> { - let lua = Lua::new(); - - let s = lua.unpack::(lua.pack("hello, world")?)?; - assert_eq!(s.to_ref(), "hello, world"); - - Ok(()) -} - #[test] fn test_table_into_lua() -> Result<()> { let lua = Lua::new(); @@ -89,24 +60,6 @@ fn test_table_into_lua() -> Result<()> { Ok(()) } -#[cfg(all(feature = "unstable", not(feature = "send")))] -#[test] -fn test_owned_table_into_lua() -> Result<()> { - let lua = Lua::new(); - - // Direct conversion - let t = lua.create_table()?.into_owned(); - let t2 = (&t).into_lua(&lua)?; - assert_eq!(t.to_ref(), *t2.as_table().unwrap()); - - // Push into stack - let f = lua.create_function(|_, (t, s): (Table, String)| t.set("s", s))?; - f.call((&t, "hello"))?; - assert_eq!("hello", t.to_ref().get::<_, String>("s")?); - - Ok(()) -} - #[test] fn test_function_into_lua() -> Result<()> { let lua = Lua::new(); @@ -124,26 +77,6 @@ fn test_function_into_lua() -> Result<()> { Ok(()) } -#[cfg(all(feature = "unstable", not(feature = "send")))] -#[test] -fn test_owned_function_into_lua() -> Result<()> { - let lua = Lua::new(); - - // Direct conversion - let f = lua - .create_function(|_, ()| Ok::<_, Error>(()))? - .into_owned(); - let f2 = (&f).into_lua(&lua)?; - assert_eq!(f.to_ref(), *f2.as_function().unwrap()); - - // Push into stack - let table = lua.create_table()?; - table.set("f", &f)?; - assert_eq!(f.to_ref(), table.get::<_, Function>("f")?); - - Ok(()) -} - #[test] fn test_thread_into_lua() -> Result<()> { let lua = Lua::new(); @@ -162,36 +95,6 @@ fn test_thread_into_lua() -> Result<()> { Ok(()) } -#[cfg(all(feature = "unstable", not(feature = "send")))] -#[test] -fn test_owned_thread_into_lua() -> Result<()> { - let lua = Lua::new(); - - // Direct conversion - let f = lua.create_function(|_, ()| Ok::<_, Error>(()))?; - let th = lua.create_thread(f)?.into_owned(); - let th2 = (&th).into_lua(&lua)?; - assert_eq!(&th.to_ref(), th2.as_thread().unwrap()); - - // Push into stack - let table = lua.create_table()?; - table.set("th", &th)?; - assert_eq!(th.to_ref(), table.get::<_, Thread>("th")?); - - Ok(()) -} - -#[cfg(all(feature = "unstable", not(feature = "send")))] -#[test] -fn test_owned_thread_from_lua() -> Result<()> { - let lua = Lua::new(); - - let th = lua.unpack::(Value::Thread(lua.current_thread()))?; - assert_eq!(th.to_ref(), lua.current_thread()); - - Ok(()) -} - #[test] fn test_anyuserdata_into_lua() -> Result<()> { let lua = Lua::new(); @@ -210,25 +113,6 @@ fn test_anyuserdata_into_lua() -> Result<()> { Ok(()) } -#[cfg(all(feature = "unstable", not(feature = "send")))] -#[test] -fn test_owned_anyuserdata_into_lua() -> Result<()> { - let lua = Lua::new(); - - // Direct conversion - let ud = lua.create_any_userdata(String::from("hello"))?.into_owned(); - let ud2 = (&ud).into_lua(&lua)?; - assert_eq!(ud.to_ref(), *ud2.as_userdata().unwrap()); - - // Push into stack - let table = lua.create_table()?; - table.set("ud", &ud)?; - assert_eq!(ud.to_ref(), table.get::<_, AnyUserData>("ud")?); - assert_eq!("hello", *table.get::<_, UserDataRef>("ud")?); - - Ok(()) -} - #[test] fn test_registry_value_into_lua() -> Result<()> { let lua = Lua::new(); diff --git a/tests/function.rs b/tests/function.rs index c95b012a..c7596bed 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -296,45 +296,3 @@ fn test_function_wrap() -> Result<()> { Ok(()) } - -#[cfg(all(feature = "unstable", not(feature = "send")))] -#[test] -fn test_owned_function() -> Result<()> { - let lua = Lua::new(); - - let f = lua - .create_function(|_, ()| Ok("hello, world!"))? - .into_owned(); - drop(lua); - - // We still should be able to call the function despite Lua is dropped - let s = f.call::<_, String>(())?; - assert_eq!(s.to_string_lossy(), "hello, world!"); - - Ok(()) -} - -#[cfg(all(feature = "unstable", not(feature = "send")))] -#[test] -fn test_owned_function_drop() -> Result<()> { - let rc = std::sync::Arc::new(()); - - { - let lua = Lua::new(); - - lua.set_app_data(rc.clone()); - - let f1 = lua - .create_function(|_, ()| Ok("hello, world!"))? - .into_owned(); - let f2 = - lua.create_function(move |_, ()| f1.to_ref().call::<_, std::string::String>(()))?; - assert_eq!(f2.call::<_, String>(())?.to_string_lossy(), "hello, world!"); - } - - // Check that Lua is properly destroyed - // It works because we collect garbage when Lua goes out of scope - assert_eq!(std::sync::Arc::strong_count(&rc), 1); - - Ok(()) -} diff --git a/tests/scope.rs b/tests/scope.rs.1 similarity index 100% rename from tests/scope.rs rename to tests/scope.rs.1 diff --git a/tests/serde.rs b/tests/serde.rs index 11c1d712..5dc4bd8f 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -71,49 +71,49 @@ fn test_serialize() -> Result<(), Box> { Ok(()) } -#[test] -fn test_serialize_in_scope() -> LuaResult<()> { - #[derive(Serialize, Clone)] - struct MyUserData(i64, String); - - impl UserData for MyUserData {} - - let lua = Lua::new(); - lua.scope(|scope| { - let ud = scope.create_ser_userdata(MyUserData(-5, "test userdata".into()))?; - assert_eq!( - serde_json::to_value(&ud).unwrap(), - serde_json::json!((-5, "test userdata")) - ); - Ok(()) - })?; - - lua.scope(|scope| { - let ud = scope.create_ser_userdata(MyUserData(-5, "test userdata".into()))?; - lua.globals().set("ud", ud) - })?; - let val = lua.load("ud").eval::()?; - match serde_json::to_value(&val) { - Ok(v) => panic!("expected destructed error, got {}", v), - Err(e) if e.to_string().contains("destructed") => {} - Err(e) => panic!("expected destructed error, got {}", e), - } - - struct MyUserDataRef<'a>(#[allow(unused)] &'a ()); - - impl<'a> UserData for MyUserDataRef<'a> {} - - lua.scope(|scope| { - let ud = scope.create_nonstatic_userdata(MyUserDataRef(&()))?; - match serde_json::to_value(&ud) { - Ok(v) => panic!("expected serialization error, got {}", v), - Err(serde_json::Error { .. }) => {} - }; - Ok(()) - })?; - - Ok(()) -} +// #[test] +// fn test_serialize_in_scope() -> LuaResult<()> { +// #[derive(Serialize, Clone)] +// struct MyUserData(i64, String); + +// impl UserData for MyUserData {} + +// let lua = Lua::new(); +// lua.scope(|scope| { +// let ud = scope.create_ser_userdata(MyUserData(-5, "test userdata".into()))?; +// assert_eq!( +// serde_json::to_value(&ud).unwrap(), +// serde_json::json!((-5, "test userdata")) +// ); +// Ok(()) +// })?; + +// lua.scope(|scope| { +// let ud = scope.create_ser_userdata(MyUserData(-5, "test userdata".into()))?; +// lua.globals().set("ud", ud) +// })?; +// let val = lua.load("ud").eval::()?; +// match serde_json::to_value(&val) { +// Ok(v) => panic!("expected destructed error, got {}", v), +// Err(e) if e.to_string().contains("destructed") => {} +// Err(e) => panic!("expected destructed error, got {}", e), +// } + +// struct MyUserDataRef<'a>(#[allow(unused)] &'a ()); + +// impl<'a> UserData for MyUserDataRef<'a> {} + +// lua.scope(|scope| { +// let ud = scope.create_nonstatic_userdata(MyUserDataRef(&()))?; +// match serde_json::to_value(&ud) { +// Ok(v) => panic!("expected serialization error, got {}", v), +// Err(serde_json::Error { .. }) => {} +// }; +// Ok(()) +// })?; + +// Ok(()) +// } #[test] fn test_serialize_any_userdata() -> Result<(), Box> { diff --git a/tests/static.rs b/tests/static.rs index 92164d71..7f6e0289 100644 --- a/tests/static.rs +++ b/tests/static.rs @@ -7,7 +7,7 @@ fn test_static_lua() -> Result<()> { let lua = Lua::new().into_static(); thread_local! { - static TABLE: RefCell>> = RefCell::new(None); + static TABLE: RefCell> = RefCell::new(None); } let f = lua.create_function(|_, table: Table| { @@ -38,7 +38,7 @@ fn test_static_lua_coroutine() -> Result<()> { let lua = Lua::new().into_static(); thread_local! { - static TABLE: RefCell>> = RefCell::new(None); + static TABLE: RefCell> = RefCell::new(None); } let f = lua.create_function(|_, table: Table| { diff --git a/tests/string.rs b/tests/string.rs index ad06c5f2..289e3ea7 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -111,22 +111,3 @@ fn test_string_pointer() -> Result<()> { Ok(()) } - -#[cfg(all(feature = "unstable", not(feature = "send")))] -#[test] -fn test_owned_string() -> Result<()> { - let lua = Lua::new(); - - let s = lua.create_string("hello, world!")?.into_owned(); - drop(lua); - - // Shortcuts - assert_eq!(s.as_bytes(), b"hello, world!"); - assert_eq!(s.to_str()?, "hello, world!"); - assert_eq!(format!("{s:?}"), "\"hello, world!\""); - - // Access via reference - assert_eq!(s.to_ref().to_string_lossy(), "hello, world!"); - - Ok(()) -} diff --git a/tests/table.rs b/tests/table.rs index 04885fc3..cdbb122c 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -455,17 +455,3 @@ fn test_table_call() -> Result<()> { Ok(()) } - -#[cfg(all(feature = "unstable", not(feature = "send")))] -#[test] -fn test_owned_table() -> Result<()> { - let lua = Lua::new(); - - let table = lua.create_table()?.into_owned(); - drop(lua); - - table.to_ref().set("abc", 123)?; - assert_eq!(table.to_ref().get::<_, i64>("abc")?, 123); - - Ok(()) -} diff --git a/tests/thread.rs b/tests/thread.rs index 2bc1988f..81f14e01 100644 --- a/tests/thread.rs +++ b/tests/thread.rs @@ -231,34 +231,3 @@ fn test_thread_pointer() -> Result<()> { Ok(()) } - -#[cfg(all(feature = "unstable", not(feature = "send")))] -#[test] -fn test_owned_thread() -> Result<()> { - let lua = Lua::new(); - - let accumulate = lua - .create_thread( - lua.load( - r#" - function (sum) - while true do - sum = sum + coroutine.yield(sum) - end - end - "#, - ) - .eval::()?, - )? - .into_owned(); - - for i in 0..4 { - accumulate.resume::<_, ()>(i)?; - } - assert_eq!(accumulate.resume::<_, i64>(4)?, 10); - assert_eq!(accumulate.status(), ThreadStatus::Resumable); - assert!(accumulate.resume::<_, ()>("error").is_err()); - assert_eq!(accumulate.status(), ThreadStatus::Error); - - Ok(()) -} diff --git a/tests/userdata.rs b/tests/userdata.rs index 7d5b6233..fd22ec31 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -47,7 +47,7 @@ fn test_methods() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { methods.add_method("get_value", |_, data, ()| Ok(data.0)); methods.add_method_mut("set_value", |_, data, args| { data.0 = args; @@ -97,7 +97,7 @@ fn test_method_variadic() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { methods.add_method("get", |_, data, ()| Ok(data.0)); methods.add_method_mut("add", |_, data, vals: Variadic| { data.0 += vals.into_iter().sum::(); @@ -122,7 +122,7 @@ fn test_metamethods() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { methods.add_method("get", |_, data, ()| Ok(data.0)); methods.add_meta_function( MetaMethod::Add, @@ -241,7 +241,7 @@ fn test_metamethod_close() -> Result<()> { struct MyUserData(Arc); impl UserData for MyUserData { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { methods.add_method("get", |_, data, ()| Ok(data.0.load(Ordering::Relaxed))); methods.add_meta_method(MetaMethod::Close, |_, data, _err: Value| { data.0.store(0, Ordering::Relaxed); @@ -287,7 +287,7 @@ fn test_gc_userdata() -> Result<()> { } impl UserData for MyUserdata { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { methods.add_method("access", |_, this, ()| { assert!(this.id == 123); Ok(()) @@ -326,7 +326,7 @@ fn test_userdata_take() -> Result<()> { struct MyUserdata(Arc); impl UserData for MyUserdata { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { methods.add_method("num", |_, this, ()| Ok(*this.0)) } } @@ -461,7 +461,7 @@ fn test_functions() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { methods.add_function("get_value", |_, ud: AnyUserData| { Ok(ud.borrow::()?.0) }); @@ -515,7 +515,7 @@ fn test_fields() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { - fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { + fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { fields.add_field("static", "constant"); fields.add_field_method_get("val", |_, data| Ok(data.0)); fields.add_field_method_set("val", |_, data, val| { @@ -562,12 +562,12 @@ fn test_fields() -> Result<()> { struct MyUserData2(i64); impl UserData for MyUserData2 { - fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { + fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { fields.add_field("z", 0); fields.add_field_method_get("x", |_, data| Ok(data.0)); } - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { methods.add_meta_method(MetaMethod::Index, |_, _, name: StdString| match &*name { "y" => Ok(Some(-1)), _ => Ok(None), @@ -594,7 +594,7 @@ fn test_metatable() -> Result<()> { struct MyUserData; impl UserData for MyUserData { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { methods.add_function("my_type_name", |_, data: AnyUserData| { let metatable = data.get_metatable()?; metatable.get::(MetaMethod::Type) @@ -640,7 +640,7 @@ fn test_metatable() -> Result<()> { struct MyUserData2; impl UserData for MyUserData2 { - fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { + fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { fields.add_meta_field_with("__index", |_| Ok(1)); } } @@ -655,7 +655,7 @@ fn test_metatable() -> Result<()> { struct MyUserData3; impl UserData for MyUserData3 { - fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { + fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { fields.add_meta_field_with(MetaMethod::Type, |_| Ok("CustomName")); } } @@ -671,11 +671,12 @@ fn test_metatable() -> Result<()> { } #[test] +#[ignore = "this functionality is deprecated"] fn test_userdata_wrapped() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { - fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { + fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { fields.add_field("static", "constant"); fields.add_field_method_get("data", |_, this| Ok(this.0)); fields.add_field_method_set("data", |_, this, val| { @@ -794,12 +795,12 @@ fn test_userdata_proxy() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { - fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { + fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { fields.add_field("static_field", 123); fields.add_field_method_get("n", |_, this| Ok(this.0)); } - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { methods.add_function("new", |_, n| Ok(Self(n))); methods.add_method("plus", |_, this, n: i64| Ok(this.0 + n)); @@ -888,7 +889,7 @@ fn test_userdata_ext() -> Result<()> { struct MyUserData(u32); impl UserData for MyUserData { - fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { + fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { fields.add_field_method_get("n", |_, this| Ok(this.0)); fields.add_field_method_set("n", |_, this, val| { this.0 = val; @@ -896,7 +897,7 @@ fn test_userdata_ext() -> Result<()> { }); } - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { methods.add_meta_method(MetaMethod::Call, |_, _this, ()| Ok("called")); methods.add_method_mut("add", |_, this, x: u32| { this.0 += x; @@ -929,7 +930,7 @@ fn test_userdata_method_errors() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { methods.add_method("get_value", |_, data, ()| Ok(data.0)); } } @@ -967,25 +968,6 @@ fn test_userdata_pointer() -> Result<()> { Ok(()) } -#[cfg(all(feature = "unstable", not(feature = "send")))] -#[test] -fn test_owned_userdata() -> Result<()> { - let lua = Lua::new(); - - let ud = lua.create_any_userdata("abc")?.into_owned(); - drop(lua); - - assert_eq!(*ud.borrow::<&str>()?, "abc"); - *ud.borrow_mut()? = "cba"; - assert!(matches!( - ud.borrow::(), - Err(Error::UserDataTypeMismatch) - )); - assert_eq!(ud.take::<&str>()?, "cba"); - - Ok(()) -} - #[cfg(feature = "macros")] #[test] fn test_userdata_derive() -> Result<()> { From 313117095cd39b96d5edbaeecae491156c5883fe Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 24 Jun 2024 22:52:15 +0100 Subject: [PATCH 113/635] Remove `parking_lot` feature flag --- .github/workflows/main.yml | 22 +++++++++++----------- Cargo.toml | 2 +- tests/userdata.rs | 10 ---------- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5810651b..9468c02a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,7 @@ jobs: - name: Build ${{ matrix.lua }} vendored run: | cargo build --features "${{ matrix.lua }},vendored" - cargo build --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable" + cargo build --features "${{ matrix.lua }},vendored,async,send,serialize,macros,unstable" shell: bash - name: Build ${{ matrix.lua }} pkg-config if: ${{ matrix.os == 'ubuntu-22.04' }} @@ -50,7 +50,7 @@ jobs: toolchain: stable target: aarch64-apple-darwin - name: Cross-compile - run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable" + run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros,unstable" build_aarch64_cross_ubuntu: name: Cross-compile to aarch64-unknown-linux-gnu @@ -71,7 +71,7 @@ jobs: sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross shell: bash - name: Cross-compile - run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable" + run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros,unstable" shell: bash build_armv7_cross_ubuntu: @@ -93,7 +93,7 @@ jobs: sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross shell: bash - name: Cross-compile - run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable" + run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros,unstable" shell: bash test: @@ -122,14 +122,14 @@ jobs: - name: Run ${{ matrix.lua }} tests run: | cargo test --features "${{ matrix.lua }},vendored" - cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" - cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,parking_lot,unstable" + cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros" + cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,unstable" shell: bash - name: Run compile tests (macos lua54) if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }} run: | TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" -- --ignored - TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable" -- --ignored + TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,unstable" -- --ignored shell: bash test_with_sanitizer: @@ -153,7 +153,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Run ${{ matrix.lua }} tests with address sanitizer run: | - cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions + cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,unstable" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions shell: bash env: RUSTFLAGS: -Z sanitizer=address @@ -226,8 +226,8 @@ jobs: - name: Run ${{ matrix.lua }} tests run: | cargo test --tests --features "${{ matrix.lua }},vendored" - cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" - cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,parking_lot,unstable" + cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros" + cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,unstable" rustfmt: name: Rustfmt @@ -255,4 +255,4 @@ jobs: - uses: giraffate/clippy-action@v1 with: reporter: 'github-pr-review' - clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable" + clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,unstable" diff --git a/Cargo.toml b/Cargo.toml index 2ab55409..cbd6086e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ with async/await features and support of writing native Lua modules in Rust. """ [package.metadata.docs.rs] -features = ["lua54", "vendored", "async", "send", "serialize", "macros", "parking_lot", "unstable"] +features = ["lua54", "vendored", "async", "send", "serialize", "macros", "unstable"] rustdoc-args = ["--cfg", "docsrs"] [workspace] diff --git a/tests/userdata.rs b/tests/userdata.rs index fd22ec31..8d5acc64 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -1,12 +1,8 @@ use std::collections::HashMap; use std::string::String as StdString; use std::sync::Arc; -#[cfg(not(feature = "parking_lot"))] use std::sync::{Mutex, RwLock}; -#[cfg(feature = "parking_lot")] -use parking_lot::{Mutex, RwLock}; - #[cfg(not(feature = "send"))] use std::{cell::RefCell, rc::Rc}; @@ -760,10 +756,7 @@ fn test_userdata_wrapped() -> Result<()> { "#, ) .exec()?; - #[cfg(not(feature = "parking_lot"))] assert_eq!(ud2.lock().unwrap().0, 3); - #[cfg(feature = "parking_lot")] - assert_eq!(ud2.lock().0, 3); globals.set("arc_mutex_ud", Nil)?; lua.gc_collect()?; assert_eq!(Arc::strong_count(&ud2), 1); @@ -779,10 +772,7 @@ fn test_userdata_wrapped() -> Result<()> { "#, ) .exec()?; - #[cfg(not(feature = "parking_lot"))] assert_eq!(ud3.read().unwrap().0, 4); - #[cfg(feature = "parking_lot")] - assert_eq!(ud3.read().0, 4); globals.set("arc_rwlock_ud", Nil)?; lua.gc_collect()?; assert_eq!(Arc::strong_count(&ud3), 1); From b3649a44e00451400489864e023ebe4514533289 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 24 Jun 2024 23:49:10 +0100 Subject: [PATCH 114/635] Move userdata-related top-level modules into the userdata module --- src/lib.rs | 9 ++------ src/lua.rs | 7 ++++--- src/userdata.rs | 13 ++++++++++-- src/{userdata_cell.rs => userdata/cell.rs} | 21 ++++++++++++------- src/{userdata_ext.rs => userdata/ext.rs} | 0 .../registry.rs} | 3 ++- 6 files changed, 33 insertions(+), 20 deletions(-) rename src/{userdata_cell.rs => userdata/cell.rs} (94%) rename src/{userdata_ext.rs => userdata/ext.rs} (100%) rename src/{userdata_impl.rs => userdata/registry.rs} (99%) diff --git a/src/lib.rs b/src/lib.rs index d1faffdf..22cf8530 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,9 +96,6 @@ mod table; mod thread; mod types; mod userdata; -mod userdata_cell; -mod userdata_ext; -mod userdata_impl; mod util; mod value; @@ -119,11 +116,9 @@ pub use crate::table::{Table, TableExt, TablePairs, TableSequence}; pub use crate::thread::{Thread, ThreadStatus}; pub use crate::types::{AppDataRef, AppDataRefMut, Integer, LightUserData, Number, RegistryKey}; pub use crate::userdata::{ - AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, + AnyUserData, AnyUserDataExt, MetaMethod, UserData, UserDataFields, UserDataMetatable, + UserDataMethods, UserDataRef, UserDataRefMut, UserDataRegistry, }; -pub use crate::userdata_cell::{UserDataRef, UserDataRefMut}; -pub use crate::userdata_ext::AnyUserDataExt; -pub use crate::userdata_impl::UserDataRegistry; pub use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value}; #[cfg(not(feature = "luau"))] diff --git a/src/lua.rs b/src/lua.rs index 44472d5a..af7c615a 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -30,9 +30,10 @@ use crate::types::{ DestructedUserdata, Integer, LightUserData, MaybeSend, Number, RegistryKey, SubtypeId, ValueRef, }; -use crate::userdata::{AnyUserData, MetaMethod, UserData}; -use crate::userdata_cell::{UserDataRef, UserDataVariant}; -use crate::userdata_impl::{UserDataProxy, UserDataRegistry}; +use crate::userdata::{ + AnyUserData, MetaMethod, UserData, UserDataProxy, UserDataRef, UserDataRegistry, + UserDataVariant, +}; use crate::util::{ self, assert_stack, check_stack, error_traceback, get_destructed_userdata_metatable, get_gc_metatable, get_gc_userdata, get_main_state, get_userdata, init_error_registry, diff --git a/src/userdata.rs b/src/userdata.rs index b21000cd..1100dc33 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -20,10 +20,15 @@ use crate::lua::{Lua, LuaGuard}; use crate::string::String; use crate::table::{Table, TablePairs}; use crate::types::{MaybeSend, SubtypeId, ValueRef}; -use crate::userdata_cell::{UserDataRef, UserDataRefMut, UserDataVariant}; use crate::util::{check_stack, get_userdata, take_userdata, StackGuard}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; -use crate::UserDataRegistry; + +// Re-export for convenience +pub(crate) use cell::UserDataVariant; +pub use cell::{UserDataRef, UserDataRefMut}; +pub use ext::AnyUserDataExt; +pub(crate) use registry::UserDataProxy; +pub use registry::UserDataRegistry; #[cfg(feature = "lua54")] pub(crate) const USER_VALUE_MAXSLOT: usize = 8; @@ -1181,6 +1186,10 @@ where } } +mod cell; +mod ext; +mod registry; + // #[cfg(test)] // mod assertions { // use super::*; diff --git a/src/userdata_cell.rs b/src/userdata/cell.rs similarity index 94% rename from src/userdata_cell.rs rename to src/userdata/cell.rs index 59b4503b..7205958a 100644 --- a/src/userdata_cell.rs +++ b/src/userdata/cell.rs @@ -202,7 +202,10 @@ impl FromLua for UserDataRef { unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { let type_id = lua.get_userdata_type_id(idx)?; match type_id { - Some(type_id) if type_id == TypeId::of::() => lua.get_userdata_ref(idx), + Some(type_id) if type_id == TypeId::of::() => { + let guard = lua.lua().lock_arc(); + (*get_userdata::>(lua.state(), idx)).try_make_ref(guard) + } _ => Err(Error::UserDataTypeMismatch), } } @@ -348,11 +351,13 @@ impl<'a, T> Deref for UserDataBorrowRef<'a, T> { } } -impl<'a, T> UserDataBorrowRef<'a, T> { +impl<'a, T> TryFrom<&'a UserDataVariant> for UserDataBorrowRef<'a, T> { + type Error = Error; + #[inline(always)] - pub(crate) fn try_from(variant: &'a UserDataVariant) -> Result { + fn try_from(variant: &'a UserDataVariant) -> Result { set_reading(variant.flag())?; - Ok(UserDataBorrowRef(&variant)) + Ok(UserDataBorrowRef(variant)) } } @@ -381,11 +386,13 @@ impl<'a, T> DerefMut for UserDataBorrowMut<'a, T> { } } -impl<'a, T> UserDataBorrowMut<'a, T> { +impl<'a, T> TryFrom<&'a UserDataVariant> for UserDataBorrowMut<'a, T> { + type Error = Error; + #[inline(always)] - pub(crate) fn try_from(variant: &'a UserDataVariant) -> Result { + fn try_from(variant: &'a UserDataVariant) -> Result { set_writing(variant.flag())?; - Ok(UserDataBorrowMut(&variant)) + Ok(UserDataBorrowMut(variant)) } } diff --git a/src/userdata_ext.rs b/src/userdata/ext.rs similarity index 100% rename from src/userdata_ext.rs rename to src/userdata/ext.rs diff --git a/src/userdata_impl.rs b/src/userdata/registry.rs similarity index 99% rename from src/userdata_impl.rs rename to src/userdata/registry.rs index b5527784..147f6797 100644 --- a/src/userdata_impl.rs +++ b/src/userdata/registry.rs @@ -11,10 +11,11 @@ use crate::error::{Error, Result}; use crate::lua::Lua; use crate::types::{Callback, MaybeSend}; use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods}; -use crate::userdata_cell::{UserDataBorrowMut, UserDataBorrowRef, UserDataVariant}; use crate::util::{get_userdata, short_type_name}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; +use super::cell::{UserDataBorrowMut, UserDataBorrowRef, UserDataVariant}; + #[cfg(not(feature = "send"))] use std::rc::Rc; From 550d6b2991c3e2ad849864e560e72b0452e7da83 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 25 Jun 2024 00:09:38 +0100 Subject: [PATCH 115/635] Remove wrapped UserData impl (Rc/Arc/etc) --- src/userdata/registry.rs | 229 +++++---------------------------------- tests/userdata.rs | 118 -------------------- 2 files changed, 25 insertions(+), 322 deletions(-) diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index 147f6797..3c92ca83 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -5,7 +5,6 @@ use std::cell::RefCell; use std::marker::PhantomData; use std::os::raw::c_int; use std::string::String as StdString; -use std::sync::{Arc, Mutex, RwLock}; use crate::error::{Error, Result}; use crate::lua::Lua; @@ -74,61 +73,22 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { }; } - Box::new(move |lua, nargs| unsafe { + Box::new(move |rawlua, nargs| unsafe { if nargs == 0 { let err = Error::from_lua_conversion("missing argument", "userdata", None); try_self_arg!(Err(err)); } - let state = lua.state(); + let state = rawlua.state(); // Find absolute "self" index before processing args let index = ffi::lua_absindex(state, -nargs); // Self was at position 1, so we pass 2 here - let args = A::from_stack_args(nargs - 1, 2, Some(&name), lua); + let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua); - match try_self_arg!(lua.get_userdata_type_id(index)) { + match try_self_arg!(rawlua.get_userdata_type_id(index)) { Some(id) if id == TypeId::of::() => { let ud = try_self_arg!(borrow_userdata_ref::(state, index)); - method(lua.lua(), &ud, args?)?.push_into_stack_multi(lua) + method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua) } - // #[cfg(not(feature = "send"))] - // Some(id) if id == TypeId::of::>() => { - // let ud = try_self_arg!(get_userdata_ref::>(state, index)); - // method(lua.lua(), ud, args?)?.push_into_stack_multi(lua) - // } - // #[cfg(not(feature = "send"))] - // Some(id) if id == TypeId::of::>>() => { - // let ud = try_self_arg!(get_userdata_ref::>>(state, index)); - // let ud = try_self_arg!(ud.try_borrow(), Error::UserDataBorrowError); - // method(lua.lua(), &ud, args?)?.push_into_stack_multi(lua) - // } - // Some(id) if id == TypeId::of::>() => { - // let ud = try_self_arg!(get_userdata_ref::>(state, index)); - // method(lua.lua(), &ud, args?)?.push_into_stack_multi(lua) - // } - // Some(id) if id == TypeId::of::>>() => { - // let ud = try_self_arg!(get_userdata_ref::>>(state, index)); - // let ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowError); - // method(lua.lua(), &ud, args?)?.push_into_stack_multi(lua) - // } - // #[cfg(feature = "parking_lot")] - // Some(id) if id == TypeId::of::>>() => { - // let ud = get_userdata_ref::>>(state, index); - // let ud = try_self_arg!(ud); - // let ud = try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowError)); - // method(lua.lua(), &ud, args?)?.push_into_stack_multi(lua) - // } - // Some(id) if id == TypeId::of::>>() => { - // let ud = try_self_arg!(get_userdata_ref::>>(state, index)); - // let ud = try_self_arg!(ud.try_read(), Error::UserDataBorrowError); - // method(lua.lua(), &ud, args?)?.push_into_stack_multi(lua) - // } - // #[cfg(feature = "parking_lot")] - // Some(id) if id == TypeId::of::>>() => { - // let ud = get_userdata_ref::>>(state, index); - // let ud = try_self_arg!(ud); - // let ud = try_self_arg!(ud.try_read().ok_or(Error::UserDataBorrowError)); - // method(lua.lua(), &ud, args?)?.push_into_stack_multi(lua) - // } _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), } }) @@ -151,7 +111,7 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { } let method = RefCell::new(method); - Box::new(move |lua, nargs| unsafe { + Box::new(move |rawlua, nargs| unsafe { let mut method = method .try_borrow_mut() .map_err(|_| Error::RecursiveMutCallback)?; @@ -159,65 +119,32 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { let err = Error::from_lua_conversion("missing argument", "userdata", None); try_self_arg!(Err(err)); } - let state = lua.state(); + let state = rawlua.state(); // Find absolute "self" index before processing args let index = ffi::lua_absindex(state, -nargs); // Self was at position 1, so we pass 2 here - let args = A::from_stack_args(nargs - 1, 2, Some(&name), lua); + let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua); - match try_self_arg!(lua.get_userdata_type_id(index)) { + match try_self_arg!(rawlua.get_userdata_type_id(index)) { Some(id) if id == TypeId::of::() => { let mut ud = try_self_arg!(borrow_userdata_mut::(state, index)); - method(lua.lua(), &mut ud, args?)?.push_into_stack_multi(lua) + method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua) } - // #[cfg(not(feature = "send"))] - // Some(id) if id == TypeId::of::>() => Err(Error::UserDataBorrowMutError), - // #[cfg(not(feature = "send"))] - // Some(id) if id == TypeId::of::>>() => { - // let ud = try_self_arg!(get_userdata_mut::>>(state, index)); - // let mut ud = try_self_arg!(ud.try_borrow_mut(), Error::UserDataBorrowMutError); - // method(lua.lua(), &mut ud, args?)?.push_into_stack_multi(lua) - // } - // Some(id) if id == TypeId::of::>() => Err(Error::UserDataBorrowMutError), - // Some(id) if id == TypeId::of::>>() => { - // let ud = try_self_arg!(get_userdata_mut::>>(state, index)); - // let mut ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowMutError); - // method(lua.lua(), &mut ud, args?)?.push_into_stack_multi(lua) - // } - // #[cfg(feature = "parking_lot")] - // Some(id) if id == TypeId::of::>>() => { - // let ud = get_userdata_mut::>>(state, index); - // let ud = try_self_arg!(ud); - // let mut ud = try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowMutError)); - // method(lua.lua(), &mut ud, args?)?.push_into_stack_multi(lua) - // } - // Some(id) if id == TypeId::of::>>() => { - // let ud = try_self_arg!(get_userdata_mut::>>(state, index)); - // let mut ud = try_self_arg!(ud.try_write(), Error::UserDataBorrowMutError); - // method(lua.lua(), &mut ud, args?)?.push_into_stack_multi(lua) - // } - // #[cfg(feature = "parking_lot")] - // Some(id) if id == TypeId::of::>>() => { - // let ud = get_userdata_mut::>>(state, index); - // let ud = try_self_arg!(ud); - // let mut ud = try_self_arg!(ud.try_write().ok_or(Error::UserDataBorrowMutError)); - // method(lua.lua(), &mut ud, args?)?.push_into_stack_multi(lua) - // } _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), } }) } #[cfg(feature = "async")] - fn box_async_method(name: String, method: M) -> AsyncCallback<'a> + fn box_async_method(name: &str, method: M) -> AsyncCallback<'a> where M: Fn(&'a Lua, &'a T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, MR: Future> + 'a, R: IntoLuaMulti, { - let name = Arc::new(get_function_name::(&name)); - let method = Arc::new(method); + let name = Rc::new(get_function_name::(name)); + let method = Rc::new(method); Box::new(move |rawlua, mut args| unsafe { let name = name.clone(); @@ -246,52 +173,6 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { let ud = std::mem::transmute::<&T, &T>(&ud); method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) } - // #[cfg(not(feature = "send"))] - // Some(id) if id == TypeId::of::>() => { - // let ud = try_self_arg!(rawlua.get_userdata_ref::>(&this)); - // let ud = std::mem::transmute::<&T, &T>(&ud); - // method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) - // } - // #[cfg(not(feature = "send"))] - // Some(id) if id == TypeId::of::>>() => { - // let ud = try_self_arg!(rawlua.get_userdata_ref::>>(&this)); - // let ud = try_self_arg!(ud.try_borrow(), Error::UserDataBorrowError); - // let ud = std::mem::transmute::<&T, &T>(&ud); - // method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) - // } - // Some(id) if id == TypeId::of::>() => { - // let ud = try_self_arg!(rawlua.get_userdata_ref::>(&this)); - // let ud = std::mem::transmute::<&T, &T>(&ud); - // method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) - // } - // Some(id) if id == TypeId::of::>>() => { - // let ud = try_self_arg!(rawlua.get_userdata_ref::>>(&this)); - // let ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowError); - // let ud = std::mem::transmute::<&T, &T>(&ud); - // method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) - // } - // #[cfg(feature = "parking_lot")] - // Some(id) if id == TypeId::of::>>() => { - // let ud = rawlua.get_userdata_ref::>>(&this); - // let ud = try_self_arg!(ud); - // let ud = try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowError)); - // let ud = std::mem::transmute::<&T, &T>(&ud); - // method(lua, ud, args?).await?.push_into_stack_multi(lua) - // } - // Some(id) if id == TypeId::of::>>() => { - // let ud = try_self_arg!(rawlua.get_userdata_ref::>>(&this)); - // let ud = try_self_arg!(ud.try_read(), Error::UserDataBorrowError); - // let ud = std::mem::transmute::<&T, &T>(&ud); - // method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) - // } - // #[cfg(feature = "parking_lot")] - // Some(id) if id == TypeId::of::>>() => { - // let ud = get_userdata_ref::>>(ref_thread, index); - // let ud = try_self_arg!(ud); - // let ud = try_self_arg!(ud.try_read().ok_or(Error::UserDataBorrowError)); - // let ud = std::mem::transmute::<&T, &T>(&ud); - // method(lua, ud, args?).await?.push_into_stack_multi(lua) - // } _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), } }) @@ -299,15 +180,15 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { } #[cfg(feature = "async")] - fn box_async_method_mut(name: String, method: M) -> AsyncCallback<'a> + fn box_async_method_mut(name: &str, method: M) -> AsyncCallback<'a> where M: Fn(&'a Lua, &'a mut T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, MR: Future> + 'a, R: IntoLuaMulti, { - let name = Arc::new(get_function_name::(&name)); - let method = Arc::new(method); + let name = Rc::new(get_function_name::(name)); + let method = Rc::new(method); Box::new(move |rawlua, mut args| unsafe { let name = name.clone(); @@ -336,53 +217,6 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud); method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) } - // #[cfg(not(feature = "send"))] - // Some(id) if id == TypeId::of::>>() => { - // Err(Error::UserDataBorrowMutError) - // } - // #[cfg(not(feature = "send"))] - // Some(id) if id == TypeId::of::>>() => { - // let ud = - // try_self_arg!(get_userdata_mut::>>(ref_thread, index)); - // let mut ud = - // try_self_arg!(ud.try_borrow_mut(), Error::UserDataBorrowMutError); - // let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud); - // method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) - // } - // #[cfg(not(feature = "send"))] - // Some(id) if id == TypeId::of::>() => Err(Error::UserDataBorrowMutError), - // Some(id) if id == TypeId::of::>>() => { - // let ud = - // try_self_arg!(get_userdata_mut::>>(ref_thread, index)); - // let mut ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowMutError); - // let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud); - // method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) - // } - // #[cfg(feature = "parking_lot")] - // Some(id) if id == TypeId::of::>>() => { - // let ud = get_userdata_mut::>>(ref_thread, index); - // let ud = try_self_arg!(ud); - // let mut ud = - // try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowMutError)); - // let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud); - // method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) - // } - // Some(id) if id == TypeId::of::>>() => { - // let ud = - // try_self_arg!(get_userdata_mut::>>(ref_thread, index)); - // let mut ud = try_self_arg!(ud.try_write(), Error::UserDataBorrowMutError); - // let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud); - // method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) - // } - // #[cfg(feature = "parking_lot")] - // Some(id) if id == TypeId::of::>>() => { - // let ud = get_userdata_mut::>>(ref_thread, index); - // let ud = try_self_arg!(ud); - // let mut ud = - // try_self_arg!(ud.try_write().ok_or(Error::UserDataBorrowMutError)); - // let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud); - // method(lua, ud, args?).await?.push_into_stack_multi(lua) - // } _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), } }) @@ -420,7 +254,7 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { } #[cfg(feature = "async")] - fn box_async_function(name: String, function: F) -> AsyncCallback<'a> + fn box_async_function(name: &str, function: F) -> AsyncCallback<'a> where F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, @@ -580,7 +414,7 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_async_method(name.clone(), method); + let callback = Self::box_async_method(&name, method); self.async_methods.push((name, callback)); } @@ -593,7 +427,7 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_async_method_mut(name.clone(), method); + let callback = Self::box_async_method_mut(&name, method); self.async_methods.push((name, callback)); } @@ -628,7 +462,7 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_async_function(name.clone(), function); + let callback = Self::box_async_function(&name, function); self.async_methods.push((name, callback)); } @@ -663,7 +497,7 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_async_method(name.clone(), method); + let callback = Self::box_async_method(&name, method); self.async_meta_methods.push((name, callback)); } @@ -676,7 +510,7 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_async_method_mut(name.clone(), method); + let callback = Self::box_async_method_mut(&name, method); self.async_meta_methods.push((name, callback)); } @@ -711,13 +545,13 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_async_function(name.clone(), function); + let callback = Self::box_async_function(&name, function); self.async_meta_methods.push((name, callback)); } } // Borrow the userdata in-place from the Lua stack -#[inline] +#[inline(always)] unsafe fn borrow_userdata_ref<'a, T>( state: *mut ffi::lua_State, index: c_int, @@ -727,7 +561,7 @@ unsafe fn borrow_userdata_ref<'a, T>( } // Borrow the userdata mutably in-place from the Lua stack -#[inline] +#[inline(always)] unsafe fn borrow_userdata_mut<'a, T>( state: *mut ffi::lua_State, index: c_int, @@ -761,19 +595,6 @@ macro_rules! lua_userdata_impl { }; } -#[cfg(not(feature = "send"))] -lua_userdata_impl!(Rc); -#[cfg(not(feature = "send"))] -lua_userdata_impl!(Rc>); - -lua_userdata_impl!(Arc); -lua_userdata_impl!(Arc>); -lua_userdata_impl!(Arc>); -#[cfg(feature = "parking_lot")] -lua_userdata_impl!(Arc>); -#[cfg(feature = "parking_lot")] -lua_userdata_impl!(Arc>); - // A special proxy object for UserData pub(crate) struct UserDataProxy(pub(crate) PhantomData); diff --git a/tests/userdata.rs b/tests/userdata.rs index 8d5acc64..ac026f90 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -1,10 +1,6 @@ use std::collections::HashMap; use std::string::String as StdString; use std::sync::Arc; -use std::sync::{Mutex, RwLock}; - -#[cfg(not(feature = "send"))] -use std::{cell::RefCell, rc::Rc}; #[cfg(feature = "lua54")] use std::sync::atomic::{AtomicI64, Ordering}; @@ -666,120 +662,6 @@ fn test_metatable() -> Result<()> { Ok(()) } -#[test] -#[ignore = "this functionality is deprecated"] -fn test_userdata_wrapped() -> Result<()> { - struct MyUserData(i64); - - impl UserData for MyUserData { - fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { - fields.add_field("static", "constant"); - fields.add_field_method_get("data", |_, this| Ok(this.0)); - fields.add_field_method_set("data", |_, this, val| { - this.0 = val; - Ok(()) - }) - } - } - - let lua = Lua::new(); - let globals = lua.globals(); - - // Rc - #[cfg(not(feature = "send"))] - { - let ud = Rc::new(MyUserData(1)); - globals.set("rc_ud", ud.clone())?; - lua.load( - r#" - assert(rc_ud.static == "constant") - local ok, err = pcall(function() rc_ud.data = 2 end) - assert( - tostring(err):sub(1, 32) == "error mutably borrowing userdata", - "expected error mutably borrowing userdata, got " .. tostring(err) - ) - assert(rc_ud.data == 1) - "#, - ) - .exec()?; - globals.set("rc_ud", Nil)?; - lua.gc_collect()?; - assert_eq!(Rc::strong_count(&ud), 1); - } - - // Rc> - #[cfg(not(feature = "send"))] - { - let ud = Rc::new(RefCell::new(MyUserData(1))); - globals.set("rc_refcell_ud", ud.clone())?; - lua.load( - r#" - assert(rc_refcell_ud.static == "constant") - rc_refcell_ud.data = rc_refcell_ud.data + 1 - assert(rc_refcell_ud.data == 2) - "#, - ) - .exec()?; - assert_eq!(ud.borrow().0, 2); - globals.set("rc_refcell_ud", Nil)?; - lua.gc_collect()?; - assert_eq!(Rc::strong_count(&ud), 1); - } - - // Arc - let ud1 = Arc::new(MyUserData(2)); - globals.set("arc_ud", ud1.clone())?; - lua.load( - r#" - assert(arc_ud.static == "constant") - local ok, err = pcall(function() arc_ud.data = 3 end) - assert( - tostring(err):sub(1, 32) == "error mutably borrowing userdata", - "expected error mutably borrowing userdata, got " .. tostring(err) - ) - assert(arc_ud.data == 2) - "#, - ) - .exec()?; - globals.set("arc_ud", Nil)?; - lua.gc_collect()?; - assert_eq!(Arc::strong_count(&ud1), 1); - - // Arc> - let ud2 = Arc::new(Mutex::new(MyUserData(2))); - globals.set("arc_mutex_ud", ud2.clone())?; - lua.load( - r#" - assert(arc_mutex_ud.static == "constant") - arc_mutex_ud.data = arc_mutex_ud.data + 1 - assert(arc_mutex_ud.data == 3) - "#, - ) - .exec()?; - assert_eq!(ud2.lock().unwrap().0, 3); - globals.set("arc_mutex_ud", Nil)?; - lua.gc_collect()?; - assert_eq!(Arc::strong_count(&ud2), 1); - - // Arc> - let ud3 = Arc::new(RwLock::new(MyUserData(3))); - globals.set("arc_rwlock_ud", ud3.clone())?; - lua.load( - r#" - assert(arc_rwlock_ud.static == "constant") - arc_rwlock_ud.data = arc_rwlock_ud.data + 1 - assert(arc_rwlock_ud.data == 4) - "#, - ) - .exec()?; - assert_eq!(ud3.read().unwrap().0, 4); - globals.set("arc_rwlock_ud", Nil)?; - lua.gc_collect()?; - assert_eq!(Arc::strong_count(&ud3), 1); - - Ok(()) -} - #[test] fn test_userdata_proxy() -> Result<()> { struct MyUserData(i64); From baa8895cfbb227c0192faa77ad4731bb7c5e536e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 25 Jun 2024 12:17:33 +0100 Subject: [PATCH 116/635] Optimize creation of userdata callbacks (registry) --- src/userdata/cell.rs | 16 +++++ src/userdata/registry.rs | 140 ++++++++++++++++++--------------------- 2 files changed, 81 insertions(+), 75 deletions(-) diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index 7205958a..294b7b55 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -361,6 +361,14 @@ impl<'a, T> TryFrom<&'a UserDataVariant> for UserDataBorrowRef<'a, T> { } } +impl<'a, T> UserDataBorrowRef<'a, T> { + #[inline(always)] + pub(crate) fn get_ref(&self) -> &'a T { + // SAFETY: `UserDataBorrowRef` is only created when the borrow flag is set to reading. + unsafe { self.0.get_ref() } + } +} + pub(crate) struct UserDataBorrowMut<'a, T>(&'a UserDataVariant); impl<'a, T> Drop for UserDataBorrowMut<'a, T> { @@ -396,6 +404,14 @@ impl<'a, T> TryFrom<&'a UserDataVariant> for UserDataBorrowMut<'a, T> { } } +impl<'a, T> UserDataBorrowMut<'a, T> { + #[inline(always)] + pub(crate) fn get_mut(&mut self) -> &'a mut T { + // SAFETY: `UserDataBorrowMut` is only created when the borrow flag is set to writing. + unsafe { self.0.get_mut() } + } +} + #[inline] fn try_value_to_userdata(value: Value) -> Result { match value { diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index 3c92ca83..d0887cb0 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -15,9 +15,6 @@ use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; use super::cell::{UserDataBorrowMut, UserDataBorrowRef, UserDataVariant}; -#[cfg(not(feature = "send"))] -use std::rc::Rc; - #[cfg(feature = "async")] use {crate::types::AsyncCallback, futures_util::future, std::future::Future}; @@ -68,9 +65,6 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { ($res:expr) => { $res.map_err(|err| Error::bad_self_argument(&name, err))? }; - ($res:expr, $err:expr) => { - $res.map_err(|_| Error::bad_self_argument(&name, $err))? - }; } Box::new(move |rawlua, nargs| unsafe { @@ -105,9 +99,6 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { ($res:expr) => { $res.map_err(|err| Error::bad_self_argument(&name, err))? }; - ($res:expr, $err:expr) => { - $res.map_err(|_| Error::bad_self_argument(&name, $err))? - }; } let method = RefCell::new(method); @@ -143,39 +134,40 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { MR: Future> + 'a, R: IntoLuaMulti, { - let name = Rc::new(get_function_name::(name)); - let method = Rc::new(method); + let name = get_function_name::(name); + macro_rules! try_self_arg { + ($res:expr) => { + match $res { + Ok(res) => res, + Err(err) => return Box::pin(future::err(Error::bad_self_argument(&name, err))), + } + }; + } Box::new(move |rawlua, mut args| unsafe { - let name = name.clone(); - let method = method.clone(); - macro_rules! try_self_arg { - ($res:expr) => { - $res.map_err(|err| Error::bad_self_argument(&name, err))? - }; - ($res:expr, $err:expr) => { - $res.map_err(|_| Error::bad_self_argument(&name, $err))? - }; - } + let this = args + .pop_front() + .ok_or_else(|| Error::from_lua_conversion("missing argument", "userdata", None)); + let lua = rawlua.lua(); + let this = try_self_arg!(AnyUserData::from_lua(try_self_arg!(this), lua)); + let args = A::from_lua_args(args, 2, Some(&name), lua); - Box::pin(async move { - let this = args.pop_front().ok_or_else(|| { - Error::from_lua_conversion("missing argument", "userdata", None) - }); - let lua = rawlua.lua(); - let this = try_self_arg!(AnyUserData::from_lua(try_self_arg!(this), lua)); - let args = A::from_lua_args(args, 2, Some(&name), lua); - - let (ref_thread, index) = (rawlua.ref_thread(), this.0.index); - match try_self_arg!(this.type_id()) { - Some(id) if id == TypeId::of::() => { - let ud = try_self_arg!(borrow_userdata_ref::(ref_thread, index)); - let ud = std::mem::transmute::<&T, &T>(&ud); - method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) - } - _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), + let (ref_thread, index) = (rawlua.ref_thread(), this.0.index); + match try_self_arg!(this.type_id()) { + Some(id) if id == TypeId::of::() => { + let ud = try_self_arg!(borrow_userdata_ref::(ref_thread, index)); + let args = match args { + Ok(args) => args, + Err(e) => return Box::pin(future::err(e)), + }; + let fut = method(lua, ud.get_ref(), args); + Box::pin(async move { fut.await?.push_into_stack_multi(&rawlua) }) + } + _ => { + let err = Error::bad_self_argument(&name, Error::UserDataTypeMismatch); + Box::pin(future::err(err)) } - }) + } }) } @@ -187,39 +179,40 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { MR: Future> + 'a, R: IntoLuaMulti, { - let name = Rc::new(get_function_name::(name)); - let method = Rc::new(method); + let name = get_function_name::(name); + macro_rules! try_self_arg { + ($res:expr) => { + match $res { + Ok(res) => res, + Err(err) => return Box::pin(future::err(Error::bad_self_argument(&name, err))), + } + }; + } Box::new(move |rawlua, mut args| unsafe { - let name = name.clone(); - let method = method.clone(); - macro_rules! try_self_arg { - ($res:expr) => { - $res.map_err(|err| Error::bad_self_argument(&name, err))? - }; - ($res:expr, $err:expr) => { - $res.map_err(|_| Error::bad_self_argument(&name, $err))? - }; - } + let this = args + .pop_front() + .ok_or_else(|| Error::from_lua_conversion("missing argument", "userdata", None)); + let lua = rawlua.lua(); + let this = try_self_arg!(AnyUserData::from_lua(try_self_arg!(this), lua)); + let args = A::from_lua_args(args, 2, Some(&name), lua); - Box::pin(async move { - let this = args.pop_front().ok_or_else(|| { - Error::from_lua_conversion("missing argument", "userdata", None) - }); - let lua = rawlua.lua(); - let this = try_self_arg!(AnyUserData::from_lua(try_self_arg!(this), lua)); - let args = A::from_lua_args(args, 2, Some(&name), lua); - - let (ref_thread, index) = (rawlua.ref_thread(), this.0.index); - match try_self_arg!(this.type_id()) { - Some(id) if id == TypeId::of::() => { - let mut ud = try_self_arg!(borrow_userdata_mut::(ref_thread, index)); - let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud); - method(lua, ud, args?).await?.push_into_stack_multi(&rawlua) - } - _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), + let (ref_thread, index) = (rawlua.ref_thread(), this.0.index); + match try_self_arg!(this.type_id()) { + Some(id) if id == TypeId::of::() => { + let mut ud = try_self_arg!(borrow_userdata_mut::(ref_thread, index)); + let args = match args { + Ok(args) => args, + Err(e) => return Box::pin(future::err(e)), + }; + let fut = method(lua, ud.get_mut(), args); + Box::pin(async move { fut.await?.push_into_stack_multi(&rawlua) }) } - }) + _ => { + let err = Error::bad_self_argument(&name, Error::UserDataTypeMismatch); + Box::pin(future::err(err)) + } + } }) } @@ -269,8 +262,7 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { Err(e) => return Box::pin(future::err(e)), }; let fut = function(lua, args); - let weak = rawlua.weak().clone(); - Box::pin(async move { fut.await?.push_into_stack_multi(&weak.lock()) }) + Box::pin(async move { fut.await?.push_into_stack_multi(&rawlua) }) }) } @@ -356,11 +348,10 @@ impl<'a, T: 'static> UserDataFields<'a, T> for UserDataRegistry<'a, T> { V: IntoLua + Clone + 'static, { let name = name.to_string(); - let name2 = name.clone(); self.meta_fields.push(( - name, + name.clone(), Box::new(move |lua, _| unsafe { - Self::check_meta_field(lua.lua(), &name2, value.clone())?.push_into_stack_multi(lua) + Self::check_meta_field(lua.lua(), &name, value.clone())?.push_into_stack_multi(lua) }), )); } @@ -371,12 +362,11 @@ impl<'a, T: 'static> UserDataFields<'a, T> for UserDataRegistry<'a, T> { R: IntoLua, { let name = name.to_string(); - let name2 = name.clone(); self.meta_fields.push(( - name, + name.clone(), Box::new(move |rawlua, _| unsafe { let lua = rawlua.lua(); - Self::check_meta_field(lua, &name2, f(lua)?)?.push_into_stack_multi(rawlua) + Self::check_meta_field(lua, &name, f(lua)?)?.push_into_stack_multi(rawlua) }), )); } From d17dc546455c438751f6210b7197e1d52070cb1b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 25 Jun 2024 17:59:17 +0100 Subject: [PATCH 117/635] Rewrite `call_async` in traits to use `impl Future` instead of `LocalBoxFuture`. More use `std::future` types instead of `future-utils`. --- src/function.rs | 4 +-- src/lua.rs | 7 ++-- src/table.rs | 50 +++++++++++++--------------- src/userdata/ext.rs | 71 ++++++++++++++++------------------------ src/userdata/registry.rs | 23 ++++++++----- 5 files changed, 72 insertions(+), 83 deletions(-) diff --git a/src/function.rs b/src/function.rs index a27069ed..30bcd448 100644 --- a/src/function.rs +++ b/src/function.rs @@ -17,7 +17,7 @@ use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti, Value}; #[cfg(feature = "async")] use { crate::types::AsyncCallback, - futures_util::future::{self, Future}, + std::future::{self, Future}, }; /// Handle to an internal Lua function. @@ -566,7 +566,7 @@ impl Function { let lua = rawlua.lua(); let args = match A::from_lua_args(args, 1, None, lua) { Ok(args) => args, - Err(e) => return Box::pin(future::err(e)), + Err(e) => return Box::pin(future::ready(Err(e))), }; let fut = func(lua, args); let weak = rawlua.weak().clone(); diff --git a/src/lua.rs b/src/lua.rs index af7c615a..6c68f334 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -61,9 +61,10 @@ use crate::{ #[cfg(feature = "async")] use { crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue}, - futures_util::future::{self, Future}, - futures_util::task::{noop_waker_ref, Context, Poll, Waker}, + futures_util::task::noop_waker_ref, + std::future::{self, Future}, std::ptr::NonNull, + std::task::{Context, Poll, Waker}, }; #[cfg(feature = "serialize")] @@ -1597,7 +1598,7 @@ impl Lua { let lua = rawlua.lua(); let args = match A::from_lua_args(args, 1, None, lua) { Ok(args) => args, - Err(e) => return Box::pin(future::err(e)), + Err(e) => return Box::pin(future::ready(Err(e))), }; let fut = func(lua, args); Box::pin(async move { fut.await?.push_into_stack_multi(rawlua) }) diff --git a/src/table.rs b/src/table.rs index 3e0edb59..e8d6d34a 100644 --- a/src/table.rs +++ b/src/table.rs @@ -18,7 +18,7 @@ use crate::util::{assert_stack, check_stack, StackGuard}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Nil, Value}; #[cfg(feature = "async")] -use futures_util::future::{self, LocalBoxFuture}; +use std::future::Future; /// Handle to an internal Lua table. #[derive(Clone)] @@ -889,10 +889,10 @@ pub trait TableExt: Sealed { /// The metamethod is called with the table as its first argument, followed by the passed arguments. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async(&self, args: A) -> LocalBoxFuture<'static, Result> + fn call_async(&self, args: A) -> impl Future> where A: IntoLuaMulti, - R: FromLuaMulti + 'static; + R: FromLuaMulti; /// Gets the function associated to `key` from the table and executes it, /// passing the table itself along with `args` as function arguments. @@ -926,10 +926,10 @@ pub trait TableExt: Sealed { /// This might invoke the `__index` metamethod. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_method(&self, name: &str, args: A) -> LocalBoxFuture<'static, Result> + fn call_async_method(&self, name: &str, args: A) -> impl Future> where A: IntoLuaMulti, - R: FromLuaMulti + 'static; + R: FromLuaMulti; /// Gets the function associated to `key` from the table and asynchronously executes it, /// passing `args` as function arguments and returning Future. @@ -939,10 +939,10 @@ pub trait TableExt: Sealed { /// This might invoke the `__index` metamethod. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_function(&self, name: &str, args: A) -> LocalBoxFuture<'static, Result> + fn call_async_function(&self, name: &str, args: A) -> impl Future> where A: IntoLuaMulti, - R: FromLuaMulti + 'static; + R: FromLuaMulti; } impl TableExt for Table { @@ -956,18 +956,17 @@ impl TableExt for Table { } #[cfg(feature = "async")] - fn call_async(&self, args: A) -> LocalBoxFuture<'static, Result> + fn call_async(&self, args: A) -> impl Future> where A: IntoLuaMulti, - R: FromLuaMulti + 'static, + R: FromLuaMulti, { let lua = self.0.lua.lock(); - let args = match args.into_lua_multi(lua.lua()) { - Ok(args) => args, - Err(e) => return Box::pin(future::err(e)), - }; - let func = Function(self.0.clone()); - Box::pin(async move { func.call_async(args).await }) + let args = args.into_lua_multi(lua.lua()); + async move { + let func = Function(self.0.clone()); + func.call_async(args?).await + } } fn call_method(&self, name: &str, args: A) -> Result @@ -987,30 +986,25 @@ impl TableExt for Table { } #[cfg(feature = "async")] - fn call_async_method(&self, name: &str, args: A) -> LocalBoxFuture<'static, Result> + fn call_async_method(&self, name: &str, args: A) -> impl Future> where A: IntoLuaMulti, - R: FromLuaMulti + 'static, + R: FromLuaMulti, { self.call_async_function(name, (self, args)) } #[cfg(feature = "async")] - fn call_async_function(&self, name: &str, args: A) -> LocalBoxFuture<'static, Result> + fn call_async_function(&self, name: &str, args: A) -> impl Future> where A: IntoLuaMulti, - R: FromLuaMulti + 'static, + R: FromLuaMulti, { let lua = self.0.lua.lock(); - match self.get::<_, Function>(name) { - Ok(func) => { - let args = match args.into_lua_multi(lua.lua()) { - Ok(args) => args, - Err(e) => return Box::pin(future::err(e)), - }; - Box::pin(async move { func.call_async(args).await }) - } - Err(e) => Box::pin(future::err(e)), + let args = args.into_lua_multi(lua.lua()); + async move { + let func = self.get::<_, Function>(name)?; + func.call_async(args?).await } } } diff --git a/src/userdata/ext.rs b/src/userdata/ext.rs index 0863d9b6..fd07192c 100644 --- a/src/userdata/ext.rs +++ b/src/userdata/ext.rs @@ -4,7 +4,7 @@ use crate::userdata::{AnyUserData, MetaMethod}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; #[cfg(feature = "async")] -use futures_util::future::{self, LocalBoxFuture}; +use std::future::Future; /// An extension trait for [`AnyUserData`] that provides a variety of convenient functionality. pub trait AnyUserDataExt: Sealed { @@ -27,10 +27,10 @@ pub trait AnyUserDataExt: Sealed { /// The metamethod is called with the userdata as its first argument, followed by the passed arguments. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async(&self, args: A) -> LocalBoxFuture<'static, Result> + fn call_async(&self, args: A) -> impl Future> where A: IntoLuaMulti, - R: FromLuaMulti + 'static; + R: FromLuaMulti; /// Calls the userdata method, assuming it has `__index` metamethod /// and a function associated to `name`. @@ -47,10 +47,10 @@ pub trait AnyUserDataExt: Sealed { /// This might invoke the `__index` metamethod. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_method(&self, name: &str, args: A) -> LocalBoxFuture<'static, Result> + fn call_async_method(&self, name: &str, args: A) -> impl Future> where A: IntoLuaMulti, - R: FromLuaMulti + 'static; + R: FromLuaMulti; /// Gets the function associated to `key` from the table and executes it, /// passing `args` as function arguments. @@ -72,10 +72,10 @@ pub trait AnyUserDataExt: Sealed { /// This might invoke the `__index` metamethod. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_function(&self, name: &str, args: A) -> LocalBoxFuture<'static, Result> + fn call_async_function(&self, name: &str, args: A) -> impl Future> where A: IntoLuaMulti, - R: FromLuaMulti + 'static; + R: FromLuaMulti; } impl AnyUserDataExt for AnyUserData { @@ -110,28 +110,19 @@ impl AnyUserDataExt for AnyUserData { } #[cfg(feature = "async")] - fn call_async(&self, args: A) -> LocalBoxFuture<'static, Result> + fn call_async(&self, args: A) -> impl Future> where A: IntoLuaMulti, - R: FromLuaMulti + 'static, + R: FromLuaMulti, { - let metatable = match self.get_metatable() { - Ok(metatable) => metatable, - Err(err) => return Box::pin(future::err(err)), - }; - match metatable.get::(MetaMethod::Call) { - Ok(Value::Function(func)) => { - let lua = self.0.lua.lock(); - let args = match (self, args).into_lua_multi(lua.lua()) { - Ok(args) => args, - Err(e) => return Box::pin(future::err(e)), - }; - Box::pin(async move { func.call_async(args).await }) + let lua = self.0.lua.lock(); + let args = (self, args).into_lua_multi(lua.lua()); + async move { + let metatable = self.get_metatable()?; + match metatable.get::(MetaMethod::Call)? { + Value::Function(func) => func.call_async(args?).await, + _ => Err(Error::runtime("attempt to call a userdata value")), } - Ok(_) => Box::pin(future::err(Error::runtime( - "attempt to call a userdata value", - ))), - Err(err) => Box::pin(future::err(err)), } } @@ -144,10 +135,10 @@ impl AnyUserDataExt for AnyUserData { } #[cfg(feature = "async")] - fn call_async_method(&self, name: &str, args: A) -> LocalBoxFuture<'static, Result> + fn call_async_method(&self, name: &str, args: A) -> impl Future> where A: IntoLuaMulti, - R: FromLuaMulti + 'static, + R: FromLuaMulti, { self.call_async_function(name, (self, args)) } @@ -167,25 +158,21 @@ impl AnyUserDataExt for AnyUserData { } #[cfg(feature = "async")] - fn call_async_function(&self, name: &str, args: A) -> LocalBoxFuture<'static, Result> + fn call_async_function(&self, name: &str, args: A) -> impl Future> where A: IntoLuaMulti, - R: FromLuaMulti + 'static, + R: FromLuaMulti, { - match self.get(name) { - Ok(Value::Function(func)) => { - let lua = self.0.lua.lock(); - let args = match args.into_lua_multi(lua.lua()) { - Ok(args) => args, - Err(e) => return Box::pin(future::err(e)), - }; - Box::pin(async move { func.call_async(args).await }) - } - Ok(val) => { - let msg = format!("attempt to call a {} value", val.type_name()); - Box::pin(future::err(Error::runtime(msg))) + let lua = self.0.lua.lock(); + let args = args.into_lua_multi(lua.lua()); + async move { + match self.get::<_, Value>(name)? { + Value::Function(func) => func.call_async(args?).await, + val => { + let msg = format!("attempt to call a {} value", val.type_name()); + Err(Error::runtime(msg)) + } } - Err(err) => Box::pin(future::err(err)), } } } diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index d0887cb0..83ebabf9 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -16,7 +16,10 @@ use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; use super::cell::{UserDataBorrowMut, UserDataBorrowRef, UserDataVariant}; #[cfg(feature = "async")] -use {crate::types::AsyncCallback, futures_util::future, std::future::Future}; +use { + crate::types::AsyncCallback, + std::future::{self, Future}, +}; /// Handle to registry for userdata methods and metamethods. pub struct UserDataRegistry<'a, T: 'static> { @@ -139,7 +142,9 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { ($res:expr) => { match $res { Ok(res) => res, - Err(err) => return Box::pin(future::err(Error::bad_self_argument(&name, err))), + Err(err) => { + return Box::pin(future::ready(Err(Error::bad_self_argument(&name, err)))) + } } }; } @@ -158,14 +163,14 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { let ud = try_self_arg!(borrow_userdata_ref::(ref_thread, index)); let args = match args { Ok(args) => args, - Err(e) => return Box::pin(future::err(e)), + Err(e) => return Box::pin(future::ready(Err(e))), }; let fut = method(lua, ud.get_ref(), args); Box::pin(async move { fut.await?.push_into_stack_multi(&rawlua) }) } _ => { let err = Error::bad_self_argument(&name, Error::UserDataTypeMismatch); - Box::pin(future::err(err)) + Box::pin(future::ready(Err(err))) } } }) @@ -184,7 +189,9 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { ($res:expr) => { match $res { Ok(res) => res, - Err(err) => return Box::pin(future::err(Error::bad_self_argument(&name, err))), + Err(err) => { + return Box::pin(future::ready(Err(Error::bad_self_argument(&name, err)))) + } } }; } @@ -203,14 +210,14 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { let mut ud = try_self_arg!(borrow_userdata_mut::(ref_thread, index)); let args = match args { Ok(args) => args, - Err(e) => return Box::pin(future::err(e)), + Err(e) => return Box::pin(future::ready(Err(e))), }; let fut = method(lua, ud.get_mut(), args); Box::pin(async move { fut.await?.push_into_stack_multi(&rawlua) }) } _ => { let err = Error::bad_self_argument(&name, Error::UserDataTypeMismatch); - Box::pin(future::err(err)) + Box::pin(future::ready(Err(err))) } } }) @@ -259,7 +266,7 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { let lua = rawlua.lua(); let args = match A::from_lua_args(args, 1, Some(&name), lua) { Ok(args) => args, - Err(e) => return Box::pin(future::err(e)), + Err(e) => return Box::pin(future::ready(Err(e))), }; let fut = function(lua, args); Box::pin(async move { fut.await?.push_into_stack_multi(&rawlua) }) From 07a5538e50bcf731349f28d0f32f18884a973b3b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 2 Jul 2024 23:42:41 +0100 Subject: [PATCH 118/635] Don't run destructors if Lua is gone --- src/lua.rs | 6 ++++++ src/types.rs | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lua.rs b/src/lua.rs index 6c68f334..b3e41136 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -3377,10 +3377,16 @@ impl LuaInner { } impl WeakLua { + #[track_caller] #[inline(always)] pub(crate) fn lock(&self) -> LuaGuard { LuaGuard::new(self.0.upgrade().unwrap()) } + + #[inline(always)] + pub(crate) fn try_lock(&self) -> Option { + Some(LuaGuard::new(self.0.upgrade()?)) + } } impl PartialEq for WeakLua { diff --git a/src/types.rs b/src/types.rs index f15f79e8..2a28441e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -311,7 +311,9 @@ impl Clone for ValueRef { impl Drop for ValueRef { fn drop(&mut self) { if self.drop { - self.lua.lock().drop_ref(self); + if let Some(lua) = self.lua.try_lock() { + lua.drop_ref(self); + } } } } From 8b2d067196073dcfb674b8d337db84ebb44676ea Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 2 Jul 2024 23:45:45 +0100 Subject: [PATCH 119/635] Fix `Lua::set_vector_metatable` --- src/lua.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lua.rs b/src/lua.rs index b3e41136..8baefea9 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1739,8 +1739,9 @@ impl Lua { #[cfg(any(all(feature = "luau", feature = "unstable"), doc))] #[cfg_attr(docsrs, doc(cfg(all(feature = "luau", feature = "unstable"))))] pub fn set_vector_metatable(&self, metatable: Option
) { + let lua = self.lock(); unsafe { - let state = self.state(); + let state = lua.state(); let _sg = StackGuard::new(state); assert_stack(state, 2); @@ -1749,7 +1750,7 @@ impl Lua { #[cfg(feature = "luau-vector4")] ffi::lua_pushvector(state, 0., 0., 0., 0.); match metatable { - Some(metatable) => self.push_ref(&metatable.0), + Some(metatable) => lua.push_ref(&metatable.0), None => ffi::lua_pushnil(state), }; ffi::lua_setmetatable(state, -2); From cd6d86a5ce1f7041a65d97f504b19b2fb3365373 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 8 Jul 2024 00:36:29 +0100 Subject: [PATCH 120/635] Split single `lua` module to multiple submodules under `state` --- src/chunk.rs | 2 +- src/conversion.rs | 38 +- src/function.rs | 14 +- src/hook.rs | 34 +- src/lib.rs | 4 +- src/lua.rs | 3804 -------------------------------------- src/luau/mod.rs | 2 +- src/luau/package.rs | 2 +- src/multi.rs | 23 +- src/serde/mod.rs | 2 +- src/serde/ser.rs | 2 +- src/state.rs | 1936 +++++++++++++++++++ src/state/extra.rs | 236 +++ src/state/raw.rs | 1421 ++++++++++++++ src/state/util.rs | 187 ++ src/thread.rs | 13 +- src/types.rs | 13 +- src/userdata.rs | 2 +- src/userdata/cell.rs | 7 +- src/userdata/registry.rs | 2 +- src/util/mod.rs | 15 +- src/value.rs | 19 +- tests/tests.rs | 6 +- 23 files changed, 3888 insertions(+), 3896 deletions(-) delete mode 100644 src/lua.rs create mode 100644 src/state.rs create mode 100644 src/state/extra.rs create mode 100644 src/state/raw.rs create mode 100644 src/state/util.rs diff --git a/src/chunk.rs b/src/chunk.rs index a0a48e18..a7d7812e 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -7,7 +7,7 @@ use std::string::String as StdString; use crate::error::{Error, ErrorContext, Result}; use crate::function::Function; -use crate::lua::{Lua, WeakLua}; +use crate::state::{Lua, WeakLua}; use crate::table::Table; use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti}; diff --git a/src/conversion.rs b/src/conversion.rs index 1194c635..f82039f3 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -12,7 +12,7 @@ use num_traits::cast; use crate::error::{Error, Result}; use crate::function::Function; -use crate::lua::{Lua, LuaInner}; +use crate::state::{Lua, RawLua}; use crate::string::String; use crate::table::Table; use crate::thread::Thread; @@ -34,7 +34,7 @@ impl IntoLua for &Value { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { lua.push_value(self) } } @@ -60,7 +60,7 @@ impl IntoLua for &String { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { lua.push_ref(&self.0); Ok(()) } @@ -93,7 +93,7 @@ impl IntoLua for &Table { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { lua.push_ref(&self.0); Ok(()) } @@ -127,7 +127,7 @@ impl IntoLua for &Function { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { lua.push_ref(&self.0); Ok(()) } @@ -161,7 +161,7 @@ impl IntoLua for &Thread { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { lua.push_ref(&self.0); Ok(()) } @@ -195,7 +195,7 @@ impl IntoLua for &AnyUserData { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { lua.push_ref(&self.0); Ok(()) } @@ -250,7 +250,7 @@ impl IntoLua for RegistryKey { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { <&RegistryKey>::push_into_stack(&self, lua) } } @@ -261,7 +261,7 @@ impl IntoLua for &RegistryKey { lua.registry_value(self) } - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { if !lua.owns_registry_value(self) { return Err(Error::MismatchedRegistryKey); } @@ -290,7 +290,7 @@ impl IntoLua for bool { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { ffi::lua_pushboolean(lua.state(), self as c_int); Ok(()) } @@ -307,7 +307,7 @@ impl FromLua for bool { } #[inline] - unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { Ok(ffi::lua_toboolean(lua.state(), idx) != 0) } } @@ -363,7 +363,7 @@ impl IntoLua for StdString { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { push_bytes_into_stack(self, lua) } } @@ -384,7 +384,7 @@ impl FromLua for StdString { } #[inline] - unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { let state = lua.state(); if ffi::lua_type(state, idx) == ffi::LUA_TSTRING { let mut size = 0; @@ -410,7 +410,7 @@ impl IntoLua for &str { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { push_bytes_into_stack(self, lua) } } @@ -522,7 +522,7 @@ impl FromLua for BString { } } - unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { let state = lua.state(); match ffi::lua_type(state, idx) { ffi::LUA_TSTRING => { @@ -553,7 +553,7 @@ impl IntoLua for &BStr { } #[inline] -unsafe fn push_bytes_into_stack(this: T, lua: &LuaInner) -> Result<()> +unsafe fn push_bytes_into_stack(this: T, lua: &RawLua) -> Result<()> where T: IntoLua + AsRef<[u8]>, { @@ -584,7 +584,7 @@ macro_rules! lua_convert_int { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { match cast(self) { Some(i) => ffi::lua_pushinteger(lua.state(), i), None => ffi::lua_pushnumber(lua.state(), self as ffi::lua_Number), @@ -881,7 +881,7 @@ impl IntoLua for Option { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { match self { Some(val) => val.push_into_stack(lua)?, None => ffi::lua_pushnil(lua.state()), @@ -900,7 +900,7 @@ impl FromLua for Option { } #[inline] - unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { if ffi::lua_isnil(lua.state(), idx) != 0 { Ok(None) } else { diff --git a/src/function.rs b/src/function.rs index 30bcd448..c4ff2815 100644 --- a/src/function.rs +++ b/src/function.rs @@ -5,7 +5,7 @@ use std::ptr; use std::slice; use crate::error::{Error, Result}; -use crate::lua::Lua; +use crate::state::Lua; use crate::table::Table; use crate::types::{Callback, MaybeSend, ValueRef}; use crate::util::{ @@ -161,11 +161,13 @@ impl Function { R: FromLuaMulti, { let lua = self.0.lua.lock(); - let thread_res = lua.create_recycled_thread(self).map(|th| { - let mut th = th.into_async(args); - th.set_recyclable(true); - th - }); + let thread_res = unsafe { + lua.create_recycled_thread(self).map(|th| { + let mut th = th.into_async(args); + th.set_recyclable(true); + th + }) + }; async move { thread_res?.await } } diff --git a/src/hook.rs b/src/hook.rs index 72f8b88b..6179f090 100644 --- a/src/hook.rs +++ b/src/hook.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; use std::cell::UnsafeCell; -use std::mem::ManuallyDrop; +use std::ops::Deref; #[cfg(not(feature = "luau"))] use std::ops::{BitOr, BitOrAssign}; use std::os::raw::c_int; @@ -8,7 +8,7 @@ use std::os::raw::c_int; use ffi::lua_Debug; use parking_lot::ReentrantMutexGuard; -use crate::lua::{Lua, LuaInner}; +use crate::state::RawLua; use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str}; /// Contains information about currently executing Lua code. @@ -20,38 +20,46 @@ use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str}; /// /// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#lua_Debug /// [`Lua::set_hook`]: crate::Lua::set_hook -pub struct Debug<'lua> { - lua: ManuallyDrop>, +pub struct Debug<'a> { + lua: EitherLua<'a>, ar: ActivationRecord, #[cfg(feature = "luau")] level: c_int, } -impl<'lua> Drop for Debug<'lua> { - fn drop(&mut self) { - if let ActivationRecord::Owned(_) = self.ar { - unsafe { ManuallyDrop::drop(&mut self.lua) } +enum EitherLua<'a> { + Owned(ReentrantMutexGuard<'a, RawLua>), + Borrowed(&'a RawLua), +} + +impl Deref for EitherLua<'_> { + type Target = RawLua; + + fn deref(&self) -> &Self::Target { + match self { + EitherLua::Owned(guard) => &*guard, + EitherLua::Borrowed(lua) => lua, } } } -impl<'lua> Debug<'lua> { +impl<'a> Debug<'a> { // We assume the lock is held when this function is called. #[cfg(not(feature = "luau"))] - pub(crate) fn new(lua: &'lua Lua, ar: *mut lua_Debug) -> Self { + pub(crate) fn new(lua: &'a RawLua, ar: *mut lua_Debug) -> Self { Debug { - lua: unsafe { lua.guard_unchecked() }, + lua: EitherLua::Borrowed(lua), ar: ActivationRecord::Borrowed(ar), } } pub(crate) fn new_owned( - guard: ReentrantMutexGuard<'lua, LuaInner>, + guard: ReentrantMutexGuard<'a, RawLua>, _level: c_int, ar: lua_Debug, ) -> Self { Debug { - lua: ManuallyDrop::new(guard), + lua: EitherLua::Owned(guard), ar: ActivationRecord::Owned(UnsafeCell::new(ar)), #[cfg(feature = "luau")] level: _level, diff --git a/src/lib.rs b/src/lib.rs index 22cf8530..d56321c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,12 +84,12 @@ mod conversion; mod error; mod function; mod hook; -mod lua; #[cfg(feature = "luau")] mod luau; mod memory; mod multi; // mod scope; +mod state; mod stdlib; mod string; mod table; @@ -107,7 +107,7 @@ pub use crate::chunk::{AsChunk, Chunk, ChunkMode}; pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result}; pub use crate::function::{Function, FunctionInfo}; pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack}; -pub use crate::lua::{GCMode, Lua, LuaOptions}; +pub use crate::state::{GCMode, Lua, LuaOptions}; pub use crate::multi::Variadic; // pub use crate::scope::Scope; pub use crate::stdlib::StdLib; diff --git a/src/lua.rs b/src/lua.rs deleted file mode 100644 index 8baefea9..00000000 --- a/src/lua.rs +++ /dev/null @@ -1,3804 +0,0 @@ -use std::any::TypeId; -use std::cell::{Cell, RefCell, UnsafeCell}; -// use std::collections::VecDeque; -use std::ffi::{CStr, CString}; -use std::fmt; -use std::marker::PhantomData; -use std::mem::{self, ManuallyDrop, MaybeUninit}; -use std::ops::Deref; -use std::os::raw::{c_char, c_int, c_void}; -use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe, Location}; -use std::ptr; -use std::result::Result as StdResult; -use std::sync::{Arc, Weak}; - -use parking_lot::{Mutex, ReentrantMutex, ReentrantMutexGuard}; -use rustc_hash::FxHashMap; - -use crate::chunk::{AsChunk, Chunk, ChunkMode}; -use crate::error::{Error, Result}; -use crate::function::Function; -use crate::hook::Debug; -use crate::memory::{MemoryState, ALLOCATOR}; -// use crate::scope::Scope; -use crate::stdlib::StdLib; -use crate::string::String; -use crate::table::Table; -use crate::thread::Thread; -use crate::types::{ - AppData, AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Callback, CallbackUpvalue, - DestructedUserdata, Integer, LightUserData, MaybeSend, Number, RegistryKey, SubtypeId, - ValueRef, -}; -use crate::userdata::{ - AnyUserData, MetaMethod, UserData, UserDataProxy, UserDataRef, UserDataRegistry, - UserDataVariant, -}; -use crate::util::{ - self, assert_stack, check_stack, error_traceback, get_destructed_userdata_metatable, - get_gc_metatable, get_gc_userdata, get_main_state, get_userdata, init_error_registry, - init_gc_metatable, init_userdata_metatable, pop_error, push_gc_userdata, push_string, - push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name, StackGuard, WrappedFailure, -}; -use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value}; - -#[cfg(not(feature = "lua54"))] -use crate::util::push_userdata; -#[cfg(feature = "lua54")] -use crate::{types::WarnCallback, userdata::USER_VALUE_MAXSLOT, util::push_userdata_uv}; - -#[cfg(not(feature = "luau"))] -use crate::{hook::HookTriggers, types::HookCallback}; - -#[cfg(feature = "luau")] -use crate::types::InterruptCallback; -#[cfg(any(feature = "luau", doc))] -use crate::{ - chunk::Compiler, - types::{Vector, VmState}, -}; - -#[cfg(feature = "async")] -use { - crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue}, - futures_util::task::noop_waker_ref, - std::future::{self, Future}, - std::ptr::NonNull, - std::task::{Context, Poll, Waker}, -}; - -#[cfg(feature = "serialize")] -use serde::Serialize; - -/// Top level Lua struct which represents an instance of Lua VM. -#[derive(Clone)] -#[repr(transparent)] -pub struct Lua(Arc>); - -#[derive(Clone)] -#[repr(transparent)] -pub(crate) struct WeakLua(Weak>); - -pub(crate) struct LuaGuard(ArcReentrantMutexGuard); - -/// An inner Lua struct which holds a raw Lua state. -pub struct LuaInner { - // The state is dynamic and depends on context - state: Cell<*mut ffi::lua_State>, - main_state: *mut ffi::lua_State, - extra: Arc>, -} - -// Data associated with the Lua. -pub(crate) struct ExtraData { - // Same layout as `Lua` - inner: MaybeUninit>>, - weak: MaybeUninit>>, - - registered_userdata: FxHashMap, - registered_userdata_mt: FxHashMap<*const c_void, Option>, - last_checked_userdata_mt: (*const c_void, Option), - - // When Lua instance dropped, setting `None` would prevent collecting `RegistryKey`s - registry_unref_list: Arc>>>, - - // Container to store arbitrary data (extensions) - app_data: AppData, - - safe: bool, - libs: StdLib, - #[cfg(feature = "module")] - skip_memory_check: bool, - - // Auxiliary thread to store references - ref_thread: *mut ffi::lua_State, - ref_stack_size: c_int, - ref_stack_top: c_int, - ref_free: Vec, - - // Pool of `WrappedFailure` enums in the ref thread (as userdata) - wrapped_failure_pool: Vec, - // Pool of `MultiValue` containers - // multivalue_pool: Vec>, - // Pool of `Thread`s (coroutines) for async execution - #[cfg(feature = "async")] - thread_pool: Vec, - - // Address of `WrappedFailure` metatable - wrapped_failure_mt_ptr: *const c_void, - - // Waker for polling futures - #[cfg(feature = "async")] - waker: NonNull, - - #[cfg(not(feature = "luau"))] - hook_callback: Option, - #[cfg(not(feature = "luau"))] - hook_thread: *mut ffi::lua_State, - #[cfg(feature = "lua54")] - warn_callback: Option, - #[cfg(feature = "luau")] - interrupt_callback: Option, - - #[cfg(feature = "luau")] - sandboxed: bool, - #[cfg(feature = "luau")] - compiler: Option, - #[cfg(feature = "luau-jit")] - enable_jit: bool, -} - -/// Mode of the Lua garbage collector (GC). -/// -/// In Lua 5.4 GC can work in two modes: incremental and generational. -/// Previous Lua versions support only incremental GC. -/// -/// More information can be found in the Lua [documentation]. -/// -/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum GCMode { - Incremental, - /// Requires `feature = "lua54"` - #[cfg(feature = "lua54")] - #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] - Generational, -} - -/// Controls Lua interpreter behavior such as Rust panics handling. -#[derive(Clone, Debug)] -#[non_exhaustive] -pub struct LuaOptions { - /// Catch Rust panics when using [`pcall`]/[`xpcall`]. - /// - /// If disabled, wraps these functions and automatically resumes panic if found. - /// Also in Lua 5.1 adds ability to provide arguments to [`xpcall`] similar to Lua >= 5.2. - /// - /// If enabled, keeps [`pcall`]/[`xpcall`] unmodified. - /// Panics are still automatically resumed if returned to the Rust side. - /// - /// Default: **true** - /// - /// [`pcall`]: https://www.lua.org/manual/5.4/manual.html#pdf-pcall - /// [`xpcall`]: https://www.lua.org/manual/5.4/manual.html#pdf-xpcall - pub catch_rust_panics: bool, - - /// Max size of thread (coroutine) object pool used to execute asynchronous functions. - /// - /// It works on Lua 5.4 and Luau, where [`lua_resetthread`] function - /// is available and allows to reuse old coroutines after resetting their state. - /// - /// Default: **0** (disabled) - /// - /// [`lua_resetthread`]: https://www.lua.org/manual/5.4/manual.html#lua_resetthread - #[cfg(feature = "async")] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub thread_pool_size: usize, -} - -impl Default for LuaOptions { - fn default() -> Self { - LuaOptions::new() - } -} - -impl LuaOptions { - /// Returns a new instance of `LuaOptions` with default parameters. - pub const fn new() -> Self { - LuaOptions { - catch_rust_panics: true, - #[cfg(feature = "async")] - thread_pool_size: 0, - } - } - - /// Sets [`catch_rust_panics`] option. - /// - /// [`catch_rust_panics`]: #structfield.catch_rust_panics - #[must_use] - pub const fn catch_rust_panics(mut self, enabled: bool) -> Self { - self.catch_rust_panics = enabled; - self - } - - /// Sets [`thread_pool_size`] option. - /// - /// [`thread_pool_size`]: #structfield.thread_pool_size - #[cfg(feature = "async")] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - #[must_use] - pub const fn thread_pool_size(mut self, size: usize) -> Self { - self.thread_pool_size = size; - self - } -} - -#[cfg(feature = "async")] -pub(crate) static ASYNC_POLL_PENDING: u8 = 0; -pub(crate) static EXTRA_REGISTRY_KEY: u8 = 0; - -const WRAPPED_FAILURE_POOL_SIZE: usize = 64; -// const MULTIVALUE_POOL_SIZE: usize = 64; -const REF_STACK_RESERVE: c_int = 1; - -/// Requires `feature = "send"` -#[cfg(feature = "send")] -#[cfg_attr(docsrs, doc(cfg(feature = "send")))] -unsafe impl Send for Lua {} - -#[cfg(not(feature = "module"))] -impl Drop for Lua { - fn drop(&mut self) { - let _ = self.gc_collect(); - } -} - -#[cfg(not(feature = "module"))] -impl Drop for LuaInner { - fn drop(&mut self) { - unsafe { - let mem_state = MemoryState::get(self.main_state); - - ffi::lua_close(self.main_state); - - // Deallocate MemoryState - if !mem_state.is_null() { - drop(Box::from_raw(mem_state)); - } - } - } -} - -impl Drop for ExtraData { - fn drop(&mut self) { - #[cfg(feature = "module")] - unsafe { - self.inner.assume_init_drop(); - } - unsafe { self.weak.assume_init_drop() }; - *self.registry_unref_list.lock() = None; - } -} - -impl fmt::Debug for Lua { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Lua({:p})", self.lock().state()) - } -} - -impl Default for Lua { - #[inline] - fn default() -> Self { - Lua::new() - } -} - -impl Lua { - /// Creates a new Lua state and loads the **safe** subset of the standard libraries. - /// - /// # Safety - /// The created Lua state would have _some_ safety guarantees and would not allow to load unsafe - /// standard libraries or C modules. - /// - /// See [`StdLib`] documentation for a list of unsafe modules that cannot be loaded. - /// - /// [`StdLib`]: crate::StdLib - pub fn new() -> Lua { - mlua_expect!( - Self::new_with(StdLib::ALL_SAFE, LuaOptions::default()), - "Cannot create new safe Lua state" - ) - } - - /// Creates a new Lua state and loads all the standard libraries. - /// - /// # Safety - /// The created Lua state would not have safety guarantees and would allow to load C modules. - pub unsafe fn unsafe_new() -> Lua { - Self::unsafe_new_with(StdLib::ALL, LuaOptions::default()) - } - - /// Creates a new Lua state and loads the specified safe subset of the standard libraries. - /// - /// Use the [`StdLib`] flags to specify the libraries you want to load. - /// - /// # Safety - /// The created Lua state would have _some_ safety guarantees and would not allow to load unsafe - /// standard libraries or C modules. - /// - /// See [`StdLib`] documentation for a list of unsafe modules that cannot be loaded. - /// - /// [`StdLib`]: crate::StdLib - pub fn new_with(libs: StdLib, options: LuaOptions) -> Result { - #[cfg(not(feature = "luau"))] - if libs.contains(StdLib::DEBUG) { - return Err(Error::SafetyError( - "The unsafe `debug` module can't be loaded using safe `new_with`".to_string(), - )); - } - #[cfg(feature = "luajit")] - if libs.contains(StdLib::FFI) { - return Err(Error::SafetyError( - "The unsafe `ffi` module can't be loaded using safe `new_with`".to_string(), - )); - } - - let lua = unsafe { Self::inner_new(libs, options) }; - - if libs.contains(StdLib::PACKAGE) { - mlua_expect!(lua.disable_c_modules(), "Error during disabling C modules"); - } - unsafe { - let rawlua = lua.lock(); - (*rawlua.extra.get()).safe = true; - } - - Ok(lua) - } - - /// Creates a new Lua state and loads the specified subset of the standard libraries. - /// - /// Use the [`StdLib`] flags to specify the libraries you want to load. - /// - /// # Safety - /// The created Lua state will not have safety guarantees and allow to load C modules. - /// - /// [`StdLib`]: crate::StdLib - pub unsafe fn unsafe_new_with(libs: StdLib, options: LuaOptions) -> Lua { - // Workaround to avoid stripping a few unused Lua symbols that could be imported - // by C modules in unsafe mode - let mut _symbols: Vec<*const extern "C-unwind" fn()> = - vec![ffi::lua_isuserdata as _, ffi::lua_tocfunction as _]; - - #[cfg(not(feature = "luau"))] - _symbols.extend_from_slice(&[ - ffi::lua_atpanic as _, - ffi::luaL_loadstring as _, - ffi::luaL_openlibs as _, - ]); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - { - _symbols.push(ffi::lua_getglobal as _); - _symbols.push(ffi::lua_setglobal as _); - _symbols.push(ffi::luaL_setfuncs as _); - } - - Self::inner_new(libs, options) - } - - /// Creates a new Lua state with required `libs` and `options` - unsafe fn inner_new(libs: StdLib, options: LuaOptions) -> Lua { - let mem_state: *mut MemoryState = Box::into_raw(Box::default()); - let mut state = ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void); - // If state is null then switch to Lua internal allocator - if state.is_null() { - drop(Box::from_raw(mem_state)); - state = ffi::luaL_newstate(); - } - assert!(!state.is_null(), "Failed to instantiate Lua VM"); - - ffi::luaL_requiref(state, cstr!("_G"), ffi::luaopen_base, 1); - ffi::lua_pop(state, 1); - - // Init Luau code generator (jit) - #[cfg(feature = "luau-jit")] - if ffi::luau_codegen_supported() != 0 { - ffi::luau_codegen_create(state); - } - - let lua = Lua::init_from_ptr(state); - let extra = lua.lock().extra.get(); - - mlua_expect!( - load_from_std_lib(state, libs), - "Error during loading standard libraries" - ); - (*extra).libs |= libs; - - if !options.catch_rust_panics { - mlua_expect!( - (|| -> Result<()> { - let _sg = StackGuard::new(state); - - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); - #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] - ffi::lua_pushvalue(state, ffi::LUA_GLOBALSINDEX); - - ffi::lua_pushcfunction(state, safe_pcall); - rawset_field(state, -2, "pcall")?; - - ffi::lua_pushcfunction(state, safe_xpcall); - rawset_field(state, -2, "xpcall")?; - - Ok(()) - })(), - "Error during applying option `catch_rust_panics`" - ) - } - - #[cfg(feature = "async")] - if options.thread_pool_size > 0 { - (*extra).thread_pool.reserve_exact(options.thread_pool_size); - } - - #[cfg(feature = "luau")] - mlua_expect!(lua.configure_luau(), "Error configuring Luau"); - - lua - } - - /// Constructs a new Lua instance from an existing raw state. - /// - /// Once called, a returned Lua state is cached in the registry and can be retrieved - /// by calling this function again. - #[allow(clippy::missing_safety_doc, clippy::arc_with_non_send_sync)] - pub unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> Lua { - assert!(!state.is_null(), "Lua state is NULL"); - if let Some(lua) = Lua::try_from_ptr(state) { - return lua; - } - - let main_state = get_main_state(state).unwrap_or(state); - let main_state_top = ffi::lua_gettop(main_state); - - mlua_expect!( - (|state| { - init_error_registry(state)?; - - // Create the internal metatables and place them in the registry - // to prevent them from being garbage collected. - - init_gc_metatable::>>(state, None)?; - init_gc_metatable::(state, None)?; - init_gc_metatable::(state, None)?; - #[cfg(feature = "async")] - { - init_gc_metatable::(state, None)?; - init_gc_metatable::(state, None)?; - init_gc_metatable::(state, None)?; - init_gc_metatable::>(state, None)?; - } - - // Init serde metatables - #[cfg(feature = "serialize")] - crate::serde::init_metatables(state)?; - - Ok::<_, Error>(()) - })(main_state), - "Error during Lua construction", - ); - - // Create ref stack thread and place it in the registry to prevent it from being garbage - // collected. - let ref_thread = mlua_expect!( - protect_lua!(main_state, 0, 0, |state| { - let thread = ffi::lua_newthread(state); - ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX); - thread - }), - "Error while creating ref thread", - ); - - let wrapped_failure_mt_ptr = { - get_gc_metatable::(main_state); - let ptr = ffi::lua_topointer(main_state, -1); - ffi::lua_pop(main_state, 1); - ptr - }; - - // Store `error_traceback` function on the ref stack - #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] - { - ffi::lua_pushcfunction(ref_thread, error_traceback); - assert_eq!(ffi::lua_gettop(ref_thread), ExtraData::ERROR_TRACEBACK_IDX); - } - - // Create ExtraData - let extra = Arc::new(UnsafeCell::new(ExtraData { - inner: MaybeUninit::uninit(), - weak: MaybeUninit::uninit(), - registered_userdata: FxHashMap::default(), - registered_userdata_mt: FxHashMap::default(), - last_checked_userdata_mt: (ptr::null(), None), - registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))), - app_data: AppData::default(), - safe: false, - libs: StdLib::NONE, - #[cfg(feature = "module")] - skip_memory_check: false, - ref_thread, - // We need some reserved stack space to move values in and out of the ref stack. - ref_stack_size: ffi::LUA_MINSTACK - REF_STACK_RESERVE, - ref_stack_top: ffi::lua_gettop(ref_thread), - ref_free: Vec::new(), - wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_SIZE), - // multivalue_pool: Vec::with_capacity(MULTIVALUE_POOL_SIZE), - #[cfg(feature = "async")] - thread_pool: Vec::new(), - wrapped_failure_mt_ptr, - #[cfg(feature = "async")] - waker: NonNull::from(noop_waker_ref()), - #[cfg(not(feature = "luau"))] - hook_callback: None, - #[cfg(not(feature = "luau"))] - hook_thread: ptr::null_mut(), - #[cfg(feature = "lua54")] - warn_callback: None, - #[cfg(feature = "luau")] - interrupt_callback: None, - #[cfg(feature = "luau")] - sandboxed: false, - #[cfg(feature = "luau")] - compiler: None, - #[cfg(feature = "luau-jit")] - enable_jit: true, - })); - - // Store it in the registry - mlua_expect!( - set_extra_data(main_state, &extra), - "Error while storing extra data" - ); - - // Register `DestructedUserdata` type - get_destructed_userdata_metatable(main_state); - let destructed_mt_ptr = ffi::lua_topointer(main_state, -1); - let destructed_ud_typeid = TypeId::of::(); - (*extra.get()) - .registered_userdata_mt - .insert(destructed_mt_ptr, Some(destructed_ud_typeid)); - ffi::lua_pop(main_state, 1); - - mlua_debug_assert!( - ffi::lua_gettop(main_state) == main_state_top, - "stack leak during creation" - ); - assert_stack(main_state, ffi::LUA_MINSTACK); - - let inner = Arc::new(ReentrantMutex::new(LuaInner { - state: Cell::new(state), - main_state, - extra: Arc::clone(&extra), - })); - - (*extra.get()).inner.write(Arc::clone(&inner)); - #[cfg(not(feature = "module"))] - Arc::decrement_strong_count(Arc::as_ptr(&inner)); - (*extra.get()).weak.write(Arc::downgrade(&inner)); - - Lua(inner) - } - - /// Loads the specified subset of the standard libraries into an existing Lua state. - /// - /// Use the [`StdLib`] flags to specify the libraries you want to load. - /// - /// [`StdLib`]: crate::StdLib - pub fn load_from_std_lib(&self, libs: StdLib) -> Result<()> { - let lua = self.lock(); - let is_safe = unsafe { (*lua.extra.get()).safe }; - - #[cfg(not(feature = "luau"))] - if is_safe && libs.contains(StdLib::DEBUG) { - return Err(Error::SafetyError( - "the unsafe `debug` module can't be loaded in safe mode".to_string(), - )); - } - #[cfg(feature = "luajit")] - if is_safe && libs.contains(StdLib::FFI) { - return Err(Error::SafetyError( - "the unsafe `ffi` module can't be loaded in safe mode".to_string(), - )); - } - - let res = unsafe { load_from_std_lib(lua.main_state, libs) }; - - // If `package` library loaded into a safe lua state then disable C modules - let curr_libs = unsafe { (*lua.extra.get()).libs }; - if is_safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) { - mlua_expect!(self.disable_c_modules(), "Error during disabling C modules"); - } - unsafe { (*lua.extra.get()).libs |= libs }; - - res - } - - /// Loads module `modname` into an existing Lua state using the specified entrypoint - /// function. - /// - /// Internally calls the Lua function `func` with the string `modname` as an argument, - /// sets the call result to `package.loaded[modname]` and returns copy of the result. - /// - /// If `package.loaded[modname]` value is not nil, returns copy of the value without - /// calling the function. - /// - /// If the function does not return a non-nil value then this method assigns true to - /// `package.loaded[modname]`. - /// - /// Behavior is similar to Lua's [`require`] function. - /// - /// [`require`]: https://www.lua.org/manual/5.4/manual.html#pdf-require - pub fn load_from_function(&self, modname: &str, func: Function) -> Result - where - T: FromLua, - { - let lua = self.lock(); - let state = lua.state(); - let loaded = unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 2)?; - protect_lua!(state, 0, 1, fn(state) { - ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); - })?; - Table(lua.pop_ref()) - }; - - let modname = self.create_string(modname)?; - let value = match loaded.raw_get(&modname)? { - Value::Nil => { - let result = match func.call(&modname)? { - Value::Nil => Value::Boolean(true), - res => res, - }; - loaded.raw_set(modname, &result)?; - result - } - res => res, - }; - T::from_lua(value, self) - } - - /// Unloads module `modname`. - /// - /// Removes module from the [`package.loaded`] table which allows to load it again. - /// It does not support unloading binary Lua modules since they are internally cached and can be - /// unloaded only by closing Lua state. - /// - /// [`package.loaded`]: https://www.lua.org/manual/5.4/manual.html#pdf-package.loaded - pub fn unload(&self, modname: &str) -> Result<()> { - let lua = self.lock(); - let state = lua.state(); - let loaded = unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 2)?; - protect_lua!(state, 0, 1, fn(state) { - ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); - })?; - Table(lua.pop_ref()) - }; - - let modname = self.create_string(modname)?; - loaded.raw_remove(modname)?; - Ok(()) - } - - /// Consumes and leaks `Lua` object, returning a static reference `&'static Lua`. - /// - /// This function is useful when the `Lua` object is supposed to live for the remainder - /// of the program's life. - /// - /// Dropping the returned reference will cause a memory leak. If this is not acceptable, - /// the reference should first be wrapped with the [`Lua::from_static`] function producing a `Lua`. - /// This `Lua` object can then be dropped which will properly release the allocated memory. - /// - /// [`Lua::from_static`]: #method.from_static - #[doc(hidden)] - pub fn into_static(self) -> &'static Self { - Box::leak(Box::new(self)) - } - - /// Constructs a `Lua` from a static reference to it. - /// - /// # Safety - /// This function is unsafe because improper use may lead to memory problems or undefined behavior. - #[doc(hidden)] - pub unsafe fn from_static(lua: &'static Lua) -> Self { - *Box::from_raw(lua as *const Lua as *mut Lua) - } - - // Executes module entrypoint function, which returns only one Value. - // The returned value then pushed onto the stack. - #[doc(hidden)] - #[cfg(not(tarpaulin_include))] - pub unsafe fn entrypoint(self, state: *mut ffi::lua_State, func: F) -> c_int - where - A: FromLuaMulti, - R: IntoLua, - F: Fn(&Lua, A) -> Result + MaybeSend + 'static, - { - let extra = self.lock().extra.get(); - // `self` is no longer needed and must be dropped at this point to avoid possible memory leak - // in case of possible longjmp (lua_error) below - drop(self); - - callback_error_ext(state, extra, move |nargs| { - let lua = (*extra).lua(); - let rawlua = lua.lock(); - let _guard = StateGuard::new(&rawlua, state); - let args = A::from_stack_args(nargs, 1, None, &rawlua)?; - func(lua, args)?.push_into_stack(&rawlua)?; - Ok(1) - }) - } - - // A simple module entrypoint without arguments - #[doc(hidden)] - #[cfg(not(tarpaulin_include))] - pub unsafe fn entrypoint1(self, state: *mut ffi::lua_State, func: F) -> c_int - where - R: IntoLua, - F: Fn(&Lua) -> Result + MaybeSend + 'static, - { - self.entrypoint(state, move |lua, _: ()| func(lua)) - } - - /// Skips memory checks for some operations. - #[doc(hidden)] - #[cfg(feature = "module")] - pub fn skip_memory_check(&self, skip: bool) { - unsafe { (*self.extra.get()).skip_memory_check = skip }; - } - - /// Enables (or disables) sandbox mode on this Lua instance. - /// - /// This method, in particular: - /// - Set all libraries to read-only - /// - Set all builtin metatables to read-only - /// - Set globals to read-only (and activates safeenv) - /// - Setup local environment table that performs writes locally and proxies reads - /// to the global environment. - /// - /// # Examples - /// - /// ``` - /// # use mlua::{Lua, Result}; - /// # fn main() -> Result<()> { - /// let lua = Lua::new(); - /// - /// lua.sandbox(true)?; - /// lua.load("var = 123").exec()?; - /// assert_eq!(lua.globals().get::<_, u32>("var")?, 123); - /// - /// // Restore the global environment (clear changes made in sandbox) - /// lua.sandbox(false)?; - /// assert_eq!(lua.globals().get::<_, Option>("var")?, None); - /// # Ok(()) - /// # } - /// ``` - /// - /// Requires `feature = "luau"` - #[cfg(any(feature = "luau", docsrs))] - #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - pub fn sandbox(&self, enabled: bool) -> Result<()> { - let lua = self.lock(); - unsafe { - if (*lua.extra.get()).sandboxed != enabled { - let state = lua.main_state; - check_stack(state, 3)?; - protect_lua!(state, 0, 0, |state| { - if enabled { - ffi::luaL_sandbox(state, 1); - ffi::luaL_sandboxthread(state); - } else { - // Restore original `LUA_GLOBALSINDEX` - ffi::lua_xpush(lua.ref_thread(), state, ffi::LUA_GLOBALSINDEX); - ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX); - ffi::luaL_sandbox(state, 0); - } - })?; - (*lua.extra.get()).sandboxed = enabled; - } - Ok(()) - } - } - - /// Sets a 'hook' function that will periodically be called as Lua code executes. - /// - /// When exactly the hook function is called depends on the contents of the `triggers` - /// parameter, see [`HookTriggers`] for more details. - /// - /// The provided hook function can error, and this error will be propagated through the Lua code - /// that was executing at the time the hook was triggered. This can be used to implement a - /// limited form of execution limits by setting [`HookTriggers.every_nth_instruction`] and - /// erroring once an instruction limit has been reached. - /// - /// This method sets a hook function for the current thread of this Lua instance. - /// If you want to set a hook function for another thread (coroutine), use [`Thread::set_hook()`] instead. - /// - /// Please note you cannot have more than one hook function set at a time for this Lua instance. - /// - /// # Example - /// - /// Shows each line number of code being executed by the Lua interpreter. - /// - /// ``` - /// # use mlua::{Lua, HookTriggers, Result}; - /// # fn main() -> Result<()> { - /// let lua = Lua::new(); - /// lua.set_hook(HookTriggers::EVERY_LINE, |_lua, debug| { - /// println!("line {}", debug.curr_line()); - /// Ok(()) - /// }); - /// - /// lua.load(r#" - /// local x = 2 + 3 - /// local y = x * 63 - /// local z = string.len(x..", "..y) - /// "#).exec() - /// # } - /// ``` - /// - /// [`HookTriggers`]: crate::HookTriggers - /// [`HookTriggers.every_nth_instruction`]: crate::HookTriggers::every_nth_instruction - #[cfg(not(feature = "luau"))] - #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] - pub fn set_hook(&self, triggers: HookTriggers, callback: F) - where - F: Fn(&Lua, Debug) -> Result<()> + MaybeSend + 'static, - { - let lua = self.lock(); - unsafe { lua.set_thread_hook(lua.state(), triggers, callback) }; - } - - /// Removes any hook previously set by [`Lua::set_hook()`] or [`Thread::set_hook()`]. - /// - /// This function has no effect if a hook was not previously set. - #[cfg(not(feature = "luau"))] - #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] - pub fn remove_hook(&self) { - let lua = self.lock(); - unsafe { - let state = lua.state(); - ffi::lua_sethook(state, None, 0, 0); - match get_main_state(lua.main_state) { - Some(main_state) if !ptr::eq(state, main_state) => { - // If main_state is different from state, remove hook from it too - ffi::lua_sethook(main_state, None, 0, 0); - } - _ => {} - }; - (*lua.extra.get()).hook_callback = None; - (*lua.extra.get()).hook_thread = ptr::null_mut(); - } - } - - /// Sets an 'interrupt' function that will periodically be called by Luau VM. - /// - /// Any Luau code is guaranteed to call this handler "eventually" - /// (in practice this can happen at any function call or at any loop iteration). - /// - /// The provided interrupt function can error, and this error will be propagated through - /// the Luau code that was executing at the time the interrupt was triggered. - /// Also this can be used to implement continuous execution limits by instructing Luau VM to yield - /// by returning [`VmState::Yield`]. - /// - /// This is similar to [`Lua::set_hook`] but in more simplified form. - /// - /// # Example - /// - /// Periodically yield Luau VM to suspend execution. - /// - /// ``` - /// # use std::sync::{Arc, atomic::{AtomicU64, Ordering}}; - /// # use mlua::{Lua, Result, ThreadStatus, VmState}; - /// # fn main() -> Result<()> { - /// let lua = Lua::new(); - /// let count = Arc::new(AtomicU64::new(0)); - /// lua.set_interrupt(move |_| { - /// if count.fetch_add(1, Ordering::Relaxed) % 2 == 0 { - /// return Ok(VmState::Yield); - /// } - /// Ok(VmState::Continue) - /// }); - /// - /// let co = lua.create_thread( - /// lua.load(r#" - /// local b = 0 - /// for _, x in ipairs({1, 2, 3}) do b += x end - /// "#) - /// .into_function()?, - /// )?; - /// while co.status() == ThreadStatus::Resumable { - /// co.resume(())?; - /// } - /// # Ok(()) - /// # } - /// ``` - #[cfg(any(feature = "luau", docsrs))] - #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - pub fn set_interrupt(&self, callback: F) - where - F: Fn(&Lua) -> Result + MaybeSend + 'static, - { - unsafe extern "C-unwind" fn interrupt_proc(state: *mut ffi::lua_State, gc: c_int) { - if gc >= 0 { - // We don't support GC interrupts since they cannot survive Lua exceptions - return; - } - let extra = extra_data(state); - let result = callback_error_ext(state, extra, move |_| { - let interrupt_cb = (*extra).interrupt_callback.clone(); - let interrupt_cb = - mlua_expect!(interrupt_cb, "no interrupt callback set in interrupt_proc"); - if Arc::strong_count(&interrupt_cb) > 2 { - return Ok(VmState::Continue); // Don't allow recursion - } - let lua = (*extra).lua(); - let rawlua = lua.lock(); - let _guard = StateGuard::new(&rawlua, state); - interrupt_cb(lua) - }); - match result { - VmState::Continue => {} - VmState::Yield => { - ffi::lua_yield(state, 0); - } - } - } - - let lua = self.lock(); - unsafe { - (*lua.extra.get()).interrupt_callback = Some(Arc::new(callback)); - (*ffi::lua_callbacks(lua.main_state)).interrupt = Some(interrupt_proc); - } - } - - /// Removes any 'interrupt' previously set by `set_interrupt`. - /// - /// This function has no effect if an 'interrupt' was not previously set. - #[cfg(any(feature = "luau", docsrs))] - #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - pub fn remove_interrupt(&self) { - let lua = self.lock(); - unsafe { - (*lua.extra.get()).interrupt_callback = None; - (*ffi::lua_callbacks(lua.main_state)).interrupt = None; - } - } - - /// Sets the warning function to be used by Lua to emit warnings. - /// - /// Requires `feature = "lua54"` - #[cfg(feature = "lua54")] - #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] - pub fn set_warning_function(&self, callback: F) - where - F: Fn(&Lua, &str, bool) -> Result<()> + MaybeSend + 'static, - { - unsafe extern "C-unwind" fn warn_proc(ud: *mut c_void, msg: *const c_char, tocont: c_int) { - let extra = ud as *mut ExtraData; - let lua = (*extra).lua(); - let rawlua = lua.lock(); - callback_error_ext(rawlua.state(), extra, |_| { - let cb = mlua_expect!( - (*extra).warn_callback.as_ref(), - "no warning callback set in warn_proc" - ); - let msg = std::string::String::from_utf8_lossy(CStr::from_ptr(msg).to_bytes()); - cb(lua, &msg, tocont != 0) - }); - } - - let lua = self.lock(); - let state = lua.main_state; - unsafe { - (*lua.extra.get()).warn_callback = Some(Box::new(callback)); - ffi::lua_setwarnf(state, Some(warn_proc), lua.extra.get() as *mut c_void); - } - } - - /// Removes warning function previously set by `set_warning_function`. - /// - /// This function has no effect if a warning function was not previously set. - /// - /// Requires `feature = "lua54"` - #[cfg(feature = "lua54")] - #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] - pub fn remove_warning_function(&self) { - let lua = self.lock(); - unsafe { - (*lua.extra.get()).warn_callback = None; - ffi::lua_setwarnf(lua.main_state, None, ptr::null_mut()); - } - } - - /// Emits a warning with the given message. - /// - /// A message in a call with `incomplete` set to `true` should be continued in - /// another call to this function. - /// - /// Requires `feature = "lua54"` - #[cfg(feature = "lua54")] - #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] - pub fn warning(&self, msg: impl AsRef, incomplete: bool) { - let msg = msg.as_ref(); - let mut bytes = vec![0; msg.len() + 1]; - bytes[..msg.len()].copy_from_slice(msg.as_bytes()); - let real_len = bytes.iter().position(|&c| c == 0).unwrap(); - bytes.truncate(real_len); - let lua = self.lock(); - unsafe { - ffi::lua_warning( - lua.state(), - bytes.as_ptr() as *const c_char, - incomplete as c_int, - ); - } - } - - /// Gets information about the interpreter runtime stack. - /// - /// This function returns [`Debug`] structure that can be used to get information about the function - /// executing at a given level. Level `0` is the current running function, whereas level `n+1` is the - /// function that has called level `n` (except for tail calls, which do not count in the stack). - /// - /// [`Debug`]: crate::hook::Debug - pub fn inspect_stack(&self, level: usize) -> Option { - let lua = self.lock(); - unsafe { - let mut ar: ffi::lua_Debug = mem::zeroed(); - let level = level as c_int; - #[cfg(not(feature = "luau"))] - if ffi::lua_getstack(lua.state(), level, &mut ar) == 0 { - return None; - } - #[cfg(feature = "luau")] - if ffi::lua_getinfo(lua.state(), level, cstr!(""), &mut ar) == 0 { - return None; - } - Some(Debug::new_owned(lua, level, ar)) - } - } - - /// Returns the amount of memory (in bytes) currently used inside this Lua state. - pub fn used_memory(&self) -> usize { - let lua = self.lock(); - unsafe { - match MemoryState::get(lua.main_state) { - mem_state if !mem_state.is_null() => (*mem_state).used_memory(), - _ => { - // Get data from the Lua GC - let used_kbytes = ffi::lua_gc(lua.main_state, ffi::LUA_GCCOUNT, 0); - let used_kbytes_rem = ffi::lua_gc(lua.main_state, ffi::LUA_GCCOUNTB, 0); - (used_kbytes as usize) * 1024 + (used_kbytes_rem as usize) - } - } - } - } - - /// Sets a memory limit (in bytes) on this Lua state. - /// - /// Once an allocation occurs that would pass this memory limit, - /// a `Error::MemoryError` is generated instead. - /// Returns previous limit (zero means no limit). - /// - /// Does not work in module mode where Lua state is managed externally. - pub fn set_memory_limit(&self, limit: usize) -> Result { - let lua = self.lock(); - unsafe { - match MemoryState::get(lua.main_state) { - mem_state if !mem_state.is_null() => Ok((*mem_state).set_memory_limit(limit)), - _ => Err(Error::MemoryLimitNotAvailable), - } - } - } - - /// Returns true if the garbage collector is currently running automatically. - /// - /// Requires `feature = "lua54/lua53/lua52/luau"` - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luau" - ))] - pub fn gc_is_running(&self) -> bool { - let lua = self.lock(); - unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCISRUNNING, 0) != 0 } - } - - /// Stop the Lua GC from running - pub fn gc_stop(&self) { - let lua = self.lock(); - unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCSTOP, 0) }; - } - - /// Restarts the Lua GC if it is not running - pub fn gc_restart(&self) { - let lua = self.lock(); - unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCRESTART, 0) }; - } - - /// Perform a full garbage-collection cycle. - /// - /// It may be necessary to call this function twice to collect all currently unreachable - /// objects. Once to finish the current gc cycle, and once to start and finish the next cycle. - pub fn gc_collect(&self) -> Result<()> { - let lua = self.lock(); - unsafe { - check_stack(lua.main_state, 2)?; - protect_lua!(lua.main_state, 0, 0, fn(state) ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0)) - } - } - - /// Steps the garbage collector one indivisible step. - /// - /// Returns true if this has finished a collection cycle. - pub fn gc_step(&self) -> Result { - self.gc_step_kbytes(0) - } - - /// Steps the garbage collector as though memory had been allocated. - /// - /// if `kbytes` is 0, then this is the same as calling `gc_step`. Returns true if this step has - /// finished a collection cycle. - pub fn gc_step_kbytes(&self, kbytes: c_int) -> Result { - let lua = self.lock(); - unsafe { - check_stack(lua.main_state, 3)?; - protect_lua!(lua.main_state, 0, 0, |state| { - ffi::lua_gc(state, ffi::LUA_GCSTEP, kbytes) != 0 - }) - } - } - - /// Sets the 'pause' value of the collector. - /// - /// Returns the previous value of 'pause'. More information can be found in the Lua - /// [documentation]. - /// - /// For Luau this parameter sets GC goal - /// - /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 - pub fn gc_set_pause(&self, pause: c_int) -> c_int { - let lua = self.lock(); - unsafe { - #[cfg(not(feature = "luau"))] - return ffi::lua_gc(lua.main_state, ffi::LUA_GCSETPAUSE, pause); - #[cfg(feature = "luau")] - return ffi::lua_gc(lua.main_state, ffi::LUA_GCSETGOAL, pause); - } - } - - /// Sets the 'step multiplier' value of the collector. - /// - /// Returns the previous value of the 'step multiplier'. More information can be found in the - /// Lua [documentation]. - /// - /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 - pub fn gc_set_step_multiplier(&self, step_multiplier: c_int) -> c_int { - let lua = self.lock(); - unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCSETSTEPMUL, step_multiplier) } - } - - /// Changes the collector to incremental mode with the given parameters. - /// - /// Returns the previous mode (always `GCMode::Incremental` in Lua < 5.4). - /// More information can be found in the Lua [documentation]. - /// - /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5.1 - pub fn gc_inc(&self, pause: c_int, step_multiplier: c_int, step_size: c_int) -> GCMode { - let lua = self.lock(); - let state = lua.main_state; - - #[cfg(any( - feature = "lua53", - feature = "lua52", - feature = "lua51", - feature = "luajit", - feature = "luau" - ))] - unsafe { - if pause > 0 { - #[cfg(not(feature = "luau"))] - ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause); - #[cfg(feature = "luau")] - ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause); - } - - if step_multiplier > 0 { - ffi::lua_gc(state, ffi::LUA_GCSETSTEPMUL, step_multiplier); - } - - #[cfg(feature = "luau")] - if step_size > 0 { - ffi::lua_gc(state, ffi::LUA_GCSETSTEPSIZE, step_size); - } - #[cfg(not(feature = "luau"))] - let _ = step_size; // Ignored - - GCMode::Incremental - } - - #[cfg(feature = "lua54")] - let prev_mode = - unsafe { ffi::lua_gc(state, ffi::LUA_GCINC, pause, step_multiplier, step_size) }; - #[cfg(feature = "lua54")] - match prev_mode { - ffi::LUA_GCINC => GCMode::Incremental, - ffi::LUA_GCGEN => GCMode::Generational, - _ => unreachable!(), - } - } - - /// Changes the collector to generational mode with the given parameters. - /// - /// Returns the previous mode. More information about the generational GC - /// can be found in the Lua 5.4 [documentation][lua_doc]. - /// - /// Requires `feature = "lua54"` - /// - /// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#2.5.2 - #[cfg(feature = "lua54")] - #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] - pub fn gc_gen(&self, minor_multiplier: c_int, major_multiplier: c_int) -> GCMode { - let lua = self.lock(); - let state = lua.main_state; - let prev_mode = - unsafe { ffi::lua_gc(state, ffi::LUA_GCGEN, minor_multiplier, major_multiplier) }; - match prev_mode { - ffi::LUA_GCGEN => GCMode::Generational, - ffi::LUA_GCINC => GCMode::Incremental, - _ => unreachable!(), - } - } - - /// Sets a default Luau compiler (with custom options). - /// - /// This compiler will be used by default to load all Lua chunks - /// including via `require` function. - /// - /// See [`Compiler`] for details and possible options. - /// - /// Requires `feature = "luau"` - #[cfg(any(feature = "luau", doc))] - #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - pub fn set_compiler(&self, compiler: Compiler) { - let lua = self.lock(); - unsafe { (*lua.extra.get()).compiler = Some(compiler) }; - } - - /// Toggles JIT compilation mode for new chunks of code. - /// - /// By default JIT is enabled. Changing this option does not have any effect on - /// already loaded functions. - #[cfg(any(feature = "luau-jit", doc))] - #[cfg_attr(docsrs, doc(cfg(feature = "luau-jit")))] - pub fn enable_jit(&self, enable: bool) { - unsafe { (*self.extra.get()).enable_jit = enable }; - } - - /// Sets Luau feature flag (global setting). - /// - /// See https://github.com/luau-lang/luau/blob/master/CONTRIBUTING.md#feature-flags for details. - #[cfg(feature = "luau")] - #[doc(hidden)] - #[allow(clippy::result_unit_err)] - pub fn set_fflag(name: &str, enabled: bool) -> StdResult<(), ()> { - if let Ok(name) = CString::new(name) { - if unsafe { ffi::luau_setfflag(name.as_ptr(), enabled as c_int) != 0 } { - return Ok(()); - } - } - Err(()) - } - - /// Returns Lua source code as a `Chunk` builder type. - /// - /// In order to actually compile or run the resulting code, you must call [`Chunk::exec`] or - /// similar on the returned builder. Code is not even parsed until one of these methods is - /// called. - /// - /// [`Chunk::exec`]: crate::Chunk::exec - #[track_caller] - pub fn load<'a>(&self, chunk: impl AsChunk<'a>) -> Chunk<'a> { - let caller = Location::caller(); - Chunk { - lua: self.weak(), - name: chunk.name().unwrap_or_else(|| caller.to_string()), - env: chunk.environment(self), - mode: chunk.mode(), - source: chunk.source(), - #[cfg(feature = "luau")] - compiler: unsafe { (*self.lock().extra.get()).compiler.clone() }, - } - } - - /// Create and return an interned Lua string. Lua strings can be arbitrary [u8] data including - /// embedded nulls, so in addition to `&str` and `&String`, you can also pass plain `&[u8]` - /// here. - pub fn create_string(&self, s: impl AsRef<[u8]>) -> Result { - let lua = self.lock(); - let state = lua.state(); - unsafe { - if lua.unlikely_memory_error() { - push_string(lua.ref_thread(), s.as_ref(), false)?; - return Ok(String(lua.pop_ref_thread())); - } - - let _sg = StackGuard::new(state); - check_stack(state, 3)?; - push_string(state, s.as_ref(), true)?; - Ok(String(lua.pop_ref())) - } - } - - /// Create and return a Luau [buffer] object from a byte slice of data. - /// - /// Requires `feature = "luau"` - /// - /// [buffer]: https://luau-lang.org/library#buffer-library - #[cfg(feature = "luau")] - pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result { - let lua = self.lock(); - let state = lua.state(); - unsafe { - if lua.unlikely_memory_error() { - crate::util::push_buffer(lua.ref_thread(), buf.as_ref(), false)?; - return Ok(AnyUserData(lua.pop_ref_thread(), SubtypeId::Buffer)); - } - - let _sg = StackGuard::new(state); - check_stack(state, 4)?; - crate::util::push_buffer(state, buf.as_ref(), true)?; - Ok(AnyUserData(lua.pop_ref(), SubtypeId::Buffer)) - } - } - - /// Creates and returns a new empty table. - pub fn create_table(&self) -> Result
{ - self.create_table_with_capacity(0, 0) - } - - /// Creates and returns a new empty table, with the specified capacity. - /// `narr` is a hint for how many elements the table will have as a sequence; - /// `nrec` is a hint for how many other elements the table will have. - /// Lua may use these hints to preallocate memory for the new table. - pub fn create_table_with_capacity(&self, narr: usize, nrec: usize) -> Result
{ - let lua = self.lock(); - let state = lua.state(); - unsafe { - if lua.unlikely_memory_error() { - push_table(lua.ref_thread(), narr, nrec, false)?; - return Ok(Table(lua.pop_ref_thread())); - } - - let _sg = StackGuard::new(state); - check_stack(state, 3)?; - push_table(state, narr, nrec, true)?; - Ok(Table(lua.pop_ref())) - } - } - - /// Creates a table and fills it with values from an iterator. - pub fn create_table_from(&self, iter: I) -> Result
- where - K: IntoLua, - V: IntoLua, - I: IntoIterator, - { - let lua = self.lock(); - let state = lua.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 6)?; - - let iter = iter.into_iter(); - let lower_bound = iter.size_hint().0; - let protect = !lua.unlikely_memory_error(); - push_table(state, 0, lower_bound, protect)?; - for (k, v) in iter { - lua.push(k)?; - lua.push(v)?; - if protect { - protect_lua!(state, 3, 1, fn(state) ffi::lua_rawset(state, -3))?; - } else { - ffi::lua_rawset(state, -3); - } - } - - Ok(Table(lua.pop_ref())) - } - } - - /// Creates a table from an iterator of values, using `1..` as the keys. - pub fn create_sequence_from(&self, iter: I) -> Result
- where - T: IntoLua, - I: IntoIterator, - { - let lua = self.lock(); - let state = lua.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 5)?; - - let iter = iter.into_iter(); - let lower_bound = iter.size_hint().0; - let protect = !lua.unlikely_memory_error(); - push_table(state, lower_bound, 0, protect)?; - for (i, v) in iter.enumerate() { - lua.push(v)?; - if protect { - protect_lua!(state, 2, 1, |state| { - ffi::lua_rawseti(state, -2, (i + 1) as Integer); - })?; - } else { - ffi::lua_rawseti(state, -2, (i + 1) as Integer); - } - } - - Ok(Table(lua.pop_ref())) - } - } - - /// Wraps a Rust function or closure, creating a callable Lua function handle to it. - /// - /// The function's return value is always a `Result`: If the function returns `Err`, the error - /// is raised as a Lua error, which can be caught using `(x)pcall` or bubble up to the Rust code - /// that invoked the Lua code. This allows using the `?` operator to propagate errors through - /// intermediate Lua code. - /// - /// If the function returns `Ok`, the contained value will be converted to one or more Lua - /// values. For details on Rust-to-Lua conversions, refer to the [`IntoLua`] and [`IntoLuaMulti`] - /// traits. - /// - /// # Examples - /// - /// Create a function which prints its argument: - /// - /// ``` - /// # use mlua::{Lua, Result}; - /// # fn main() -> Result<()> { - /// # let lua = Lua::new(); - /// let greet = lua.create_function(|_, name: String| { - /// println!("Hello, {}!", name); - /// Ok(()) - /// }); - /// # let _ = greet; // used - /// # Ok(()) - /// # } - /// ``` - /// - /// Use tuples to accept multiple arguments: - /// - /// ``` - /// # use mlua::{Lua, Result}; - /// # fn main() -> Result<()> { - /// # let lua = Lua::new(); - /// let print_person = lua.create_function(|_, (name, age): (String, u8)| { - /// println!("{} is {} years old!", name, age); - /// Ok(()) - /// }); - /// # let _ = print_person; // used - /// # Ok(()) - /// # } - /// ``` - /// - /// [`IntoLua`]: crate::IntoLua - /// [`IntoLuaMulti`]: crate::IntoLuaMulti - pub fn create_function(&self, func: F) -> Result - where - A: FromLuaMulti, - R: IntoLuaMulti, - F: Fn(&Lua, A) -> Result + MaybeSend + 'static, - { - let lua = self.lock(); - lua.create_callback(Box::new(move |lua, nargs| unsafe { - let args = A::from_stack_args(nargs, 1, None, lua)?; - func(lua.lua(), args)?.push_into_stack_multi(lua) - })) - } - - /// Wraps a Rust mutable closure, creating a callable Lua function handle to it. - /// - /// This is a version of [`create_function`] that accepts a FnMut argument. Refer to - /// [`create_function`] for more information about the implementation. - /// - /// [`create_function`]: #method.create_function - pub fn create_function_mut(&self, func: F) -> Result - where - A: FromLuaMulti, - R: IntoLuaMulti, - F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, - { - let func = RefCell::new(func); - self.create_function(move |lua, args| { - (*func - .try_borrow_mut() - .map_err(|_| Error::RecursiveMutCallback)?)(lua, args) - }) - } - - /// Wraps a C function, creating a callable Lua function handle to it. - /// - /// # Safety - /// This function is unsafe because provides a way to execute unsafe C function. - pub unsafe fn create_c_function(&self, func: ffi::lua_CFunction) -> Result { - let lua = self.lock(); - let state = lua.state(); - check_stack(state, 1)?; - ffi::lua_pushcfunction(state, func); - Ok(Function(lua.pop_ref())) - } - - /// Wraps a Rust async function or closure, creating a callable Lua function handle to it. - /// - /// While executing the function Rust will poll Future and if the result is not ready, call - /// `yield()` passing internal representation of a `Poll::Pending` value. - /// - /// The function must be called inside Lua coroutine ([`Thread`]) to be able to suspend its execution. - /// An executor should be used to poll [`AsyncThread`] and mlua will take a provided Waker - /// in that case. Otherwise noop waker will be used if try to call the function outside of Rust - /// executors. - /// - /// The family of `call_async()` functions takes care about creating [`Thread`]. - /// - /// Requires `feature = "async"` - /// - /// # Examples - /// - /// Non blocking sleep: - /// - /// ``` - /// use std::time::Duration; - /// use mlua::{Lua, Result}; - /// - /// async fn sleep(_lua: &Lua, n: u64) -> Result<&'static str> { - /// tokio::time::sleep(Duration::from_millis(n)).await; - /// Ok("done") - /// } - /// - /// #[tokio::main] - /// async fn main() -> Result<()> { - /// let lua = Lua::new(); - /// lua.globals().set("sleep", lua.create_async_function(sleep)?)?; - /// let res: String = lua.load("return sleep(...)").call_async(100).await?; // Sleep 100ms - /// assert_eq!(res, "done"); - /// Ok(()) - /// } - /// ``` - /// - /// [`Thread`]: crate::Thread - /// [`AsyncThread`]: crate::AsyncThread - #[cfg(feature = "async")] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub fn create_async_function<'lua, 'a, F, A, FR, R>(&'lua self, func: F) -> Result - where - 'lua: 'a, - F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, - A: FromLuaMulti, - FR: Future> + 'a, - R: IntoLuaMulti, - { - let lua = self.lock(); - lua.create_async_callback(Box::new(move |rawlua, args| unsafe { - // let rawlua = mem::transmute::<&LuaInner, &LuaInner>(rawlua); - let lua = rawlua.lua(); - let args = match A::from_lua_args(args, 1, None, lua) { - Ok(args) => args, - Err(e) => return Box::pin(future::ready(Err(e))), - }; - let fut = func(lua, args); - Box::pin(async move { fut.await?.push_into_stack_multi(rawlua) }) - })) - } - - /// Wraps a Lua function into a new thread (or coroutine). - /// - /// Equivalent to `coroutine.create`. - pub fn create_thread(&self, func: Function) -> Result { - self.lock().create_thread_inner(&func) - } - - /// Creates a Lua userdata object from a custom userdata type. - /// - /// All userdata instances of the same type `T` shares the same metatable. - #[inline] - pub fn create_userdata(&self, data: T) -> Result - where - T: UserData + MaybeSend + 'static, - { - let lua = self.lock(); - unsafe { lua.make_userdata(UserDataVariant::new(data)) } - } - - /// Creates a Lua userdata object from a custom serializable userdata type. - /// - /// Requires `feature = "serialize"` - #[cfg(feature = "serialize")] - #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] - #[inline] - pub fn create_ser_userdata(&self, data: T) -> Result - where - T: UserData + Serialize + MaybeSend + 'static, - { - let lua = self.lock(); - unsafe { lua.make_userdata(UserDataVariant::new_ser(data)) } - } - - /// Creates a Lua userdata object from a custom Rust type. - /// - /// You can register the type using [`Lua::register_userdata_type()`] to add fields or methods - /// _before_ calling this method. - /// Otherwise, the userdata object will have an empty metatable. - /// - /// All userdata instances of the same type `T` shares the same metatable. - #[inline] - pub fn create_any_userdata(&self, data: T) -> Result - where - T: MaybeSend + 'static, - { - let lua = self.lock(); - unsafe { lua.make_any_userdata(UserDataVariant::new(data)) } - } - - /// Creates a Lua userdata object from a custom serializable Rust type. - /// - /// See [`Lua::create_any_userdata()`] for more details. - /// - /// Requires `feature = "serialize"` - #[cfg(feature = "serialize")] - #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] - #[inline] - pub fn create_ser_any_userdata(&self, data: T) -> Result - where - T: Serialize + MaybeSend + 'static, - { - let lua = self.lock(); - unsafe { lua.make_any_userdata(UserDataVariant::new_ser(data)) } - } - - /// Registers a custom Rust type in Lua to use in userdata objects. - /// - /// This methods provides a way to add fields or methods to userdata objects of a type `T`. - pub fn register_userdata_type( - &self, - f: impl FnOnce(&mut UserDataRegistry), - ) -> Result<()> { - let mut registry = UserDataRegistry::new(); - f(&mut registry); - - let lua = self.lock(); - unsafe { - // Deregister the type if it already registered - let type_id = TypeId::of::(); - if let Some(&table_id) = (*lua.extra.get()).registered_userdata.get(&type_id) { - ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, table_id); - } - - // Register the type - lua.register_userdata_metatable(registry)?; - } - Ok(()) - } - - /// Create a Lua userdata "proxy" object from a custom userdata type. - /// - /// Proxy object is an empty userdata object that has `T` metatable attached. - /// The main purpose of this object is to provide access to static fields and functions - /// without creating an instance of type `T`. - /// - /// You can get or set uservalues on this object but you cannot borrow any Rust type. - /// - /// # Examples - /// - /// ``` - /// # use mlua::{Lua, Result, UserData, UserDataFields, UserDataMethods}; - /// # fn main() -> Result<()> { - /// # let lua = Lua::new(); - /// struct MyUserData(i32); - /// - /// impl UserData for MyUserData { - /// fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { - /// fields.add_field_method_get("val", |_, this| Ok(this.0)); - /// } - /// - /// fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { - /// methods.add_function("new", |_, value: i32| Ok(MyUserData(value))); - /// } - /// } - /// - /// lua.globals().set("MyUserData", lua.create_proxy::()?)?; - /// - /// lua.load("assert(MyUserData.new(321).val == 321)").exec()?; - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub fn create_proxy(&self) -> Result - where - T: UserData + 'static, - { - let lua = self.lock(); - unsafe { lua.make_userdata(UserDataVariant::new(UserDataProxy::(PhantomData))) } - } - - /// Sets the metatable for a Luau builtin vector type. - #[cfg(any(all(feature = "luau", feature = "unstable"), doc))] - #[cfg_attr(docsrs, doc(cfg(all(feature = "luau", feature = "unstable"))))] - pub fn set_vector_metatable(&self, metatable: Option
) { - let lua = self.lock(); - unsafe { - let state = lua.state(); - let _sg = StackGuard::new(state); - assert_stack(state, 2); - - #[cfg(not(feature = "luau-vector4"))] - ffi::lua_pushvector(state, 0., 0., 0.); - #[cfg(feature = "luau-vector4")] - ffi::lua_pushvector(state, 0., 0., 0., 0.); - match metatable { - Some(metatable) => lua.push_ref(&metatable.0), - None => ffi::lua_pushnil(state), - }; - ffi::lua_setmetatable(state, -2); - } - } - - /// Returns a handle to the global environment. - pub fn globals(&self) -> Table { - let lua = self.lock(); - let state = lua.state(); - unsafe { - let _sg = StackGuard::new(state); - assert_stack(state, 1); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); - #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] - ffi::lua_pushvalue(state, ffi::LUA_GLOBALSINDEX); - Table(lua.pop_ref()) - } - } - - /// Returns a handle to the active `Thread`. For calls to `Lua` this will be the main Lua thread, - /// for parameters given to a callback, this will be whatever Lua thread called the callback. - pub fn current_thread(&self) -> Thread { - let lua = self.lock(); - let state = lua.state(); - unsafe { - let _sg = StackGuard::new(state); - assert_stack(state, 1); - ffi::lua_pushthread(state); - Thread::new(&lua, lua.pop_ref()) - } - } - - /// Calls the given function with a `Scope` parameter, giving the function the ability to create - /// userdata and callbacks from rust types that are !Send or non-'static. - /// - /// The lifetime of any function or userdata created through `Scope` lasts only until the - /// completion of this method call, on completion all such created values are automatically - /// dropped and Lua references to them are invalidated. If a script accesses a value created - /// through `Scope` outside of this method, a Lua error will result. Since we can ensure the - /// lifetime of values created through `Scope`, and we know that `Lua` cannot be sent to another - /// thread while `Scope` is live, it is safe to allow !Send datatypes and whose lifetimes only - /// outlive the scope lifetime. - /// - /// Inside the scope callback, all handles created through Scope will share the same unique 'lua - /// lifetime of the parent `Lua`. This allows scoped and non-scoped values to be mixed in - /// API calls, which is very useful (e.g. passing a scoped userdata to a non-scoped function). - /// However, this also enables handles to scoped values to be trivially leaked from the given - /// callback. This is not dangerous, though! After the callback returns, all scoped values are - /// invalidated, which means that though references may exist, the Rust types backing them have - /// dropped. `Function` types will error when called, and `AnyUserData` will be typeless. It - /// would be impossible to prevent handles to scoped values from escaping anyway, since you - /// would always be able to smuggle them through Lua state. - // pub fn scope<'lua, 'scope, R>( - // &'lua self, - // f: impl FnOnce(&Scope<'lua, 'scope>) -> Result, - // ) -> Result - // where - // 'lua: 'scope, - // { - // f(&Scope::new(self)) - // } - - /// Attempts to coerce a Lua value into a String in a manner consistent with Lua's internal - /// behavior. - /// - /// To succeed, the value must be a string (in which case this is a no-op), an integer, or a - /// number. - pub fn coerce_string(&self, v: Value) -> Result> { - Ok(match v { - Value::String(s) => Some(s), - v => unsafe { - let lua = self.lock(); - let state = lua.state(); - let _sg = StackGuard::new(state); - check_stack(state, 4)?; - - lua.push_value(&v)?; - let res = if lua.unlikely_memory_error() { - ffi::lua_tolstring(state, -1, ptr::null_mut()) - } else { - protect_lua!(state, 1, 1, |state| { - ffi::lua_tolstring(state, -1, ptr::null_mut()) - })? - }; - if !res.is_null() { - Some(String(lua.pop_ref())) - } else { - None - } - }, - }) - } - - /// Attempts to coerce a Lua value into an integer in a manner consistent with Lua's internal - /// behavior. - /// - /// To succeed, the value must be an integer, a floating point number that has an exact - /// representation as an integer, or a string that can be converted to an integer. Refer to the - /// Lua manual for details. - pub fn coerce_integer(&self, v: Value) -> Result> { - Ok(match v { - Value::Integer(i) => Some(i), - v => unsafe { - let lua = self.lock(); - let state = lua.state(); - let _sg = StackGuard::new(state); - check_stack(state, 2)?; - - lua.push_value(&v)?; - let mut isint = 0; - let i = ffi::lua_tointegerx(state, -1, &mut isint); - if isint == 0 { - None - } else { - Some(i) - } - }, - }) - } - - /// Attempts to coerce a Lua value into a Number in a manner consistent with Lua's internal - /// behavior. - /// - /// To succeed, the value must be a number or a string that can be converted to a number. Refer - /// to the Lua manual for details. - pub fn coerce_number(&self, v: Value) -> Result> { - Ok(match v { - Value::Number(n) => Some(n), - v => unsafe { - let lua = self.lock(); - let state = lua.state(); - let _sg = StackGuard::new(state); - check_stack(state, 2)?; - - lua.push_value(&v)?; - let mut isnum = 0; - let n = ffi::lua_tonumberx(state, -1, &mut isnum); - if isnum == 0 { - None - } else { - Some(n) - } - }, - }) - } - - /// Converts a value that implements `IntoLua` into a `Value` instance. - pub fn pack(&self, t: T) -> Result { - t.into_lua(self) - } - - /// Converts a `Value` instance into a value that implements `FromLua`. - pub fn unpack(&self, value: Value) -> Result { - T::from_lua(value, self) - } - - /// Converts a value that implements `IntoLuaMulti` into a `MultiValue` instance. - pub fn pack_multi(&self, t: T) -> Result { - t.into_lua_multi(self) - } - - /// Converts a `MultiValue` instance into a value that implements `FromLuaMulti`. - pub fn unpack_multi(&self, value: MultiValue) -> Result { - T::from_lua_multi(value, self) - } - - /// Set a value in the Lua registry based on a string name. - /// - /// This value will be available to rust from all `Lua` instances which share the same main - /// state. - pub fn set_named_registry_value(&self, name: &str, t: T) -> Result<()> - where - T: IntoLua, - { - let lua = self.lock(); - let state = lua.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 5)?; - - lua.push(t)?; - rawset_field(state, ffi::LUA_REGISTRYINDEX, name) - } - } - - /// Get a value from the Lua registry based on a string name. - /// - /// Any Lua instance which shares the underlying main state may call this method to - /// get a value previously set by [`set_named_registry_value`]. - /// - /// [`set_named_registry_value`]: #method.set_named_registry_value - pub fn named_registry_value(&self, name: &str) -> Result - where - T: FromLua, - { - let lua = self.lock(); - let state = lua.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 3)?; - - let protect = !lua.unlikely_memory_error(); - push_string(state, name.as_bytes(), protect)?; - ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX); - - T::from_stack(-1, &lua) - } - } - - /// Removes a named value in the Lua registry. - /// - /// Equivalent to calling [`set_named_registry_value`] with a value of Nil. - /// - /// [`set_named_registry_value`]: #method.set_named_registry_value - pub fn unset_named_registry_value(&self, name: &str) -> Result<()> { - self.set_named_registry_value(name, Nil) - } - - /// Place a value in the Lua registry with an auto-generated key. - /// - /// This value will be available to Rust from all `Lua` instances which share the same main - /// state. - /// - /// Be warned, garbage collection of values held inside the registry is not automatic, see - /// [`RegistryKey`] for more details. - /// However, dropped [`RegistryKey`]s automatically reused to store new values. - /// - /// [`RegistryKey`]: crate::RegistryKey - pub fn create_registry_value(&self, t: T) -> Result { - let lua = self.lock(); - let state = lua.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 4)?; - - lua.push(t)?; - - let unref_list = (*lua.extra.get()).registry_unref_list.clone(); - - // Check if the value is nil (no need to store it in the registry) - if ffi::lua_isnil(state, -1) != 0 { - return Ok(RegistryKey::new(ffi::LUA_REFNIL, unref_list)); - } - - // Try to reuse previously allocated slot - let free_registry_id = unref_list.lock().as_mut().and_then(|x| x.pop()); - if let Some(registry_id) = free_registry_id { - // It must be safe to replace the value without triggering memory error - ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); - return Ok(RegistryKey::new(registry_id, unref_list)); - } - - // Allocate a new RegistryKey slot - let registry_id = if lua.unlikely_memory_error() { - ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) - } else { - protect_lua!(state, 1, 0, |state| { - ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) - })? - }; - Ok(RegistryKey::new(registry_id, unref_list)) - } - } - - /// Get a value from the Lua registry by its `RegistryKey` - /// - /// Any Lua instance which shares the underlying main state may call this method to get a value - /// previously placed by [`create_registry_value`]. - /// - /// [`create_registry_value`]: #method.create_registry_value - pub fn registry_value(&self, key: &RegistryKey) -> Result { - let lua = self.lock(); - if !lua.owns_registry_value(key) { - return Err(Error::MismatchedRegistryKey); - } - - let state = lua.state(); - match key.id() { - ffi::LUA_REFNIL => T::from_lua(Value::Nil, self), - registry_id => unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 1)?; - - ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); - T::from_stack(-1, &lua) - }, - } - } - - /// Removes a value from the Lua registry. - /// - /// You may call this function to manually remove a value placed in the registry with - /// [`create_registry_value`]. In addition to manual `RegistryKey` removal, you can also call - /// [`expire_registry_values`] to automatically remove values from the registry whose - /// `RegistryKey`s have been dropped. - /// - /// [`create_registry_value`]: #method.create_registry_value - /// [`expire_registry_values`]: #method.expire_registry_values - pub fn remove_registry_value(&self, key: RegistryKey) -> Result<()> { - let lua = self.lock(); - if !lua.owns_registry_value(&key) { - return Err(Error::MismatchedRegistryKey); - } - - unsafe { - ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, key.take()); - } - Ok(()) - } - - /// Replaces a value in the Lua registry by its `RegistryKey`. - /// - /// See [`create_registry_value`] for more details. - /// - /// [`create_registry_value`]: #method.create_registry_value - pub fn replace_registry_value(&self, key: &RegistryKey, t: T) -> Result<()> { - let lua = self.lock(); - if !lua.owns_registry_value(key) { - return Err(Error::MismatchedRegistryKey); - } - - let t = t.into_lua(self)?; - - let state = lua.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 2)?; - - match (t, key.id()) { - (Value::Nil, ffi::LUA_REFNIL) => { - // Do nothing, no need to replace nil with nil - } - (Value::Nil, registry_id) => { - // Remove the value - ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, registry_id); - key.set_id(ffi::LUA_REFNIL); - } - (value, ffi::LUA_REFNIL) => { - // Allocate a new `RegistryKey` - let new_key = self.create_registry_value(value)?; - key.set_id(new_key.take()); - } - (value, registry_id) => { - // It must be safe to replace the value without triggering memory error - lua.push_value(&value)?; - ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); - } - } - } - Ok(()) - } - - /// Returns true if the given `RegistryKey` was created by a `Lua` which shares the underlying - /// main state with this `Lua` instance. - /// - /// Other than this, methods that accept a `RegistryKey` will return - /// `Error::MismatchedRegistryKey` if passed a `RegistryKey` that was not created with a - /// matching `Lua` state. - pub fn owns_registry_value(&self, key: &RegistryKey) -> bool { - self.lock().owns_registry_value(key) - } - - /// Remove any registry values whose `RegistryKey`s have all been dropped. - /// - /// Unlike normal handle values, `RegistryKey`s do not automatically remove themselves on Drop, - /// but you can call this method to remove any unreachable registry values not manually removed - /// by `Lua::remove_registry_value`. - pub fn expire_registry_values(&self) { - let lua = self.lock(); - let state = lua.state(); - unsafe { - let mut unref_list = (*lua.extra.get()).registry_unref_list.lock(); - let unref_list = mem::replace(&mut *unref_list, Some(Vec::new())); - for id in mlua_expect!(unref_list, "unref list not set") { - ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, id); - } - } - } - - /// 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. - /// - /// # Panics - /// - /// Panics if the app data container is currently borrowed. - /// - /// # 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(()) - /// } - /// ``` - #[track_caller] - pub fn set_app_data(&self, data: T) -> Option { - let lua = self.lock(); - let extra = unsafe { &*lua.extra.get() }; - extra.app_data.insert(data) - } - - /// Tries to set or replace an application data object of type `T`. - /// - /// Returns: - /// - `Ok(Some(old_data))` if the data object of type `T` was successfully replaced. - /// - `Ok(None)` if the data object of type `T` was successfully inserted. - /// - `Err(data)` if the data object of type `T` was not inserted because the container is currently borrowed. - /// - /// See [`Lua::set_app_data()`] for examples. - pub fn try_set_app_data(&self, data: T) -> StdResult, T> { - let lua = self.lock(); - let extra = unsafe { &*lua.extra.get() }; - extra.app_data.try_insert(data) - } - - /// Gets a reference to an application data object stored by [`Lua::set_app_data()`] of type `T`. - /// - /// # Panics - /// - /// Panics if the data object of type `T` is currently mutably borrowed. Multiple immutable reads - /// can be taken out at the same time. - #[track_caller] - pub fn app_data_ref(&self) -> Option> { - let guard = self.lock_arc(); - let extra = unsafe { &*guard.extra.get() }; - extra.app_data.borrow(Some(guard)) - } - - /// Gets a mutable reference to an application data object stored by [`Lua::set_app_data()`] of type `T`. - /// - /// # Panics - /// - /// Panics if the data object of type `T` is currently borrowed. - #[track_caller] - pub fn app_data_mut(&self) -> Option> { - let guard = self.lock_arc(); - let extra = unsafe { &*guard.extra.get() }; - extra.app_data.borrow_mut(Some(guard)) - } - - /// Removes an application data of type `T`. - /// - /// # Panics - /// - /// Panics if the app data container is currently borrowed. - #[track_caller] - pub fn remove_app_data(&self) -> Option { - let lua = self.lock(); - let extra = unsafe { &*lua.extra.get() }; - extra.app_data.remove() - } - - // FIXME - // /// Pushes a value that implements `IntoLua` onto the Lua stack. - // /// - // /// Uses 2 stack spaces, does not call checkstack. - // #[doc(hidden)] - // #[inline(always)] - // pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> { - // // value.push_into_stack(self) - // } - - /// Returns internal `Poll::Pending` constant used for executing async callbacks. - #[cfg(feature = "async")] - #[doc(hidden)] - #[inline] - pub fn poll_pending() -> LightUserData { - LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut c_void) - } - - // Luau version located in `luau/mod.rs` - #[cfg(not(feature = "luau"))] - fn disable_c_modules(&self) -> Result<()> { - let package: Table = self.globals().get("package")?; - - package.set( - "loadlib", - self.create_function(|_, ()| -> Result<()> { - Err(Error::SafetyError( - "package.loadlib is disabled in safe mode".to_string(), - )) - })?, - )?; - - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - let searchers: Table = package.get("searchers")?; - #[cfg(any(feature = "lua51", feature = "luajit"))] - let searchers: Table = package.get("loaders")?; - - let loader = self.create_function(|_, ()| Ok("\n\tcan't load C modules in safe mode"))?; - - // The third and fourth searchers looks for a loader as a C library - searchers.raw_set(3, loader)?; - searchers.raw_remove(4)?; - - Ok(()) - } - - pub(crate) unsafe fn try_from_ptr(state: *mut ffi::lua_State) -> Option { - let extra = extra_data(state); - if extra.is_null() { - return None; - } - Some(Lua(Arc::clone((*extra).inner.assume_init_ref()))) - } - - #[inline(always)] - pub(crate) fn lock(&self) -> ReentrantMutexGuard { - self.0.lock() - } - - #[inline(always)] - pub(crate) fn lock_arc(&self) -> LuaGuard { - LuaGuard(self.0.lock_arc()) - } - - #[inline(always)] - pub(crate) unsafe fn guard_unchecked(&self) -> ManuallyDrop> { - ManuallyDrop::new(self.0.make_guard_unchecked()) - } - - #[inline(always)] - pub(crate) fn weak(&self) -> WeakLua { - WeakLua(Arc::downgrade(&self.0)) - } -} - -impl LuaInner { - #[inline(always)] - pub(crate) fn lua(&self) -> &Lua { - unsafe { (*self.extra.get()).lua() } - } - - #[inline(always)] - pub(crate) fn weak(&self) -> &WeakLua { - unsafe { (*self.extra.get()).weak() } - } - - #[inline(always)] - pub(crate) fn state(&self) -> *mut ffi::lua_State { - self.state.get() - } - - #[cfg(feature = "luau")] - #[inline(always)] - pub(crate) fn main_state(&self) -> *mut ffi::lua_State { - self.main_state - } - - #[inline(always)] - pub(crate) fn ref_thread(&self) -> *mut ffi::lua_State { - unsafe { (*self.extra.get()).ref_thread } - } - - /// See [`Lua::try_set_app_data`] - pub(crate) fn try_set_app_data( - &self, - data: T, - ) -> StdResult, T> { - let extra = unsafe { &*self.extra.get() }; - extra.app_data.try_insert(data) - } - - /// See [`Lua::app_data_ref`] - #[track_caller] - pub(crate) fn app_data_ref(&self) -> Option> { - let extra = unsafe { &*self.extra.get() }; - extra.app_data.borrow(None) - } - - /// See [`Lua::app_data_mut`] - #[track_caller] - pub(crate) fn app_data_mut(&self) -> Option> { - let extra = unsafe { &*self.extra.get() }; - extra.app_data.borrow_mut(None) - } - - /// See [`Lua::create_registry_value`] - pub(crate) fn owns_registry_value(&self, key: &RegistryKey) -> bool { - let registry_unref_list = unsafe { &(*self.extra.get()).registry_unref_list }; - Arc::ptr_eq(&key.unref_list, registry_unref_list) - } - - pub(crate) fn load_chunk( - &self, - name: Option<&CStr>, - env: Option
, - mode: Option, - source: &[u8], - ) -> Result { - let state = self.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 1)?; - - let mode_str = match mode { - Some(ChunkMode::Binary) => cstr!("b"), - Some(ChunkMode::Text) => cstr!("t"), - None => cstr!("bt"), - }; - - match ffi::luaL_loadbufferx( - state, - source.as_ptr() as *const c_char, - source.len(), - name.map(|n| n.as_ptr()).unwrap_or_else(ptr::null), - mode_str, - ) { - ffi::LUA_OK => { - if let Some(env) = env { - self.push_ref(&env.0); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - ffi::lua_setupvalue(state, -2, 1); - #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] - ffi::lua_setfenv(state, -2); - } - - #[cfg(feature = "luau-jit")] - if (*self.extra.get()).enable_jit && ffi::luau_codegen_supported() != 0 { - ffi::luau_codegen_compile(state, -1); - } - - Ok(Function(self.pop_ref())) - } - err => Err(pop_error(state, err)), - } - } - } - - /// Sets a 'hook' function for a thread (coroutine). - #[cfg(not(feature = "luau"))] - pub(crate) unsafe fn set_thread_hook( - &self, - state: *mut ffi::lua_State, - triggers: HookTriggers, - callback: F, - ) where - F: Fn(&Lua, Debug) -> Result<()> + MaybeSend + 'static, - { - unsafe extern "C-unwind" fn hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) { - let extra = extra_data(state); - if (*extra).hook_thread != state { - // Hook was destined for a different thread, ignore - ffi::lua_sethook(state, None, 0, 0); - return; - } - callback_error_ext(state, extra, move |_| { - let hook_cb = (*extra).hook_callback.clone(); - let hook_cb = mlua_expect!(hook_cb, "no hook callback set in hook_proc"); - if Arc::strong_count(&hook_cb) > 2 { - return Ok(()); // Don't allow recursion - } - let lua = (*extra).lua(); - let rawlua = lua.lock(); - let _guard = StateGuard::new(&rawlua, state); - let debug = Debug::new(lua, ar); - hook_cb(lua, debug) - }) - } - - (*self.extra.get()).hook_callback = Some(Arc::new(callback)); - (*self.extra.get()).hook_thread = state; // Mark for what thread the hook is set - ffi::lua_sethook(state, Some(hook_proc), triggers.mask(), triggers.count()); - } - - /// Wraps a Lua function into a new thread (or coroutine). - /// - /// Takes function by reference. - fn create_thread_inner(&self, func: &Function) -> Result { - let state = self.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 3)?; - - let thread_state = if self.unlikely_memory_error() { - ffi::lua_newthread(state) - } else { - protect_lua!(state, 0, 1, |state| ffi::lua_newthread(state))? - }; - self.push_ref(&func.0); - ffi::lua_xmove(state, thread_state, 1); - - Ok(Thread::new(&self, self.pop_ref())) - } - } - - /// Wraps a Lua function into a new or recycled thread (coroutine). - #[cfg(feature = "async")] - pub(crate) fn create_recycled_thread(&self, func: &Function) -> Result { - #[cfg(any(feature = "lua54", feature = "luau"))] - unsafe { - let state = self.state(); - let _sg = StackGuard::new(state); - check_stack(state, 1)?; - - if let Some(index) = (*self.extra.get()).thread_pool.pop() { - let thread_state = ffi::lua_tothread(self.ref_thread(), index); - self.push_ref(&func.0); - ffi::lua_xmove(state, thread_state, 1); - - #[cfg(feature = "luau")] - { - // Inherit `LUA_GLOBALSINDEX` from the caller - ffi::lua_xpush(state, thread_state, ffi::LUA_GLOBALSINDEX); - ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX); - } - - return Ok(Thread::new(self, ValueRef::new(self, index))); - } - }; - self.create_thread_inner(func) - } - - /// Resets thread (coroutine) and returns to the pool for later use. - #[cfg(feature = "async")] - #[cfg(any(feature = "lua54", feature = "luau"))] - pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) -> bool { - let extra = &mut *self.extra.get(); - if extra.thread_pool.len() < extra.thread_pool.capacity() { - let thread_state = ffi::lua_tothread(extra.ref_thread, thread.0.index); - #[cfg(all(feature = "lua54", not(feature = "vendored")))] - let status = ffi::lua_resetthread(thread_state); - #[cfg(all(feature = "lua54", feature = "vendored"))] - let status = ffi::lua_closethread(thread_state, self.state()); - #[cfg(feature = "lua54")] - if status != ffi::LUA_OK { - // Error object is on top, drop it - ffi::lua_settop(thread_state, 0); - } - #[cfg(feature = "luau")] - ffi::lua_resetthread(thread_state); - extra.thread_pool.push(thread.0.index); - thread.0.drop = false; - return true; - } - false - } - - // FIXME - // #[inline] - // pub(crate) fn pop_multivalue_from_pool(&self) -> Option> { - // let extra = unsafe { &mut *self.extra.get() }; - // extra.multivalue_pool.pop() - // } - - // FIXME - // #[inline] - // pub(crate) fn push_multivalue_to_pool(&self, mut multivalue: VecDeque) { - // let extra = unsafe { &mut *self.extra.get() }; - // if extra.multivalue_pool.len() < MULTIVALUE_POOL_SIZE { - // multivalue.clear(); - // extra - // .multivalue_pool - // .push(unsafe { mem::transmute(multivalue) }); - // } - // } - - /// Pushes a value that implements `IntoLua` onto the Lua stack. - /// - /// Uses 2 stack spaces, does not call checkstack. - #[doc(hidden)] - #[inline(always)] - pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> { - value.push_into_stack(self) - } - - /// Pushes a `Value` (by reference) onto the Lua stack. - /// - /// Uses 2 stack spaces, does not call `checkstack`. - pub(crate) unsafe fn push_value(&self, value: &Value) -> Result<()> { - let state = self.state(); - match value { - Value::Nil => ffi::lua_pushnil(state), - Value::Boolean(b) => ffi::lua_pushboolean(state, *b as c_int), - Value::LightUserData(ud) => ffi::lua_pushlightuserdata(state, ud.0), - Value::Integer(i) => ffi::lua_pushinteger(state, *i), - Value::Number(n) => ffi::lua_pushnumber(state, *n), - #[cfg(feature = "luau")] - Value::Vector(v) => { - #[cfg(not(feature = "luau-vector4"))] - ffi::lua_pushvector(state, v.x(), v.y(), v.z()); - #[cfg(feature = "luau-vector4")] - ffi::lua_pushvector(state, v.x(), v.y(), v.z(), v.w()); - } - Value::String(s) => self.push_ref(&s.0), - Value::Table(t) => self.push_ref(&t.0), - Value::Function(f) => self.push_ref(&f.0), - Value::Thread(t) => self.push_ref(&t.0), - Value::UserData(ud) => self.push_ref(&ud.0), - Value::Error(err) => { - let protect = !self.unlikely_memory_error(); - push_gc_userdata(state, WrappedFailure::Error(*err.clone()), protect)?; - } - } - Ok(()) - } - - /// Pops a value from the Lua stack. - /// - /// Uses 2 stack spaces, does not call checkstack. - #[doc(hidden)] - pub(crate) unsafe fn pop_value(&self) -> Value { - let state = self.state(); - match ffi::lua_type(state, -1) { - ffi::LUA_TNIL => { - ffi::lua_pop(state, 1); - Nil - } - - ffi::LUA_TBOOLEAN => { - let b = Value::Boolean(ffi::lua_toboolean(state, -1) != 0); - ffi::lua_pop(state, 1); - b - } - - ffi::LUA_TLIGHTUSERDATA => { - let ud = Value::LightUserData(LightUserData(ffi::lua_touserdata(state, -1))); - ffi::lua_pop(state, 1); - ud - } - - #[cfg(any(feature = "lua54", feature = "lua53"))] - ffi::LUA_TNUMBER => { - let v = if ffi::lua_isinteger(state, -1) != 0 { - Value::Integer(ffi::lua_tointeger(state, -1)) - } else { - Value::Number(ffi::lua_tonumber(state, -1)) - }; - ffi::lua_pop(state, 1); - v - } - - #[cfg(any( - feature = "lua52", - feature = "lua51", - feature = "luajit", - feature = "luau" - ))] - ffi::LUA_TNUMBER => { - let n = ffi::lua_tonumber(state, -1); - ffi::lua_pop(state, 1); - match num_traits::cast(n) { - Some(i) if (n - (i as Number)).abs() < Number::EPSILON => Value::Integer(i), - _ => Value::Number(n), - } - } - - #[cfg(feature = "luau")] - ffi::LUA_TVECTOR => { - let v = ffi::lua_tovector(state, -1); - mlua_debug_assert!(!v.is_null(), "vector is null"); - #[cfg(not(feature = "luau-vector4"))] - let vec = Value::Vector(Vector([*v, *v.add(1), *v.add(2)])); - #[cfg(feature = "luau-vector4")] - let vec = Value::Vector(Vector([*v, *v.add(1), *v.add(2), *v.add(3)])); - ffi::lua_pop(state, 1); - vec - } - - ffi::LUA_TSTRING => Value::String(String(self.pop_ref())), - - ffi::LUA_TTABLE => Value::Table(Table(self.pop_ref())), - - ffi::LUA_TFUNCTION => Value::Function(Function(self.pop_ref())), - - ffi::LUA_TUSERDATA => { - let wrapped_failure_mt_ptr = (*self.extra.get()).wrapped_failure_mt_ptr; - // We must prevent interaction with userdata types other than UserData OR a WrappedError. - // WrappedPanics are automatically resumed. - match get_gc_userdata::(state, -1, wrapped_failure_mt_ptr).as_mut() - { - Some(WrappedFailure::Error(err)) => { - let err = err.clone(); - ffi::lua_pop(state, 1); - Value::Error(Box::new(err)) - } - Some(WrappedFailure::Panic(panic)) => { - if let Some(panic) = panic.take() { - ffi::lua_pop(state, 1); - resume_unwind(panic); - } - // Previously resumed panic? - ffi::lua_pop(state, 1); - Nil - } - _ => Value::UserData(AnyUserData(self.pop_ref(), SubtypeId::None)), - } - } - - ffi::LUA_TTHREAD => Value::Thread(Thread::new(self, self.pop_ref())), - - #[cfg(feature = "luau")] - ffi::LUA_TBUFFER => { - // Buffer is represented as a userdata type - Value::UserData(AnyUserData(self.pop_ref(), SubtypeId::Buffer)) - } - - #[cfg(feature = "luajit")] - ffi::LUA_TCDATA => { - // CDATA is represented as a userdata type - Value::UserData(AnyUserData(self.pop_ref(), SubtypeId::CData)) - } - - _ => mlua_panic!("LUA_TNONE in pop_value"), - } - } - - /// Returns value at given stack index without popping it. - /// - /// Uses 2 stack spaces, does not call checkstack. - pub(crate) unsafe fn stack_value(&self, idx: c_int) -> Value { - let state = self.state(); - match ffi::lua_type(state, idx) { - ffi::LUA_TNIL => Nil, - - ffi::LUA_TBOOLEAN => Value::Boolean(ffi::lua_toboolean(state, idx) != 0), - - ffi::LUA_TLIGHTUSERDATA => { - Value::LightUserData(LightUserData(ffi::lua_touserdata(state, idx))) - } - - #[cfg(any(feature = "lua54", feature = "lua53"))] - ffi::LUA_TNUMBER => { - if ffi::lua_isinteger(state, idx) != 0 { - Value::Integer(ffi::lua_tointeger(state, idx)) - } else { - Value::Number(ffi::lua_tonumber(state, idx)) - } - } - - #[cfg(any( - feature = "lua52", - feature = "lua51", - feature = "luajit", - feature = "luau" - ))] - ffi::LUA_TNUMBER => { - let n = ffi::lua_tonumber(state, idx); - match num_traits::cast(n) { - Some(i) if (n - (i as Number)).abs() < Number::EPSILON => Value::Integer(i), - _ => Value::Number(n), - } - } - - #[cfg(feature = "luau")] - ffi::LUA_TVECTOR => { - let v = ffi::lua_tovector(state, idx); - mlua_debug_assert!(!v.is_null(), "vector is null"); - #[cfg(not(feature = "luau-vector4"))] - return Value::Vector(Vector([*v, *v.add(1), *v.add(2)])); - #[cfg(feature = "luau-vector4")] - return Value::Vector(Vector([*v, *v.add(1), *v.add(2), *v.add(3)])); - } - - ffi::LUA_TSTRING => { - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::String(String(self.pop_ref_thread())) - } - - ffi::LUA_TTABLE => { - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::Table(Table(self.pop_ref_thread())) - } - - ffi::LUA_TFUNCTION => { - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::Function(Function(self.pop_ref_thread())) - } - - ffi::LUA_TUSERDATA => { - let wrapped_failure_mt_ptr = (*self.extra.get()).wrapped_failure_mt_ptr; - // We must prevent interaction with userdata types other than UserData OR a WrappedError. - // WrappedPanics are automatically resumed. - match get_gc_userdata::(state, idx, wrapped_failure_mt_ptr).as_mut() - { - Some(WrappedFailure::Error(err)) => Value::Error(Box::new(err.clone())), - Some(WrappedFailure::Panic(panic)) => { - if let Some(panic) = panic.take() { - resume_unwind(panic); - } - // Previously resumed panic? - Value::Nil - } - _ => { - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::None)) - } - } - } - - ffi::LUA_TTHREAD => { - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::Thread(Thread::new(self, self.pop_ref_thread())) - } - - #[cfg(feature = "luau")] - ffi::LUA_TBUFFER => { - // Buffer is represented as a userdata type - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::Buffer)) - } - - #[cfg(feature = "luajit")] - ffi::LUA_TCDATA => { - // CData is represented as a userdata type - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::CData)) - } - - _ => mlua_panic!("LUA_TNONE in pop_value"), - } - } - - // Pushes a ValueRef value onto the stack, uses 1 stack space, does not call checkstack - pub(crate) fn push_ref(&self, vref: &ValueRef) { - assert!( - self.weak() == &vref.lua, - "Lua instance passed Value created from a different main Lua state" - ); - unsafe { ffi::lua_xpush(self.ref_thread(), self.state(), vref.index) }; - } - - // Pops the topmost element of the stack and stores a reference to it. This pins the object, - // preventing garbage collection until the returned `ValueRef` is dropped. - // - // References are stored in the stack of a specially created auxiliary thread that exists only - // to store reference values. This is much faster than storing these in the registry, and also - // much more flexible and requires less bookkeeping than storing them directly in the currently - // used stack. The implementation is somewhat biased towards the use case of a relatively small - // number of short term references being created, and `RegistryKey` being used for long term - // references. - pub(crate) unsafe fn pop_ref(&self) -> ValueRef { - ffi::lua_xmove(self.state(), self.ref_thread(), 1); - let index = ref_stack_pop(self.extra.get()); - ValueRef::new(self, index) - } - - // Same as `pop_ref` but assumes the value is already on the reference thread - pub(crate) unsafe fn pop_ref_thread(&self) -> ValueRef { - let index = ref_stack_pop(self.extra.get()); - ValueRef::new(self, index) - } - - pub(crate) fn clone_ref(&self, vref: &ValueRef) -> ValueRef { - unsafe { - ffi::lua_pushvalue(self.ref_thread(), vref.index); - let index = ref_stack_pop(self.extra.get()); - ValueRef::new(self, index) - } - } - - pub(crate) fn drop_ref(&self, vref: &ValueRef) { - unsafe { - let ref_thread = self.ref_thread(); - ffi::lua_pushnil(ref_thread); - ffi::lua_replace(ref_thread, vref.index); - (*self.extra.get()).ref_free.push(vref.index); - } - } - - #[inline] - pub(crate) unsafe fn push_error_traceback(&self) { - let state = self.state(); - #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] - ffi::lua_xpush(self.ref_thread(), state, ExtraData::ERROR_TRACEBACK_IDX); - // Lua 5.2+ support light C functions that does not require extra allocations - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - ffi::lua_pushcfunction(state, error_traceback); - } - - #[inline] - pub(crate) unsafe fn unlikely_memory_error(&self) -> bool { - // MemoryInfo is empty in module mode so we cannot predict memory limits - match MemoryState::get(self.main_state) { - mem_state if !mem_state.is_null() => (*mem_state).memory_limit() == 0, - #[cfg(feature = "module")] - _ => (*self.extra.get()).skip_memory_check, // Check the special flag (only for module mode) - #[cfg(not(feature = "module"))] - _ => false, - } - } - - pub(crate) unsafe fn make_userdata(&self, data: UserDataVariant) -> Result - where - T: UserData + 'static, - { - self.make_userdata_with_metatable(data, || { - // Check if userdata/metatable is already registered - let type_id = TypeId::of::(); - if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) { - return Ok(table_id as Integer); - } - - // Create new metatable from UserData definition - let mut registry = UserDataRegistry::new(); - T::register(&mut registry); - - self.register_userdata_metatable(registry) - }) - } - - pub(crate) unsafe fn make_any_userdata( - &self, - data: UserDataVariant, - ) -> Result - where - T: 'static, - { - self.make_userdata_with_metatable(data, || { - // Check if userdata/metatable is already registered - let type_id = TypeId::of::(); - if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) { - return Ok(table_id as Integer); - } - - // Create empty metatable - let registry = UserDataRegistry::new(); - self.register_userdata_metatable::(registry) - }) - } - - unsafe fn make_userdata_with_metatable( - &self, - data: UserDataVariant, - get_metatable_id: impl FnOnce() -> Result, - ) -> Result { - let state = self.state(); - let _sg = StackGuard::new(state); - check_stack(state, 3)?; - - // We push metatable first to ensure having correct metatable with `__gc` method - ffi::lua_pushnil(state); - ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, get_metatable_id()?); - let protect = !self.unlikely_memory_error(); - #[cfg(not(feature = "lua54"))] - push_userdata(state, data, protect)?; - #[cfg(feature = "lua54")] - push_userdata_uv(state, data, USER_VALUE_MAXSLOT as c_int, protect)?; - ffi::lua_replace(state, -3); - ffi::lua_setmetatable(state, -2); - - // Set empty environment for Lua 5.1 - #[cfg(any(feature = "lua51", feature = "luajit"))] - if protect { - protect_lua!(state, 1, 1, fn(state) { - ffi::lua_newtable(state); - ffi::lua_setuservalue(state, -2); - })?; - } else { - ffi::lua_newtable(state); - ffi::lua_setuservalue(state, -2); - } - - Ok(AnyUserData(self.pop_ref(), SubtypeId::None)) - } - - unsafe fn register_userdata_metatable( - &self, - mut registry: UserDataRegistry, - ) -> Result { - let state = self.state(); - let _sg = StackGuard::new(state); - check_stack(state, 13)?; - - // Prepare metatable, add meta methods first and then meta fields - let metatable_nrec = registry.meta_methods.len() + registry.meta_fields.len(); - #[cfg(feature = "async")] - let metatable_nrec = metatable_nrec + registry.async_meta_methods.len(); - push_table(state, 0, metatable_nrec, true)?; - for (k, m) in registry.meta_methods { - self.push(self.create_callback(m)?)?; - rawset_field(state, -2, MetaMethod::validate(&k)?)?; - } - #[cfg(feature = "async")] - for (k, m) in registry.async_meta_methods { - self.push(self.create_async_callback(m)?)?; - rawset_field(state, -2, MetaMethod::validate(&k)?)?; - } - let mut has_name = false; - for (k, f) in registry.meta_fields { - has_name = has_name || k == MetaMethod::Type; - let inner = mem::transmute::<&LuaInner, &LuaInner>(self); - mlua_assert!(f(inner, 0)? == 1, "field function must return one value"); - rawset_field(state, -2, MetaMethod::validate(&k)?)?; - } - // Set `__name/__type` if not provided - if !has_name { - let type_name = short_type_name::(); - push_string(state, type_name.as_bytes(), !self.unlikely_memory_error())?; - rawset_field(state, -2, MetaMethod::Type.name())?; - } - let metatable_index = ffi::lua_absindex(state, -1); - - let mut extra_tables_count = 0; - - let fields_nrec = registry.fields.len(); - if fields_nrec > 0 { - // If __index is a table then update it inplace - let index_type = ffi::lua_getfield(state, metatable_index, cstr!("__index")); - match index_type { - ffi::LUA_TNIL | ffi::LUA_TTABLE => { - if index_type == ffi::LUA_TNIL { - // Create a new table - ffi::lua_pop(state, 1); - push_table(state, 0, fields_nrec, true)?; - } - for (k, f) in registry.fields { - let inner = mem::transmute::<&LuaInner, &LuaInner>(self); - mlua_assert!(f(inner, 0)? == 1, "field function must return one value"); - rawset_field(state, -2, &k)?; - } - rawset_field(state, metatable_index, "__index")?; - } - _ => { - ffi::lua_pop(state, 1); - // Propagate fields to the field getters - for (k, f) in registry.fields { - registry.field_getters.push((k, f)) - } - } - } - } - - let mut field_getters_index = None; - let field_getters_nrec = registry.field_getters.len(); - if field_getters_nrec > 0 { - push_table(state, 0, field_getters_nrec, true)?; - for (k, m) in registry.field_getters { - self.push(self.create_callback(m)?)?; - rawset_field(state, -2, &k)?; - } - field_getters_index = Some(ffi::lua_absindex(state, -1)); - extra_tables_count += 1; - } - - let mut field_setters_index = None; - let field_setters_nrec = registry.field_setters.len(); - if field_setters_nrec > 0 { - push_table(state, 0, field_setters_nrec, true)?; - for (k, m) in registry.field_setters { - self.push(self.create_callback(m)?)?; - rawset_field(state, -2, &k)?; - } - field_setters_index = Some(ffi::lua_absindex(state, -1)); - extra_tables_count += 1; - } - - let mut methods_index = None; - let methods_nrec = registry.methods.len(); - #[cfg(feature = "async")] - let methods_nrec = methods_nrec + registry.async_methods.len(); - if methods_nrec > 0 { - // If __index is a table then update it inplace - let index_type = ffi::lua_getfield(state, metatable_index, cstr!("__index")); - match index_type { - ffi::LUA_TTABLE => {} // Update the existing table - _ => { - // Create a new table - ffi::lua_pop(state, 1); - push_table(state, 0, methods_nrec, true)?; - } - } - for (k, m) in registry.methods { - self.push(self.create_callback(m)?)?; - rawset_field(state, -2, &k)?; - } - #[cfg(feature = "async")] - for (k, m) in registry.async_methods { - self.push(self.create_async_callback(m)?)?; - rawset_field(state, -2, &k)?; - } - match index_type { - ffi::LUA_TTABLE => { - ffi::lua_pop(state, 1); // All done - } - ffi::LUA_TNIL => { - rawset_field(state, metatable_index, "__index")?; // Set the new table as __index - } - _ => { - methods_index = Some(ffi::lua_absindex(state, -1)); - extra_tables_count += 1; - } - } - } - - #[cfg(feature = "luau")] - let extra_init = None; - #[cfg(not(feature = "luau"))] - let extra_init: Option Result<()>> = Some(|state| { - ffi::lua_pushcfunction(state, util::userdata_destructor::>); - rawset_field(state, -2, "__gc") - }); - - init_userdata_metatable( - state, - metatable_index, - field_getters_index, - field_setters_index, - methods_index, - extra_init, - )?; - - // Pop extra tables to get metatable on top of the stack - ffi::lua_pop(state, extra_tables_count); - - let mt_ptr = ffi::lua_topointer(state, -1); - let id = protect_lua!(state, 1, 0, |state| { - ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) - })?; - - let type_id = TypeId::of::(); - (*self.extra.get()).registered_userdata.insert(type_id, id); - (*self.extra.get()) - .registered_userdata_mt - .insert(mt_ptr, Some(type_id)); - - Ok(id as Integer) - } - - #[inline] - pub(crate) unsafe fn register_raw_userdata_metatable( - &self, - ptr: *const c_void, - type_id: Option, - ) { - (*self.extra.get()) - .registered_userdata_mt - .insert(ptr, type_id); - } - - #[inline] - pub(crate) unsafe fn deregister_raw_userdata_metatable(&self, ptr: *const c_void) { - (*self.extra.get()).registered_userdata_mt.remove(&ptr); - if (*self.extra.get()).last_checked_userdata_mt.0 == ptr { - (*self.extra.get()).last_checked_userdata_mt = (ptr::null(), None); - } - } - - #[inline(always)] - pub(crate) unsafe fn get_userdata_ref(&self, idx: c_int) -> Result> { - let guard = self.lua().lock_arc(); - (*get_userdata::>(self.state(), idx)).try_make_ref(guard) - } - - // Returns `TypeId` for the userdata ref, checking that it's registered and not destructed. - // - // Returns `None` if the userdata is registered but non-static. - pub(crate) unsafe fn get_userdata_ref_type_id( - &self, - vref: &ValueRef, - ) -> Result> { - self.get_userdata_type_id_inner(self.ref_thread(), vref.index) - } - - // Same as `get_userdata_ref_type_id` but assumes the userdata is already on the stack. - pub(crate) unsafe fn get_userdata_type_id(&self, idx: c_int) -> Result> { - self.get_userdata_type_id_inner(self.state(), idx) - } - - unsafe fn get_userdata_type_id_inner( - &self, - state: *mut ffi::lua_State, - idx: c_int, - ) -> Result> { - if ffi::lua_getmetatable(state, idx) == 0 { - return Err(Error::UserDataTypeMismatch); - } - let mt_ptr = ffi::lua_topointer(state, -1); - ffi::lua_pop(state, 1); - - // Fast path to skip looking up the metatable in the map - let (last_mt, last_type_id) = (*self.extra.get()).last_checked_userdata_mt; - if last_mt == mt_ptr { - return Ok(last_type_id); - } - - match (*self.extra.get()).registered_userdata_mt.get(&mt_ptr) { - Some(&type_id) if type_id == Some(TypeId::of::()) => { - Err(Error::UserDataDestructed) - } - Some(&type_id) => { - (*self.extra.get()).last_checked_userdata_mt = (mt_ptr, type_id); - Ok(type_id) - } - None => Err(Error::UserDataTypeMismatch), - } - } - - // Pushes a ValueRef (userdata) value onto the stack, returning their `TypeId`. - // Uses 1 stack space, does not call checkstack. - pub(crate) unsafe fn push_userdata_ref(&self, vref: &ValueRef) -> Result> { - let type_id = self.get_userdata_type_id_inner(self.ref_thread(), vref.index)?; - self.push_ref(vref); - Ok(type_id) - } - - // Creates a Function out of a Callback containing a 'static Fn. - pub(crate) fn create_callback(&self, func: Callback) -> Result { - unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int { - // Normal functions can be scoped and therefore destroyed, - // so we need to check that the first upvalue is valid - let (upvalue, extra) = match ffi::lua_type(state, ffi::lua_upvalueindex(1)) { - ffi::LUA_TUSERDATA => { - let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); - (upvalue, (*upvalue).extra.get()) - } - _ => (ptr::null_mut(), ptr::null_mut()), - }; - callback_error_ext(state, extra, |nargs| { - // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) - if upvalue.is_null() { - return Err(Error::CallbackDestructed); - } - - let lua = (*extra).lua().lock(); - let _guard = StateGuard::new(&lua, state); - let func = &*(*upvalue).data; - - func(mem::transmute::<&LuaInner, &LuaInner>(&lua), nargs) - }) - } - - let state = self.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 4)?; - - let func = mem::transmute(func); - let extra = Arc::clone(&self.extra); - let protect = !self.unlikely_memory_error(); - push_gc_userdata(state, CallbackUpvalue { data: func, extra }, protect)?; - if protect { - protect_lua!(state, 1, 1, fn(state) { - ffi::lua_pushcclosure(state, call_callback, 1); - })?; - } else { - ffi::lua_pushcclosure(state, call_callback, 1); - } - - Ok(Function(self.pop_ref())) - } - } - - #[cfg(feature = "async")] - pub(crate) fn create_async_callback(&self, func: AsyncCallback) -> Result { - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luau" - ))] - unsafe { - if !(*self.extra.get()).libs.contains(StdLib::COROUTINE) { - load_from_std_lib(self.main_state, StdLib::COROUTINE)?; - (*self.extra.get()).libs |= StdLib::COROUTINE; - } - } - - unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int { - // Async functions cannot be scoped and therefore destroyed, - // so the first upvalue is always valid - let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); - let extra = (*upvalue).extra.get(); - callback_error_ext(state, extra, |nargs| { - // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) - let lua = (*extra).lua(); - // The lock must be already held as the callback is executed - let rawlua = lua.guard_unchecked(); - let rawlua = mem::transmute::<&LuaInner, &LuaInner>(&rawlua); - let _guard = StateGuard::new(rawlua, state); - - let args = MultiValue::from_stack_multi(nargs, rawlua)?; - let func = &*(*upvalue).data; - let fut = func(rawlua, args); - let extra = Arc::clone(&(*upvalue).extra); - let protect = !rawlua.unlikely_memory_error(); - push_gc_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?; - if protect { - protect_lua!(state, 1, 1, fn(state) { - ffi::lua_pushcclosure(state, poll_future, 1); - })?; - } else { - ffi::lua_pushcclosure(state, poll_future, 1); - } - - Ok(1) - }) - } - - unsafe extern "C-unwind" fn poll_future(state: *mut ffi::lua_State) -> c_int { - let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); - let extra = (*upvalue).extra.get(); - callback_error_ext(state, extra, |_| { - // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) - let lua = (*extra).lua(); - // The lock must be already held as the future is polled - let rawlua = lua.guard_unchecked(); - let _guard = StateGuard::new(&rawlua, state); - - let fut = &mut (*upvalue).data; - let mut ctx = Context::from_waker(rawlua.waker()); - match fut.as_mut().poll(&mut ctx) { - Poll::Pending => { - ffi::lua_pushnil(state); - ffi::lua_pushlightuserdata(state, Lua::poll_pending().0); - Ok(2) - } - Poll::Ready(nresults) => { - match nresults? { - nresults @ 0..=2 => { - // Fast path for up to 2 results without creating a table - ffi::lua_pushinteger(state, nresults as _); - if nresults > 0 { - ffi::lua_insert(state, -nresults - 1); - } - Ok(nresults + 1) - } - nresults => { - let results = MultiValue::from_stack_multi(nresults, &rawlua)?; - ffi::lua_pushinteger(state, nresults as _); - rawlua.push(lua.create_sequence_from(results)?)?; - Ok(2) - } - } - } - } - }) - } - - let state = self.state(); - let get_poll = unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 4)?; - - let func = mem::transmute(func); - let extra = Arc::clone(&self.extra); - let protect = !self.unlikely_memory_error(); - let upvalue = AsyncCallbackUpvalue { data: func, extra }; - push_gc_userdata(state, upvalue, protect)?; - if protect { - protect_lua!(state, 1, 1, fn(state) { - ffi::lua_pushcclosure(state, call_callback, 1); - })?; - } else { - ffi::lua_pushcclosure(state, call_callback, 1); - } - - Function(self.pop_ref()) - }; - - unsafe extern "C-unwind" fn unpack(state: *mut ffi::lua_State) -> c_int { - let len = ffi::lua_tointeger(state, 2); - ffi::luaL_checkstack(state, len as c_int, ptr::null()); - for i in 1..=len { - ffi::lua_rawgeti(state, 1, i); - } - len as c_int - } - - let lua = self.lua(); - let coroutine = lua.globals().get::<_, Table>("coroutine")?; - - let env = lua.create_table_with_capacity(0, 3)?; - env.set("get_poll", get_poll)?; - // Cache `yield` function - env.set("yield", coroutine.get::<_, Function>("yield")?)?; - unsafe { - env.set("unpack", lua.create_c_function(unpack)?)?; - } - - lua.load( - r#" - local poll = get_poll(...) - while true do - local nres, res, res2 = poll() - if nres ~= nil then - if nres == 0 then - return - elseif nres == 1 then - return res - elseif nres == 2 then - return res, res2 - else - return unpack(res, nres) - end - end - yield(res) -- `res` is a "pending" value - end - "#, - ) - .try_cache() - .set_name("__mlua_async_poll") - .set_environment(env) - .into_function() - } - - #[cfg(feature = "async")] - #[inline] - pub(crate) unsafe fn waker(&self) -> &Waker { - (*self.extra.get()).waker.as_ref() - } - - #[cfg(feature = "async")] - #[inline] - pub(crate) unsafe fn set_waker(&self, waker: NonNull) -> NonNull { - mem::replace(&mut (*self.extra.get()).waker, waker) - } -} - -impl WeakLua { - #[track_caller] - #[inline(always)] - pub(crate) fn lock(&self) -> LuaGuard { - LuaGuard::new(self.0.upgrade().unwrap()) - } - - #[inline(always)] - pub(crate) fn try_lock(&self) -> Option { - Some(LuaGuard::new(self.0.upgrade()?)) - } -} - -impl PartialEq for WeakLua { - fn eq(&self, other: &Self) -> bool { - Weak::ptr_eq(&self.0, &other.0) - } -} - -impl Eq for WeakLua {} - -impl LuaGuard { - pub(crate) fn new(handle: Arc>) -> Self { - Self(handle.lock_arc()) - } -} - -impl Deref for LuaGuard { - type Target = LuaInner; - - fn deref(&self) -> &Self::Target { - &*self.0 - } -} - -impl ExtraData { - // Index of `error_traceback` function in auxiliary thread stack - #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] - const ERROR_TRACEBACK_IDX: c_int = 1; - - #[inline(always)] - const fn lua(&self) -> &Lua { - unsafe { mem::transmute(self.inner.assume_init_ref()) } - } - - #[inline(always)] - const fn weak(&self) -> &WeakLua { - unsafe { mem::transmute(self.weak.assume_init_ref()) } - } -} - -struct StateGuard<'a>(&'a LuaInner, *mut ffi::lua_State); - -impl<'a> StateGuard<'a> { - fn new(inner: &'a LuaInner, mut state: *mut ffi::lua_State) -> Self { - state = inner.state.replace(state); - Self(inner, state) - } -} - -impl<'a> Drop for StateGuard<'a> { - fn drop(&mut self) { - self.0.state.set(self.1); - } -} - -unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData { - #[cfg(feature = "luau")] - if cfg!(not(feature = "module")) { - // In the main app we can use `lua_callbacks` to access ExtraData - return (*ffi::lua_callbacks(state)).userdata as *mut _; - } - - let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void; - if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, extra_key) != ffi::LUA_TUSERDATA { - // `ExtraData` can be null only when Lua state is foreign. - // This case in used in `Lua::try_from_ptr()`. - ffi::lua_pop(state, 1); - return ptr::null_mut(); - } - let extra_ptr = ffi::lua_touserdata(state, -1) as *mut Arc>; - ffi::lua_pop(state, 1); - (*extra_ptr).get() -} - -unsafe fn set_extra_data( - state: *mut ffi::lua_State, - extra: &Arc>, -) -> Result<()> { - #[cfg(feature = "luau")] - if cfg!(not(feature = "module")) { - (*ffi::lua_callbacks(state)).userdata = extra.get() as *mut _; - return Ok(()); - } - - push_gc_userdata(state, Arc::clone(extra), true)?; - protect_lua!(state, 1, 0, fn(state) { - let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void; - ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, extra_key); - }) -} - -// Creates required entries in the metatable cache (see `util::METATABLE_CACHE`) -pub(crate) fn init_metatable_cache(cache: &mut FxHashMap) { - cache.insert(TypeId::of::>>(), 0); - cache.insert(TypeId::of::(), 0); - cache.insert(TypeId::of::(), 0); - - #[cfg(feature = "async")] - { - cache.insert(TypeId::of::(), 0); - cache.insert(TypeId::of::(), 0); - cache.insert(TypeId::of::(), 0); - cache.insert(TypeId::of::>(), 0); - } -} - -// An optimized version of `callback_error` that does not allocate `WrappedFailure` userdata -// and instead reuses unsed values from previous calls (or allocates new). -unsafe fn callback_error_ext(state: *mut ffi::lua_State, mut extra: *mut ExtraData, f: F) -> R -where - F: FnOnce(c_int) -> Result, -{ - if extra.is_null() { - extra = extra_data(state); - } - - let nargs = ffi::lua_gettop(state); - - enum PreallocatedFailure { - New(*mut WrappedFailure), - Existing(i32), - } - - impl PreallocatedFailure { - unsafe fn reserve(state: *mut ffi::lua_State, extra: *mut ExtraData) -> Self { - match (*extra).wrapped_failure_pool.pop() { - Some(index) => PreallocatedFailure::Existing(index), - None => { - // We need to check stack for Luau in case when callback is called from interrupt - // See https://github.com/Roblox/luau/issues/446 and mlua #142 and #153 - #[cfg(feature = "luau")] - ffi::lua_rawcheckstack(state, 2); - // Place it to the beginning of the stack - let ud = WrappedFailure::new_userdata(state); - ffi::lua_insert(state, 1); - PreallocatedFailure::New(ud) - } - } - } - - unsafe fn r#use( - &self, - state: *mut ffi::lua_State, - extra: *mut ExtraData, - ) -> *mut WrappedFailure { - let ref_thread = (*extra).ref_thread; - match *self { - PreallocatedFailure::New(ud) => { - ffi::lua_settop(state, 1); - ud - } - PreallocatedFailure::Existing(index) => { - ffi::lua_settop(state, 0); - #[cfg(feature = "luau")] - ffi::lua_rawcheckstack(state, 2); - ffi::lua_pushvalue(ref_thread, index); - ffi::lua_xmove(ref_thread, state, 1); - ffi::lua_pushnil(ref_thread); - ffi::lua_replace(ref_thread, index); - (*extra).ref_free.push(index); - ffi::lua_touserdata(state, -1) as *mut WrappedFailure - } - } - } - - unsafe fn release(self, state: *mut ffi::lua_State, extra: *mut ExtraData) { - let ref_thread = (*extra).ref_thread; - match self { - PreallocatedFailure::New(_) => { - if (*extra).wrapped_failure_pool.len() < WRAPPED_FAILURE_POOL_SIZE { - ffi::lua_rotate(state, 1, -1); - ffi::lua_xmove(state, ref_thread, 1); - let index = ref_stack_pop(extra); - (*extra).wrapped_failure_pool.push(index); - } else { - ffi::lua_remove(state, 1); - } - } - PreallocatedFailure::Existing(index) => { - if (*extra).wrapped_failure_pool.len() < WRAPPED_FAILURE_POOL_SIZE { - (*extra).wrapped_failure_pool.push(index); - } else { - ffi::lua_pushnil(ref_thread); - ffi::lua_replace(ref_thread, index); - (*extra).ref_free.push(index); - } - } - } - } - } - - // We cannot shadow Rust errors with Lua ones, so we need to reserve pre-allocated memory - // to store a wrapped failure (error or panic) *before* we proceed. - let prealloc_failure = PreallocatedFailure::reserve(state, extra); - - match catch_unwind(AssertUnwindSafe(|| f(nargs))) { - Ok(Ok(r)) => { - // Return unused `WrappedFailure` to the pool - prealloc_failure.release(state, extra); - r - } - Ok(Err(err)) => { - let wrapped_error = prealloc_failure.r#use(state, extra); - - // Build `CallbackError` with traceback - let traceback = if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 { - ffi::luaL_traceback(state, state, ptr::null(), 0); - let traceback = util::to_string(state, -1); - ffi::lua_pop(state, 1); - traceback - } else { - "".to_string() - }; - let cause = Arc::new(err); - ptr::write( - wrapped_error, - WrappedFailure::Error(Error::CallbackError { traceback, cause }), - ); - get_gc_metatable::(state); - ffi::lua_setmetatable(state, -2); - - ffi::lua_error(state) - } - Err(p) => { - let wrapped_panic = prealloc_failure.r#use(state, extra); - ptr::write(wrapped_panic, WrappedFailure::Panic(Some(p))); - get_gc_metatable::(state); - ffi::lua_setmetatable(state, -2); - ffi::lua_error(state) - } - } -} - -// Uses 3 stack spaces -unsafe fn load_from_std_lib(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> { - #[inline(always)] - pub unsafe fn requiref( - state: *mut ffi::lua_State, - modname: &str, - openf: ffi::lua_CFunction, - glb: c_int, - ) -> Result<()> { - let modname = mlua_expect!(CString::new(modname), "modname contains nil byte"); - protect_lua!(state, 0, 1, |state| { - ffi::luaL_requiref(state, modname.as_ptr() as *const c_char, openf, glb) - }) - } - - #[cfg(feature = "luajit")] - struct GcGuard(*mut ffi::lua_State); - - #[cfg(feature = "luajit")] - impl GcGuard { - fn new(state: *mut ffi::lua_State) -> Self { - // Stop collector during library initialization - unsafe { ffi::lua_gc(state, ffi::LUA_GCSTOP, 0) }; - GcGuard(state) - } - } - - #[cfg(feature = "luajit")] - impl Drop for GcGuard { - fn drop(&mut self) { - unsafe { ffi::lua_gc(self.0, ffi::LUA_GCRESTART, -1) }; - } - } - - // Stop collector during library initialization - #[cfg(feature = "luajit")] - let _gc_guard = GcGuard::new(state); - - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luau" - ))] - { - if libs.contains(StdLib::COROUTINE) { - requiref(state, ffi::LUA_COLIBNAME, ffi::luaopen_coroutine, 1)?; - ffi::lua_pop(state, 1); - } - } - - if libs.contains(StdLib::TABLE) { - requiref(state, ffi::LUA_TABLIBNAME, ffi::luaopen_table, 1)?; - ffi::lua_pop(state, 1); - } - - #[cfg(not(feature = "luau"))] - if libs.contains(StdLib::IO) { - requiref(state, ffi::LUA_IOLIBNAME, ffi::luaopen_io, 1)?; - ffi::lua_pop(state, 1); - } - - if libs.contains(StdLib::OS) { - requiref(state, ffi::LUA_OSLIBNAME, ffi::luaopen_os, 1)?; - ffi::lua_pop(state, 1); - } - - if libs.contains(StdLib::STRING) { - requiref(state, ffi::LUA_STRLIBNAME, ffi::luaopen_string, 1)?; - ffi::lua_pop(state, 1); - } - - #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] - { - if libs.contains(StdLib::UTF8) { - requiref(state, ffi::LUA_UTF8LIBNAME, ffi::luaopen_utf8, 1)?; - ffi::lua_pop(state, 1); - } - } - - #[cfg(any(feature = "lua52", feature = "luau"))] - { - if libs.contains(StdLib::BIT) { - requiref(state, ffi::LUA_BITLIBNAME, ffi::luaopen_bit32, 1)?; - ffi::lua_pop(state, 1); - } - } - - #[cfg(feature = "luajit")] - { - if libs.contains(StdLib::BIT) { - requiref(state, ffi::LUA_BITLIBNAME, ffi::luaopen_bit, 1)?; - ffi::lua_pop(state, 1); - } - } - - #[cfg(feature = "luau")] - if libs.contains(StdLib::BUFFER) { - requiref(state, ffi::LUA_BUFFERLIBNAME, ffi::luaopen_buffer, 1)?; - ffi::lua_pop(state, 1); - } - - if libs.contains(StdLib::MATH) { - requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?; - ffi::lua_pop(state, 1); - } - - if libs.contains(StdLib::DEBUG) { - requiref(state, ffi::LUA_DBLIBNAME, ffi::luaopen_debug, 1)?; - ffi::lua_pop(state, 1); - } - - #[cfg(not(feature = "luau"))] - if libs.contains(StdLib::PACKAGE) { - requiref(state, ffi::LUA_LOADLIBNAME, ffi::luaopen_package, 1)?; - ffi::lua_pop(state, 1); - } - #[cfg(feature = "luau")] - if libs.contains(StdLib::PACKAGE) { - let lua: &Lua = mem::transmute((*extra_data(state)).inner.assume_init_ref()); - crate::luau::register_package_module(lua)?; - } - - #[cfg(feature = "luajit")] - { - if libs.contains(StdLib::JIT) { - requiref(state, ffi::LUA_JITLIBNAME, ffi::luaopen_jit, 1)?; - ffi::lua_pop(state, 1); - } - - if libs.contains(StdLib::FFI) { - requiref(state, ffi::LUA_FFILIBNAME, ffi::luaopen_ffi, 1)?; - ffi::lua_pop(state, 1); - } - } - - Ok(()) -} - -unsafe fn ref_stack_pop(extra: *mut ExtraData) -> c_int { - let extra = &mut *extra; - if let Some(free) = extra.ref_free.pop() { - ffi::lua_replace(extra.ref_thread, free); - return free; - } - - // Try to grow max stack size - if extra.ref_stack_top >= extra.ref_stack_size { - let mut inc = extra.ref_stack_size; // Try to double stack size - while inc > 0 && ffi::lua_checkstack(extra.ref_thread, inc) == 0 { - inc /= 2; - } - if inc == 0 { - // Pop item on top of the stack to avoid stack leaking and successfully run destructors - // during unwinding. - ffi::lua_pop(extra.ref_thread, 1); - let top = extra.ref_stack_top; - // It is a user error to create enough references to exhaust the Lua max stack size for - // the ref thread. - panic!( - "cannot create a Lua reference, out of auxiliary stack space (used {top} slots)" - ); - } - extra.ref_stack_size += inc; - } - extra.ref_stack_top += 1; - extra.ref_stack_top -} - -#[cfg(test)] -mod assertions { - use super::*; - - // Lua has lots of interior mutability, should not be RefUnwindSafe - static_assertions::assert_not_impl_any!(Lua: std::panic::RefUnwindSafe); - - #[cfg(not(feature = "send"))] - static_assertions::assert_not_impl_any!(Lua: Send); - #[cfg(feature = "send")] - static_assertions::assert_impl_all!(Lua: Send); -} diff --git a/src/luau/mod.rs b/src/luau/mod.rs index 1d185155..139860b8 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -2,7 +2,7 @@ use std::ffi::CStr; use std::os::raw::{c_float, c_int}; use crate::error::Result; -use crate::lua::Lua; +use crate::state::Lua; // Since Luau has some missing standard functions, we re-implement them here diff --git a/src/luau/package.rs b/src/luau/package.rs index 880f2e88..cf3fe0ab 100644 --- a/src/luau/package.rs +++ b/src/luau/package.rs @@ -7,7 +7,7 @@ use std::{env, fs}; use crate::chunk::ChunkMode; use crate::error::Result; -use crate::lua::Lua; +use crate::state::Lua; use crate::table::Table; use crate::types::RegistryKey; use crate::value::{IntoLua, Value}; diff --git a/src/multi.rs b/src/multi.rs index e56aeb9e..317c14e2 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -5,7 +5,8 @@ use std::os::raw::c_int; use std::result::Result as StdResult; use crate::error::Result; -use crate::lua::{Lua, LuaInner}; +use crate::state::Lua; +use crate::state::RawLua; use crate::util::check_stack; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil}; @@ -21,7 +22,7 @@ impl IntoLuaMulti for StdResult { } #[inline] - unsafe fn push_into_stack_multi(self, lua: &LuaInner) -> Result { + unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { match self { Ok(val) => (val,).push_into_stack_multi(lua), Err(err) => (Nil, err).push_into_stack_multi(lua), @@ -39,7 +40,7 @@ impl IntoLuaMulti for StdResult<(), E> { } #[inline] - unsafe fn push_into_stack_multi(self, lua: &LuaInner) -> Result { + unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { match self { Ok(_) => Ok(0), Err(err) => (Nil, err).push_into_stack_multi(lua), @@ -56,7 +57,7 @@ impl IntoLuaMulti for T { } #[inline] - unsafe fn push_into_stack_multi(self, lua: &LuaInner) -> Result { + unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { self.push_into_stack(lua)?; Ok(1) } @@ -74,7 +75,7 @@ impl FromLuaMulti for T { } #[inline] - unsafe fn from_stack_multi(nvals: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack_multi(nvals: c_int, lua: &RawLua) -> Result { if nvals == 0 { return T::from_lua(Nil, lua.lua()); } @@ -86,7 +87,7 @@ impl FromLuaMulti for T { nargs: c_int, i: usize, to: Option<&str>, - lua: &LuaInner, + lua: &RawLua, ) -> Result { if nargs == 0 { return T::from_lua_arg(Nil, i, to, lua.lua()); @@ -209,7 +210,7 @@ macro_rules! impl_tuple { } #[inline] - unsafe fn push_into_stack_multi(self, _lua: &LuaInner) -> Result { + unsafe fn push_into_stack_multi(self, _lua: &RawLua) -> Result { Ok(0) } } @@ -221,7 +222,7 @@ macro_rules! impl_tuple { } #[inline] - unsafe fn from_stack_multi(nvals: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack_multi(nvals: c_int, lua: &RawLua) -> Result { if nvals > 0 { ffi::lua_pop(lua.state(), nvals); } @@ -247,7 +248,7 @@ macro_rules! impl_tuple { #[allow(non_snake_case)] #[inline] - unsafe fn push_into_stack_multi(self, lua: &LuaInner) -> Result { + unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { let ($($name,)* $last,) = self; let mut nresults = 0; $( @@ -288,7 +289,7 @@ macro_rules! impl_tuple { #[allow(unused_mut, non_snake_case)] #[inline] - unsafe fn from_stack_multi(mut nvals: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack_multi(mut nvals: c_int, lua: &RawLua) -> Result { $( let $name = if nvals > 0 { nvals -= 1; @@ -303,7 +304,7 @@ macro_rules! impl_tuple { #[allow(unused_mut, non_snake_case)] #[inline] - unsafe fn from_stack_args(mut nargs: c_int, mut i: usize, to: Option<&str>, lua: &LuaInner) -> Result { + unsafe fn from_stack_args(mut nargs: c_int, mut i: usize, to: Option<&str>, lua: &RawLua) -> Result { $( let $name = if nargs > 0 { nargs -= 1; diff --git a/src/serde/mod.rs b/src/serde/mod.rs index 94952810..46c2d915 100644 --- a/src/serde/mod.rs +++ b/src/serde/mod.rs @@ -5,7 +5,7 @@ use std::os::raw::c_void; use serde::{de::DeserializeOwned, ser::Serialize}; use crate::error::Result; -use crate::lua::Lua; +use crate::state::Lua; use crate::private::Sealed; use crate::table::Table; use crate::util::check_stack; diff --git a/src/serde/ser.rs b/src/serde/ser.rs index df3a16ce..315308cf 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -2,7 +2,7 @@ use serde::{ser, Serialize}; use super::LuaSerdeExt; use crate::error::{Error, Result}; -use crate::lua::Lua; +use crate::state::Lua; use crate::table::Table; use crate::value::{IntoLua, Value}; diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 00000000..6373ce31 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,1936 @@ +use std::any::TypeId; +use std::cell::RefCell; +// use std::collections::VecDeque; +use std::fmt; +use std::marker::PhantomData; +use std::ops::Deref; +use std::os::raw::{c_int, c_void}; +use std::panic::Location; +use std::result::Result as StdResult; +use std::sync::{Arc, Weak}; +use std::{mem, ptr}; + +use parking_lot::{ReentrantMutex, ReentrantMutexGuard}; + +use crate::chunk::{AsChunk, Chunk}; +use crate::error::{Error, Result}; +use crate::function::Function; +use crate::hook::Debug; +use crate::memory::MemoryState; +// use crate::scope::Scope; +use crate::stdlib::StdLib; +use crate::string::String; +use crate::table::Table; +use crate::thread::Thread; +use crate::types::{ + AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LightUserData, MaybeSend, Number, + RegistryKey, +}; +use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataVariant}; +use crate::util::{assert_stack, check_stack, push_string, push_table, rawset_field, StackGuard}; +use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value}; + +#[cfg(not(feature = "luau"))] +use crate::hook::HookTriggers; + +#[cfg(any(feature = "luau", doc))] +use crate::{chunk::Compiler, types::VmState}; + +#[cfg(feature = "async")] +use std::future::{self, Future}; + +#[cfg(feature = "serialize")] +use serde::Serialize; + +pub(crate) use extra::ExtraData; +pub use raw::RawLua; +use util::{callback_error_ext, StateGuard}; + +/// Top level Lua struct which represents an instance of Lua VM. +#[derive(Clone)] +#[repr(transparent)] +pub struct Lua(Arc>); + +#[derive(Clone)] +#[repr(transparent)] +pub(crate) struct WeakLua(Weak>); + +pub(crate) struct LuaGuard(ArcReentrantMutexGuard); + +/// Mode of the Lua garbage collector (GC). +/// +/// In Lua 5.4 GC can work in two modes: incremental and generational. +/// Previous Lua versions support only incremental GC. +/// +/// More information can be found in the Lua [documentation]. +/// +/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum GCMode { + Incremental, + /// Requires `feature = "lua54"` + #[cfg(feature = "lua54")] + #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] + Generational, +} + +/// Controls Lua interpreter behavior such as Rust panics handling. +#[derive(Clone, Debug)] +#[non_exhaustive] +pub struct LuaOptions { + /// Catch Rust panics when using [`pcall`]/[`xpcall`]. + /// + /// If disabled, wraps these functions and automatically resumes panic if found. + /// Also in Lua 5.1 adds ability to provide arguments to [`xpcall`] similar to Lua >= 5.2. + /// + /// If enabled, keeps [`pcall`]/[`xpcall`] unmodified. + /// Panics are still automatically resumed if returned to the Rust side. + /// + /// Default: **true** + /// + /// [`pcall`]: https://www.lua.org/manual/5.4/manual.html#pdf-pcall + /// [`xpcall`]: https://www.lua.org/manual/5.4/manual.html#pdf-xpcall + pub catch_rust_panics: bool, + + /// Max size of thread (coroutine) object pool used to execute asynchronous functions. + /// + /// It works on Lua 5.4 and Luau, where [`lua_resetthread`] function + /// is available and allows to reuse old coroutines after resetting their state. + /// + /// Default: **0** (disabled) + /// + /// [`lua_resetthread`]: https://www.lua.org/manual/5.4/manual.html#lua_resetthread + #[cfg(feature = "async")] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + pub thread_pool_size: usize, +} + +impl Default for LuaOptions { + fn default() -> Self { + LuaOptions::new() + } +} + +impl LuaOptions { + /// Returns a new instance of `LuaOptions` with default parameters. + pub const fn new() -> Self { + LuaOptions { + catch_rust_panics: true, + #[cfg(feature = "async")] + thread_pool_size: 0, + } + } + + /// Sets [`catch_rust_panics`] option. + /// + /// [`catch_rust_panics`]: #structfield.catch_rust_panics + #[must_use] + pub const fn catch_rust_panics(mut self, enabled: bool) -> Self { + self.catch_rust_panics = enabled; + self + } + + /// Sets [`thread_pool_size`] option. + /// + /// [`thread_pool_size`]: #structfield.thread_pool_size + #[cfg(feature = "async")] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + #[must_use] + pub const fn thread_pool_size(mut self, size: usize) -> Self { + self.thread_pool_size = size; + self + } +} + +/// Requires `feature = "send"` +#[cfg(feature = "send")] +#[cfg_attr(docsrs, doc(cfg(feature = "send")))] +unsafe impl Send for Lua {} + +#[cfg(not(feature = "module"))] +impl Drop for Lua { + fn drop(&mut self) { + let _ = self.gc_collect(); + } +} + +impl fmt::Debug for Lua { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Lua({:p})", self.lock().state()) + } +} + +impl Default for Lua { + #[inline] + fn default() -> Self { + Lua::new() + } +} + +impl Lua { + /// Creates a new Lua state and loads the **safe** subset of the standard libraries. + /// + /// # Safety + /// The created Lua state would have _some_ safety guarantees and would not allow to load unsafe + /// standard libraries or C modules. + /// + /// See [`StdLib`] documentation for a list of unsafe modules that cannot be loaded. + /// + /// [`StdLib`]: crate::StdLib + pub fn new() -> Lua { + mlua_expect!( + Self::new_with(StdLib::ALL_SAFE, LuaOptions::default()), + "Cannot create a Lua state" + ) + } + + /// Creates a new Lua state and loads all the standard libraries. + /// + /// # Safety + /// The created Lua state would not have safety guarantees and would allow to load C modules. + pub unsafe fn unsafe_new() -> Lua { + Self::unsafe_new_with(StdLib::ALL, LuaOptions::default()) + } + + /// Creates a new Lua state and loads the specified safe subset of the standard libraries. + /// + /// Use the [`StdLib`] flags to specify the libraries you want to load. + /// + /// # Safety + /// The created Lua state would have _some_ safety guarantees and would not allow to load unsafe + /// standard libraries or C modules. + /// + /// See [`StdLib`] documentation for a list of unsafe modules that cannot be loaded. + /// + /// [`StdLib`]: crate::StdLib + pub fn new_with(libs: StdLib, options: LuaOptions) -> Result { + #[cfg(not(feature = "luau"))] + if libs.contains(StdLib::DEBUG) { + return Err(Error::SafetyError( + "The unsafe `debug` module can't be loaded using safe `new_with`".to_string(), + )); + } + #[cfg(feature = "luajit")] + if libs.contains(StdLib::FFI) { + return Err(Error::SafetyError( + "The unsafe `ffi` module can't be loaded using safe `new_with`".to_string(), + )); + } + + let lua = unsafe { Self::inner_new(libs, options) }; + + if libs.contains(StdLib::PACKAGE) { + mlua_expect!(lua.disable_c_modules(), "Error disabling C modules"); + } + unsafe { lua.lock().set_safe() }; + + Ok(lua) + } + + /// Creates a new Lua state and loads the specified subset of the standard libraries. + /// + /// Use the [`StdLib`] flags to specify the libraries you want to load. + /// + /// # Safety + /// The created Lua state will not have safety guarantees and allow to load C modules. + /// + /// [`StdLib`]: crate::StdLib + pub unsafe fn unsafe_new_with(libs: StdLib, options: LuaOptions) -> Lua { + // Workaround to avoid stripping a few unused Lua symbols that could be imported + // by C modules in unsafe mode + let mut _symbols: Vec<*const extern "C-unwind" fn()> = + vec![ffi::lua_isuserdata as _, ffi::lua_tocfunction as _]; + + #[cfg(not(feature = "luau"))] + _symbols.extend_from_slice(&[ + ffi::lua_atpanic as _, + ffi::luaL_loadstring as _, + ffi::luaL_openlibs as _, + ]); + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + { + _symbols.push(ffi::lua_getglobal as _); + _symbols.push(ffi::lua_setglobal as _); + _symbols.push(ffi::luaL_setfuncs as _); + } + + Self::inner_new(libs, options) + } + + /// Creates a new Lua state with required `libs` and `options` + unsafe fn inner_new(libs: StdLib, options: LuaOptions) -> Lua { + let lua = Lua(RawLua::new(libs, options)); + + #[cfg(feature = "luau")] + mlua_expect!(lua.configure_luau(), "Error configuring Luau"); + + lua + } + + /// Constructs a new Lua instance from an existing raw state. + /// + /// Once called, a returned Lua state is cached in the registry and can be retrieved + /// by calling this function again. + #[allow(clippy::missing_safety_doc)] + #[inline] + pub unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> Lua { + Lua(RawLua::init_from_ptr(state)) + } + + /// FIXME: Deprecated load_from_std_lib + + /// Loads the specified subset of the standard libraries into an existing Lua state. + /// + /// Use the [`StdLib`] flags to specify the libraries you want to load. + pub fn load_std_libs(&self, libs: StdLib) -> Result<()> { + unsafe { self.lock().load_std_libs(libs) } + } + + /// Loads module `modname` into an existing Lua state using the specified entrypoint + /// function. + /// + /// Internally calls the Lua function `func` with the string `modname` as an argument, + /// sets the call result to `package.loaded[modname]` and returns copy of the result. + /// + /// If `package.loaded[modname]` value is not nil, returns copy of the value without + /// calling the function. + /// + /// If the function does not return a non-nil value then this method assigns true to + /// `package.loaded[modname]`. + /// + /// Behavior is similar to Lua's [`require`] function. + /// + /// [`require`]: https://www.lua.org/manual/5.4/manual.html#pdf-require + pub fn load_from_function(&self, modname: &str, func: Function) -> Result + where + T: FromLua, + { + let lua = self.lock(); + let state = lua.state(); + let loaded = unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 2)?; + protect_lua!(state, 0, 1, fn(state) { + ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); + })?; + Table(lua.pop_ref()) + }; + + let modname = unsafe { lua.create_string(modname)? }; + let value = match loaded.raw_get(&modname)? { + Value::Nil => { + let result = match func.call(&modname)? { + Value::Nil => Value::Boolean(true), + res => res, + }; + loaded.raw_set(modname, &result)?; + result + } + res => res, + }; + T::from_lua(value, self) + } + + /// Unloads module `modname`. + /// + /// Removes module from the [`package.loaded`] table which allows to load it again. + /// It does not support unloading binary Lua modules since they are internally cached and can be + /// unloaded only by closing Lua state. + /// + /// [`package.loaded`]: https://www.lua.org/manual/5.4/manual.html#pdf-package.loaded + pub fn unload(&self, modname: &str) -> Result<()> { + let lua = self.lock(); + let state = lua.state(); + let loaded = unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 2)?; + protect_lua!(state, 0, 1, fn(state) { + ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); + })?; + Table(lua.pop_ref()) + }; + + loaded.raw_set(modname, Nil) + } + + /// Consumes and leaks `Lua` object, returning a static reference `&'static Lua`. + /// + /// This function is useful when the `Lua` object is supposed to live for the remainder + /// of the program's life. + /// + /// Dropping the returned reference will cause a memory leak. If this is not acceptable, + /// the reference should first be wrapped with the [`Lua::from_static`] function producing a `Lua`. + /// This `Lua` object can then be dropped which will properly release the allocated memory. + /// + /// [`Lua::from_static`]: #method.from_static + /// + /// FIXME: remove + #[doc(hidden)] + pub fn into_static(self) -> &'static Self { + Box::leak(Box::new(self)) + } + + /// Constructs a `Lua` from a static reference to it. + /// + /// # Safety + /// This function is unsafe because improper use may lead to memory problems or undefined behavior. + /// + /// FIXME: remove + #[doc(hidden)] + pub unsafe fn from_static(lua: &'static Lua) -> Self { + *Box::from_raw(lua as *const Lua as *mut Lua) + } + + // Executes module entrypoint function, which returns only one Value. + // The returned value then pushed onto the stack. + #[doc(hidden)] + #[cfg(not(tarpaulin_include))] + pub unsafe fn entrypoint(self, state: *mut ffi::lua_State, func: F) -> c_int + where + F: Fn(&Lua, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, + R: IntoLua, + { + let extra = self.lock().extra.get(); + // `self` is no longer needed and must be dropped at this point to avoid possible memory leak + // in case of possible longjmp (lua_error) below + drop(self); + + callback_error_ext(state, extra, move |nargs| { + let lua = (*extra).lua(); + let rawlua = lua.lock(); + let _guard = StateGuard::new(&rawlua, state); + let args = A::from_stack_args(nargs, 1, None, &rawlua)?; + func(lua, args)?.push_into_stack(&rawlua)?; + Ok(1) + }) + } + + // A simple module entrypoint without arguments + #[doc(hidden)] + #[cfg(not(tarpaulin_include))] + pub unsafe fn entrypoint1(self, state: *mut ffi::lua_State, func: F) -> c_int + where + R: IntoLua, + F: Fn(&Lua) -> Result + MaybeSend + 'static, + { + self.entrypoint(state, move |lua, _: ()| func(lua)) + } + + /// Skips memory checks for some operations. + #[doc(hidden)] + #[cfg(feature = "module")] + pub fn skip_memory_check(&self, skip: bool) { + unsafe { (*self.extra.get()).skip_memory_check = skip }; + } + + /// Enables (or disables) sandbox mode on this Lua instance. + /// + /// This method, in particular: + /// - Set all libraries to read-only + /// - Set all builtin metatables to read-only + /// - Set globals to read-only (and activates safeenv) + /// - Setup local environment table that performs writes locally and proxies reads + /// to the global environment. + /// + /// # Examples + /// + /// ``` + /// # use mlua::{Lua, Result}; + /// # fn main() -> Result<()> { + /// let lua = Lua::new(); + /// + /// lua.sandbox(true)?; + /// lua.load("var = 123").exec()?; + /// assert_eq!(lua.globals().get::<_, u32>("var")?, 123); + /// + /// // Restore the global environment (clear changes made in sandbox) + /// lua.sandbox(false)?; + /// assert_eq!(lua.globals().get::<_, Option>("var")?, None); + /// # Ok(()) + /// # } + /// ``` + /// + /// Requires `feature = "luau"` + #[cfg(any(feature = "luau", docsrs))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn sandbox(&self, enabled: bool) -> Result<()> { + let lua = self.lock(); + unsafe { + if (*lua.extra.get()).sandboxed != enabled { + let state = lua.main_state; + check_stack(state, 3)?; + protect_lua!(state, 0, 0, |state| { + if enabled { + ffi::luaL_sandbox(state, 1); + ffi::luaL_sandboxthread(state); + } else { + // Restore original `LUA_GLOBALSINDEX` + ffi::lua_xpush(lua.ref_thread(), state, ffi::LUA_GLOBALSINDEX); + ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX); + ffi::luaL_sandbox(state, 0); + } + })?; + (*lua.extra.get()).sandboxed = enabled; + } + Ok(()) + } + } + + /// Sets a 'hook' function that will periodically be called as Lua code executes. + /// + /// When exactly the hook function is called depends on the contents of the `triggers` + /// parameter, see [`HookTriggers`] for more details. + /// + /// The provided hook function can error, and this error will be propagated through the Lua code + /// that was executing at the time the hook was triggered. This can be used to implement a + /// limited form of execution limits by setting [`HookTriggers.every_nth_instruction`] and + /// erroring once an instruction limit has been reached. + /// + /// This method sets a hook function for the current thread of this Lua instance. + /// If you want to set a hook function for another thread (coroutine), use [`Thread::set_hook()`] instead. + /// + /// Please note you cannot have more than one hook function set at a time for this Lua instance. + /// + /// # Example + /// + /// Shows each line number of code being executed by the Lua interpreter. + /// + /// ``` + /// # use mlua::{Lua, HookTriggers, Result}; + /// # fn main() -> Result<()> { + /// let lua = Lua::new(); + /// lua.set_hook(HookTriggers::EVERY_LINE, |_lua, debug| { + /// println!("line {}", debug.curr_line()); + /// Ok(()) + /// }); + /// + /// lua.load(r#" + /// local x = 2 + 3 + /// local y = x * 63 + /// local z = string.len(x..", "..y) + /// "#).exec() + /// # } + /// ``` + /// + /// [`HookTriggers`]: crate::HookTriggers + /// [`HookTriggers.every_nth_instruction`]: crate::HookTriggers::every_nth_instruction + #[cfg(not(feature = "luau"))] + #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] + pub fn set_hook(&self, triggers: HookTriggers, callback: F) + where + F: Fn(&Lua, Debug) -> Result<()> + MaybeSend + 'static, + { + let lua = self.lock(); + unsafe { lua.set_thread_hook(lua.state(), triggers, callback) }; + } + + /// Removes any hook previously set by [`Lua::set_hook()`] or [`Thread::set_hook()`]. + /// + /// This function has no effect if a hook was not previously set. + #[cfg(not(feature = "luau"))] + #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] + pub fn remove_hook(&self) { + let lua = self.lock(); + unsafe { + let state = lua.state(); + ffi::lua_sethook(state, None, 0, 0); + match crate::util::get_main_state(lua.main_state) { + Some(main_state) if !ptr::eq(state, main_state) => { + // If main_state is different from state, remove hook from it too + ffi::lua_sethook(main_state, None, 0, 0); + } + _ => {} + }; + (*lua.extra.get()).hook_callback = None; + (*lua.extra.get()).hook_thread = ptr::null_mut(); + } + } + + /// Sets an 'interrupt' function that will periodically be called by Luau VM. + /// + /// Any Luau code is guaranteed to call this handler "eventually" + /// (in practice this can happen at any function call or at any loop iteration). + /// + /// The provided interrupt function can error, and this error will be propagated through + /// the Luau code that was executing at the time the interrupt was triggered. + /// Also this can be used to implement continuous execution limits by instructing Luau VM to yield + /// by returning [`VmState::Yield`]. + /// + /// This is similar to [`Lua::set_hook`] but in more simplified form. + /// + /// # Example + /// + /// Periodically yield Luau VM to suspend execution. + /// + /// ``` + /// # use std::sync::{Arc, atomic::{AtomicU64, Ordering}}; + /// # use mlua::{Lua, Result, ThreadStatus, VmState}; + /// # fn main() -> Result<()> { + /// let lua = Lua::new(); + /// let count = Arc::new(AtomicU64::new(0)); + /// lua.set_interrupt(move |_| { + /// if count.fetch_add(1, Ordering::Relaxed) % 2 == 0 { + /// return Ok(VmState::Yield); + /// } + /// Ok(VmState::Continue) + /// }); + /// + /// let co = lua.create_thread( + /// lua.load(r#" + /// local b = 0 + /// for _, x in ipairs({1, 2, 3}) do b += x end + /// "#) + /// .into_function()?, + /// )?; + /// while co.status() == ThreadStatus::Resumable { + /// co.resume(())?; + /// } + /// # Ok(()) + /// # } + /// ``` + #[cfg(any(feature = "luau", docsrs))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn set_interrupt(&self, callback: F) + where + F: Fn(&Lua) -> Result + MaybeSend + 'static, + { + unsafe extern "C-unwind" fn interrupt_proc(state: *mut ffi::lua_State, gc: c_int) { + if gc >= 0 { + // We don't support GC interrupts since they cannot survive Lua exceptions + return; + } + let extra = ExtraData::get(state); + let result = callback_error_ext(state, extra, move |_| { + let interrupt_cb = (*extra).interrupt_callback.clone(); + let interrupt_cb = + mlua_expect!(interrupt_cb, "no interrupt callback set in interrupt_proc"); + if Arc::strong_count(&interrupt_cb) > 2 { + return Ok(VmState::Continue); // Don't allow recursion + } + let _guard = StateGuard::new((*extra).raw_lua(), state); + interrupt_cb((*extra).lua()) + }); + match result { + VmState::Continue => {} + VmState::Yield => { + ffi::lua_yield(state, 0); + } + } + } + + // Set interrupt callback + let lua = self.lock(); + unsafe { + (*lua.extra.get()).interrupt_callback = Some(Arc::new(callback)); + (*ffi::lua_callbacks(lua.main_state)).interrupt = Some(interrupt_proc); + } + } + + /// Removes any 'interrupt' previously set by `set_interrupt`. + /// + /// This function has no effect if an 'interrupt' was not previously set. + #[cfg(any(feature = "luau", docsrs))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn remove_interrupt(&self) { + let lua = self.lock(); + unsafe { + (*lua.extra.get()).interrupt_callback = None; + (*ffi::lua_callbacks(lua.main_state)).interrupt = None; + } + } + + /// Sets the warning function to be used by Lua to emit warnings. + /// + /// Requires `feature = "lua54"` + #[cfg(feature = "lua54")] + #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] + pub fn set_warning_function(&self, callback: F) + where + F: Fn(&Lua, &str, bool) -> Result<()> + MaybeSend + 'static, + { + use std::ffi::CStr; + use std::os::raw::c_char; + use std::string::String as StdString; + + unsafe extern "C-unwind" fn warn_proc(ud: *mut c_void, msg: *const c_char, tocont: c_int) { + let extra = ud as *mut ExtraData; + callback_error_ext((*extra).raw_lua().state(), extra, |_| { + let cb = mlua_expect!( + (*extra).warn_callback.as_ref(), + "no warning callback set in warn_proc" + ); + let msg = StdString::from_utf8_lossy(CStr::from_ptr(msg).to_bytes()); + cb((*extra).lua(), &msg, tocont != 0) + }); + } + + let lua = self.lock(); + let state = lua.main_state; + unsafe { + (*lua.extra.get()).warn_callback = Some(Box::new(callback)); + ffi::lua_setwarnf(state, Some(warn_proc), lua.extra.get() as *mut c_void); + } + } + + /// Removes warning function previously set by `set_warning_function`. + /// + /// This function has no effect if a warning function was not previously set. + /// + /// Requires `feature = "lua54"` + #[cfg(feature = "lua54")] + #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] + pub fn remove_warning_function(&self) { + let lua = self.lock(); + unsafe { + (*lua.extra.get()).warn_callback = None; + ffi::lua_setwarnf(lua.main_state, None, ptr::null_mut()); + } + } + + /// Emits a warning with the given message. + /// + /// A message in a call with `incomplete` set to `true` should be continued in + /// another call to this function. + /// + /// Requires `feature = "lua54"` + #[cfg(feature = "lua54")] + #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] + pub fn warning(&self, msg: impl AsRef, incomplete: bool) { + let msg = msg.as_ref(); + let mut bytes = vec![0; msg.len() + 1]; + bytes[..msg.len()].copy_from_slice(msg.as_bytes()); + let real_len = bytes.iter().position(|&c| c == 0).unwrap(); + bytes.truncate(real_len); + let lua = self.lock(); + unsafe { + ffi::lua_warning(lua.state(), bytes.as_ptr() as *const _, incomplete as c_int); + } + } + + /// Gets information about the interpreter runtime stack. + /// + /// This function returns [`Debug`] structure that can be used to get information about the function + /// executing at a given level. Level `0` is the current running function, whereas level `n+1` is the + /// function that has called level `n` (except for tail calls, which do not count in the stack). + /// + /// [`Debug`]: crate::hook::Debug + pub fn inspect_stack(&self, level: usize) -> Option { + let lua = self.lock(); + unsafe { + let mut ar: ffi::lua_Debug = mem::zeroed(); + let level = level as c_int; + #[cfg(not(feature = "luau"))] + if ffi::lua_getstack(lua.state(), level, &mut ar) == 0 { + return None; + } + #[cfg(feature = "luau")] + if ffi::lua_getinfo(lua.state(), level, cstr!(""), &mut ar) == 0 { + return None; + } + Some(Debug::new_owned(lua, level, ar)) + } + } + + /// Returns the amount of memory (in bytes) currently used inside this Lua state. + pub fn used_memory(&self) -> usize { + let lua = self.lock(); + unsafe { + match MemoryState::get(lua.main_state) { + mem_state if !mem_state.is_null() => (*mem_state).used_memory(), + _ => { + // Get data from the Lua GC + let used_kbytes = ffi::lua_gc(lua.main_state, ffi::LUA_GCCOUNT, 0); + let used_kbytes_rem = ffi::lua_gc(lua.main_state, ffi::LUA_GCCOUNTB, 0); + (used_kbytes as usize) * 1024 + (used_kbytes_rem as usize) + } + } + } + } + + /// Sets a memory limit (in bytes) on this Lua state. + /// + /// Once an allocation occurs that would pass this memory limit, + /// a `Error::MemoryError` is generated instead. + /// Returns previous limit (zero means no limit). + /// + /// Does not work in module mode where Lua state is managed externally. + pub fn set_memory_limit(&self, limit: usize) -> Result { + let lua = self.lock(); + unsafe { + match MemoryState::get(lua.main_state) { + mem_state if !mem_state.is_null() => Ok((*mem_state).set_memory_limit(limit)), + _ => Err(Error::MemoryLimitNotAvailable), + } + } + } + + /// Returns true if the garbage collector is currently running automatically. + /// + /// Requires `feature = "lua54/lua53/lua52/luau"` + #[cfg(any( + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luau" + ))] + pub fn gc_is_running(&self) -> bool { + let lua = self.lock(); + unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCISRUNNING, 0) != 0 } + } + + /// Stop the Lua GC from running + pub fn gc_stop(&self) { + let lua = self.lock(); + unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCSTOP, 0) }; + } + + /// Restarts the Lua GC if it is not running + pub fn gc_restart(&self) { + let lua = self.lock(); + unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCRESTART, 0) }; + } + + /// Perform a full garbage-collection cycle. + /// + /// It may be necessary to call this function twice to collect all currently unreachable + /// objects. Once to finish the current gc cycle, and once to start and finish the next cycle. + pub fn gc_collect(&self) -> Result<()> { + let lua = self.lock(); + unsafe { + check_stack(lua.main_state, 2)?; + protect_lua!(lua.main_state, 0, 0, fn(state) ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0)) + } + } + + /// Steps the garbage collector one indivisible step. + /// + /// Returns true if this has finished a collection cycle. + pub fn gc_step(&self) -> Result { + self.gc_step_kbytes(0) + } + + /// Steps the garbage collector as though memory had been allocated. + /// + /// if `kbytes` is 0, then this is the same as calling `gc_step`. Returns true if this step has + /// finished a collection cycle. + pub fn gc_step_kbytes(&self, kbytes: c_int) -> Result { + let lua = self.lock(); + unsafe { + check_stack(lua.main_state, 3)?; + protect_lua!(lua.main_state, 0, 0, |state| { + ffi::lua_gc(state, ffi::LUA_GCSTEP, kbytes) != 0 + }) + } + } + + /// Sets the 'pause' value of the collector. + /// + /// Returns the previous value of 'pause'. More information can be found in the Lua + /// [documentation]. + /// + /// For Luau this parameter sets GC goal + /// + /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 + pub fn gc_set_pause(&self, pause: c_int) -> c_int { + let lua = self.lock(); + unsafe { + #[cfg(not(feature = "luau"))] + return ffi::lua_gc(lua.main_state, ffi::LUA_GCSETPAUSE, pause); + #[cfg(feature = "luau")] + return ffi::lua_gc(lua.main_state, ffi::LUA_GCSETGOAL, pause); + } + } + + /// Sets the 'step multiplier' value of the collector. + /// + /// Returns the previous value of the 'step multiplier'. More information can be found in the + /// Lua [documentation]. + /// + /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 + pub fn gc_set_step_multiplier(&self, step_multiplier: c_int) -> c_int { + let lua = self.lock(); + unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCSETSTEPMUL, step_multiplier) } + } + + /// Changes the collector to incremental mode with the given parameters. + /// + /// Returns the previous mode (always `GCMode::Incremental` in Lua < 5.4). + /// More information can be found in the Lua [documentation]. + /// + /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5.1 + pub fn gc_inc(&self, pause: c_int, step_multiplier: c_int, step_size: c_int) -> GCMode { + let lua = self.lock(); + let state = lua.main_state; + + #[cfg(any( + feature = "lua53", + feature = "lua52", + feature = "lua51", + feature = "luajit", + feature = "luau" + ))] + unsafe { + if pause > 0 { + #[cfg(not(feature = "luau"))] + ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause); + #[cfg(feature = "luau")] + ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause); + } + + if step_multiplier > 0 { + ffi::lua_gc(state, ffi::LUA_GCSETSTEPMUL, step_multiplier); + } + + #[cfg(feature = "luau")] + if step_size > 0 { + ffi::lua_gc(state, ffi::LUA_GCSETSTEPSIZE, step_size); + } + #[cfg(not(feature = "luau"))] + let _ = step_size; // Ignored + + GCMode::Incremental + } + + #[cfg(feature = "lua54")] + let prev_mode = + unsafe { ffi::lua_gc(state, ffi::LUA_GCINC, pause, step_multiplier, step_size) }; + #[cfg(feature = "lua54")] + match prev_mode { + ffi::LUA_GCINC => GCMode::Incremental, + ffi::LUA_GCGEN => GCMode::Generational, + _ => unreachable!(), + } + } + + /// Changes the collector to generational mode with the given parameters. + /// + /// Returns the previous mode. More information about the generational GC + /// can be found in the Lua 5.4 [documentation][lua_doc]. + /// + /// Requires `feature = "lua54"` + /// + /// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#2.5.2 + #[cfg(feature = "lua54")] + #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] + pub fn gc_gen(&self, minor_multiplier: c_int, major_multiplier: c_int) -> GCMode { + let lua = self.lock(); + let state = lua.main_state; + let prev_mode = + unsafe { ffi::lua_gc(state, ffi::LUA_GCGEN, minor_multiplier, major_multiplier) }; + match prev_mode { + ffi::LUA_GCGEN => GCMode::Generational, + ffi::LUA_GCINC => GCMode::Incremental, + _ => unreachable!(), + } + } + + /// Sets a default Luau compiler (with custom options). + /// + /// This compiler will be used by default to load all Lua chunks + /// including via `require` function. + /// + /// See [`Compiler`] for details and possible options. + /// + /// Requires `feature = "luau"` + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn set_compiler(&self, compiler: Compiler) { + let lua = self.lock(); + unsafe { (*lua.extra.get()).compiler = Some(compiler) }; + } + + /// Toggles JIT compilation mode for new chunks of code. + /// + /// By default JIT is enabled. Changing this option does not have any effect on + /// already loaded functions. + #[cfg(any(feature = "luau-jit", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau-jit")))] + pub fn enable_jit(&self, enable: bool) { + unsafe { (*self.extra.get()).enable_jit = enable }; + } + + /// Sets Luau feature flag (global setting). + /// + /// See https://github.com/luau-lang/luau/blob/master/CONTRIBUTING.md#feature-flags for details. + #[cfg(feature = "luau")] + #[doc(hidden)] + #[allow(clippy::result_unit_err)] + pub fn set_fflag(name: &str, enabled: bool) -> StdResult<(), ()> { + if let Ok(name) = std::ffi::CString::new(name) { + if unsafe { ffi::luau_setfflag(name.as_ptr(), enabled as c_int) != 0 } { + return Ok(()); + } + } + Err(()) + } + + /// Returns Lua source code as a `Chunk` builder type. + /// + /// In order to actually compile or run the resulting code, you must call [`Chunk::exec`] or + /// similar on the returned builder. Code is not even parsed until one of these methods is + /// called. + /// + /// [`Chunk::exec`]: crate::Chunk::exec + #[track_caller] + pub fn load<'a>(&self, chunk: impl AsChunk<'a>) -> Chunk<'a> { + let caller = Location::caller(); + Chunk { + lua: self.weak(), + name: chunk.name().unwrap_or_else(|| caller.to_string()), + env: chunk.environment(self), + mode: chunk.mode(), + source: chunk.source(), + #[cfg(feature = "luau")] + compiler: unsafe { (*self.lock().extra.get()).compiler.clone() }, + } + } + + /// Create and return an interned Lua string. Lua strings can be arbitrary `[u8]` data including + /// embedded nulls, so in addition to `&str` and `&String`, you can also pass plain `&[u8]` + /// here. + #[inline] + pub fn create_string(&self, s: impl AsRef<[u8]>) -> Result { + unsafe { self.lock().create_string(s) } + } + + /// Create and return a Luau [buffer] object from a byte slice of data. + /// + /// Requires `feature = "luau"` + /// + /// [buffer]: https://luau-lang.org/library#buffer-library + #[cfg(feature = "luau")] + pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result { + use crate::types::SubtypeId; + + let lua = self.lock(); + let state = lua.state(); + unsafe { + if lua.unlikely_memory_error() { + crate::util::push_buffer(lua.ref_thread(), buf.as_ref(), false)?; + return Ok(AnyUserData(lua.pop_ref_thread(), SubtypeId::Buffer)); + } + + let _sg = StackGuard::new(state); + check_stack(state, 4)?; + crate::util::push_buffer(state, buf.as_ref(), true)?; + Ok(AnyUserData(lua.pop_ref(), SubtypeId::Buffer)) + } + } + + /// Creates and returns a new empty table. + pub fn create_table(&self) -> Result
{ + self.create_table_with_capacity(0, 0) + } + + /// Creates and returns a new empty table, with the specified capacity. + /// `narr` is a hint for how many elements the table will have as a sequence; + /// `nrec` is a hint for how many other elements the table will have. + /// Lua may use these hints to preallocate memory for the new table. + pub fn create_table_with_capacity(&self, narr: usize, nrec: usize) -> Result
{ + unsafe { self.lock().create_table_with_capacity(narr, nrec) } + } + + /// Creates a table and fills it with values from an iterator. + pub fn create_table_from(&self, iter: I) -> Result
+ where + K: IntoLua, + V: IntoLua, + I: IntoIterator, + { + let lua = self.lock(); + let state = lua.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 6)?; + + let iter = iter.into_iter(); + let lower_bound = iter.size_hint().0; + let protect = !lua.unlikely_memory_error(); + push_table(state, 0, lower_bound, protect)?; + for (k, v) in iter { + lua.push(k)?; + lua.push(v)?; + if protect { + protect_lua!(state, 3, 1, fn(state) ffi::lua_rawset(state, -3))?; + } else { + ffi::lua_rawset(state, -3); + } + } + + Ok(Table(lua.pop_ref())) + } + } + + /// Creates a table from an iterator of values, using `1..` as the keys. + pub fn create_sequence_from(&self, iter: I) -> Result
+ where + T: IntoLua, + I: IntoIterator, + { + unsafe { self.lock().create_sequence_from(iter) } + } + + /// Wraps a Rust function or closure, creating a callable Lua function handle to it. + /// + /// The function's return value is always a `Result`: If the function returns `Err`, the error + /// is raised as a Lua error, which can be caught using `(x)pcall` or bubble up to the Rust code + /// that invoked the Lua code. This allows using the `?` operator to propagate errors through + /// intermediate Lua code. + /// + /// If the function returns `Ok`, the contained value will be converted to one or more Lua + /// values. For details on Rust-to-Lua conversions, refer to the [`IntoLua`] and [`IntoLuaMulti`] + /// traits. + /// + /// # Examples + /// + /// Create a function which prints its argument: + /// + /// ``` + /// # use mlua::{Lua, Result}; + /// # fn main() -> Result<()> { + /// # let lua = Lua::new(); + /// let greet = lua.create_function(|_, name: String| { + /// println!("Hello, {}!", name); + /// Ok(()) + /// }); + /// # let _ = greet; // used + /// # Ok(()) + /// # } + /// ``` + /// + /// Use tuples to accept multiple arguments: + /// + /// ``` + /// # use mlua::{Lua, Result}; + /// # fn main() -> Result<()> { + /// # let lua = Lua::new(); + /// let print_person = lua.create_function(|_, (name, age): (String, u8)| { + /// println!("{} is {} years old!", name, age); + /// Ok(()) + /// }); + /// # let _ = print_person; // used + /// # Ok(()) + /// # } + /// ``` + /// + /// [`IntoLua`]: crate::IntoLua + /// [`IntoLuaMulti`]: crate::IntoLuaMulti + pub fn create_function(&self, func: F) -> Result + where + F: Fn(&Lua, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, + R: IntoLuaMulti, + { + (self.lock()).create_callback(Box::new(move |rawlua, nargs| unsafe { + let args = A::from_stack_args(nargs, 1, None, rawlua)?; + func(rawlua.lua(), args)?.push_into_stack_multi(rawlua) + })) + } + + /// Wraps a Rust mutable closure, creating a callable Lua function handle to it. + /// + /// This is a version of [`create_function`] that accepts a FnMut argument. Refer to + /// [`create_function`] for more information about the implementation. + /// + /// [`create_function`]: #method.create_function + pub fn create_function_mut(&self, func: F) -> Result + where + F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, + R: IntoLuaMulti, + { + let func = RefCell::new(func); + self.create_function(move |lua, args| { + (*func + .try_borrow_mut() + .map_err(|_| Error::RecursiveMutCallback)?)(lua, args) + }) + } + + /// Wraps a C function, creating a callable Lua function handle to it. + /// + /// # Safety + /// This function is unsafe because provides a way to execute unsafe C function. + pub unsafe fn create_c_function(&self, func: ffi::lua_CFunction) -> Result { + let lua = self.lock(); + ffi::lua_pushcfunction(lua.ref_thread(), func); + Ok(Function(lua.pop_ref_thread())) + } + + /// Wraps a Rust async function or closure, creating a callable Lua function handle to it. + /// + /// While executing the function Rust will poll Future and if the result is not ready, call + /// `yield()` passing internal representation of a `Poll::Pending` value. + /// + /// The function must be called inside Lua coroutine ([`Thread`]) to be able to suspend its execution. + /// An executor should be used to poll [`AsyncThread`] and mlua will take a provided Waker + /// in that case. Otherwise noop waker will be used if try to call the function outside of Rust + /// executors. + /// + /// The family of `call_async()` functions takes care about creating [`Thread`]. + /// + /// Requires `feature = "async"` + /// + /// # Examples + /// + /// Non blocking sleep: + /// + /// ``` + /// use std::time::Duration; + /// use mlua::{Lua, Result}; + /// + /// async fn sleep(_lua: &Lua, n: u64) -> Result<&'static str> { + /// tokio::time::sleep(Duration::from_millis(n)).await; + /// Ok("done") + /// } + /// + /// #[tokio::main] + /// async fn main() -> Result<()> { + /// let lua = Lua::new(); + /// lua.globals().set("sleep", lua.create_async_function(sleep)?)?; + /// let res: String = lua.load("return sleep(...)").call_async(100).await?; // Sleep 100ms + /// assert_eq!(res, "done"); + /// Ok(()) + /// } + /// ``` + /// + /// [`Thread`]: crate::Thread + /// [`AsyncThread`]: crate::AsyncThread + #[cfg(feature = "async")] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + pub fn create_async_function<'lua, 'a, F, A, FR, R>(&'lua self, func: F) -> Result + where + 'lua: 'a, + F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, + A: FromLuaMulti, + FR: Future> + 'a, + R: IntoLuaMulti, + { + (self.lock()).create_async_callback(Box::new(move |rawlua, args| unsafe { + let lua = rawlua.lua(); + let args = match A::from_lua_args(args, 1, None, lua) { + Ok(args) => args, + Err(e) => return Box::pin(future::ready(Err(e))), + }; + let fut = func(lua, args); + Box::pin(async move { fut.await?.push_into_stack_multi(rawlua) }) + })) + } + + /// Wraps a Lua function into a new thread (or coroutine). + /// + /// Equivalent to `coroutine.create`. + pub fn create_thread(&self, func: Function) -> Result { + unsafe { self.lock().create_thread(&func) } + } + + /// Creates a Lua userdata object from a custom userdata type. + /// + /// All userdata instances of the same type `T` shares the same metatable. + #[inline] + pub fn create_userdata(&self, data: T) -> Result + where + T: UserData + MaybeSend + 'static, + { + unsafe { self.lock().make_userdata(UserDataVariant::new(data)) } + } + + /// Creates a Lua userdata object from a custom serializable userdata type. + /// + /// Requires `feature = "serialize"` + #[cfg(feature = "serialize")] + #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] + #[inline] + pub fn create_ser_userdata(&self, data: T) -> Result + where + T: UserData + Serialize + MaybeSend + 'static, + { + unsafe { self.lock().make_userdata(UserDataVariant::new_ser(data)) } + } + + /// Creates a Lua userdata object from a custom Rust type. + /// + /// You can register the type using [`Lua::register_userdata_type()`] to add fields or methods + /// _before_ calling this method. + /// Otherwise, the userdata object will have an empty metatable. + /// + /// All userdata instances of the same type `T` shares the same metatable. + #[inline] + pub fn create_any_userdata(&self, data: T) -> Result + where + T: MaybeSend + 'static, + { + unsafe { self.lock().make_any_userdata(UserDataVariant::new(data)) } + } + + /// Creates a Lua userdata object from a custom serializable Rust type. + /// + /// See [`Lua::create_any_userdata()`] for more details. + /// + /// Requires `feature = "serialize"` + #[cfg(feature = "serialize")] + #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] + #[inline] + pub fn create_ser_any_userdata(&self, data: T) -> Result + where + T: Serialize + MaybeSend + 'static, + { + unsafe { (self.lock()).make_any_userdata(UserDataVariant::new_ser(data)) } + } + + /// Registers a custom Rust type in Lua to use in userdata objects. + /// + /// This methods provides a way to add fields or methods to userdata objects of a type `T`. + pub fn register_userdata_type( + &self, + f: impl FnOnce(&mut UserDataRegistry), + ) -> Result<()> { + let mut registry = UserDataRegistry::new(); + f(&mut registry); + + let lua = self.lock(); + unsafe { + // Deregister the type if it already registered + let type_id = TypeId::of::(); + if let Some(&table_id) = (*lua.extra.get()).registered_userdata.get(&type_id) { + ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, table_id); + } + + // Register the type + lua.register_userdata_metatable(registry)?; + } + Ok(()) + } + + /// Create a Lua userdata "proxy" object from a custom userdata type. + /// + /// Proxy object is an empty userdata object that has `T` metatable attached. + /// The main purpose of this object is to provide access to static fields and functions + /// without creating an instance of type `T`. + /// + /// You can get or set uservalues on this object but you cannot borrow any Rust type. + /// + /// # Examples + /// + /// ``` + /// # use mlua::{Lua, Result, UserData, UserDataFields, UserDataMethods}; + /// # fn main() -> Result<()> { + /// # let lua = Lua::new(); + /// struct MyUserData(i32); + /// + /// impl UserData for MyUserData { + /// fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { + /// fields.add_field_method_get("val", |_, this| Ok(this.0)); + /// } + /// + /// fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { + /// methods.add_function("new", |_, value: i32| Ok(MyUserData(value))); + /// } + /// } + /// + /// lua.globals().set("MyUserData", lua.create_proxy::()?)?; + /// + /// lua.load("assert(MyUserData.new(321).val == 321)").exec()?; + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn create_proxy(&self) -> Result + where + T: UserData + 'static, + { + let ud = UserDataProxy::(PhantomData); + unsafe { self.lock().make_userdata(UserDataVariant::new(ud)) } + } + + /// Sets the metatable for a Luau builtin vector type. + #[cfg(any(all(feature = "luau", feature = "unstable"), doc))] + #[cfg_attr(docsrs, doc(cfg(all(feature = "luau", feature = "unstable"))))] + pub fn set_vector_metatable(&self, metatable: Option
) { + let lua = self.lock(); + unsafe { + let state = lua.state(); + let _sg = StackGuard::new(state); + assert_stack(state, 2); + + #[cfg(not(feature = "luau-vector4"))] + ffi::lua_pushvector(state, 0., 0., 0.); + #[cfg(feature = "luau-vector4")] + ffi::lua_pushvector(state, 0., 0., 0., 0.); + match metatable { + Some(metatable) => lua.push_ref(&metatable.0), + None => ffi::lua_pushnil(state), + }; + ffi::lua_setmetatable(state, -2); + } + } + + /// Returns a handle to the global environment. + pub fn globals(&self) -> Table { + let lua = self.lock(); + let state = lua.state(); + unsafe { + let _sg = StackGuard::new(state); + assert_stack(state, 1); + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + ffi::lua_pushvalue(state, ffi::LUA_GLOBALSINDEX); + Table(lua.pop_ref()) + } + } + + /// Returns a handle to the active `Thread`. For calls to `Lua` this will be the main Lua thread, + /// for parameters given to a callback, this will be whatever Lua thread called the callback. + pub fn current_thread(&self) -> Thread { + let lua = self.lock(); + let state = lua.state(); + unsafe { + let _sg = StackGuard::new(state); + assert_stack(state, 1); + ffi::lua_pushthread(state); + Thread(lua.pop_ref(), state) + } + } + + /// Calls the given function with a `Scope` parameter, giving the function the ability to create + /// userdata and callbacks from rust types that are !Send or non-'static. + /// + /// The lifetime of any function or userdata created through `Scope` lasts only until the + /// completion of this method call, on completion all such created values are automatically + /// dropped and Lua references to them are invalidated. If a script accesses a value created + /// through `Scope` outside of this method, a Lua error will result. Since we can ensure the + /// lifetime of values created through `Scope`, and we know that `Lua` cannot be sent to another + /// thread while `Scope` is live, it is safe to allow !Send datatypes and whose lifetimes only + /// outlive the scope lifetime. + /// + /// Inside the scope callback, all handles created through Scope will share the same unique 'lua + /// lifetime of the parent `Lua`. This allows scoped and non-scoped values to be mixed in + /// API calls, which is very useful (e.g. passing a scoped userdata to a non-scoped function). + /// However, this also enables handles to scoped values to be trivially leaked from the given + /// callback. This is not dangerous, though! After the callback returns, all scoped values are + /// invalidated, which means that though references may exist, the Rust types backing them have + /// dropped. `Function` types will error when called, and `AnyUserData` will be typeless. It + /// would be impossible to prevent handles to scoped values from escaping anyway, since you + /// would always be able to smuggle them through Lua state. + // pub fn scope<'lua, 'scope, R>( + // &'lua self, + // f: impl FnOnce(&Scope<'lua, 'scope>) -> Result, + // ) -> Result + // where + // 'lua: 'scope, + // { + // f(&Scope::new(self)) + // } + + /// Attempts to coerce a Lua value into a String in a manner consistent with Lua's internal + /// behavior. + /// + /// To succeed, the value must be a string (in which case this is a no-op), an integer, or a + /// number. + pub fn coerce_string(&self, v: Value) -> Result> { + Ok(match v { + Value::String(s) => Some(s), + v => unsafe { + let lua = self.lock(); + let state = lua.state(); + let _sg = StackGuard::new(state); + check_stack(state, 4)?; + + lua.push_value(&v)?; + let res = if lua.unlikely_memory_error() { + ffi::lua_tolstring(state, -1, ptr::null_mut()) + } else { + protect_lua!(state, 1, 1, |state| { + ffi::lua_tolstring(state, -1, ptr::null_mut()) + })? + }; + if !res.is_null() { + Some(String(lua.pop_ref())) + } else { + None + } + }, + }) + } + + /// Attempts to coerce a Lua value into an integer in a manner consistent with Lua's internal + /// behavior. + /// + /// To succeed, the value must be an integer, a floating point number that has an exact + /// representation as an integer, or a string that can be converted to an integer. Refer to the + /// Lua manual for details. + pub fn coerce_integer(&self, v: Value) -> Result> { + Ok(match v { + Value::Integer(i) => Some(i), + v => unsafe { + let lua = self.lock(); + let state = lua.state(); + let _sg = StackGuard::new(state); + check_stack(state, 2)?; + + lua.push_value(&v)?; + let mut isint = 0; + let i = ffi::lua_tointegerx(state, -1, &mut isint); + if isint == 0 { + None + } else { + Some(i) + } + }, + }) + } + + /// Attempts to coerce a Lua value into a Number in a manner consistent with Lua's internal + /// behavior. + /// + /// To succeed, the value must be a number or a string that can be converted to a number. Refer + /// to the Lua manual for details. + pub fn coerce_number(&self, v: Value) -> Result> { + Ok(match v { + Value::Number(n) => Some(n), + v => unsafe { + let lua = self.lock(); + let state = lua.state(); + let _sg = StackGuard::new(state); + check_stack(state, 2)?; + + lua.push_value(&v)?; + let mut isnum = 0; + let n = ffi::lua_tonumberx(state, -1, &mut isnum); + if isnum == 0 { + None + } else { + Some(n) + } + }, + }) + } + + /// Converts a value that implements `IntoLua` into a `Value` instance. + #[inline] + pub fn pack(&self, t: T) -> Result { + t.into_lua(self) + } + + /// Converts a `Value` instance into a value that implements `FromLua`. + #[inline] + pub fn unpack(&self, value: Value) -> Result { + T::from_lua(value, self) + } + + /// Converts a value that implements `IntoLuaMulti` into a `MultiValue` instance. + #[inline] + pub fn pack_multi(&self, t: T) -> Result { + t.into_lua_multi(self) + } + + /// Converts a `MultiValue` instance into a value that implements `FromLuaMulti`. + #[inline] + pub fn unpack_multi(&self, value: MultiValue) -> Result { + T::from_lua_multi(value, self) + } + + /// Set a value in the Lua registry based on a string name. + /// + /// This value will be available to rust from all `Lua` instances which share the same main + /// state. + pub fn set_named_registry_value(&self, name: &str, t: T) -> Result<()> + where + T: IntoLua, + { + let lua = self.lock(); + let state = lua.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 5)?; + + lua.push(t)?; + rawset_field(state, ffi::LUA_REGISTRYINDEX, name) + } + } + + /// Get a value from the Lua registry based on a string name. + /// + /// Any Lua instance which shares the underlying main state may call this method to + /// get a value previously set by [`Lua::set_named_registry_value`]. + pub fn named_registry_value(&self, name: &str) -> Result + where + T: FromLua, + { + let lua = self.lock(); + let state = lua.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 3)?; + + let protect = !lua.unlikely_memory_error(); + push_string(state, name.as_bytes(), protect)?; + ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX); + + T::from_stack(-1, &lua) + } + } + + /// Removes a named value in the Lua registry. + /// + /// Equivalent to calling [`Lua::set_named_registry_value`] with a value of Nil. + pub fn unset_named_registry_value(&self, name: &str) -> Result<()> { + self.set_named_registry_value(name, Nil) + } + + /// Place a value in the Lua registry with an auto-generated key. + /// + /// This value will be available to Rust from all `Lua` instances which share the same main + /// state. + /// + /// Be warned, garbage collection of values held inside the registry is not automatic, see + /// [`RegistryKey`] for more details. + /// However, dropped [`RegistryKey`]s automatically reused to store new values. + pub fn create_registry_value(&self, t: T) -> Result { + let lua = self.lock(); + let state = lua.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 4)?; + + lua.push(t)?; + + let unref_list = (*lua.extra.get()).registry_unref_list.clone(); + + // Check if the value is nil (no need to store it in the registry) + if ffi::lua_isnil(state, -1) != 0 { + return Ok(RegistryKey::new(ffi::LUA_REFNIL, unref_list)); + } + + // Try to reuse previously allocated slot + let free_registry_id = unref_list.lock().as_mut().and_then(|x| x.pop()); + if let Some(registry_id) = free_registry_id { + // It must be safe to replace the value without triggering memory error + ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); + return Ok(RegistryKey::new(registry_id, unref_list)); + } + + // Allocate a new RegistryKey slot + let registry_id = if lua.unlikely_memory_error() { + ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) + } else { + protect_lua!(state, 1, 0, |state| { + ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) + })? + }; + Ok(RegistryKey::new(registry_id, unref_list)) + } + } + + /// Get a value from the Lua registry by its `RegistryKey` + /// + /// Any Lua instance which shares the underlying main state may call this method to get a value + /// previously placed by [`Lua::create_registry_value`]. + pub fn registry_value(&self, key: &RegistryKey) -> Result { + let lua = self.lock(); + if !lua.owns_registry_value(key) { + return Err(Error::MismatchedRegistryKey); + } + + let state = lua.state(); + match key.id() { + ffi::LUA_REFNIL => T::from_lua(Value::Nil, self), + registry_id => unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 1)?; + + ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); + T::from_stack(-1, &lua) + }, + } + } + + /// Removes a value from the Lua registry. + /// + /// You may call this function to manually remove a value placed in the registry with + /// [`Lua::create_registry_value`]. In addition to manual [`RegistryKey`] removal, you can also call + /// [`Lua::expire_registry_values`] to automatically remove values from the registry whose + /// [`RegistryKey`]s have been dropped. + pub fn remove_registry_value(&self, key: RegistryKey) -> Result<()> { + let lua = self.lock(); + if !lua.owns_registry_value(&key) { + return Err(Error::MismatchedRegistryKey); + } + + unsafe { + ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, key.take()); + } + Ok(()) + } + + /// Replaces a value in the Lua registry by its [`RegistryKey`]. + /// + /// See [`Lua::create_registry_value`] for more details. + pub fn replace_registry_value(&self, key: &RegistryKey, t: T) -> Result<()> { + let lua = self.lock(); + if !lua.owns_registry_value(key) { + return Err(Error::MismatchedRegistryKey); + } + + let t = t.into_lua(self)?; + + let state = lua.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 2)?; + + match (t, key.id()) { + (Value::Nil, ffi::LUA_REFNIL) => { + // Do nothing, no need to replace nil with nil + } + (Value::Nil, registry_id) => { + // Remove the value + ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, registry_id); + key.set_id(ffi::LUA_REFNIL); + } + (value, ffi::LUA_REFNIL) => { + // Allocate a new `RegistryKey` + let new_key = self.create_registry_value(value)?; + key.set_id(new_key.take()); + } + (value, registry_id) => { + // It must be safe to replace the value without triggering memory error + lua.push_value(&value)?; + ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); + } + } + } + Ok(()) + } + + /// Returns true if the given [`RegistryKey`] was created by a [`Lua`] which shares the underlying + /// main state with this [`Lua`] instance. + /// + /// Other than this, methods that accept a [`RegistryKey`] will return + /// [`Error::MismatchedRegistryKey`] if passed a [`RegistryKey`] that was not created with a + /// matching [`Lua`] state. + #[inline] + pub fn owns_registry_value(&self, key: &RegistryKey) -> bool { + self.lock().owns_registry_value(key) + } + + /// Remove any registry values whose [`RegistryKey`]s have all been dropped. + /// + /// Unlike normal handle values, [`RegistryKey`]s do not automatically remove themselves on Drop, + /// but you can call this method to remove any unreachable registry values not manually removed + /// by [`Lua::remove_registry_value`]. + pub fn expire_registry_values(&self) { + let lua = self.lock(); + let state = lua.state(); + unsafe { + let mut unref_list = (*lua.extra.get()).registry_unref_list.lock(); + let unref_list = mem::replace(&mut *unref_list, Some(Vec::new())); + for id in mlua_expect!(unref_list, "unref list not set") { + ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, id); + } + } + } + + /// 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. + /// + /// # Panics + /// + /// Panics if the app data container is currently borrowed. + /// + /// # 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(()) + /// } + /// ``` + #[track_caller] + pub fn set_app_data(&self, data: T) -> Option { + let lua = self.lock(); + let extra = unsafe { &*lua.extra.get() }; + extra.app_data.insert(data) + } + + /// Tries to set or replace an application data object of type `T`. + /// + /// Returns: + /// - `Ok(Some(old_data))` if the data object of type `T` was successfully replaced. + /// - `Ok(None)` if the data object of type `T` was successfully inserted. + /// - `Err(data)` if the data object of type `T` was not inserted because the container is currently borrowed. + /// + /// See [`Lua::set_app_data()`] for examples. + pub fn try_set_app_data(&self, data: T) -> StdResult, T> { + let lua = self.lock(); + let extra = unsafe { &*lua.extra.get() }; + extra.app_data.try_insert(data) + } + + /// Gets a reference to an application data object stored by [`Lua::set_app_data()`] of type `T`. + /// + /// # Panics + /// + /// Panics if the data object of type `T` is currently mutably borrowed. Multiple immutable reads + /// can be taken out at the same time. + #[track_caller] + pub fn app_data_ref(&self) -> Option> { + let guard = self.lock_arc(); + let extra = unsafe { &*guard.extra.get() }; + extra.app_data.borrow(Some(guard)) + } + + /// Gets a mutable reference to an application data object stored by [`Lua::set_app_data()`] of type `T`. + /// + /// # Panics + /// + /// Panics if the data object of type `T` is currently borrowed. + #[track_caller] + pub fn app_data_mut(&self) -> Option> { + let guard = self.lock_arc(); + let extra = unsafe { &*guard.extra.get() }; + extra.app_data.borrow_mut(Some(guard)) + } + + /// Removes an application data of type `T`. + /// + /// # Panics + /// + /// Panics if the app data container is currently borrowed. + #[track_caller] + pub fn remove_app_data(&self) -> Option { + let lua = self.lock(); + let extra = unsafe { &*lua.extra.get() }; + extra.app_data.remove() + } + + /// Pushes a value that implements `IntoLua` onto the Lua stack. + /// + /// Uses 2 stack spaces, does not call checkstack. + #[doc(hidden)] + #[inline(always)] + pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> { + self.lock().push(value) + } + + /// Returns an internal `Poll::Pending` constant used for executing async callbacks. + #[cfg(feature = "async")] + #[doc(hidden)] + #[inline(always)] + pub fn poll_pending() -> LightUserData { + static ASYNC_POLL_PENDING: u8 = 0; + LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut c_void) + } + + // Luau version located in `luau/mod.rs` + #[cfg(not(feature = "luau"))] + fn disable_c_modules(&self) -> Result<()> { + let package: Table = self.globals().get("package")?; + + package.set( + "loadlib", + self.create_function(|_, ()| -> Result<()> { + Err(Error::SafetyError( + "package.loadlib is disabled in safe mode".to_string(), + )) + })?, + )?; + + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + let searchers: Table = package.get("searchers")?; + #[cfg(any(feature = "lua51", feature = "luajit"))] + let searchers: Table = package.get("loaders")?; + + let loader = self.create_function(|_, ()| Ok("\n\tcan't load C modules in safe mode"))?; + + // The third and fourth searchers looks for a loader as a C library + searchers.raw_set(3, loader)?; + searchers.raw_remove(4)?; + + Ok(()) + } + + #[inline(always)] + pub(crate) fn lock(&self) -> ReentrantMutexGuard { + self.0.lock() + } + + #[inline(always)] + pub(crate) fn lock_arc(&self) -> LuaGuard { + LuaGuard(self.0.lock_arc()) + } + + #[inline(always)] + pub(crate) fn weak(&self) -> WeakLua { + WeakLua(Arc::downgrade(&self.0)) + } +} + +impl WeakLua { + #[track_caller] + #[inline(always)] + pub(crate) fn lock(&self) -> LuaGuard { + LuaGuard::new(self.0.upgrade().unwrap()) + } + + #[inline(always)] + pub(crate) fn try_lock(&self) -> Option { + Some(LuaGuard::new(self.0.upgrade()?)) + } +} + +impl PartialEq for WeakLua { + fn eq(&self, other: &Self) -> bool { + Weak::ptr_eq(&self.0, &other.0) + } +} + +impl Eq for WeakLua {} + +impl LuaGuard { + pub(crate) fn new(handle: Arc>) -> Self { + Self(handle.lock_arc()) + } +} + +impl Deref for LuaGuard { + type Target = RawLua; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +pub(crate) mod extra; +mod raw; +pub(crate) mod util; + +// #[cfg(test)] +// mod assertions { +// use super::*; + +// // Lua has lots of interior mutability, should not be RefUnwindSafe +// static_assertions::assert_not_impl_any!(Lua: std::panic::RefUnwindSafe); + +// #[cfg(not(feature = "send"))] +// static_assertions::assert_not_impl_any!(Lua: Send); +// #[cfg(feature = "send")] +// static_assertions::assert_impl_all!(Lua: Send); +// } diff --git a/src/state/extra.rs b/src/state/extra.rs new file mode 100644 index 00000000..2a075b40 --- /dev/null +++ b/src/state/extra.rs @@ -0,0 +1,236 @@ +use std::any::TypeId; +use std::cell::UnsafeCell; +// use std::collections::VecDeque; +use std::mem::{self, MaybeUninit}; +use std::os::raw::{c_int, c_void}; +use std::ptr; +use std::sync::{Arc, Weak}; + +use parking_lot::{Mutex, ReentrantMutex}; +use rustc_hash::FxHashMap; + +use crate::error::Result; +use crate::state::RawLua; +use crate::stdlib::StdLib; +use crate::types::AppData; +use crate::util::{get_gc_metatable, push_gc_userdata, WrappedFailure}; + +#[cfg(any(feature = "luau", doc))] +use crate::chunk::Compiler; + +#[cfg(feature = "async")] +use {futures_util::task::noop_waker_ref, std::ptr::NonNull, std::task::Waker}; + +use super::{Lua, WeakLua}; + +// Unique key to store `ExtraData` in the registry +static EXTRA_REGISTRY_KEY: u8 = 0; + +const WRAPPED_FAILURE_POOL_SIZE: usize = 64; +// const MULTIVALUE_POOL_SIZE: usize = 64; +const REF_STACK_RESERVE: c_int = 1; + +/// Data associated with the Lua state. +pub(crate) struct ExtraData { + // Same layout as `Lua` + pub(super) lua: MaybeUninit>>, + // Same layout as `WeakLua` + pub(super) weak: MaybeUninit>>, + + pub(super) registered_userdata: FxHashMap, + pub(super) registered_userdata_mt: FxHashMap<*const c_void, Option>, + pub(super) last_checked_userdata_mt: (*const c_void, Option), + + // When Lua instance dropped, setting `None` would prevent collecting `RegistryKey`s + pub(super) registry_unref_list: Arc>>>, + + // Container to store arbitrary data (extensions) + pub(super) app_data: AppData, + + pub(super) safe: bool, + pub(super) libs: StdLib, + #[cfg(feature = "module")] + pub(super) skip_memory_check: bool, + + // Auxiliary thread to store references + pub(super) ref_thread: *mut ffi::lua_State, + pub(super) ref_stack_size: c_int, + pub(super) ref_stack_top: c_int, + pub(super) ref_free: Vec, + + // Pool of `WrappedFailure` enums in the ref thread (as userdata) + pub(super) wrapped_failure_pool: Vec, + // Pool of `MultiValue` containers + // multivalue_pool: Vec>, + // Pool of `Thread`s (coroutines) for async execution + #[cfg(feature = "async")] + pub(super) thread_pool: Vec, + + // Address of `WrappedFailure` metatable + pub(super) wrapped_failure_mt_ptr: *const c_void, + + // Waker for polling futures + #[cfg(feature = "async")] + pub(super) waker: NonNull, + + #[cfg(not(feature = "luau"))] + pub(super) hook_callback: Option, + #[cfg(not(feature = "luau"))] + pub(super) hook_thread: *mut ffi::lua_State, + #[cfg(feature = "lua54")] + pub(super) warn_callback: Option, + #[cfg(feature = "luau")] + pub(super) interrupt_callback: Option, + + #[cfg(feature = "luau")] + pub(super) sandboxed: bool, + #[cfg(feature = "luau")] + pub(super) compiler: Option, + #[cfg(feature = "luau-jit")] + pub(super) enable_jit: bool, +} + +impl Drop for ExtraData { + fn drop(&mut self) { + #[cfg(feature = "module")] + unsafe { + self.inner.assume_init_drop(); + } + unsafe { self.weak.assume_init_drop() }; + *self.registry_unref_list.lock() = None; + } +} + +impl ExtraData { + // Index of `error_traceback` function in auxiliary thread stack + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + pub(super) const ERROR_TRACEBACK_IDX: c_int = 1; + + pub(super) unsafe fn init(state: *mut ffi::lua_State) -> Arc> { + // Create ref stack thread and place it in the registry to prevent it + // from being garbage collected. + let ref_thread = mlua_expect!( + protect_lua!(state, 0, 0, |state| { + let thread = ffi::lua_newthread(state); + ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX); + thread + }), + "Error while creating ref thread", + ); + + let wrapped_failure_mt_ptr = { + get_gc_metatable::(state); + let ptr = ffi::lua_topointer(state, -1); + ffi::lua_pop(state, 1); + ptr + }; + + // Store `error_traceback` function on the ref stack + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + { + ffi::lua_pushcfunction(ref_thread, crate::util::error_traceback); + assert_eq!(ffi::lua_gettop(ref_thread), Self::ERROR_TRACEBACK_IDX); + } + + let extra = Arc::new(UnsafeCell::new(ExtraData { + lua: MaybeUninit::uninit(), + weak: MaybeUninit::uninit(), + registered_userdata: FxHashMap::default(), + registered_userdata_mt: FxHashMap::default(), + last_checked_userdata_mt: (ptr::null(), None), + registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))), + app_data: AppData::default(), + safe: false, + libs: StdLib::NONE, + #[cfg(feature = "module")] + skip_memory_check: false, + ref_thread, + // We need some reserved stack space to move values in and out of the ref stack. + ref_stack_size: ffi::LUA_MINSTACK - REF_STACK_RESERVE, + ref_stack_top: ffi::lua_gettop(ref_thread), + ref_free: Vec::new(), + wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_SIZE), + // multivalue_pool: Vec::with_capacity(MULTIVALUE_POOL_SIZE), + #[cfg(feature = "async")] + thread_pool: Vec::new(), + wrapped_failure_mt_ptr, + #[cfg(feature = "async")] + waker: NonNull::from(noop_waker_ref()), + #[cfg(not(feature = "luau"))] + hook_callback: None, + #[cfg(not(feature = "luau"))] + hook_thread: ptr::null_mut(), + #[cfg(feature = "lua54")] + warn_callback: None, + #[cfg(feature = "luau")] + interrupt_callback: None, + #[cfg(feature = "luau")] + sandboxed: false, + #[cfg(feature = "luau")] + compiler: None, + #[cfg(feature = "luau-jit")] + enable_jit: true, + })); + + // Store it in the registry + mlua_expect!(Self::store(&extra, state), "Error while storing extra data"); + + extra + } + + pub(super) unsafe fn set_lua(&mut self, lua: &Arc>) { + self.lua.write(Arc::clone(lua)); + if cfg!(not(feature = "module")) { + Arc::decrement_strong_count(Arc::as_ptr(lua)); + } + self.weak.write(Arc::downgrade(lua)); + } + + pub(super) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { + #[cfg(feature = "luau")] + if cfg!(not(feature = "module")) { + // In the main app we can use `lua_callbacks` to access ExtraData + return (*ffi::lua_callbacks(state)).userdata as *mut _; + } + + let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void; + if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, extra_key) != ffi::LUA_TUSERDATA { + // `ExtraData` can be null only when Lua state is foreign. + // This case in used in `Lua::try_from_ptr()`. + ffi::lua_pop(state, 1); + return ptr::null_mut(); + } + let extra_ptr = ffi::lua_touserdata(state, -1) as *mut Arc>; + ffi::lua_pop(state, 1); + (*extra_ptr).get() + } + + unsafe fn store(extra: &Arc>, state: *mut ffi::lua_State) -> Result<()> { + #[cfg(feature = "luau")] + if cfg!(not(feature = "module")) { + (*ffi::lua_callbacks(state)).userdata = extra.get() as *mut _; + return Ok(()); + } + + push_gc_userdata(state, Arc::clone(extra), true)?; + protect_lua!(state, 1, 0, fn(state) { + let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void; + ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, extra_key); + }) + } + + #[inline(always)] + pub(super) unsafe fn lua(&self) -> &Lua { + mem::transmute(self.lua.assume_init_ref()) + } + + #[inline(always)] + pub(super) unsafe fn raw_lua(&self) -> &RawLua { + &*self.lua.assume_init_ref().data_ptr() + } + + #[inline(always)] + pub(super) unsafe fn weak(&self) -> &WeakLua { + mem::transmute(self.weak.assume_init_ref()) + } +} diff --git a/src/state/raw.rs b/src/state/raw.rs new file mode 100644 index 00000000..d47cf317 --- /dev/null +++ b/src/state/raw.rs @@ -0,0 +1,1421 @@ +use std::any::TypeId; +use std::cell::{Cell, UnsafeCell}; +use std::ffi::{CStr, CString}; +use std::os::raw::{c_char, c_int, c_void}; +use std::panic::resume_unwind; +use std::result::Result as StdResult; +use std::sync::Arc; +use std::{mem, ptr}; + +use parking_lot::ReentrantMutex; + +use crate::chunk::ChunkMode; +use crate::error::{Error, Result}; +use crate::function::Function; +use crate::memory::{MemoryState, ALLOCATOR}; +use crate::state::util::{callback_error_ext, ref_stack_pop, StateGuard}; +use crate::stdlib::StdLib; +use crate::string::String; +use crate::table::Table; +use crate::thread::Thread; +use crate::types::{ + AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, + LightUserData, MaybeSend, RegistryKey, SubtypeId, ValueRef, +}; +use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataRegistry, UserDataVariant}; +use crate::util::{ + assert_stack, check_stack, get_destructed_userdata_metatable, get_gc_userdata, get_main_state, + get_userdata, init_error_registry, init_gc_metatable, init_userdata_metatable, pop_error, + push_gc_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, + short_type_name, StackGuard, WrappedFailure, +}; +use crate::value::{FromLuaMulti, IntoLua, MultiValue, Nil, Value}; + +use super::extra::ExtraData; +use super::{Lua, LuaOptions, WeakLua}; + +#[cfg(not(feature = "luau"))] +use crate::hook::{Debug, HookTriggers}; + +#[cfg(feature = "async")] +use { + crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue}, + std::ptr::NonNull, + std::task::{Context, Poll, Waker}, +}; + +/// An inner Lua struct which holds a raw Lua state. +pub struct RawLua { + // The state is dynamic and depends on context + pub(super) state: Cell<*mut ffi::lua_State>, + pub(super) main_state: *mut ffi::lua_State, + pub(super) extra: Arc>, +} + +#[cfg(not(feature = "module"))] +impl Drop for RawLua { + fn drop(&mut self) { + unsafe { + let mem_state = MemoryState::get(self.main_state); + + ffi::lua_close(self.main_state); + + // Deallocate `MemoryState` + if !mem_state.is_null() { + drop(Box::from_raw(mem_state)); + } + } + } +} + +impl RawLua { + #[inline(always)] + pub(crate) fn lua(&self) -> &Lua { + unsafe { (*self.extra.get()).lua() } + } + + #[inline(always)] + pub(crate) fn weak(&self) -> &WeakLua { + unsafe { (*self.extra.get()).weak() } + } + + #[inline(always)] + pub(crate) fn state(&self) -> *mut ffi::lua_State { + self.state.get() + } + + #[cfg(feature = "luau")] + #[inline(always)] + pub(crate) fn main_state(&self) -> *mut ffi::lua_State { + self.main_state + } + + #[inline(always)] + pub(crate) fn ref_thread(&self) -> *mut ffi::lua_State { + unsafe { (*self.extra.get()).ref_thread } + } + + pub(super) unsafe fn new(libs: StdLib, options: LuaOptions) -> Arc> { + let mem_state: *mut MemoryState = Box::into_raw(Box::default()); + let mut state = ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void); + // If state is null then switch to Lua internal allocator + if state.is_null() { + drop(Box::from_raw(mem_state)); + state = ffi::luaL_newstate(); + } + assert!(!state.is_null(), "Failed to create a Lua VM"); + + ffi::luaL_requiref(state, cstr!("_G"), ffi::luaopen_base, 1); + ffi::lua_pop(state, 1); + + // Init Luau code generator (jit) + #[cfg(feature = "luau-jit")] + if ffi::luau_codegen_supported() != 0 { + ffi::luau_codegen_create(state); + } + + let rawlua = Self::init_from_ptr(state); + let extra = rawlua.lock().extra.get(); + + mlua_expect!( + load_from_std_lib(state, libs), + "Error during loading standard libraries" + ); + (*extra).libs |= libs; + + if !options.catch_rust_panics { + mlua_expect!( + (|| -> Result<()> { + let _sg = StackGuard::new(state); + + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + ffi::lua_pushvalue(state, ffi::LUA_GLOBALSINDEX); + + ffi::lua_pushcfunction(state, safe_pcall); + rawset_field(state, -2, "pcall")?; + + ffi::lua_pushcfunction(state, safe_xpcall); + rawset_field(state, -2, "xpcall")?; + + Ok(()) + })(), + "Error during applying option `catch_rust_panics`" + ) + } + + #[cfg(feature = "async")] + if options.thread_pool_size > 0 { + (*extra).thread_pool.reserve_exact(options.thread_pool_size); + } + + rawlua + } + + pub(super) unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> Arc> { + assert!(!state.is_null(), "Lua state is NULL"); + if let Some(lua) = Self::try_from_ptr(state) { + return lua; + } + + let main_state = get_main_state(state).unwrap_or(state); + let main_state_top = ffi::lua_gettop(main_state); + + mlua_expect!( + (|state| { + init_error_registry(state)?; + + // Create the internal metatables and store them in the registry + // to prevent from being garbage collected. + + init_gc_metatable::>>(state, None)?; + init_gc_metatable::(state, None)?; + init_gc_metatable::(state, None)?; + #[cfg(feature = "async")] + { + init_gc_metatable::(state, None)?; + init_gc_metatable::(state, None)?; + init_gc_metatable::(state, None)?; + init_gc_metatable::>(state, None)?; + } + + // Init serde metatables + #[cfg(feature = "serialize")] + crate::serde::init_metatables(state)?; + + Ok::<_, Error>(()) + })(main_state), + "Error during Lua initialization", + ); + + // Init ExtraData + let extra = ExtraData::init(main_state); + + // Register `DestructedUserdata` type + get_destructed_userdata_metatable(main_state); + let destructed_mt_ptr = ffi::lua_topointer(main_state, -1); + let destructed_ud_typeid = TypeId::of::(); + (*extra.get()) + .registered_userdata_mt + .insert(destructed_mt_ptr, Some(destructed_ud_typeid)); + ffi::lua_pop(main_state, 1); + + mlua_debug_assert!( + ffi::lua_gettop(main_state) == main_state_top, + "stack leak during creation" + ); + assert_stack(main_state, ffi::LUA_MINSTACK); + + let rawlua = Arc::new(ReentrantMutex::new(RawLua { + state: Cell::new(state), + main_state, + extra: Arc::clone(&extra), + })); + (*extra.get()).set_lua(&rawlua); + + rawlua + } + + pub(super) unsafe fn try_from_ptr( + state: *mut ffi::lua_State, + ) -> Option>> { + match ExtraData::get(state) { + extra if extra.is_null() => None, + extra => Some(Arc::clone(&(*extra).lua().0)), + } + } + + /// Marks the Lua state as safe. + #[inline(always)] + pub(super) unsafe fn set_safe(&self) { + (*self.extra.get()).safe = true; + } + + /// Loads the specified subset of the standard libraries into an existing Lua state. + /// + /// Use the [`StdLib`] flags to specify the libraries you want to load. + /// + /// [`StdLib`]: crate::StdLib + pub(super) unsafe fn load_std_libs(&self, libs: StdLib) -> Result<()> { + let is_safe = (*self.extra.get()).safe; + + #[cfg(not(feature = "luau"))] + if is_safe && libs.contains(StdLib::DEBUG) { + return Err(Error::SafetyError( + "the unsafe `debug` module can't be loaded in safe mode".to_string(), + )); + } + #[cfg(feature = "luajit")] + if is_safe && libs.contains(StdLib::FFI) { + return Err(Error::SafetyError( + "the unsafe `ffi` module can't be loaded in safe mode".to_string(), + )); + } + + let res = load_from_std_lib(self.main_state, libs); + + // If `package` library loaded into a safe lua state then disable C modules + let curr_libs = (*self.extra.get()).libs; + if is_safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) { + mlua_expect!( + self.lua().disable_c_modules(), + "Error during disabling C modules" + ); + } + unsafe { (*self.extra.get()).libs |= libs }; + + res + } + + /// See [`Lua::try_set_app_data`] + #[inline] + pub(crate) fn try_set_app_data( + &self, + data: T, + ) -> StdResult, T> { + let extra = unsafe { &*self.extra.get() }; + extra.app_data.try_insert(data) + } + + /// See [`Lua::app_data_ref`] + #[track_caller] + #[inline] + pub(crate) fn app_data_ref(&self) -> Option> { + let extra = unsafe { &*self.extra.get() }; + extra.app_data.borrow(None) + } + + /// See [`Lua::app_data_mut`] + #[track_caller] + #[inline] + pub(crate) fn app_data_mut(&self) -> Option> { + let extra = unsafe { &*self.extra.get() }; + extra.app_data.borrow_mut(None) + } + + /// See [`Lua::create_registry_value`] + #[inline] + pub(crate) fn owns_registry_value(&self, key: &RegistryKey) -> bool { + let registry_unref_list = unsafe { &(*self.extra.get()).registry_unref_list }; + Arc::ptr_eq(&key.unref_list, registry_unref_list) + } + + pub(crate) fn load_chunk( + &self, + name: Option<&CStr>, + env: Option
, + mode: Option, + source: &[u8], + ) -> Result { + let state = self.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 1)?; + + let mode_str = match mode { + Some(ChunkMode::Binary) => cstr!("b"), + Some(ChunkMode::Text) => cstr!("t"), + None => cstr!("bt"), + }; + + match ffi::luaL_loadbufferx( + state, + source.as_ptr() as *const c_char, + source.len(), + name.map(|n| n.as_ptr()).unwrap_or_else(ptr::null), + mode_str, + ) { + ffi::LUA_OK => { + if let Some(env) = env { + self.push_ref(&env.0); + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + ffi::lua_setupvalue(state, -2, 1); + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + ffi::lua_setfenv(state, -2); + } + + #[cfg(feature = "luau-jit")] + if (*self.extra.get()).enable_jit && ffi::luau_codegen_supported() != 0 { + ffi::luau_codegen_compile(state, -1); + } + + Ok(Function(self.pop_ref())) + } + err => Err(pop_error(state, err)), + } + } + } + + /// Sets a 'hook' function for a thread (coroutine). + #[cfg(not(feature = "luau"))] + pub(crate) unsafe fn set_thread_hook( + &self, + state: *mut ffi::lua_State, + triggers: HookTriggers, + callback: F, + ) where + F: Fn(&Lua, Debug) -> Result<()> + MaybeSend + 'static, + { + unsafe extern "C-unwind" fn hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) { + let extra = ExtraData::get(state); + if (*extra).hook_thread != state { + // Hook was destined for a different thread, ignore + ffi::lua_sethook(state, None, 0, 0); + return; + } + callback_error_ext(state, extra, move |_| { + let hook_cb = (*extra).hook_callback.clone(); + let hook_cb = mlua_expect!(hook_cb, "no hook callback set in hook_proc"); + if Arc::strong_count(&hook_cb) > 2 { + return Ok(()); // Don't allow recursion + } + let rawlua = (*extra).raw_lua(); + let _guard = StateGuard::new(rawlua, state); + let debug = Debug::new(rawlua, ar); + hook_cb((*extra).lua(), debug) + }) + } + + (*self.extra.get()).hook_callback = Some(Arc::new(callback)); + (*self.extra.get()).hook_thread = state; // Mark for what thread the hook is set + ffi::lua_sethook(state, Some(hook_proc), triggers.mask(), triggers.count()); + } + + /// See [`Lua::create_string`] + pub(crate) unsafe fn create_string(&self, s: impl AsRef<[u8]>) -> Result { + let state = self.state(); + if self.unlikely_memory_error() { + push_string(self.ref_thread(), s.as_ref(), false)?; + return Ok(String(self.pop_ref_thread())); + } + + let _sg = StackGuard::new(state); + check_stack(state, 3)?; + push_string(state, s.as_ref(), true)?; + Ok(String(self.pop_ref())) + } + + /// See [`Lua::create_table_with_capacity`] + pub(crate) unsafe fn create_table_with_capacity( + &self, + narr: usize, + nrec: usize, + ) -> Result
{ + if self.unlikely_memory_error() { + push_table(self.ref_thread(), narr, nrec, false)?; + return Ok(Table(self.pop_ref_thread())); + } + + let state = self.state(); + let _sg = StackGuard::new(state); + check_stack(state, 3)?; + push_table(state, narr, nrec, true)?; + Ok(Table(self.pop_ref())) + } + + /// See [`Lua::create_sequence_from`] + pub(crate) unsafe fn create_sequence_from(&self, iter: I) -> Result
+ where + T: IntoLua, + I: IntoIterator, + { + let state = self.state(); + let _sg = StackGuard::new(state); + check_stack(state, 5)?; + + let iter = iter.into_iter(); + let lower_bound = iter.size_hint().0; + let protect = !self.unlikely_memory_error(); + push_table(state, lower_bound, 0, protect)?; + for (i, v) in iter.enumerate() { + self.push(v)?; + if protect { + protect_lua!(state, 2, 1, |state| { + ffi::lua_rawseti(state, -2, (i + 1) as Integer); + })?; + } else { + ffi::lua_rawseti(state, -2, (i + 1) as Integer); + } + } + + Ok(Table(self.pop_ref())) + } + + /// Wraps a Lua function into a new thread (or coroutine). + /// + /// Takes function by reference. + pub(crate) unsafe fn create_thread(&self, func: &Function) -> Result { + let state = self.state(); + let _sg = StackGuard::new(state); + check_stack(state, 3)?; + + let thread_state = if self.unlikely_memory_error() { + ffi::lua_newthread(state) + } else { + protect_lua!(state, 0, 1, |state| ffi::lua_newthread(state))? + }; + let thread = Thread(self.pop_ref(), thread_state); + ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index); + Ok(thread) + } + + /// Wraps a Lua function into a new or recycled thread (coroutine). + #[cfg(feature = "async")] + pub(crate) unsafe fn create_recycled_thread(&self, func: &Function) -> Result { + #[cfg(any(feature = "lua54", feature = "luau"))] + if let Some(index) = (*self.extra.get()).thread_pool.pop() { + let thread_state = ffi::lua_tothread(self.ref_thread(), index); + ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index); + + #[cfg(feature = "luau")] + { + // Inherit `LUA_GLOBALSINDEX` from the caller + ffi::lua_xpush(self.state(), thread_state, ffi::LUA_GLOBALSINDEX); + ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX); + } + + return Ok(Thread(ValueRef::new(self, index), thread_state)); + } + + self.create_thread(func) + } + + /// Resets thread (coroutine) and returns it to the pool for later use. + #[cfg(feature = "async")] + #[cfg(any(feature = "lua54", feature = "luau"))] + pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) -> bool { + let extra = &mut *self.extra.get(); + if extra.thread_pool.len() < extra.thread_pool.capacity() { + let thread_state = ffi::lua_tothread(extra.ref_thread, thread.0.index); + #[cfg(all(feature = "lua54", not(feature = "vendored")))] + let status = ffi::lua_resetthread(thread_state); + #[cfg(all(feature = "lua54", feature = "vendored"))] + let status = ffi::lua_closethread(thread_state, self.state()); + #[cfg(feature = "lua54")] + if status != ffi::LUA_OK { + // Error object is on top, drop it + ffi::lua_settop(thread_state, 0); + } + #[cfg(feature = "luau")] + ffi::lua_resetthread(thread_state); + extra.thread_pool.push(thread.0.index); + thread.0.drop = false; + return true; + } + false + } + + // FIXME + // #[inline] + // pub(crate) fn pop_multivalue_from_pool(&self) -> Option> { + // let extra = unsafe { &mut *self.extra.get() }; + // extra.multivalue_pool.pop() + // } + + // FIXME + // #[inline] + // pub(crate) fn push_multivalue_to_pool(&self, mut multivalue: VecDeque) { + // let extra = unsafe { &mut *self.extra.get() }; + // if extra.multivalue_pool.len() < MULTIVALUE_POOL_SIZE { + // multivalue.clear(); + // extra + // .multivalue_pool + // .push(unsafe { mem::transmute(multivalue) }); + // } + // } + + /// Pushes a value that implements `IntoLua` onto the Lua stack. + /// + /// Uses 2 stack spaces, does not call checkstack. + #[doc(hidden)] + #[inline(always)] + pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> { + value.push_into_stack(self) + } + + /// Pushes a `Value` (by reference) onto the Lua stack. + /// + /// Uses 2 stack spaces, does not call `checkstack`. + pub(crate) unsafe fn push_value(&self, value: &Value) -> Result<()> { + let state = self.state(); + match value { + Value::Nil => ffi::lua_pushnil(state), + Value::Boolean(b) => ffi::lua_pushboolean(state, *b as c_int), + Value::LightUserData(ud) => ffi::lua_pushlightuserdata(state, ud.0), + Value::Integer(i) => ffi::lua_pushinteger(state, *i), + Value::Number(n) => ffi::lua_pushnumber(state, *n), + #[cfg(feature = "luau")] + Value::Vector(v) => { + #[cfg(not(feature = "luau-vector4"))] + ffi::lua_pushvector(state, v.x(), v.y(), v.z()); + #[cfg(feature = "luau-vector4")] + ffi::lua_pushvector(state, v.x(), v.y(), v.z(), v.w()); + } + Value::String(s) => self.push_ref(&s.0), + Value::Table(t) => self.push_ref(&t.0), + Value::Function(f) => self.push_ref(&f.0), + Value::Thread(t) => self.push_ref(&t.0), + Value::UserData(ud) => self.push_ref(&ud.0), + Value::Error(err) => { + let protect = !self.unlikely_memory_error(); + push_gc_userdata(state, WrappedFailure::Error(*err.clone()), protect)?; + } + } + Ok(()) + } + + /// Pops a value from the Lua stack. + /// + /// Uses 2 stack spaces, does not call `checkstack`. + pub(crate) unsafe fn pop_value(&self) -> Value { + let value = self.stack_value(-1); + ffi::lua_pop(self.state(), 1); + value + } + + /// Returns value at given stack index without popping it. + /// + /// Uses 2 stack spaces, does not call checkstack. + pub(crate) unsafe fn stack_value(&self, idx: c_int) -> Value { + let state = self.state(); + match ffi::lua_type(state, idx) { + ffi::LUA_TNIL => Nil, + + ffi::LUA_TBOOLEAN => Value::Boolean(ffi::lua_toboolean(state, idx) != 0), + + ffi::LUA_TLIGHTUSERDATA => { + Value::LightUserData(LightUserData(ffi::lua_touserdata(state, idx))) + } + + #[cfg(any(feature = "lua54", feature = "lua53"))] + ffi::LUA_TNUMBER => { + if ffi::lua_isinteger(state, idx) != 0 { + Value::Integer(ffi::lua_tointeger(state, idx)) + } else { + Value::Number(ffi::lua_tonumber(state, idx)) + } + } + + #[cfg(any( + feature = "lua52", + feature = "lua51", + feature = "luajit", + feature = "luau" + ))] + ffi::LUA_TNUMBER => { + use crate::types::Number; + + let n = ffi::lua_tonumber(state, idx); + match num_traits::cast(n) { + Some(i) if (n - (i as Number)).abs() < Number::EPSILON => Value::Integer(i), + _ => Value::Number(n), + } + } + + #[cfg(feature = "luau")] + ffi::LUA_TVECTOR => { + let v = ffi::lua_tovector(state, idx); + mlua_debug_assert!(!v.is_null(), "vector is null"); + #[cfg(not(feature = "luau-vector4"))] + return Value::Vector(crate::types::Vector([*v, *v.add(1), *v.add(2)])); + #[cfg(feature = "luau-vector4")] + return Value::Vector(crate::types::Vector([*v, *v.add(1), *v.add(2), *v.add(3)])); + } + + ffi::LUA_TSTRING => { + ffi::lua_xpush(state, self.ref_thread(), idx); + Value::String(String(self.pop_ref_thread())) + } + + ffi::LUA_TTABLE => { + ffi::lua_xpush(state, self.ref_thread(), idx); + Value::Table(Table(self.pop_ref_thread())) + } + + ffi::LUA_TFUNCTION => { + ffi::lua_xpush(state, self.ref_thread(), idx); + Value::Function(Function(self.pop_ref_thread())) + } + + ffi::LUA_TUSERDATA => { + // If the userdata is `WrappedFailure`, process it as an error or panic. + let failure_mt_ptr = (*self.extra.get()).wrapped_failure_mt_ptr; + match get_gc_userdata::(state, idx, failure_mt_ptr).as_mut() { + Some(WrappedFailure::Error(err)) => Value::Error(Box::new(err.clone())), + Some(WrappedFailure::Panic(panic)) => { + if let Some(panic) = panic.take() { + resume_unwind(panic); + } + // Previously resumed panic? + Value::Nil + } + _ => { + ffi::lua_xpush(state, self.ref_thread(), idx); + Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::None)) + } + } + } + + ffi::LUA_TTHREAD => { + ffi::lua_xpush(state, self.ref_thread(), idx); + let thread_state = ffi::lua_tothread(self.ref_thread(), -1); + Value::Thread(Thread(self.pop_ref_thread(), thread_state)) + } + + #[cfg(feature = "luau")] + ffi::LUA_TBUFFER => { + // Buffer is represented as a userdata type + ffi::lua_xpush(state, self.ref_thread(), idx); + Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::Buffer)) + } + + #[cfg(feature = "luajit")] + ffi::LUA_TCDATA => { + // CData is represented as a userdata type + ffi::lua_xpush(state, self.ref_thread(), idx); + Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::CData)) + } + + _ => mlua_panic!("unexpected value type on stack"), + } + } + + // Pushes a ValueRef value onto the stack, uses 1 stack space, does not call checkstack + #[inline] + pub(crate) fn push_ref(&self, vref: &ValueRef) { + assert!( + self.weak() == &vref.lua, + "Lua instance passed Value created from a different main Lua state" + ); + unsafe { ffi::lua_xpush(self.ref_thread(), self.state(), vref.index) }; + } + + // Pops the topmost element of the stack and stores a reference to it. This pins the object, + // preventing garbage collection until the returned `ValueRef` is dropped. + // + // References are stored on the stack of a specially created auxiliary thread that exists only + // to store reference values. This is much faster than storing these in the registry, and also + // much more flexible and requires less bookkeeping than storing them directly in the currently + // used stack. + #[inline] + pub(crate) unsafe fn pop_ref(&self) -> ValueRef { + ffi::lua_xmove(self.state(), self.ref_thread(), 1); + let index = ref_stack_pop(self.extra.get()); + ValueRef::new(self, index) + } + + // Same as `pop_ref` but assumes the value is already on the reference thread + #[inline] + pub(crate) unsafe fn pop_ref_thread(&self) -> ValueRef { + let index = ref_stack_pop(self.extra.get()); + ValueRef::new(self, index) + } + + #[inline] + pub(crate) unsafe fn clone_ref(&self, vref: &ValueRef) -> ValueRef { + ffi::lua_pushvalue(self.ref_thread(), vref.index); + let index = ref_stack_pop(self.extra.get()); + ValueRef::new(self, index) + } + + pub(crate) unsafe fn drop_ref(&self, vref: &ValueRef) { + let ref_thread = self.ref_thread(); + ffi::lua_pushnil(ref_thread); + ffi::lua_replace(ref_thread, vref.index); + (*self.extra.get()).ref_free.push(vref.index); + } + + #[inline] + pub(crate) unsafe fn push_error_traceback(&self) { + let state = self.state(); + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + ffi::lua_xpush(self.ref_thread(), state, ExtraData::ERROR_TRACEBACK_IDX); + // Lua 5.2+ support light C functions that does not require extra allocations + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + ffi::lua_pushcfunction(state, crate::util::error_traceback); + } + + #[inline] + pub(crate) unsafe fn unlikely_memory_error(&self) -> bool { + // MemoryInfo is empty in module mode so we cannot predict memory limits + match MemoryState::get(self.main_state) { + mem_state if !mem_state.is_null() => (*mem_state).memory_limit() == 0, + #[cfg(feature = "module")] + _ => (*self.extra.get()).skip_memory_check, // Check the special flag (only for module mode) + #[cfg(not(feature = "module"))] + _ => false, + } + } + + pub(crate) unsafe fn make_userdata(&self, data: UserDataVariant) -> Result + where + T: UserData + 'static, + { + self.make_userdata_with_metatable(data, || { + // Check if userdata/metatable is already registered + let type_id = TypeId::of::(); + if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) { + return Ok(table_id as Integer); + } + + // Create a new metatable from `UserData` definition + let mut registry = UserDataRegistry::new(); + T::register(&mut registry); + + self.register_userdata_metatable(registry) + }) + } + + pub(crate) unsafe fn make_any_userdata( + &self, + data: UserDataVariant, + ) -> Result + where + T: 'static, + { + self.make_userdata_with_metatable(data, || { + // Check if userdata/metatable is already registered + let type_id = TypeId::of::(); + if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) { + return Ok(table_id as Integer); + } + + // Create an empty metatable + let registry = UserDataRegistry::new(); + self.register_userdata_metatable::(registry) + }) + } + + unsafe fn make_userdata_with_metatable( + &self, + data: UserDataVariant, + get_metatable_id: impl FnOnce() -> Result, + ) -> Result { + let state = self.state(); + let _sg = StackGuard::new(state); + check_stack(state, 3)?; + + // We push metatable first to ensure having correct metatable with `__gc` method + ffi::lua_pushnil(state); + ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, get_metatable_id()?); + let protect = !self.unlikely_memory_error(); + #[cfg(not(feature = "lua54"))] + crate::util::push_userdata(state, data, protect)?; + #[cfg(feature = "lua54")] + crate::util::push_userdata_uv( + state, + data, + crate::userdata::USER_VALUE_MAXSLOT as c_int, + protect, + )?; + ffi::lua_replace(state, -3); + ffi::lua_setmetatable(state, -2); + + // Set empty environment for Lua 5.1 + #[cfg(any(feature = "lua51", feature = "luajit"))] + if protect { + protect_lua!(state, 1, 1, fn(state) { + ffi::lua_newtable(state); + ffi::lua_setuservalue(state, -2); + })?; + } else { + ffi::lua_newtable(state); + ffi::lua_setuservalue(state, -2); + } + + Ok(AnyUserData(self.pop_ref(), SubtypeId::None)) + } + + pub(crate) unsafe fn register_userdata_metatable( + &self, + mut registry: UserDataRegistry, + ) -> Result { + let state = self.state(); + let _sg = StackGuard::new(state); + check_stack(state, 13)?; + + // Prepare metatable, add meta methods first and then meta fields + let metatable_nrec = registry.meta_methods.len() + registry.meta_fields.len(); + #[cfg(feature = "async")] + let metatable_nrec = metatable_nrec + registry.async_meta_methods.len(); + push_table(state, 0, metatable_nrec, true)?; + for (k, m) in registry.meta_methods { + self.push(self.create_callback(m)?)?; + rawset_field(state, -2, MetaMethod::validate(&k)?)?; + } + #[cfg(feature = "async")] + for (k, m) in registry.async_meta_methods { + self.push(self.create_async_callback(m)?)?; + rawset_field(state, -2, MetaMethod::validate(&k)?)?; + } + let mut has_name = false; + for (k, f) in registry.meta_fields { + has_name = has_name || k == MetaMethod::Type; + let rawlua = mem::transmute::<&RawLua, &RawLua>(self); + mlua_assert!(f(rawlua, 0)? == 1, "field function must return one value"); + rawset_field(state, -2, MetaMethod::validate(&k)?)?; + } + // Set `__name/__type` if not provided + if !has_name { + let type_name = short_type_name::(); + push_string(state, type_name.as_bytes(), !self.unlikely_memory_error())?; + rawset_field(state, -2, MetaMethod::Type.name())?; + } + let metatable_index = ffi::lua_absindex(state, -1); + + let mut extra_tables_count = 0; + + let fields_nrec = registry.fields.len(); + if fields_nrec > 0 { + // If `__index` is a table then update it in-place + let index_type = ffi::lua_getfield(state, metatable_index, cstr!("__index")); + match index_type { + ffi::LUA_TNIL | ffi::LUA_TTABLE => { + if index_type == ffi::LUA_TNIL { + // Create a new table + ffi::lua_pop(state, 1); + push_table(state, 0, fields_nrec, true)?; + } + for (k, f) in registry.fields { + let rawlua = mem::transmute::<&RawLua, &RawLua>(self); + mlua_assert!(f(rawlua, 0)? == 1, "field function must return one value"); + rawset_field(state, -2, &k)?; + } + rawset_field(state, metatable_index, "__index")?; + } + _ => { + ffi::lua_pop(state, 1); + // Propagate fields to the field getters + for (k, f) in registry.fields { + registry.field_getters.push((k, f)) + } + } + } + } + + let mut field_getters_index = None; + let field_getters_nrec = registry.field_getters.len(); + if field_getters_nrec > 0 { + push_table(state, 0, field_getters_nrec, true)?; + for (k, m) in registry.field_getters { + self.push(self.create_callback(m)?)?; + rawset_field(state, -2, &k)?; + } + field_getters_index = Some(ffi::lua_absindex(state, -1)); + extra_tables_count += 1; + } + + let mut field_setters_index = None; + let field_setters_nrec = registry.field_setters.len(); + if field_setters_nrec > 0 { + push_table(state, 0, field_setters_nrec, true)?; + for (k, m) in registry.field_setters { + self.push(self.create_callback(m)?)?; + rawset_field(state, -2, &k)?; + } + field_setters_index = Some(ffi::lua_absindex(state, -1)); + extra_tables_count += 1; + } + + let mut methods_index = None; + let methods_nrec = registry.methods.len(); + #[cfg(feature = "async")] + let methods_nrec = methods_nrec + registry.async_methods.len(); + if methods_nrec > 0 { + // If `__index` is a table then update it in-place + let index_type = ffi::lua_getfield(state, metatable_index, cstr!("__index")); + match index_type { + ffi::LUA_TTABLE => {} // Update the existing table + _ => { + // Create a new table + ffi::lua_pop(state, 1); + push_table(state, 0, methods_nrec, true)?; + } + } + for (k, m) in registry.methods { + self.push(self.create_callback(m)?)?; + rawset_field(state, -2, &k)?; + } + #[cfg(feature = "async")] + for (k, m) in registry.async_methods { + self.push(self.create_async_callback(m)?)?; + rawset_field(state, -2, &k)?; + } + match index_type { + ffi::LUA_TTABLE => { + ffi::lua_pop(state, 1); // All done + } + ffi::LUA_TNIL => { + rawset_field(state, metatable_index, "__index")?; // Set the new table as `__index` + } + _ => { + methods_index = Some(ffi::lua_absindex(state, -1)); + extra_tables_count += 1; + } + } + } + + #[cfg(feature = "luau")] + let extra_init = None; + #[cfg(not(feature = "luau"))] + let extra_init: Option Result<()>> = Some(|state| { + ffi::lua_pushcfunction( + state, + crate::util::userdata_destructor::>, + ); + rawset_field(state, -2, "__gc") + }); + + init_userdata_metatable( + state, + metatable_index, + field_getters_index, + field_setters_index, + methods_index, + extra_init, + )?; + + // Pop extra tables to get metatable on top of the stack + ffi::lua_pop(state, extra_tables_count); + + let mt_ptr = ffi::lua_topointer(state, -1); + let id = protect_lua!(state, 1, 0, |state| { + ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) + })?; + + let type_id = TypeId::of::(); + (*self.extra.get()).registered_userdata.insert(type_id, id); + (*self.extra.get()) + .registered_userdata_mt + .insert(mt_ptr, Some(type_id)); + + Ok(id as Integer) + } + + // #[inline] + // pub(crate) unsafe fn register_raw_userdata_metatable( + // &self, + // ptr: *const c_void, + // type_id: Option, + // ) { + // (*self.extra.get()) + // .registered_userdata_mt + // .insert(ptr, type_id); + // } + + // #[inline] + // pub(crate) unsafe fn deregister_raw_userdata_metatable(&self, ptr: *const c_void) { + // (*self.extra.get()).registered_userdata_mt.remove(&ptr); + // if (*self.extra.get()).last_checked_userdata_mt.0 == ptr { + // (*self.extra.get()).last_checked_userdata_mt = (ptr::null(), None); + // } + // } + + // #[inline(always)] + // pub(crate) unsafe fn get_userdata_ref(&self, idx: c_int) -> Result> { + // let guard = self.lua().lock_arc(); + // (*get_userdata::>(self.state(), idx)).try_make_ref(guard) + // } + + // Returns `TypeId` for the userdata ref, checking that it's registered and not destructed. + // + // Returns `None` if the userdata is registered but non-static. + pub(crate) unsafe fn get_userdata_ref_type_id( + &self, + vref: &ValueRef, + ) -> Result> { + self.get_userdata_type_id_inner(self.ref_thread(), vref.index) + } + + // Same as `get_userdata_ref_type_id` but assumes the userdata is already on the stack. + pub(crate) unsafe fn get_userdata_type_id(&self, idx: c_int) -> Result> { + self.get_userdata_type_id_inner(self.state(), idx) + } + + unsafe fn get_userdata_type_id_inner( + &self, + state: *mut ffi::lua_State, + idx: c_int, + ) -> Result> { + if ffi::lua_getmetatable(state, idx) == 0 { + return Err(Error::UserDataTypeMismatch); + } + let mt_ptr = ffi::lua_topointer(state, -1); + ffi::lua_pop(state, 1); + + // Fast path to skip looking up the metatable in the map + let (last_mt, last_type_id) = (*self.extra.get()).last_checked_userdata_mt; + if last_mt == mt_ptr { + return Ok(last_type_id); + } + + match (*self.extra.get()).registered_userdata_mt.get(&mt_ptr) { + Some(&type_id) if type_id == Some(TypeId::of::()) => { + Err(Error::UserDataDestructed) + } + Some(&type_id) => { + (*self.extra.get()).last_checked_userdata_mt = (mt_ptr, type_id); + Ok(type_id) + } + None => Err(Error::UserDataTypeMismatch), + } + } + + // Pushes a ValueRef (userdata) value onto the stack, returning their `TypeId`. + // Uses 1 stack space, does not call checkstack. + pub(crate) unsafe fn push_userdata_ref(&self, vref: &ValueRef) -> Result> { + let type_id = self.get_userdata_type_id_inner(self.ref_thread(), vref.index)?; + self.push_ref(vref); + Ok(type_id) + } + + // Creates a Function out of a Callback containing a 'static Fn. + pub(crate) fn create_callback(&self, func: Callback) -> Result { + unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int { + // Normal functions can be scoped and therefore destroyed, + // so we need to check that the first upvalue is valid + let (upvalue, extra) = match ffi::lua_type(state, ffi::lua_upvalueindex(1)) { + ffi::LUA_TUSERDATA => { + let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); + (upvalue, (*upvalue).extra.get()) + } + _ => (ptr::null_mut(), ptr::null_mut()), + }; + callback_error_ext(state, extra, |nargs| { + // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) + if upvalue.is_null() { + return Err(Error::CallbackDestructed); + } + + // The lock must be already held as the callback is executed + let rawlua = (*extra).raw_lua(); + let _guard = StateGuard::new(rawlua, state); + let func = &*(*upvalue).data; + + func(rawlua, nargs) + }) + } + + let state = self.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 4)?; + + let func = mem::transmute(func); + let extra = Arc::clone(&self.extra); + let protect = !self.unlikely_memory_error(); + push_gc_userdata(state, CallbackUpvalue { data: func, extra }, protect)?; + if protect { + protect_lua!(state, 1, 1, fn(state) { + ffi::lua_pushcclosure(state, call_callback, 1); + })?; + } else { + ffi::lua_pushcclosure(state, call_callback, 1); + } + + Ok(Function(self.pop_ref())) + } + } + + #[cfg(feature = "async")] + pub(crate) fn create_async_callback(&self, func: AsyncCallback) -> Result { + #[cfg(any( + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luau" + ))] + unsafe { + if !(*self.extra.get()).libs.contains(StdLib::COROUTINE) { + load_from_std_lib(self.main_state, StdLib::COROUTINE)?; + (*self.extra.get()).libs |= StdLib::COROUTINE; + } + } + + unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int { + // Async functions cannot be scoped and therefore destroyed, + // so the first upvalue is always valid + let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); + let extra = (*upvalue).extra.get(); + callback_error_ext(state, extra, |nargs| { + // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) + // The lock must be already held as the callback is executed + let rawlua = (*extra).raw_lua(); + let _guard = StateGuard::new(rawlua, state); + + let args = MultiValue::from_stack_multi(nargs, rawlua)?; + let func = &*(*upvalue).data; + let fut = func(rawlua, args); + let extra = Arc::clone(&(*upvalue).extra); + let protect = !rawlua.unlikely_memory_error(); + push_gc_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?; + if protect { + protect_lua!(state, 1, 1, fn(state) { + ffi::lua_pushcclosure(state, poll_future, 1); + })?; + } else { + ffi::lua_pushcclosure(state, poll_future, 1); + } + + Ok(1) + }) + } + + unsafe extern "C-unwind" fn poll_future(state: *mut ffi::lua_State) -> c_int { + let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); + let extra = (*upvalue).extra.get(); + callback_error_ext(state, extra, |_| { + // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) + // The lock must be already held as the future is polled + let rawlua = (*extra).raw_lua(); + let _guard = StateGuard::new(&rawlua, state); + + let fut = &mut (*upvalue).data; + let mut ctx = Context::from_waker(rawlua.waker()); + match fut.as_mut().poll(&mut ctx) { + Poll::Pending => { + ffi::lua_pushnil(state); + ffi::lua_pushlightuserdata(state, Lua::poll_pending().0); + Ok(2) + } + Poll::Ready(nresults) => { + match nresults? { + nresults @ 0..=2 => { + // Fast path for up to 2 results without creating a table + ffi::lua_pushinteger(state, nresults as _); + if nresults > 0 { + ffi::lua_insert(state, -nresults - 1); + } + Ok(nresults + 1) + } + nresults => { + let results = MultiValue::from_stack_multi(nresults, &rawlua)?; + ffi::lua_pushinteger(state, nresults as _); + rawlua.push(rawlua.create_sequence_from(results)?)?; + Ok(2) + } + } + } + } + }) + } + + let state = self.state(); + let get_poll = unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 4)?; + + let func = mem::transmute(func); + let extra = Arc::clone(&self.extra); + let protect = !self.unlikely_memory_error(); + let upvalue = AsyncCallbackUpvalue { data: func, extra }; + push_gc_userdata(state, upvalue, protect)?; + if protect { + protect_lua!(state, 1, 1, fn(state) { + ffi::lua_pushcclosure(state, call_callback, 1); + })?; + } else { + ffi::lua_pushcclosure(state, call_callback, 1); + } + + Function(self.pop_ref()) + }; + + unsafe extern "C-unwind" fn unpack(state: *mut ffi::lua_State) -> c_int { + let len = ffi::lua_tointeger(state, 2); + ffi::luaL_checkstack(state, len as c_int, ptr::null()); + for i in 1..=len { + ffi::lua_rawgeti(state, 1, i); + } + len as c_int + } + + let lua = self.lua(); + let coroutine = lua.globals().get::<_, Table>("coroutine")?; + + let env = lua.create_table_with_capacity(0, 3)?; + env.set("get_poll", get_poll)?; + // Cache `yield` function + env.set("yield", coroutine.get::<_, Function>("yield")?)?; + unsafe { + env.set("unpack", lua.create_c_function(unpack)?)?; + } + + lua.load( + r#" + local poll = get_poll(...) + while true do + local nres, res, res2 = poll() + if nres ~= nil then + if nres == 0 then + return + elseif nres == 1 then + return res + elseif nres == 2 then + return res, res2 + else + return unpack(res, nres) + end + end + yield(res) -- `res` is a "pending" value + end + "#, + ) + .try_cache() + .set_name("__mlua_async_poll") + .set_environment(env) + .into_function() + } + + #[cfg(feature = "async")] + #[inline] + pub(crate) unsafe fn waker(&self) -> &Waker { + (*self.extra.get()).waker.as_ref() + } + + #[cfg(feature = "async")] + #[inline] + pub(crate) unsafe fn set_waker(&self, waker: NonNull) -> NonNull { + mem::replace(&mut (*self.extra.get()).waker, waker) + } +} + +// Uses 3 stack spaces +unsafe fn load_from_std_lib(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> { + #[inline(always)] + pub unsafe fn requiref( + state: *mut ffi::lua_State, + modname: &str, + openf: ffi::lua_CFunction, + glb: c_int, + ) -> Result<()> { + let modname = mlua_expect!(CString::new(modname), "modname contains nil byte"); + protect_lua!(state, 0, 1, |state| { + ffi::luaL_requiref(state, modname.as_ptr() as *const c_char, openf, glb) + }) + } + + #[cfg(feature = "luajit")] + struct GcGuard(*mut ffi::lua_State); + + #[cfg(feature = "luajit")] + impl GcGuard { + fn new(state: *mut ffi::lua_State) -> Self { + // Stop collector during library initialization + unsafe { ffi::lua_gc(state, ffi::LUA_GCSTOP, 0) }; + GcGuard(state) + } + } + + #[cfg(feature = "luajit")] + impl Drop for GcGuard { + fn drop(&mut self) { + unsafe { ffi::lua_gc(self.0, ffi::LUA_GCRESTART, -1) }; + } + } + + // Stop collector during library initialization + #[cfg(feature = "luajit")] + let _gc_guard = GcGuard::new(state); + + #[cfg(any( + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luau" + ))] + { + if libs.contains(StdLib::COROUTINE) { + requiref(state, ffi::LUA_COLIBNAME, ffi::luaopen_coroutine, 1)?; + ffi::lua_pop(state, 1); + } + } + + if libs.contains(StdLib::TABLE) { + requiref(state, ffi::LUA_TABLIBNAME, ffi::luaopen_table, 1)?; + ffi::lua_pop(state, 1); + } + + #[cfg(not(feature = "luau"))] + if libs.contains(StdLib::IO) { + requiref(state, ffi::LUA_IOLIBNAME, ffi::luaopen_io, 1)?; + ffi::lua_pop(state, 1); + } + + if libs.contains(StdLib::OS) { + requiref(state, ffi::LUA_OSLIBNAME, ffi::luaopen_os, 1)?; + ffi::lua_pop(state, 1); + } + + if libs.contains(StdLib::STRING) { + requiref(state, ffi::LUA_STRLIBNAME, ffi::luaopen_string, 1)?; + ffi::lua_pop(state, 1); + } + + #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] + { + if libs.contains(StdLib::UTF8) { + requiref(state, ffi::LUA_UTF8LIBNAME, ffi::luaopen_utf8, 1)?; + ffi::lua_pop(state, 1); + } + } + + #[cfg(any(feature = "lua52", feature = "luau"))] + { + if libs.contains(StdLib::BIT) { + requiref(state, ffi::LUA_BITLIBNAME, ffi::luaopen_bit32, 1)?; + ffi::lua_pop(state, 1); + } + } + + #[cfg(feature = "luajit")] + { + if libs.contains(StdLib::BIT) { + requiref(state, ffi::LUA_BITLIBNAME, ffi::luaopen_bit, 1)?; + ffi::lua_pop(state, 1); + } + } + + #[cfg(feature = "luau")] + if libs.contains(StdLib::BUFFER) { + requiref(state, ffi::LUA_BUFFERLIBNAME, ffi::luaopen_buffer, 1)?; + ffi::lua_pop(state, 1); + } + + if libs.contains(StdLib::MATH) { + requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?; + ffi::lua_pop(state, 1); + } + + if libs.contains(StdLib::DEBUG) { + requiref(state, ffi::LUA_DBLIBNAME, ffi::luaopen_debug, 1)?; + ffi::lua_pop(state, 1); + } + + #[cfg(not(feature = "luau"))] + if libs.contains(StdLib::PACKAGE) { + requiref(state, ffi::LUA_LOADLIBNAME, ffi::luaopen_package, 1)?; + ffi::lua_pop(state, 1); + } + #[cfg(feature = "luau")] + if libs.contains(StdLib::PACKAGE) { + let lua = (*ExtraData::get(state)).lua(); + crate::luau::register_package_module(lua)?; + } + + #[cfg(feature = "luajit")] + { + if libs.contains(StdLib::JIT) { + requiref(state, ffi::LUA_JITLIBNAME, ffi::luaopen_jit, 1)?; + ffi::lua_pop(state, 1); + } + + if libs.contains(StdLib::FFI) { + requiref(state, ffi::LUA_FFILIBNAME, ffi::luaopen_ffi, 1)?; + ffi::lua_pop(state, 1); + } + } + + Ok(()) +} diff --git a/src/state/util.rs b/src/state/util.rs new file mode 100644 index 00000000..8c6a66b1 --- /dev/null +++ b/src/state/util.rs @@ -0,0 +1,187 @@ +use std::os::raw::c_int; +use std::panic::{catch_unwind, AssertUnwindSafe}; +use std::ptr; +use std::sync::Arc; + +use crate::error::{Error, Result}; +use crate::state::{ExtraData, RawLua}; +use crate::util::{self, get_gc_metatable, WrappedFailure}; + +const WRAPPED_FAILURE_POOL_SIZE: usize = 64; +// const MULTIVALUE_POOL_SIZE: usize = 64; + +pub(super) struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State); + +impl<'a> StateGuard<'a> { + pub(super) fn new(inner: &'a RawLua, mut state: *mut ffi::lua_State) -> Self { + state = inner.state.replace(state); + Self(inner, state) + } +} + +impl<'a> Drop for StateGuard<'a> { + fn drop(&mut self) { + self.0.state.set(self.1); + } +} + +// An optimized version of `callback_error` that does not allocate `WrappedFailure` userdata +// and instead reuses unsed values from previous calls (or allocates new). +pub(super) unsafe fn callback_error_ext( + state: *mut ffi::lua_State, + mut extra: *mut ExtraData, + f: F, +) -> R +where + F: FnOnce(c_int) -> Result, +{ + if extra.is_null() { + extra = ExtraData::get(state); + } + + let nargs = ffi::lua_gettop(state); + + enum PreallocatedFailure { + New(*mut WrappedFailure), + Existing(i32), + } + + impl PreallocatedFailure { + unsafe fn reserve(state: *mut ffi::lua_State, extra: *mut ExtraData) -> Self { + match (*extra).wrapped_failure_pool.pop() { + Some(index) => PreallocatedFailure::Existing(index), + None => { + // We need to check stack for Luau in case when callback is called from interrupt + // See https://github.com/Roblox/luau/issues/446 and mlua #142 and #153 + #[cfg(feature = "luau")] + ffi::lua_rawcheckstack(state, 2); + // Place it to the beginning of the stack + let ud = WrappedFailure::new_userdata(state); + ffi::lua_insert(state, 1); + PreallocatedFailure::New(ud) + } + } + } + + unsafe fn r#use( + &self, + state: *mut ffi::lua_State, + extra: *mut ExtraData, + ) -> *mut WrappedFailure { + let ref_thread = (*extra).ref_thread; + match *self { + PreallocatedFailure::New(ud) => { + ffi::lua_settop(state, 1); + ud + } + PreallocatedFailure::Existing(index) => { + ffi::lua_settop(state, 0); + #[cfg(feature = "luau")] + ffi::lua_rawcheckstack(state, 2); + ffi::lua_pushvalue(ref_thread, index); + ffi::lua_xmove(ref_thread, state, 1); + ffi::lua_pushnil(ref_thread); + ffi::lua_replace(ref_thread, index); + (*extra).ref_free.push(index); + ffi::lua_touserdata(state, -1) as *mut WrappedFailure + } + } + } + + unsafe fn release(self, state: *mut ffi::lua_State, extra: *mut ExtraData) { + let ref_thread = (*extra).ref_thread; + match self { + PreallocatedFailure::New(_) => { + if (*extra).wrapped_failure_pool.len() < WRAPPED_FAILURE_POOL_SIZE { + ffi::lua_rotate(state, 1, -1); + ffi::lua_xmove(state, ref_thread, 1); + let index = ref_stack_pop(extra); + (*extra).wrapped_failure_pool.push(index); + } else { + ffi::lua_remove(state, 1); + } + } + PreallocatedFailure::Existing(index) => { + if (*extra).wrapped_failure_pool.len() < WRAPPED_FAILURE_POOL_SIZE { + (*extra).wrapped_failure_pool.push(index); + } else { + ffi::lua_pushnil(ref_thread); + ffi::lua_replace(ref_thread, index); + (*extra).ref_free.push(index); + } + } + } + } + } + + // We cannot shadow Rust errors with Lua ones, so we need to reserve pre-allocated memory + // to store a wrapped failure (error or panic) *before* we proceed. + let prealloc_failure = PreallocatedFailure::reserve(state, extra); + + match catch_unwind(AssertUnwindSafe(|| f(nargs))) { + Ok(Ok(r)) => { + // Return unused `WrappedFailure` to the pool + prealloc_failure.release(state, extra); + r + } + Ok(Err(err)) => { + let wrapped_error = prealloc_failure.r#use(state, extra); + + // Build `CallbackError` with traceback + let traceback = if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 { + ffi::luaL_traceback(state, state, ptr::null(), 0); + let traceback = util::to_string(state, -1); + ffi::lua_pop(state, 1); + traceback + } else { + "".to_string() + }; + let cause = Arc::new(err); + ptr::write( + wrapped_error, + WrappedFailure::Error(Error::CallbackError { traceback, cause }), + ); + get_gc_metatable::(state); + ffi::lua_setmetatable(state, -2); + + ffi::lua_error(state) + } + Err(p) => { + let wrapped_panic = prealloc_failure.r#use(state, extra); + ptr::write(wrapped_panic, WrappedFailure::Panic(Some(p))); + get_gc_metatable::(state); + ffi::lua_setmetatable(state, -2); + ffi::lua_error(state) + } + } +} + +pub(super) unsafe fn ref_stack_pop(extra: *mut ExtraData) -> c_int { + let extra = &mut *extra; + if let Some(free) = extra.ref_free.pop() { + ffi::lua_replace(extra.ref_thread, free); + return free; + } + + // Try to grow max stack size + if extra.ref_stack_top >= extra.ref_stack_size { + let mut inc = extra.ref_stack_size; // Try to double stack size + while inc > 0 && ffi::lua_checkstack(extra.ref_thread, inc) == 0 { + inc /= 2; + } + if inc == 0 { + // Pop item on top of the stack to avoid stack leaking and successfully run destructors + // during unwinding. + ffi::lua_pop(extra.ref_thread, 1); + let top = extra.ref_stack_top; + // It is a user error to create enough references to exhaust the Lua max stack size for + // the ref thread. + panic!( + "cannot create a Lua reference, out of auxiliary stack space (used {top} slots)" + ); + } + extra.ref_stack_size += inc; + } + extra.ref_stack_top += 1; + extra.ref_stack_top +} diff --git a/src/thread.rs b/src/thread.rs index b6a0e867..177ce189 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -2,8 +2,8 @@ use std::os::raw::{c_int, c_void}; use crate::error::{Error, Result}; #[allow(unused)] -use crate::lua::Lua; -use crate::lua::LuaInner; +use crate::state::Lua; +use crate::state::RawLua; use crate::types::ValueRef; use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard}; use crate::value::{FromLuaMulti, IntoLuaMulti}; @@ -64,11 +64,6 @@ pub struct AsyncThread { impl Thread { #[inline(always)] - pub(crate) fn new(lua: &LuaInner, r#ref: ValueRef) -> Self { - let state = unsafe { ffi::lua_tothread(lua.ref_thread(), r#ref.index) }; - Thread(r#ref, state) - } - const fn state(&self) -> *mut ffi::lua_State { self.1 } @@ -502,7 +497,7 @@ unsafe fn is_poll_pending(state: *mut ffi::lua_State) -> bool { #[cfg(feature = "async")] struct WakerGuard<'lua, 'a> { - lua: &'lua LuaInner, + lua: &'lua RawLua, prev: NonNull, _phantom: PhantomData<&'a ()>, } @@ -510,7 +505,7 @@ struct WakerGuard<'lua, 'a> { #[cfg(feature = "async")] impl<'lua, 'a> WakerGuard<'lua, 'a> { #[inline] - pub fn new(lua: &'lua LuaInner, waker: &'a Waker) -> Result> { + pub fn new(lua: &'lua RawLua, waker: &'a Waker) -> Result> { let prev = unsafe { lua.set_waker(NonNull::from(waker)) }; Ok(WakerGuard { lua, diff --git a/src/types.rs b/src/types.rs index 2a28441e..d23eb731 100644 --- a/src/types.rs +++ b/src/types.rs @@ -14,7 +14,7 @@ use rustc_hash::FxHashMap; use crate::error::Result; #[cfg(not(feature = "luau"))] use crate::hook::Debug; -use crate::lua::{ExtraData, Lua, LuaGuard, LuaInner, WeakLua}; +use crate::state::{ExtraData, Lua, LuaGuard, RawLua, WeakLua}; #[cfg(feature = "async")] use {crate::value::MultiValue, futures_util::future::LocalBoxFuture}; @@ -41,7 +41,7 @@ pub(crate) enum SubtypeId { #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct LightUserData(pub *mut c_void); -pub(crate) type Callback<'a> = Box Result + 'static>; +pub(crate) type Callback<'a> = Box Result + 'static>; pub(crate) struct Upvalue { pub(crate) data: T, @@ -52,7 +52,7 @@ pub(crate) type CallbackUpvalue = Upvalue>; #[cfg(feature = "async")] pub(crate) type AsyncCallback<'a> = - Box LocalBoxFuture<'a, Result> + 'static>; + Box LocalBoxFuture<'a, Result> + 'static>; #[cfg(feature = "async")] pub(crate) type AsyncCallbackUpvalue = Upvalue>; @@ -281,7 +281,8 @@ pub(crate) struct ValueRef { } impl ValueRef { - pub(crate) fn new(lua: &LuaInner, index: c_int) -> Self { + #[inline] + pub(crate) fn new(lua: &RawLua, index: c_int) -> Self { ValueRef { lua: lua.weak().clone(), index, @@ -304,7 +305,7 @@ impl fmt::Debug for ValueRef { impl Clone for ValueRef { fn clone(&self) -> Self { - self.lua.lock().clone_ref(self) + unsafe { self.lua.lock().clone_ref(self) } } } @@ -312,7 +313,7 @@ impl Drop for ValueRef { fn drop(&mut self) { if self.drop { if let Some(lua) = self.lua.try_lock() { - lua.drop_ref(self); + unsafe { lua.drop_ref(self) }; } } } diff --git a/src/userdata.rs b/src/userdata.rs index 1100dc33..81889d85 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -16,7 +16,7 @@ use { use crate::error::{Error, Result}; use crate::function::Function; -use crate::lua::{Lua, LuaGuard}; +use crate::state::{Lua, LuaGuard}; use crate::string::String; use crate::table::{Table, TablePairs}; use crate::types::{MaybeSend, SubtypeId, ValueRef}; diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index 294b7b55..33a53618 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -9,7 +9,8 @@ use std::rc::Rc; use serde::ser::{Serialize, Serializer}; use crate::error::{Error, Result}; -use crate::lua::{Lua, LuaGuard, LuaInner}; +use crate::state::{Lua, LuaGuard}; +use crate::state::RawLua; use crate::userdata::AnyUserData; use crate::util::get_userdata; use crate::value::{FromLua, Value}; @@ -199,7 +200,7 @@ impl FromLua for UserDataRef { try_value_to_userdata::(value)?.borrow() } - unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { let type_id = lua.get_userdata_type_id(idx)?; match type_id { Some(type_id) if type_id == TypeId::of::() => { @@ -268,7 +269,7 @@ impl FromLua for UserDataRefMut { try_value_to_userdata::(value)?.borrow_mut() } - unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { let type_id = lua.get_userdata_type_id(idx)?; match type_id { Some(type_id) if type_id == TypeId::of::() => { diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index 83ebabf9..6cb9a40f 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -7,7 +7,7 @@ use std::os::raw::c_int; use std::string::String as StdString; use crate::error::{Error, Result}; -use crate::lua::Lua; +use crate::state::Lua; use crate::types::{Callback, MaybeSend}; use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods}; use crate::util::{get_userdata, short_type_name}; diff --git a/src/util/mod.rs b/src/util/mod.rs index ff8b28e9..1fb81ecf 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,5 +1,6 @@ use std::any::{Any, TypeId}; use std::borrow::Cow; +use std::cell::UnsafeCell; use std::ffi::CStr; use std::fmt::Write; use std::mem::MaybeUninit; @@ -18,7 +19,19 @@ pub(crate) use short_names::short_type_name; 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); + map.insert(TypeId::of::(), 0); + + #[cfg(feature = "async")] + { + map.insert(TypeId::of::(), 0); + map.insert(TypeId::of::(), 0); + map.insert(TypeId::of::(), 0); + map.insert(TypeId::of::>(), 0); + } + map.insert(TypeId::of::(), 0); map.insert(TypeId::of::(), 0); map diff --git a/src/value.rs b/src/value.rs index e589d8c9..d733ce73 100644 --- a/src/value.rs +++ b/src/value.rs @@ -19,7 +19,7 @@ use { use crate::error::{Error, Result}; use crate::function::Function; -use crate::lua::{Lua, LuaInner}; +use crate::state::{Lua, RawLua}; use crate::string::String; use crate::table::Table; use crate::thread::Thread; @@ -698,7 +698,7 @@ pub trait IntoLua: Sized { /// This method does not check Lua stack space. #[doc(hidden)] #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { lua.push_value(&self.into_lua(lua.lua())?) } } @@ -726,19 +726,14 @@ pub trait FromLua: Sized { /// Performs the conversion for a value in the Lua stack at index `idx`. #[doc(hidden)] #[inline] - unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { Self::from_lua(lua.stack_value(idx), lua.lua()) } /// Same as `from_lua_arg` but for a value in the Lua stack at index `idx`. #[doc(hidden)] #[inline] - unsafe fn from_stack_arg( - idx: c_int, - i: usize, - to: Option<&str>, - lua: &LuaInner, - ) -> Result { + unsafe fn from_stack_arg(idx: c_int, i: usize, to: Option<&str>, lua: &RawLua) -> Result { Self::from_stack(idx, lua).map_err(|err| Error::BadArgument { to: to.map(|s| s.to_string()), pos: i, @@ -876,7 +871,7 @@ pub trait IntoLuaMulti: Sized { /// Returns number of pushed values. #[doc(hidden)] #[inline] - unsafe fn push_into_stack_multi(self, lua: &LuaInner) -> Result { + unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { let values = self.into_lua_multi(lua.lua())?; let len: c_int = values.len().try_into().unwrap(); unsafe { @@ -916,7 +911,7 @@ pub trait FromLuaMulti: Sized { /// Performs the conversion for a number of values in the Lua stack. #[doc(hidden)] #[inline] - unsafe fn from_stack_multi(nvals: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack_multi(nvals: c_int, lua: &RawLua) -> Result { let mut values = MultiValue::with_lua_and_capacity(lua.lua(), nvals as usize); for idx in 0..nvals { values.push_back(lua.stack_value(-nvals + idx)); @@ -935,7 +930,7 @@ pub trait FromLuaMulti: Sized { nargs: c_int, i: usize, to: Option<&str>, - lua: &LuaInner, + lua: &RawLua, ) -> Result { let _ = (i, to); Self::from_stack_multi(nargs, lua) diff --git a/tests/tests.rs b/tests/tests.rs index aab4f483..50e02edf 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -17,7 +17,7 @@ use mlua::{ fn test_safety() -> Result<()> { let lua = Lua::new(); assert!(lua.load(r#"require "debug""#).exec().is_err()); - match lua.load_from_std_lib(StdLib::DEBUG) { + match lua.load_std_libs(StdLib::DEBUG) { Err(Error::SafetyError(_)) => {} Err(e) => panic!("expected SafetyError, got {:?}", e), Ok(_) => panic!("expected SafetyError, got no error"), @@ -53,7 +53,7 @@ fn test_safety() -> Result<()> { // Test safety rules after dynamically loading `package` library let lua = Lua::new_with(StdLib::NONE, LuaOptions::default())?; assert!(lua.globals().get::<_, Option>("require")?.is_none()); - lua.load_from_std_lib(StdLib::PACKAGE)?; + lua.load_std_libs(StdLib::PACKAGE)?; match lua.load(r#"package.loadlib()"#).exec() { Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() { Error::SafetyError(_) => {} @@ -657,7 +657,7 @@ fn test_recursive_mut_callback_error() -> Result<()> { let lua = Lua::new(); let mut v = Some(Box::new(123)); - let f = lua.create_function_mut::<_, (), _>(move |lua, mutate: bool| { + let f = lua.create_function_mut(move |lua, mutate: bool| { if mutate { v = None; } else { From c1395ab5438ad03293aabf685bf1aef7207f39ed Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 8 Jul 2024 01:00:06 +0100 Subject: [PATCH 121/635] clippy --- src/conversion.rs | 29 ++++++++++++++--------------- src/hook.rs | 4 +++- src/lib.rs | 2 +- src/multi.rs | 3 +-- src/serde/mod.rs | 2 +- src/state.rs | 2 +- src/state/extra.rs | 11 ++++++----- src/state/raw.rs | 22 ++++++++++++---------- src/types.rs | 3 ++- src/userdata/cell.rs | 33 ++++++++++++--------------------- src/userdata/registry.rs | 12 ++++++------ src/util/mod.rs | 3 ++- 12 files changed, 61 insertions(+), 65 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index f82039f3..2defef3c 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -2,7 +2,6 @@ use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::ffi::{CStr, CString}; use std::hash::{BuildHasher, Hash}; -use std::mem::transmute; use std::os::raw::c_int; use std::string::String as StdString; use std::{slice, str}; @@ -23,14 +22,14 @@ use crate::value::{FromLua, IntoLua, Nil, Value}; impl IntoLua for Value { #[inline] fn into_lua(self, _: &Lua) -> Result { - unsafe { Ok(transmute(self)) } + Ok(self) } } impl IntoLua for &Value { #[inline] fn into_lua(self, _: &Lua) -> Result { - unsafe { Ok(transmute(self.clone())) } + Ok(self.clone()) } #[inline] @@ -49,14 +48,14 @@ impl FromLua for Value { impl IntoLua for String { #[inline] fn into_lua(self, _: &Lua) -> Result { - unsafe { Ok(Value::String(transmute(self))) } + Ok(Value::String(self)) } } impl IntoLua for &String { #[inline] fn into_lua(self, _: &Lua) -> Result { - unsafe { Ok(Value::String(transmute(self.clone()))) } + Ok(Value::String(self.clone())) } #[inline] @@ -82,14 +81,14 @@ impl FromLua for String { impl IntoLua for Table { #[inline] fn into_lua(self, _: &Lua) -> Result { - unsafe { Ok(Value::Table(transmute(self))) } + Ok(Value::Table(self)) } } impl IntoLua for &Table { #[inline] fn into_lua(self, _: &Lua) -> Result { - unsafe { Ok(Value::Table(transmute(self.clone()))) } + Ok(Value::Table(self.clone())) } #[inline] @@ -116,14 +115,14 @@ impl FromLua for Table { impl IntoLua for Function { #[inline] fn into_lua(self, _: &Lua) -> Result { - unsafe { Ok(Value::Function(transmute(self))) } + Ok(Value::Function(self)) } } impl IntoLua for &Function { #[inline] fn into_lua(self, _: &Lua) -> Result { - unsafe { Ok(Value::Function(transmute(self.clone()))) } + Ok(Value::Function(self.clone())) } #[inline] @@ -150,14 +149,14 @@ impl FromLua for Function { impl IntoLua for Thread { #[inline] fn into_lua(self, _: &Lua) -> Result { - unsafe { Ok(Value::Thread(transmute(self))) } + Ok(Value::Thread(self)) } } impl IntoLua for &Thread { #[inline] fn into_lua(self, _: &Lua) -> Result { - unsafe { Ok(Value::Thread(transmute(self.clone()))) } + Ok(Value::Thread(self.clone())) } #[inline] @@ -184,14 +183,14 @@ impl FromLua for Thread { impl IntoLua for AnyUserData { #[inline] fn into_lua(self, _: &Lua) -> Result { - unsafe { Ok(Value::UserData(transmute(self))) } + Ok(Value::UserData(self)) } } impl IntoLua for &AnyUserData { #[inline] fn into_lua(self, _: &Lua) -> Result { - unsafe { Ok(Value::UserData(transmute(self.clone()))) } + Ok(Value::UserData(self.clone())) } #[inline] @@ -359,7 +358,7 @@ impl FromLua for crate::types::Vector { impl IntoLua for StdString { #[inline] fn into_lua(self, lua: &Lua) -> Result { - Ok(Value::String(lua.create_string(&self)?)) + Ok(Value::String(lua.create_string(self)?)) } #[inline] @@ -493,7 +492,7 @@ impl IntoLua for Cow<'_, CStr> { impl IntoLua for BString { #[inline] fn into_lua(self, lua: &Lua) -> Result { - Ok(Value::String(lua.create_string(&self)?)) + Ok(Value::String(lua.create_string(self)?)) } } diff --git a/src/hook.rs b/src/hook.rs index 6179f090..03cf5549 100644 --- a/src/hook.rs +++ b/src/hook.rs @@ -29,6 +29,7 @@ pub struct Debug<'a> { enum EitherLua<'a> { Owned(ReentrantMutexGuard<'a, RawLua>), + #[cfg(not(feature = "luau"))] Borrowed(&'a RawLua), } @@ -37,7 +38,8 @@ impl Deref for EitherLua<'_> { fn deref(&self) -> &Self::Target { match self { - EitherLua::Owned(guard) => &*guard, + EitherLua::Owned(guard) => guard, + #[cfg(not(feature = "luau"))] EitherLua::Borrowed(lua) => lua, } } diff --git a/src/lib.rs b/src/lib.rs index d56321c2..ee8d51b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,8 +107,8 @@ pub use crate::chunk::{AsChunk, Chunk, ChunkMode}; pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result}; pub use crate::function::{Function, FunctionInfo}; pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack}; -pub use crate::state::{GCMode, Lua, LuaOptions}; pub use crate::multi::Variadic; +pub use crate::state::{GCMode, Lua, LuaOptions}; // pub use crate::scope::Scope; pub use crate::stdlib::StdLib; pub use crate::string::String; diff --git a/src/multi.rs b/src/multi.rs index 317c14e2..4f0c6632 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -1,5 +1,4 @@ use std::iter::FromIterator; -use std::mem::transmute; use std::ops::{Deref, DerefMut}; use std::os::raw::c_int; use std::result::Result as StdResult; @@ -99,7 +98,7 @@ impl FromLuaMulti for T { impl IntoLuaMulti for MultiValue { #[inline] fn into_lua_multi(self, _: &Lua) -> Result { - unsafe { Ok(transmute(self)) } + Ok(self) } } diff --git a/src/serde/mod.rs b/src/serde/mod.rs index 46c2d915..dee3a81e 100644 --- a/src/serde/mod.rs +++ b/src/serde/mod.rs @@ -5,8 +5,8 @@ use std::os::raw::c_void; use serde::{de::DeserializeOwned, ser::Serialize}; use crate::error::Result; -use crate::state::Lua; use crate::private::Sealed; +use crate::state::Lua; use crate::table::Table; use crate::util::check_stack; use crate::value::Value; diff --git a/src/state.rs b/src/state.rs index 6373ce31..903cd628 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1914,7 +1914,7 @@ impl Deref for LuaGuard { type Target = RawLua; fn deref(&self) -> &Self::Target { - &*self.0 + &self.0 } } diff --git a/src/state/extra.rs b/src/state/extra.rs index 2a075b40..37753c47 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -1,5 +1,6 @@ use std::any::TypeId; use std::cell::UnsafeCell; +use std::rc::Rc; // use std::collections::VecDeque; use std::mem::{self, MaybeUninit}; use std::os::raw::{c_int, c_void}; @@ -106,7 +107,7 @@ impl ExtraData { #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] pub(super) const ERROR_TRACEBACK_IDX: c_int = 1; - pub(super) unsafe fn init(state: *mut ffi::lua_State) -> Arc> { + pub(super) unsafe fn init(state: *mut ffi::lua_State) -> Rc> { // Create ref stack thread and place it in the registry to prevent it // from being garbage collected. let ref_thread = mlua_expect!( @@ -132,7 +133,7 @@ impl ExtraData { assert_eq!(ffi::lua_gettop(ref_thread), Self::ERROR_TRACEBACK_IDX); } - let extra = Arc::new(UnsafeCell::new(ExtraData { + let extra = Rc::new(UnsafeCell::new(ExtraData { lua: MaybeUninit::uninit(), weak: MaybeUninit::uninit(), registered_userdata: FxHashMap::default(), @@ -200,19 +201,19 @@ impl ExtraData { ffi::lua_pop(state, 1); return ptr::null_mut(); } - let extra_ptr = ffi::lua_touserdata(state, -1) as *mut Arc>; + let extra_ptr = ffi::lua_touserdata(state, -1) as *mut Rc>; ffi::lua_pop(state, 1); (*extra_ptr).get() } - unsafe fn store(extra: &Arc>, state: *mut ffi::lua_State) -> Result<()> { + unsafe fn store(extra: &Rc>, state: *mut ffi::lua_State) -> Result<()> { #[cfg(feature = "luau")] if cfg!(not(feature = "module")) { (*ffi::lua_callbacks(state)).userdata = extra.get() as *mut _; return Ok(()); } - push_gc_userdata(state, Arc::clone(extra), true)?; + push_gc_userdata(state, Rc::clone(extra), true)?; protect_lua!(state, 1, 0, fn(state) { let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void; ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, extra_key); diff --git a/src/state/raw.rs b/src/state/raw.rs index d47cf317..ed3f21d4 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -3,6 +3,7 @@ use std::cell::{Cell, UnsafeCell}; use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_int, c_void}; use std::panic::resume_unwind; +use std::rc::Rc; use std::result::Result as StdResult; use std::sync::Arc; use std::{mem, ptr}; @@ -49,7 +50,7 @@ pub struct RawLua { // The state is dynamic and depends on context pub(super) state: Cell<*mut ffi::lua_State>, pub(super) main_state: *mut ffi::lua_State, - pub(super) extra: Arc>, + pub(super) extra: Rc>, } #[cfg(not(feature = "module"))] @@ -169,7 +170,7 @@ impl RawLua { // Create the internal metatables and store them in the registry // to prevent from being garbage collected. - init_gc_metatable::>>(state, None)?; + init_gc_metatable::>>(state, None)?; init_gc_metatable::(state, None)?; init_gc_metatable::(state, None)?; #[cfg(feature = "async")] @@ -207,10 +208,11 @@ impl RawLua { ); assert_stack(main_state, ffi::LUA_MINSTACK); + #[allow(clippy::arc_with_non_send_sync)] let rawlua = Arc::new(ReentrantMutex::new(RawLua { state: Cell::new(state), main_state, - extra: Arc::clone(&extra), + extra: Rc::clone(&extra), })); (*extra.get()).set_lua(&rawlua); @@ -1102,8 +1104,8 @@ impl RawLua { let _sg = StackGuard::new(state); check_stack(state, 4)?; - let func = mem::transmute(func); - let extra = Arc::clone(&self.extra); + let func = mem::transmute::>(func); + let extra = Rc::clone(&self.extra); let protect = !self.unlikely_memory_error(); push_gc_userdata(state, CallbackUpvalue { data: func, extra }, protect)?; if protect { @@ -1147,7 +1149,7 @@ impl RawLua { let args = MultiValue::from_stack_multi(nargs, rawlua)?; let func = &*(*upvalue).data; let fut = func(rawlua, args); - let extra = Arc::clone(&(*upvalue).extra); + let extra = Rc::clone(&(*upvalue).extra); let protect = !rawlua.unlikely_memory_error(); push_gc_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?; if protect { @@ -1169,7 +1171,7 @@ impl RawLua { // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) // The lock must be already held as the future is polled let rawlua = (*extra).raw_lua(); - let _guard = StateGuard::new(&rawlua, state); + let _guard = StateGuard::new(rawlua, state); let fut = &mut (*upvalue).data; let mut ctx = Context::from_waker(rawlua.waker()); @@ -1190,7 +1192,7 @@ impl RawLua { Ok(nresults + 1) } nresults => { - let results = MultiValue::from_stack_multi(nresults, &rawlua)?; + let results = MultiValue::from_stack_multi(nresults, rawlua)?; ffi::lua_pushinteger(state, nresults as _); rawlua.push(rawlua.create_sequence_from(results)?)?; Ok(2) @@ -1206,8 +1208,8 @@ impl RawLua { let _sg = StackGuard::new(state); check_stack(state, 4)?; - let func = mem::transmute(func); - let extra = Arc::clone(&self.extra); + let func = mem::transmute::>(func); + let extra = Rc::clone(&self.extra); let protect = !self.unlikely_memory_error(); let upvalue = AsyncCallbackUpvalue { data: func, extra }; push_gc_userdata(state, upvalue, protect)?; diff --git a/src/types.rs b/src/types.rs index d23eb731..be3b0dae 100644 --- a/src/types.rs +++ b/src/types.rs @@ -3,6 +3,7 @@ use std::cell::{Cell, Ref, RefCell, RefMut, UnsafeCell}; use std::hash::{Hash, Hasher}; use std::ops::{Deref, DerefMut}; use std::os::raw::{c_int, c_void}; +use std::rc::Rc; use std::result::Result as StdResult; use std::sync::atomic::{AtomicI32, Ordering}; use std::sync::Arc; @@ -45,7 +46,7 @@ pub(crate) type Callback<'a> = Box Result + pub(crate) struct Upvalue { pub(crate) data: T, - pub(crate) extra: Arc>, + pub(crate) extra: Rc>, } pub(crate) type CallbackUpvalue = Upvalue>; diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index 33a53618..2ad4eb11 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -9,8 +9,8 @@ use std::rc::Rc; use serde::ser::{Serialize, Serializer}; use crate::error::{Error, Result}; -use crate::state::{Lua, LuaGuard}; use crate::state::RawLua; +use crate::state::{Lua, LuaGuard}; use crate::userdata::AnyUserData; use crate::util::get_userdata; use crate::value::{FromLua, Value}; @@ -89,20 +89,11 @@ impl UserDataVariant { } #[inline(always)] - unsafe fn get_ref(&self) -> &T { - match self { - Self::Default(inner) => &*inner.value.get(), - #[cfg(feature = "serialize")] - Self::Serializable(inner) => &*(inner.value.get() as *mut Box), - } - } - - #[inline(always)] - unsafe fn get_mut(&self) -> &mut T { + fn as_ptr(&self) -> *mut T { match self { - Self::Default(inner) => &mut *inner.value.get(), + Self::Default(inner) => inner.value.get(), #[cfg(feature = "serialize")] - Self::Serializable(inner) => &mut *(inner.value.get() as *mut Box), + Self::Serializable(inner) => unsafe { &mut **(inner.value.get() as *mut Box) }, } } } @@ -164,7 +155,7 @@ impl Deref for UserDataRef { #[inline] fn deref(&self) -> &T { - unsafe { self.variant.get_ref() } + unsafe { &*self.variant.as_ptr() } } } @@ -226,14 +217,14 @@ impl Deref for UserDataRefMut { #[inline] fn deref(&self) -> &Self::Target { - unsafe { self.variant.get_ref() } + unsafe { &*self.variant.as_ptr() } } } impl DerefMut for UserDataRefMut { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { self.variant.get_mut() } + unsafe { &mut *self.variant.as_ptr() } } } @@ -348,7 +339,7 @@ impl<'a, T> Deref for UserDataBorrowRef<'a, T> { #[inline] fn deref(&self) -> &T { - unsafe { self.0.get_ref() } + unsafe { &*self.0.as_ptr() } } } @@ -366,7 +357,7 @@ impl<'a, T> UserDataBorrowRef<'a, T> { #[inline(always)] pub(crate) fn get_ref(&self) -> &'a T { // SAFETY: `UserDataBorrowRef` is only created when the borrow flag is set to reading. - unsafe { self.0.get_ref() } + unsafe { &*self.0.as_ptr() } } } @@ -384,14 +375,14 @@ impl<'a, T> Deref for UserDataBorrowMut<'a, T> { #[inline] fn deref(&self) -> &T { - unsafe { self.0.get_ref() } + unsafe { &*self.0.as_ptr() } } } impl<'a, T> DerefMut for UserDataBorrowMut<'a, T> { #[inline] fn deref_mut(&mut self) -> &mut T { - unsafe { self.0.get_mut() } + unsafe { &mut *self.0.as_ptr() } } } @@ -409,7 +400,7 @@ impl<'a, T> UserDataBorrowMut<'a, T> { #[inline(always)] pub(crate) fn get_mut(&mut self) -> &'a mut T { // SAFETY: `UserDataBorrowMut` is only created when the borrow flag is set to writing. - unsafe { self.0.get_mut() } + unsafe { &mut *self.0.as_ptr() } } } diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index 6cb9a40f..90282c10 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -166,7 +166,7 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { Err(e) => return Box::pin(future::ready(Err(e))), }; let fut = method(lua, ud.get_ref(), args); - Box::pin(async move { fut.await?.push_into_stack_multi(&rawlua) }) + Box::pin(async move { fut.await?.push_into_stack_multi(rawlua) }) } _ => { let err = Error::bad_self_argument(&name, Error::UserDataTypeMismatch); @@ -213,7 +213,7 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { Err(e) => return Box::pin(future::ready(Err(e))), }; let fut = method(lua, ud.get_mut(), args); - Box::pin(async move { fut.await?.push_into_stack_multi(&rawlua) }) + Box::pin(async move { fut.await?.push_into_stack_multi(rawlua) }) } _ => { let err = Error::bad_self_argument(&name, Error::UserDataTypeMismatch); @@ -261,7 +261,7 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { FR: Future> + 'a, R: IntoLuaMulti, { - let name = get_function_name::(&name); + let name = get_function_name::(name); Box::new(move |rawlua, args| unsafe { let lua = rawlua.lua(); let args = match A::from_lua_args(args, 1, Some(&name), lua) { @@ -269,7 +269,7 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { Err(e) => return Box::pin(future::ready(Err(e))), }; let fut = function(lua, args); - Box::pin(async move { fut.await?.push_into_stack_multi(&rawlua) }) + Box::pin(async move { fut.await?.push_into_stack_multi(rawlua) }) }) } @@ -315,8 +315,8 @@ impl<'a, T: 'static> UserDataFields<'a, T> for UserDataRegistry<'a, T> { R: IntoLua, { let name = name.to_string(); - let callback = Self::box_method(&name, move |lua, data, ()| method(lua, &data)); - self.field_getters.push((name.into(), callback)); + let callback = Self::box_method(&name, move |lua, data, ()| method(lua, data)); + self.field_getters.push((name, callback)); } fn add_field_method_set(&mut self, name: impl ToString, method: M) diff --git a/src/util/mod.rs b/src/util/mod.rs index 1fb81ecf..966a919a 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -6,6 +6,7 @@ use std::fmt::Write; use std::mem::MaybeUninit; use std::os::raw::{c_char, c_int, c_void}; use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe}; +use std::rc::Rc; use std::sync::Arc; use std::{ptr, slice, str}; @@ -20,7 +21,7 @@ pub(crate) use short_names::short_type_name; static METATABLE_CACHE: Lazy> = Lazy::new(|| { let mut map = FxHashMap::with_capacity_and_hasher(32, Default::default()); - map.insert(TypeId::of::>>(), 0); + map.insert(TypeId::of::>>(), 0); map.insert(TypeId::of::(), 0); map.insert(TypeId::of::(), 0); From 658f2a13ea9199014a9f59787446d42658cd801a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 8 Jul 2024 13:14:28 +0100 Subject: [PATCH 122/635] Support multi threads under `send` feature flag --- src/function.rs | 15 +++++---- src/hook.rs | 2 +- src/state.rs | 61 ++++++++++++++++++------------------ src/state/extra.rs | 26 ++++++++-------- src/state/raw.rs | 31 ++++++++++--------- src/string.rs | 15 +++++---- src/table.rs | 15 +++++---- src/thread.rs | 8 +++++ src/types.rs | 38 +++++++++++++---------- src/types/sync.rs | 77 ++++++++++++++++++++++++++++++++++++++++++++++ src/userdata.rs | 15 +++++---- src/value.rs | 16 +++++----- tests/tests.rs | 27 ++++++++++++---- 13 files changed, 233 insertions(+), 113 deletions(-) create mode 100644 src/types/sync.rs diff --git a/src/function.rs b/src/function.rs index c4ff2815..3495236a 100644 --- a/src/function.rs +++ b/src/function.rs @@ -594,9 +594,12 @@ impl IntoLua for WrappedAsyncFunction { } } -// #[cfg(test)] -// mod assertions { -// use super::*; - -// static_assertions::assert_not_impl_any!(Function: Send); -// } +#[cfg(test)] +mod assertions { + use super::*; + + #[cfg(not(feature = "send"))] + static_assertions::assert_not_impl_any!(Function: Send); + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(Function: Send, Sync); +} diff --git a/src/hook.rs b/src/hook.rs index 03cf5549..37f4b1e8 100644 --- a/src/hook.rs +++ b/src/hook.rs @@ -6,9 +6,9 @@ use std::ops::{BitOr, BitOrAssign}; use std::os::raw::c_int; use ffi::lua_Debug; -use parking_lot::ReentrantMutexGuard; use crate::state::RawLua; +use crate::types::ReentrantMutexGuard; use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str}; /// Contains information about currently executing Lua code. diff --git a/src/state.rs b/src/state.rs index 903cd628..2cba49f8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -6,12 +6,10 @@ use std::marker::PhantomData; use std::ops::Deref; use std::os::raw::{c_int, c_void}; use std::panic::Location; +use std::rc::Rc; use std::result::Result as StdResult; -use std::sync::{Arc, Weak}; use std::{mem, ptr}; -use parking_lot::{ReentrantMutex, ReentrantMutexGuard}; - use crate::chunk::{AsChunk, Chunk}; use crate::error::{Error, Result}; use crate::function::Function; @@ -24,7 +22,7 @@ use crate::table::Table; use crate::thread::Thread; use crate::types::{ AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LightUserData, MaybeSend, Number, - RegistryKey, + ReentrantMutex, ReentrantMutexGuard, RegistryKey, XRc, XWeak, }; use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataVariant}; use crate::util::{assert_stack, check_stack, push_string, push_table, rawset_field, StackGuard}; @@ -49,11 +47,11 @@ use util::{callback_error_ext, StateGuard}; /// Top level Lua struct which represents an instance of Lua VM. #[derive(Clone)] #[repr(transparent)] -pub struct Lua(Arc>); +pub struct Lua(XRc>); #[derive(Clone)] #[repr(transparent)] -pub(crate) struct WeakLua(Weak>); +pub(crate) struct WeakLua(XWeak>); pub(crate) struct LuaGuard(ArcReentrantMutexGuard); @@ -142,11 +140,6 @@ impl LuaOptions { } } -/// Requires `feature = "send"` -#[cfg(feature = "send")] -#[cfg_attr(docsrs, doc(cfg(feature = "send")))] -unsafe impl Send for Lua {} - #[cfg(not(feature = "module"))] impl Drop for Lua { fn drop(&mut self) { @@ -421,7 +414,8 @@ impl Lua { #[doc(hidden)] #[cfg(feature = "module")] pub fn skip_memory_check(&self, skip: bool) { - unsafe { (*self.extra.get()).skip_memory_check = skip }; + let lua = self.lock(); + unsafe { (*lua.extra.get()).skip_memory_check = skip }; } /// Enables (or disables) sandbox mode on this Lua instance. @@ -605,7 +599,7 @@ impl Lua { let interrupt_cb = (*extra).interrupt_callback.clone(); let interrupt_cb = mlua_expect!(interrupt_cb, "no interrupt callback set in interrupt_proc"); - if Arc::strong_count(&interrupt_cb) > 2 { + if Rc::strong_count(&interrupt_cb) > 2 { return Ok(VmState::Continue); // Don't allow recursion } let _guard = StateGuard::new((*extra).raw_lua(), state); @@ -622,7 +616,7 @@ impl Lua { // Set interrupt callback let lua = self.lock(); unsafe { - (*lua.extra.get()).interrupt_callback = Some(Arc::new(callback)); + (*lua.extra.get()).interrupt_callback = Some(Rc::new(callback)); (*ffi::lua_callbacks(lua.main_state)).interrupt = Some(interrupt_proc); } } @@ -947,7 +941,8 @@ impl Lua { #[cfg(any(feature = "luau-jit", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau-jit")))] pub fn enable_jit(&self, enable: bool) { - unsafe { (*self.extra.get()).enable_jit = enable }; + let lua = self.lock(); + unsafe { (*lua.extra.get()).enable_jit = enable }; } /// Sets Luau feature flag (global setting). @@ -1879,7 +1874,7 @@ impl Lua { #[inline(always)] pub(crate) fn weak(&self) -> WeakLua { - WeakLua(Arc::downgrade(&self.0)) + WeakLua(XRc::downgrade(&self.0)) } } @@ -1887,7 +1882,7 @@ impl WeakLua { #[track_caller] #[inline(always)] pub(crate) fn lock(&self) -> LuaGuard { - LuaGuard::new(self.0.upgrade().unwrap()) + LuaGuard::new(self.0.upgrade().expect("Lua instance is destroyed")) } #[inline(always)] @@ -1898,15 +1893,21 @@ impl WeakLua { impl PartialEq for WeakLua { fn eq(&self, other: &Self) -> bool { - Weak::ptr_eq(&self.0, &other.0) + XWeak::ptr_eq(&self.0, &other.0) } } impl Eq for WeakLua {} impl LuaGuard { - pub(crate) fn new(handle: Arc>) -> Self { - Self(handle.lock_arc()) + #[cfg(feature = "send")] + pub(crate) fn new(handle: XRc>) -> Self { + LuaGuard(handle.lock_arc()) + } + + #[cfg(not(feature = "send"))] + pub(crate) fn new(handle: XRc>) -> Self { + LuaGuard(handle.into_lock_arc()) } } @@ -1922,15 +1923,15 @@ pub(crate) mod extra; mod raw; pub(crate) mod util; -// #[cfg(test)] -// mod assertions { -// use super::*; +#[cfg(test)] +mod assertions { + use super::*; -// // Lua has lots of interior mutability, should not be RefUnwindSafe -// static_assertions::assert_not_impl_any!(Lua: std::panic::RefUnwindSafe); + // Lua has lots of interior mutability, should not be RefUnwindSafe + static_assertions::assert_not_impl_any!(Lua: std::panic::RefUnwindSafe); -// #[cfg(not(feature = "send"))] -// static_assertions::assert_not_impl_any!(Lua: Send); -// #[cfg(feature = "send")] -// static_assertions::assert_impl_all!(Lua: Send); -// } + #[cfg(not(feature = "send"))] + static_assertions::assert_not_impl_any!(Lua: Send); + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(Lua: Send, Sync); +} diff --git a/src/state/extra.rs b/src/state/extra.rs index 37753c47..e3806857 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -5,15 +5,15 @@ use std::rc::Rc; use std::mem::{self, MaybeUninit}; use std::os::raw::{c_int, c_void}; use std::ptr; -use std::sync::{Arc, Weak}; +use std::sync::Arc; -use parking_lot::{Mutex, ReentrantMutex}; +use parking_lot::Mutex; use rustc_hash::FxHashMap; use crate::error::Result; use crate::state::RawLua; use crate::stdlib::StdLib; -use crate::types::AppData; +use crate::types::{AppData, ReentrantMutex, XRc, XWeak}; use crate::util::{get_gc_metatable, push_gc_userdata, WrappedFailure}; #[cfg(any(feature = "luau", doc))] @@ -34,9 +34,9 @@ const REF_STACK_RESERVE: c_int = 1; /// Data associated with the Lua state. pub(crate) struct ExtraData { // Same layout as `Lua` - pub(super) lua: MaybeUninit>>, + pub(super) lua: MaybeUninit>>, // Same layout as `WeakLua` - pub(super) weak: MaybeUninit>>, + pub(super) weak: MaybeUninit>>, pub(super) registered_userdata: FxHashMap, pub(super) registered_userdata_mt: FxHashMap<*const c_void, Option>, @@ -107,7 +107,7 @@ impl ExtraData { #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] pub(super) const ERROR_TRACEBACK_IDX: c_int = 1; - pub(super) unsafe fn init(state: *mut ffi::lua_State) -> Rc> { + pub(super) unsafe fn init(state: *mut ffi::lua_State) -> XRc> { // Create ref stack thread and place it in the registry to prevent it // from being garbage collected. let ref_thread = mlua_expect!( @@ -133,7 +133,7 @@ impl ExtraData { assert_eq!(ffi::lua_gettop(ref_thread), Self::ERROR_TRACEBACK_IDX); } - let extra = Rc::new(UnsafeCell::new(ExtraData { + let extra = XRc::new(UnsafeCell::new(ExtraData { lua: MaybeUninit::uninit(), weak: MaybeUninit::uninit(), registered_userdata: FxHashMap::default(), @@ -179,12 +179,12 @@ impl ExtraData { extra } - pub(super) unsafe fn set_lua(&mut self, lua: &Arc>) { - self.lua.write(Arc::clone(lua)); + pub(super) unsafe fn set_lua(&mut self, lua: &XRc>) { + self.lua.write(XRc::clone(lua)); if cfg!(not(feature = "module")) { - Arc::decrement_strong_count(Arc::as_ptr(lua)); + XRc::decrement_strong_count(XRc::as_ptr(lua)); } - self.weak.write(Arc::downgrade(lua)); + self.weak.write(XRc::downgrade(lua)); } pub(super) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { @@ -206,14 +206,14 @@ impl ExtraData { (*extra_ptr).get() } - unsafe fn store(extra: &Rc>, state: *mut ffi::lua_State) -> Result<()> { + unsafe fn store(extra: &XRc>, state: *mut ffi::lua_State) -> Result<()> { #[cfg(feature = "luau")] if cfg!(not(feature = "module")) { (*ffi::lua_callbacks(state)).userdata = extra.get() as *mut _; return Ok(()); } - push_gc_userdata(state, Rc::clone(extra), true)?; + push_gc_userdata(state, XRc::clone(extra), true)?; protect_lua!(state, 1, 0, fn(state) { let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void; ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, extra_key); diff --git a/src/state/raw.rs b/src/state/raw.rs index ed3f21d4..860a108c 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -8,8 +8,6 @@ use std::result::Result as StdResult; use std::sync::Arc; use std::{mem, ptr}; -use parking_lot::ReentrantMutex; - use crate::chunk::ChunkMode; use crate::error::{Error, Result}; use crate::function::Function; @@ -21,7 +19,7 @@ use crate::table::Table; use crate::thread::Thread; use crate::types::{ AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, - LightUserData, MaybeSend, RegistryKey, SubtypeId, ValueRef, + LightUserData, MaybeSend, ReentrantMutex, RegistryKey, SubtypeId, ValueRef, XRc, }; use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataRegistry, UserDataVariant}; use crate::util::{ @@ -50,7 +48,7 @@ pub struct RawLua { // The state is dynamic and depends on context pub(super) state: Cell<*mut ffi::lua_State>, pub(super) main_state: *mut ffi::lua_State, - pub(super) extra: Rc>, + pub(super) extra: XRc>, } #[cfg(not(feature = "module"))] @@ -69,6 +67,9 @@ impl Drop for RawLua { } } +#[cfg(feature = "send")] +unsafe impl Send for RawLua {} + impl RawLua { #[inline(always)] pub(crate) fn lua(&self) -> &Lua { @@ -96,7 +97,7 @@ impl RawLua { unsafe { (*self.extra.get()).ref_thread } } - pub(super) unsafe fn new(libs: StdLib, options: LuaOptions) -> Arc> { + pub(super) unsafe fn new(libs: StdLib, options: LuaOptions) -> XRc> { let mem_state: *mut MemoryState = Box::into_raw(Box::default()); let mut state = ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void); // If state is null then switch to Lua internal allocator @@ -154,7 +155,7 @@ impl RawLua { rawlua } - pub(super) unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> Arc> { + pub(super) unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> XRc> { assert!(!state.is_null(), "Lua state is NULL"); if let Some(lua) = Self::try_from_ptr(state) { return lua; @@ -209,10 +210,10 @@ impl RawLua { assert_stack(main_state, ffi::LUA_MINSTACK); #[allow(clippy::arc_with_non_send_sync)] - let rawlua = Arc::new(ReentrantMutex::new(RawLua { + let rawlua = XRc::new(ReentrantMutex::new(RawLua { state: Cell::new(state), main_state, - extra: Rc::clone(&extra), + extra: XRc::clone(&extra), })); (*extra.get()).set_lua(&rawlua); @@ -221,10 +222,10 @@ impl RawLua { pub(super) unsafe fn try_from_ptr( state: *mut ffi::lua_State, - ) -> Option>> { + ) -> Option>> { match ExtraData::get(state) { extra if extra.is_null() => None, - extra => Some(Arc::clone(&(*extra).lua().0)), + extra => Some(XRc::clone(&(*extra).lua().0)), } } @@ -369,7 +370,7 @@ impl RawLua { callback_error_ext(state, extra, move |_| { let hook_cb = (*extra).hook_callback.clone(); let hook_cb = mlua_expect!(hook_cb, "no hook callback set in hook_proc"); - if Arc::strong_count(&hook_cb) > 2 { + if Rc::strong_count(&hook_cb) > 2 { return Ok(()); // Don't allow recursion } let rawlua = (*extra).raw_lua(); @@ -379,7 +380,7 @@ impl RawLua { }) } - (*self.extra.get()).hook_callback = Some(Arc::new(callback)); + (*self.extra.get()).hook_callback = Some(Rc::new(callback)); (*self.extra.get()).hook_thread = state; // Mark for what thread the hook is set ffi::lua_sethook(state, Some(hook_proc), triggers.mask(), triggers.count()); } @@ -1105,7 +1106,7 @@ impl RawLua { check_stack(state, 4)?; let func = mem::transmute::>(func); - let extra = Rc::clone(&self.extra); + let extra = XRc::clone(&self.extra); let protect = !self.unlikely_memory_error(); push_gc_userdata(state, CallbackUpvalue { data: func, extra }, protect)?; if protect { @@ -1149,7 +1150,7 @@ impl RawLua { let args = MultiValue::from_stack_multi(nargs, rawlua)?; let func = &*(*upvalue).data; let fut = func(rawlua, args); - let extra = Rc::clone(&(*upvalue).extra); + let extra = XRc::clone(&(*upvalue).extra); let protect = !rawlua.unlikely_memory_error(); push_gc_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?; if protect { @@ -1209,7 +1210,7 @@ impl RawLua { check_stack(state, 4)?; let func = mem::transmute::>(func); - let extra = Rc::clone(&self.extra); + let extra = XRc::clone(&self.extra); let protect = !self.unlikely_memory_error(); let upvalue = AsyncCallbackUpvalue { data: func, extra }; push_gc_userdata(state, upvalue, protect)?; diff --git a/src/string.rs b/src/string.rs index cde30eef..ac026f12 100644 --- a/src/string.rs +++ b/src/string.rs @@ -202,9 +202,12 @@ impl Serialize for String { } } -// #[cfg(test)] -// mod assertions { -// use super::*; - -// static_assertions::assert_not_impl_any!(String: Send); -// } +#[cfg(test)] +mod assertions { + use super::*; + + #[cfg(not(feature = "send"))] + static_assertions::assert_not_impl_any!(String: Send); + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(String: Send, Sync); +} diff --git a/src/table.rs b/src/table.rs index e8d6d34a..478da12e 100644 --- a/src/table.rs +++ b/src/table.rs @@ -1219,9 +1219,12 @@ where } } -// #[cfg(test)] -// mod assertions { -// use super::*; - -// static_assertions::assert_not_impl_any!(Table: Send); -// } +#[cfg(test)] +mod assertions { + use super::*; + + #[cfg(not(feature = "send"))] + static_assertions::assert_not_impl_any!(Table: Send); + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(Table: Send, Sync); +} diff --git a/src/thread.rs b/src/thread.rs index 177ce189..db654963 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -46,6 +46,11 @@ pub enum ThreadStatus { #[derive(Clone, Debug)] pub struct Thread(pub(crate) ValueRef, pub(crate) *mut ffi::lua_State); +#[cfg(feature = "send")] +unsafe impl Send for Thread {} +#[cfg(feature = "send")] +unsafe impl Sync for Thread {} + /// Thread (coroutine) representation as an async [`Future`] or [`Stream`]. /// /// Requires `feature = "async"` @@ -526,5 +531,8 @@ impl<'lua, 'a> Drop for WakerGuard<'lua, 'a> { mod assertions { use super::*; + #[cfg(not(feature = "send"))] static_assertions::assert_not_impl_any!(Thread: Send); + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(Thread: Send, Sync); } diff --git a/src/types.rs b/src/types.rs index be3b0dae..dc91cc1a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -9,7 +9,7 @@ use std::sync::atomic::{AtomicI32, Ordering}; use std::sync::Arc; use std::{fmt, mem, ptr}; -use parking_lot::{Mutex, RawMutex, RawThreadId}; +use parking_lot::Mutex; use rustc_hash::FxHashMap; use crate::error::Result; @@ -23,6 +23,9 @@ use {crate::value::MultiValue, futures_util::future::LocalBoxFuture}; #[cfg(all(feature = "luau", feature = "serialize"))] use serde::ser::{Serialize, SerializeTupleStruct, Serializer}; +// Re-export mutex wrappers +pub(crate) use sync::{ArcReentrantMutexGuard, ReentrantMutex, ReentrantMutexGuard, XRc, XWeak}; + /// Type of Lua integer numbers. pub type Integer = ffi::lua_Integer; /// Type of Lua floating point numbers. @@ -70,16 +73,16 @@ pub enum VmState { } #[cfg(all(feature = "send", not(feature = "luau")))] -pub(crate) type HookCallback = Arc Result<()> + Send>; +pub(crate) type HookCallback = Rc Result<()> + Send>; #[cfg(all(not(feature = "send"), not(feature = "luau")))] -pub(crate) type HookCallback = Arc Result<()>>; +pub(crate) type HookCallback = Rc Result<()>>; -#[cfg(all(feature = "luau", feature = "send"))] -pub(crate) type InterruptCallback = Arc Result + Send>; +#[cfg(all(feature = "send", feature = "luau"))] +pub(crate) type InterruptCallback = Rc Result + Send>; -#[cfg(all(feature = "luau", not(feature = "send")))] -pub(crate) type InterruptCallback = Arc Result>; +#[cfg(all(not(feature = "send"), feature = "luau"))] +pub(crate) type InterruptCallback = Rc Result>; #[cfg(all(feature = "send", feature = "lua54"))] pub(crate) type WarnCallback = Box Result<()> + Send>; @@ -183,9 +186,6 @@ impl PartialEq<[f32; Self::SIZE]> for Vector { } } -pub(crate) type ArcReentrantMutexGuard = - parking_lot::lock_api::ArcReentrantMutexGuard; - pub(crate) struct DestructedUserdata; /// An auto generated key into the Lua registry. @@ -482,10 +482,16 @@ impl fmt::Debug for AppDataRefMut<'_, T> { } } -// #[cfg(test)] -// mod assertions { -// use super::*; +mod sync; + +#[cfg(test)] +mod assertions { + use super::*; + + static_assertions::assert_impl_all!(RegistryKey: Send, Sync); -// static_assertions::assert_impl_all!(RegistryKey: Send, Sync); -// static_assertions::assert_not_impl_any!(ValueRef: Send); -// } + #[cfg(not(feature = "send"))] + static_assertions::assert_not_impl_any!(ValueRef: Send); + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(ValueRef: Send, Sync); +} diff --git a/src/types/sync.rs b/src/types/sync.rs new file mode 100644 index 00000000..7f9adbd6 --- /dev/null +++ b/src/types/sync.rs @@ -0,0 +1,77 @@ +#[cfg(feature = "send")] +mod inner { + use parking_lot::{RawMutex, RawThreadId}; + use std::sync::{Arc, Weak}; + + pub(crate) type XRc = Arc; + pub(crate) type XWeak = Weak; + + pub(crate) type ReentrantMutex = parking_lot::ReentrantMutex; + + pub(crate) type ReentrantMutexGuard<'a, T> = parking_lot::ReentrantMutexGuard<'a, T>; + + pub(crate) type ArcReentrantMutexGuard = + parking_lot::lock_api::ArcReentrantMutexGuard; +} + +#[cfg(not(feature = "send"))] +mod inner { + use std::ops::Deref; + use std::rc::{Rc, Weak}; + + pub(crate) type XRc = Rc; + pub(crate) type XWeak = Weak; + + pub(crate) struct ReentrantMutex(T); + + impl ReentrantMutex { + #[inline(always)] + pub(crate) fn new(val: T) -> Self { + ReentrantMutex(val) + } + + #[inline(always)] + pub(crate) fn lock(&self) -> ReentrantMutexGuard { + ReentrantMutexGuard(&self.0) + } + + #[inline(always)] + pub(crate) fn lock_arc(self: &XRc) -> ArcReentrantMutexGuard { + ArcReentrantMutexGuard(Rc::clone(self)) + } + + #[inline(always)] + pub(crate) fn into_lock_arc(self: XRc) -> ArcReentrantMutexGuard { + ArcReentrantMutexGuard(self) + } + + #[inline(always)] + pub(crate) fn data_ptr(&self) -> *const T { + &self.0 as *const _ + } + } + + pub(crate) struct ReentrantMutexGuard<'a, T>(&'a T); + + impl<'a, T> Deref for ReentrantMutexGuard<'a, T> { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.0 + } + } + + pub(crate) struct ArcReentrantMutexGuard(XRc>); + + impl Deref for ArcReentrantMutexGuard { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.0 .0 + } + } +} + +pub(crate) use inner::{ArcReentrantMutexGuard, ReentrantMutex, ReentrantMutexGuard, XRc, XWeak}; diff --git a/src/userdata.rs b/src/userdata.rs index 81889d85..855d4d55 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -1190,9 +1190,12 @@ mod cell; mod ext; mod registry; -// #[cfg(test)] -// mod assertions { -// use super::*; - -// static_assertions::assert_not_impl_any!(AnyUserData: Send); -// } +#[cfg(test)] +mod assertions { + use super::*; + + #[cfg(not(feature = "send"))] + static_assertions::assert_not_impl_any!(AnyUserData: Send); + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(AnyUserData: Send, Sync); +} diff --git a/src/value.rs b/src/value.rs index d733ce73..82dc8951 100644 --- a/src/value.rs +++ b/src/value.rs @@ -9,14 +9,6 @@ use std::{fmt, mem, ptr, str}; use num_traits::FromPrimitive; -#[cfg(feature = "serialize")] -use { - crate::table::SerializableTable, - rustc_hash::FxHashSet, - serde::ser::{self, Serialize, Serializer}, - std::{cell::RefCell, rc::Rc, result::Result as StdResult}, -}; - use crate::error::{Error, Result}; use crate::function::Function; use crate::state::{Lua, RawLua}; @@ -27,6 +19,14 @@ use crate::types::{Integer, LightUserData, Number, SubtypeId}; use crate::userdata::AnyUserData; use crate::util::{check_stack, StackGuard}; +#[cfg(feature = "serialize")] +use { + crate::table::SerializableTable, + rustc_hash::FxHashSet, + serde::ser::{self, Serialize, Serializer}, + std::{cell::RefCell, rc::Rc, result::Result as StdResult}, +}; + /// A dynamically typed Lua value. The `String`, `Table`, `Function`, `Thread`, and `UserData` /// variants contain handle types into the internal Lua state. It is a logic error to mix handle /// types between separate `Lua` instances, and doing so will result in a panic. diff --git a/tests/tests.rs b/tests/tests.rs index 50e02edf..8284fc2f 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1324,11 +1324,26 @@ fn test_luajit_cdata() -> Result<()> { #[test] #[cfg(feature = "send")] #[cfg(not(target_arch = "wasm32"))] -fn test_send() { +fn test_multi_thread() -> Result<()> { let lua = Lua::new(); - std::thread::spawn(move || { - let _lua = lua; - }) - .join() - .unwrap(); + + lua.globals().set("i", 0)?; + let func = lua.load("i = i + 1").into_function()?; + + std::thread::scope(|s| { + s.spawn(|| { + for _ in 0..5 { + func.call::<_, ()>(()).unwrap(); + } + }); + s.spawn(|| { + for _ in 0..5 { + func.call::<_, ()>(()).unwrap(); + } + }); + }); + + assert_eq!(lua.globals().get::<_, i32>("i")?, 10); + + Ok(()) } From 7a75c7305227d31c941a002006da684b28806b5b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 8 Jul 2024 23:10:28 +0100 Subject: [PATCH 123/635] Move `AppData` to `types::app_data` mod --- src/types.rs | 165 ++------------------------------------ src/types/app_data.rs | 180 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+), 159 deletions(-) create mode 100644 src/types/app_data.rs diff --git a/src/types.rs b/src/types.rs index dc91cc1a..abd88af2 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,21 +1,17 @@ -use std::any::{Any, TypeId}; -use std::cell::{Cell, Ref, RefCell, RefMut, UnsafeCell}; +use std::cell::UnsafeCell; use std::hash::{Hash, Hasher}; -use std::ops::{Deref, DerefMut}; use std::os::raw::{c_int, c_void}; use std::rc::Rc; -use std::result::Result as StdResult; use std::sync::atomic::{AtomicI32, Ordering}; use std::sync::Arc; use std::{fmt, mem, ptr}; use parking_lot::Mutex; -use rustc_hash::FxHashMap; use crate::error::Result; #[cfg(not(feature = "luau"))] use crate::hook::Debug; -use crate::state::{ExtraData, Lua, LuaGuard, RawLua, WeakLua}; +use crate::state::{ExtraData, Lua, RawLua, WeakLua}; #[cfg(feature = "async")] use {crate::value::MultiValue, futures_util::future::LocalBoxFuture}; @@ -24,6 +20,7 @@ use {crate::value::MultiValue, futures_util::future::LocalBoxFuture}; use serde::ser::{Serialize, SerializeTupleStruct, Serializer}; // Re-export mutex wrappers +pub use app_data::{AppData, AppDataRef, AppDataRefMut}; pub(crate) use sync::{ArcReentrantMutexGuard, ReentrantMutex, ReentrantMutexGuard, XRc, XWeak}; /// Type of Lua integer numbers. @@ -49,7 +46,7 @@ pub(crate) type Callback<'a> = Box Result + pub(crate) struct Upvalue { pub(crate) data: T, - pub(crate) extra: Rc>, + pub(crate) extra: XRc>, } pub(crate) type CallbackUpvalue = Upvalue>; @@ -167,7 +164,7 @@ impl Vector { #[cfg(all(feature = "luau", feature = "serialize"))] impl Serialize for Vector { - fn serialize(&self, serializer: S) -> StdResult { + fn serialize(&self, serializer: S) -> std::result::Result { let mut ts = serializer.serialize_tuple_struct("Vector", Self::SIZE)?; ts.serialize_field(&self.x())?; ts.serialize_field(&self.y())?; @@ -331,157 +328,7 @@ impl PartialEq for ValueRef { } } -#[derive(Debug, Default)] -pub(crate) struct AppData { - #[cfg(not(feature = "send"))] - container: UnsafeCell>>>, - #[cfg(feature = "send")] - container: UnsafeCell>>>, - borrow: Cell, -} - -impl AppData { - #[track_caller] - pub(crate) fn insert(&self, data: T) -> Option { - match self.try_insert(data) { - Ok(data) => data, - Err(_) => panic!("cannot mutably borrow app data container"), - } - } - - pub(crate) fn try_insert(&self, data: T) -> StdResult, T> { - if self.borrow.get() != 0 { - return Err(data); - } - // SAFETY: we checked that there are no other references to the container - Ok(unsafe { &mut *self.container.get() } - .insert(TypeId::of::(), RefCell::new(Box::new(data))) - .and_then(|data| data.into_inner().downcast::().ok().map(|data| *data))) - } - - #[track_caller] - pub(crate) fn borrow(&self, guard: Option) -> Option> { - let data = unsafe { &*self.container.get() } - .get(&TypeId::of::())? - .borrow(); - self.borrow.set(self.borrow.get() + 1); - Some(AppDataRef { - data: Ref::filter_map(data, |data| data.downcast_ref()).ok()?, - borrow: &self.borrow, - _guard: guard, - }) - } - - #[track_caller] - pub(crate) fn borrow_mut( - &self, - guard: Option, - ) -> Option> { - let data = unsafe { &*self.container.get() } - .get(&TypeId::of::())? - .borrow_mut(); - self.borrow.set(self.borrow.get() + 1); - Some(AppDataRefMut { - data: RefMut::filter_map(data, |data| data.downcast_mut()).ok()?, - borrow: &self.borrow, - _guard: guard, - }) - } - - #[track_caller] - pub(crate) fn remove(&self) -> Option { - if self.borrow.get() != 0 { - panic!("cannot mutably borrow app data container"); - } - // SAFETY: we checked that there are no other references to the container - unsafe { &mut *self.container.get() } - .remove(&TypeId::of::())? - .into_inner() - .downcast::() - .ok() - .map(|data| *data) - } -} - -/// A wrapper type for an immutably borrowed value from an app data container. -/// -/// This type is similar to [`Ref`]. -pub struct AppDataRef<'a, T: ?Sized + 'a> { - data: Ref<'a, T>, - borrow: &'a Cell, - _guard: Option, -} - -impl Drop for AppDataRef<'_, T> { - fn drop(&mut self) { - self.borrow.set(self.borrow.get() - 1); - } -} - -impl Deref for AppDataRef<'_, T> { - type Target = T; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.data - } -} - -impl fmt::Display for AppDataRef<'_, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - (**self).fmt(f) - } -} - -impl fmt::Debug for AppDataRef<'_, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - (**self).fmt(f) - } -} - -/// A wrapper type for a mutably borrowed value from an app data container. -/// -/// This type is similar to [`RefMut`]. -pub struct AppDataRefMut<'a, T: ?Sized + 'a> { - data: RefMut<'a, T>, - borrow: &'a Cell, - _guard: Option, -} - -impl Drop for AppDataRefMut<'_, T> { - fn drop(&mut self) { - self.borrow.set(self.borrow.get() - 1); - } -} - -impl Deref for AppDataRefMut<'_, T> { - type Target = T; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.data - } -} - -impl DerefMut for AppDataRefMut<'_, T> { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.data - } -} - -impl fmt::Display for AppDataRefMut<'_, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - (**self).fmt(f) - } -} - -impl fmt::Debug for AppDataRefMut<'_, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - (**self).fmt(f) - } -} - +mod app_data; mod sync; #[cfg(test)] diff --git a/src/types/app_data.rs b/src/types/app_data.rs new file mode 100644 index 00000000..3b6a4ab0 --- /dev/null +++ b/src/types/app_data.rs @@ -0,0 +1,180 @@ +use std::any::{Any, TypeId}; +use std::cell::{Cell, Ref, RefCell, RefMut, UnsafeCell}; +use std::fmt; +use std::ops::{Deref, DerefMut}; +use std::result::Result as StdResult; + +use rustc_hash::FxHashMap; + +use crate::state::LuaGuard; + +use super::MaybeSend; + +#[cfg(not(feature = "send"))] +type Container = UnsafeCell>>>; + +#[cfg(feature = "send")] +type Container = UnsafeCell>>>; + +/// A container for arbitrary data associated with the Lua state. +#[derive(Debug, Default)] +pub struct AppData { + container: Container, + borrow: Cell, +} + +impl AppData { + #[track_caller] + pub(crate) fn insert(&self, data: T) -> Option { + match self.try_insert(data) { + Ok(data) => data, + Err(_) => panic!("cannot mutably borrow app data container"), + } + } + + pub(crate) fn try_insert(&self, data: T) -> StdResult, T> { + if self.borrow.get() != 0 { + return Err(data); + } + // SAFETY: we checked that there are no other references to the container + Ok(unsafe { &mut *self.container.get() } + .insert(TypeId::of::(), RefCell::new(Box::new(data))) + .and_then(|data| data.into_inner().downcast::().ok().map(|data| *data))) + } + + #[track_caller] + pub(crate) fn borrow(&self, guard: Option) -> Option> { + let data = unsafe { &*self.container.get() } + .get(&TypeId::of::())? + .borrow(); + self.borrow.set(self.borrow.get() + 1); + Some(AppDataRef { + data: Ref::filter_map(data, |data| data.downcast_ref()).ok()?, + borrow: &self.borrow, + _guard: guard, + }) + } + + #[track_caller] + pub(crate) fn borrow_mut( + &self, + guard: Option, + ) -> Option> { + let data = unsafe { &*self.container.get() } + .get(&TypeId::of::())? + .borrow_mut(); + self.borrow.set(self.borrow.get() + 1); + Some(AppDataRefMut { + data: RefMut::filter_map(data, |data| data.downcast_mut()).ok()?, + borrow: &self.borrow, + _guard: guard, + }) + } + + #[track_caller] + pub(crate) fn remove(&self) -> Option { + if self.borrow.get() != 0 { + panic!("cannot mutably borrow app data container"); + } + // SAFETY: we checked that there are no other references to the container + unsafe { &mut *self.container.get() } + .remove(&TypeId::of::())? + .into_inner() + .downcast::() + .ok() + .map(|data| *data) + } +} + +/// A wrapper type for an immutably borrowed value from an app data container. +/// +/// This type is similar to [`Ref`]. +pub struct AppDataRef<'a, T: ?Sized + 'a> { + data: Ref<'a, T>, + borrow: &'a Cell, + _guard: Option, +} + +impl Drop for AppDataRef<'_, T> { + fn drop(&mut self) { + self.borrow.set(self.borrow.get() - 1); + } +} + +impl Deref for AppDataRef<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl fmt::Display for AppDataRef<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +impl fmt::Debug for AppDataRef<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +/// A wrapper type for a mutably borrowed value from an app data container. +/// +/// This type is similar to [`RefMut`]. +pub struct AppDataRefMut<'a, T: ?Sized + 'a> { + data: RefMut<'a, T>, + borrow: &'a Cell, + _guard: Option, +} + +impl Drop for AppDataRefMut<'_, T> { + fn drop(&mut self) { + self.borrow.set(self.borrow.get() - 1); + } +} + +impl Deref for AppDataRefMut<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl DerefMut for AppDataRefMut<'_, T> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} + +impl fmt::Display for AppDataRefMut<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +impl fmt::Debug for AppDataRefMut<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +#[cfg(test)] +mod assertions { + use super::*; + + #[cfg(not(feature = "send"))] + static_assertions::assert_not_impl_any!(AppData: Send); + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(AppData: Send); + + // Must be !Send + static_assertions::assert_not_impl_any!(AppDataRef<()>: Send); + static_assertions::assert_not_impl_any!(AppDataRefMut<()>: Send); +} From b4892c228c7a4448ec490ee7257c1a53a52eef0f Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 10 Jul 2024 22:46:02 +0100 Subject: [PATCH 124/635] Add rustfmt.toml --- benches/benchmark.rs | 15 +---- examples/async_http_server.rs | 3 +- examples/async_tcp_server.rs | 8 +-- examples/guided_tour.rs | 12 +--- examples/serialize.rs | 9 ++- mlua-sys/src/lib.rs | 5 +- mlua-sys/src/lua51/compat.rs | 31 ++-------- mlua-sys/src/lua51/lauxlib.rs | 7 +-- mlua-sys/src/lua51/lua.rs | 7 +-- mlua-sys/src/lua52/compat.rs | 21 +------ mlua-sys/src/lua52/lauxlib.rs | 25 ++------ mlua-sys/src/lua52/lua.rs | 22 +------ mlua-sys/src/lua53/compat.rs | 7 +-- mlua-sys/src/lua53/lauxlib.rs | 25 ++------ mlua-sys/src/lua53/lua.rs | 10 +--- mlua-sys/src/lua54/lauxlib.rs | 25 ++------ mlua-sys/src/lua54/lua.rs | 20 ++----- mlua-sys/src/luau/compat.rs | 42 +++---------- mlua-sys/src/luau/lauxlib.rs | 4 +- mlua-sys/src/luau/lua.rs | 36 ++---------- mlua-sys/src/macros.rs | 3 +- mlua_derive/src/from_lua.rs | 4 +- mlua_derive/src/token.rs | 17 ++---- rustfmt.toml | 4 ++ src/chunk.rs | 15 ++--- src/conversion.rs | 53 ++++++++--------- src/error.rs | 3 +- src/function.rs | 28 ++++----- src/hook.rs | 23 ++------ src/lib.rs | 27 ++++----- src/luau/mod.rs | 5 +- src/luau/package.rs | 9 ++- src/macros.rs | 3 +- src/multi.rs | 10 +--- src/prelude.rs | 25 ++++---- src/serde/de.rs | 25 ++------ src/serde/mod.rs | 6 +- src/serde/ser.rs | 18 ++---- src/state.rs | 107 ++++++++++++++++------------------ src/state/raw.rs | 78 ++++++------------------- src/state/util.rs | 10 +--- src/stdlib.rs | 7 +-- src/table.rs | 10 ++-- src/thread.rs | 5 +- src/types.rs | 3 +- src/types/app_data.rs | 8 +-- src/userdata.rs | 44 ++++++-------- src/userdata/cell.rs | 7 +-- src/userdata/ext.rs | 6 +- src/userdata/registry.rs | 15 ++--- src/util/mod.rs | 51 +++++----------- src/util/short_names.rs | 20 ++----- src/value.rs | 23 +++----- tests/async.rs | 40 +++++-------- tests/chunk.rs | 3 +- tests/conversion.rs | 8 +-- tests/error.rs | 14 ++--- tests/function.rs | 8 +-- tests/hooks.rs | 36 ++++-------- tests/luau.rs | 13 ++--- tests/memory.rs | 7 +-- tests/serde.rs | 14 ++--- tests/static.rs | 19 +++--- tests/string.rs | 14 +---- tests/table.rs | 35 +++-------- tests/tests.rs | 101 ++++++++------------------------ tests/userdata.rs | 72 +++++++---------------- tests/value.rs | 17 ++---- 68 files changed, 423 insertions(+), 984 deletions(-) create mode 100644 rustfmt.toml diff --git a/benches/benchmark.rs b/benches/benchmark.rs index b0a0fbb4..da67f1d5 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -183,9 +183,7 @@ fn function_call_concat(c: &mut Criterion) { let lua = Lua::new(); let concat = lua - .create_function(|_, (a, b): (LuaString, LuaString)| { - Ok(format!("{}{}", a.to_str()?, b.to_str()?)) - }) + .create_function(|_, (a, b): (LuaString, LuaString)| Ok(format!("{}{}", a.to_str()?, b.to_str()?))) .unwrap(); let i = AtomicUsize::new(0); @@ -383,17 +381,10 @@ fn userdata_async_call_method(c: &mut Criterion) { b.to_async(rt).iter_batched( || { collect_gc_twice(&lua); - ( - method.clone(), - ud.clone(), - i.fetch_add(1, Ordering::Relaxed), - ) + (method.clone(), ud.clone(), i.fetch_add(1, Ordering::Relaxed)) }, |(method, ud, i)| async move { - assert_eq!( - method.call_async::<_, usize>((ud, i)).await.unwrap(), - 123 + i - ); + assert_eq!(method.call_async::<_, usize>((ud, i)).await.unwrap(), 123 + i); }, BatchSize::SmallInput, ); diff --git a/examples/async_http_server.rs b/examples/async_http_server.rs index 7da84905..030006fb 100644 --- a/examples/async_http_server.rs +++ b/examples/async_http_server.rs @@ -4,7 +4,8 @@ use std::net::SocketAddr; use std::rc::Rc; use futures::future::LocalBoxFuture; -use http_body_util::{combinators::BoxBody, BodyExt as _, Empty, Full}; +use http_body_util::combinators::BoxBody; +use http_body_util::{BodyExt as _, Empty, Full}; use hyper::body::{Bytes, Incoming}; use hyper::{Request, Response}; use hyper_util::rt::TokioIo; diff --git a/examples/async_tcp_server.rs b/examples/async_tcp_server.rs index edc41493..a6b65f7c 100644 --- a/examples/async_tcp_server.rs +++ b/examples/async_tcp_server.rs @@ -12,9 +12,7 @@ struct LuaTcpStream(TcpStream); impl UserData for LuaTcpStream { fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_method("peer_addr", |_, this, ()| { - Ok(this.0.peer_addr()?.to_string()) - }); + methods.add_method("peer_addr", |_, this, ()| Ok(this.0.peer_addr()?.to_string())); methods.add_async_method_mut("read", |lua, this, size| async move { let mut buf = vec![0; size]; @@ -53,9 +51,7 @@ async fn run_server(lua: Lua, handler: RegistryKey) -> io::Result<()> { let lua = lua.clone(); let handler = handler.clone(); task::spawn_local(async move { - let handler: Function = lua - .registry_value(&handler) - .expect("cannot get Lua handler"); + let handler: Function = lua.registry_value(&handler).expect("cannot get Lua handler"); let stream = LuaTcpStream(stream); if let Err(err) = handler.call_async::<_, ()>(stream).await { diff --git a/examples/guided_tour.rs b/examples/guided_tour.rs index 60ba1596..2a84d34a 100644 --- a/examples/guided_tour.rs +++ b/examples/guided_tour.rs @@ -1,9 +1,7 @@ use std::f32; use std::iter::FromIterator; -use mlua::{ - chunk, FromLua, Function, Lua, MetaMethod, Result, UserData, UserDataMethods, Value, Variadic, -}; +use mlua::{chunk, FromLua, Function, Lua, MetaMethod, Result, UserData, UserDataMethods, Value, Variadic}; fn main() -> Result<()> { // You can create a new Lua state with `Lua::new()`. This loads the default Lua std library @@ -179,13 +177,7 @@ fn main() -> Result<()> { let vec2_constructor = lua.create_function(|_, (x, y): (f32, f32)| Ok(Vec2(x, y)))?; globals.set("vec2", vec2_constructor)?; - assert!( - (lua.load("(vec2(1, 2) + vec2(2, 2)):magnitude()") - .eval::()? - - 5.0) - .abs() - < f32::EPSILON - ); + assert!((lua.load("(vec2(1, 2) + vec2(2, 2)):magnitude()").eval::()? - 5.0).abs() < f32::EPSILON); // Normally, Rust types passed to `Lua` must be `'static`, because there is no way to be // sure of their lifetime inside the Lua state. There is, however, a limited way to lift this diff --git a/examples/serialize.rs b/examples/serialize.rs index 7c9ae69e..fff968c4 100644 --- a/examples/serialize.rs +++ b/examples/serialize.rs @@ -28,9 +28,14 @@ fn main() -> Result<()> { let globals = lua.globals(); // Create Car struct from a Lua table - let car: Car = lua.from_value(lua.load(r#" + let car: Car = lua.from_value( + lua.load( + r#" {active = true, model = "Volkswagen Golf", transmission = "Automatic", engine = {v = 1499, kw = 90}} - "#).eval()?)?; + "#, + ) + .eval()?, + )?; // Set it as (serializable) userdata globals.set("null", lua.null())?; diff --git a/mlua-sys/src/lib.rs b/mlua-sys/src/lib.rs index 0e73ea73..629dfd88 100644 --- a/mlua-sys/src/lib.rs +++ b/mlua-sys/src/lib.rs @@ -54,10 +54,7 @@ pub const LUA_TRACEBACK_STACK: c_int = 11; target_arch = "sparc", target_arch = "wasm32", target_arch = "hexagon", - all( - target_arch = "riscv32", - not(any(target_os = "espidf", target_os = "zkvm")) - ), + all(target_arch = "riscv32", not(any(target_os = "espidf", target_os = "zkvm"))), all(target_arch = "xtensa", not(target_os = "espidf")), ))] #[doc(hidden)] diff --git a/mlua-sys/src/lua51/compat.rs b/mlua-sys/src/lua51/compat.rs index 890ba3be..942bb4da 100644 --- a/mlua-sys/src/lua51/compat.rs +++ b/mlua-sys/src/lua51/compat.rs @@ -2,9 +2,8 @@ //! //! Based on github.com/keplerproject/lua-compat-5.3 -use std::mem; use std::os::raw::{c_char, c_int, c_void}; -use std::ptr; +use std::{mem, ptr}; use super::lauxlib::*; use super::lua::*; @@ -328,12 +327,7 @@ pub unsafe fn lua_setuservalue(L: *mut lua_State, idx: c_int) { } #[inline(always)] -pub unsafe fn lua_dump( - L: *mut lua_State, - writer: lua_Writer, - data: *mut c_void, - _strip: c_int, -) -> c_int { +pub unsafe fn lua_dump(L: *mut lua_State, writer: lua_Writer, data: *mut c_void, _strip: c_int) -> c_int { lua_dump_(L, writer, data) } @@ -365,12 +359,7 @@ pub unsafe fn lua_pushglobaltable(L: *mut lua_State) { } #[inline(always)] -pub unsafe fn lua_resume( - L: *mut lua_State, - _from: *mut lua_State, - narg: c_int, - nres: *mut c_int, -) -> c_int { +pub unsafe fn lua_resume(L: *mut lua_State, _from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int { let ret = lua_resume_(L, narg); if (ret == LUA_OK || ret == LUA_YIELD) && !(nres.is_null()) { *nres = lua_gettop(L); @@ -446,12 +435,7 @@ pub unsafe fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer { res } -pub unsafe fn luaL_traceback( - L: *mut lua_State, - L1: *mut lua_State, - msg: *const c_char, - mut level: c_int, -) { +pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, mut level: c_int) { let mut ar: lua_Debug = mem::zeroed(); let top = lua_gettop(L); let numlevels = compat53_countlevels(L1); @@ -543,12 +527,7 @@ pub unsafe fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_ch 0 } -pub unsafe fn luaL_requiref( - L: *mut lua_State, - modname: *const c_char, - openf: lua_CFunction, - glb: c_int, -) { +pub unsafe fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int) { luaL_checkstack(L, 3, cstr!("not enough stack slots available")); luaL_getsubtable(L, LUA_REGISTRYINDEX, cstr!("_LOADED")); if lua_getfield(L, -1, modname) == LUA_TNIL { diff --git a/mlua-sys/src/lua51/lauxlib.rs b/mlua-sys/src/lua51/lauxlib.rs index 1565d3f1..54238858 100644 --- a/mlua-sys/src/lua51/lauxlib.rs +++ b/mlua-sys/src/lua51/lauxlib.rs @@ -63,12 +63,7 @@ extern "C-unwind" { pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int); pub fn luaL_loadfile(L: *mut lua_State, filename: *const c_char) -> c_int; - pub fn luaL_loadbuffer( - L: *mut lua_State, - buff: *const c_char, - sz: usize, - name: *const c_char, - ) -> c_int; + pub fn luaL_loadbuffer(L: *mut lua_State, buff: *const c_char, sz: usize, name: *const c_char) -> c_int; pub fn luaL_loadstring(L: *mut lua_State, s: *const c_char) -> c_int; pub fn luaL_newstate() -> *mut lua_State; diff --git a/mlua-sys/src/lua51/lua.rs b/mlua-sys/src/lua51/lua.rs index 7103a72b..f222b383 100644 --- a/mlua-sys/src/lua51/lua.rs +++ b/mlua-sys/src/lua51/lua.rs @@ -379,12 +379,7 @@ extern "C-unwind" { pub fn lua_getupvalue(L: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char; pub fn lua_setupvalue(L: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char; - pub fn lua_sethook( - L: *mut lua_State, - func: Option, - mask: c_int, - count: c_int, - ) -> c_int; + pub fn lua_sethook(L: *mut lua_State, func: Option, mask: c_int, count: c_int) -> c_int; pub fn lua_gethook(L: *mut lua_State) -> Option; pub fn lua_gethookmask(L: *mut lua_State) -> c_int; pub fn lua_gethookcount(L: *mut lua_State) -> c_int; diff --git a/mlua-sys/src/lua52/compat.rs b/mlua-sys/src/lua52/compat.rs index 49dcc7bc..32f321dd 100644 --- a/mlua-sys/src/lua52/compat.rs +++ b/mlua-sys/src/lua52/compat.rs @@ -157,22 +157,12 @@ pub unsafe fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer) { } #[inline(always)] -pub unsafe fn lua_dump( - L: *mut lua_State, - writer: lua_Writer, - data: *mut c_void, - _strip: c_int, -) -> c_int { +pub unsafe fn lua_dump(L: *mut lua_State, writer: lua_Writer, data: *mut c_void, _strip: c_int) -> c_int { lua_dump_(L, writer, data) } #[inline(always)] -pub unsafe fn lua_resume( - L: *mut lua_State, - from: *mut lua_State, - narg: c_int, - nres: *mut c_int, -) -> c_int { +pub unsafe fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int { let ret = lua_resume_(L, from, narg); if (ret == LUA_OK || ret == LUA_YIELD) && !(nres.is_null()) { *nres = lua_gettop(L); @@ -240,12 +230,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) lua_tolstring(L, -1, len) } -pub unsafe fn luaL_requiref( - L: *mut lua_State, - modname: *const c_char, - openf: lua_CFunction, - glb: c_int, -) { +pub unsafe fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int) { luaL_checkstack(L, 3, cstr!("not enough stack slots available")); luaL_getsubtable(L, LUA_REGISTRYINDEX, cstr!("_LOADED")); if lua_getfield(L, -1, modname) == LUA_TNIL { diff --git a/mlua-sys/src/lua52/lauxlib.rs b/mlua-sys/src/lua52/lauxlib.rs index 570023dc..d5cdf664 100644 --- a/mlua-sys/src/lua52/lauxlib.rs +++ b/mlua-sys/src/lua52/lauxlib.rs @@ -25,12 +25,8 @@ extern "C-unwind" { pub fn luaL_tolstring_(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char; pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int; pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char; - pub fn luaL_optlstring( - L: *mut lua_State, - arg: c_int, - def: *const c_char, - l: *mut usize, - ) -> *const c_char; + pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize) + -> *const c_char; pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number; pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number; pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer; @@ -71,8 +67,7 @@ extern "C-unwind" { pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int; pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int); - pub fn luaL_loadfilex(L: *mut lua_State, filename: *const c_char, mode: *const c_char) - -> c_int; + pub fn luaL_loadfilex(L: *mut lua_State, filename: *const c_char, mode: *const c_char) -> c_int; } #[inline(always)] @@ -109,12 +104,7 @@ extern "C-unwind" { pub fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, level: c_int); #[link_name = "luaL_requiref"] - pub fn luaL_requiref_( - L: *mut lua_State, - modname: *const c_char, - openf: lua_CFunction, - glb: c_int, - ); + pub fn luaL_requiref_(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int); } // @@ -173,12 +163,7 @@ pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) { // luaL_opt would be implemented here but it is undocumented, so it's omitted #[inline(always)] -pub unsafe fn luaL_loadbuffer( - L: *mut lua_State, - s: *const c_char, - sz: usize, - n: *const c_char, -) -> c_int { +pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int { luaL_loadbufferx(L, s, sz, n, ptr::null()) } diff --git a/mlua-sys/src/lua52/lua.rs b/mlua-sys/src/lua52/lua.rs index c52bc9f2..6714611d 100644 --- a/mlua-sys/src/lua52/lua.rs +++ b/mlua-sys/src/lua52/lua.rs @@ -222,13 +222,7 @@ extern "C-unwind" { // // 'load' and 'call' functions (load and run Lua code) // - pub fn lua_callk( - L: *mut lua_State, - nargs: c_int, - nresults: c_int, - ctx: c_int, - k: Option, - ); + pub fn lua_callk(L: *mut lua_State, nargs: c_int, nresults: c_int, ctx: c_int, k: Option); pub fn lua_pcallk( L: *mut lua_State, nargs: c_int, @@ -266,12 +260,7 @@ extern "C-unwind" { // // Coroutine functions // - pub fn lua_yieldk( - L: *mut lua_State, - nresults: c_int, - ctx: c_int, - k: Option, - ) -> c_int; + pub fn lua_yieldk(L: *mut lua_State, nresults: c_int, ctx: c_int, k: Option) -> c_int; #[link_name = "lua_resume"] pub fn lua_resume_(L: *mut lua_State, from: *mut lua_State, narg: c_int) -> c_int; pub fn lua_status(L: *mut lua_State) -> c_int; @@ -471,12 +460,7 @@ extern "C-unwind" { pub fn lua_upvalueid(L: *mut lua_State, fidx: c_int, n: c_int) -> *mut c_void; pub fn lua_upvaluejoin(L: *mut lua_State, fidx1: c_int, n1: c_int, fidx2: c_int, n2: c_int); - pub fn lua_sethook( - L: *mut lua_State, - func: Option, - mask: c_int, - count: c_int, - ) -> c_int; + pub fn lua_sethook(L: *mut lua_State, func: Option, mask: c_int, count: c_int) -> c_int; pub fn lua_gethook(L: *mut lua_State) -> Option; pub fn lua_gethookmask(L: *mut lua_State) -> c_int; pub fn lua_gethookcount(L: *mut lua_State) -> c_int; diff --git a/mlua-sys/src/lua53/compat.rs b/mlua-sys/src/lua53/compat.rs index 71d8b5e2..5ea404f1 100644 --- a/mlua-sys/src/lua53/compat.rs +++ b/mlua-sys/src/lua53/compat.rs @@ -5,12 +5,7 @@ use std::os::raw::c_int; use super::lua::*; #[inline(always)] -pub unsafe fn lua_resume( - L: *mut lua_State, - from: *mut lua_State, - narg: c_int, - nres: *mut c_int, -) -> c_int { +pub unsafe fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int { let ret = lua_resume_(L, from, narg); if (ret == LUA_OK || ret == LUA_YIELD) && !(nres.is_null()) { *nres = lua_gettop(L); diff --git a/mlua-sys/src/lua53/lauxlib.rs b/mlua-sys/src/lua53/lauxlib.rs index 258ab301..7c851ac1 100644 --- a/mlua-sys/src/lua53/lauxlib.rs +++ b/mlua-sys/src/lua53/lauxlib.rs @@ -30,12 +30,8 @@ extern "C-unwind" { pub fn luaL_tolstring_(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char; pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int; pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char; - pub fn luaL_optlstring( - L: *mut lua_State, - arg: c_int, - def: *const c_char, - l: *mut usize, - ) -> *const c_char; + pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize) + -> *const c_char; pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number; pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number; pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer; @@ -73,8 +69,7 @@ extern "C-unwind" { pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int; pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int); - pub fn luaL_loadfilex(L: *mut lua_State, filename: *const c_char, mode: *const c_char) - -> c_int; + pub fn luaL_loadfilex(L: *mut lua_State, filename: *const c_char, mode: *const c_char) -> c_int; } #[inline(always)] @@ -110,12 +105,7 @@ extern "C-unwind" { pub fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, level: c_int); - pub fn luaL_requiref( - L: *mut lua_State, - modname: *const c_char, - openf: lua_CFunction, - glb: c_int, - ); + pub fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int); } // @@ -179,12 +169,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> // luaL_opt would be implemented here but it is undocumented, so it's omitted #[inline(always)] -pub unsafe fn luaL_loadbuffer( - L: *mut lua_State, - s: *const c_char, - sz: usize, - n: *const c_char, -) -> c_int { +pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int { luaL_loadbufferx(L, s, sz, n, ptr::null()) } diff --git a/mlua-sys/src/lua53/lua.rs b/mlua-sys/src/lua53/lua.rs index 24dbba20..47010f5d 100644 --- a/mlua-sys/src/lua53/lua.rs +++ b/mlua-sys/src/lua53/lua.rs @@ -1,9 +1,8 @@ //! Contains definitions from `lua.h`. use std::marker::{PhantomData, PhantomPinned}; -use std::mem; use std::os::raw::{c_char, c_double, c_int, c_uchar, c_void}; -use std::ptr; +use std::{mem, ptr}; // Mark for precompiled code (`Lua`) pub const LUA_SIGNATURE: &[u8] = b"\x1bLua"; @@ -251,12 +250,7 @@ extern "C-unwind" { mode: *const c_char, ) -> c_int; - pub fn lua_dump( - L: *mut lua_State, - writer: lua_Writer, - data: *mut c_void, - strip: c_int, - ) -> c_int; + pub fn lua_dump(L: *mut lua_State, writer: lua_Writer, data: *mut c_void, strip: c_int) -> c_int; } #[inline(always)] diff --git a/mlua-sys/src/lua54/lauxlib.rs b/mlua-sys/src/lua54/lauxlib.rs index 5be2ba6b..b4873baa 100644 --- a/mlua-sys/src/lua54/lauxlib.rs +++ b/mlua-sys/src/lua54/lauxlib.rs @@ -29,12 +29,8 @@ extern "C-unwind" { pub fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char; pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int; pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char; - pub fn luaL_optlstring( - L: *mut lua_State, - arg: c_int, - def: *const c_char, - l: *mut usize, - ) -> *const c_char; + pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize) + -> *const c_char; pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number; pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number; pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer; @@ -72,8 +68,7 @@ extern "C-unwind" { pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int; pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int); - pub fn luaL_loadfilex(L: *mut lua_State, filename: *const c_char, mode: *const c_char) - -> c_int; + pub fn luaL_loadfilex(L: *mut lua_State, filename: *const c_char, mode: *const c_char) -> c_int; } #[inline(always)] @@ -111,12 +106,7 @@ extern "C-unwind" { pub fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, level: c_int); - pub fn luaL_requiref( - L: *mut lua_State, - modname: *const c_char, - openf: lua_CFunction, - glb: c_int, - ); + pub fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int); } // @@ -175,12 +165,7 @@ pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) { // luaL_opt would be implemented here but it is undocumented, so it's omitted #[inline(always)] -pub unsafe fn luaL_loadbuffer( - L: *mut lua_State, - s: *const c_char, - sz: usize, - n: *const c_char, -) -> c_int { +pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int { luaL_loadbufferx(L, s, sz, n, ptr::null()) } diff --git a/mlua-sys/src/lua54/lua.rs b/mlua-sys/src/lua54/lua.rs index f628e69b..796bec7f 100644 --- a/mlua-sys/src/lua54/lua.rs +++ b/mlua-sys/src/lua54/lua.rs @@ -1,9 +1,8 @@ //! Contains definitions from `lua.h`. use std::marker::{PhantomData, PhantomPinned}; -use std::mem; use std::os::raw::{c_char, c_double, c_int, c_uchar, c_ushort, c_void}; -use std::ptr; +use std::{mem, ptr}; // Mark for precompiled code (`Lua`) pub const LUA_SIGNATURE: &[u8] = b"\x1bLua"; @@ -101,8 +100,7 @@ pub type lua_Alloc = unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void; /// Type for warning functions -pub type lua_WarnFunction = - unsafe extern "C-unwind" fn(ud: *mut c_void, msg: *const c_char, tocont: c_int); +pub type lua_WarnFunction = unsafe extern "C-unwind" fn(ud: *mut c_void, msg: *const c_char, tocont: c_int); #[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] extern "C-unwind" { @@ -266,12 +264,7 @@ extern "C-unwind" { mode: *const c_char, ) -> c_int; - pub fn lua_dump( - L: *mut lua_State, - writer: lua_Writer, - data: *mut c_void, - strip: c_int, - ) -> c_int; + pub fn lua_dump(L: *mut lua_State, writer: lua_Writer, data: *mut c_void, strip: c_int) -> c_int; } #[inline(always)] @@ -295,12 +288,7 @@ extern "C-unwind" { ctx: lua_KContext, k: Option, ) -> c_int; - pub fn lua_resume( - L: *mut lua_State, - from: *mut lua_State, - narg: c_int, - nres: *mut c_int, - ) -> c_int; + pub fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int; pub fn lua_status(L: *mut lua_State) -> c_int; pub fn lua_isyieldable(L: *mut lua_State) -> c_int; } diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index 98fa9c90..47ec5b6d 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -3,9 +3,8 @@ //! Based on github.com/keplerproject/lua-compat-5.3 use std::ffi::CStr; -use std::mem; use std::os::raw::{c_char, c_int, c_void}; -use std::ptr; +use std::{mem, ptr}; use super::lauxlib::*; use super::lua::*; @@ -53,11 +52,7 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) -> 0 // not found } -unsafe fn compat53_pushglobalfuncname( - L: *mut lua_State, - level: c_int, - ar: *mut lua_Debug, -) -> c_int { +unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int { let top = lua_gettop(L); // push function lua_getinfo(L, level, cstr!("f"), ar); @@ -281,12 +276,7 @@ pub unsafe fn lua_pushglobaltable(L: *mut lua_State) { } #[inline(always)] -pub unsafe fn lua_resume( - L: *mut lua_State, - from: *mut lua_State, - narg: c_int, - nres: *mut c_int, -) -> c_int { +pub unsafe fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int { let ret = lua_resume_(L, from, narg); if (ret == LUA_OK || ret == LUA_YIELD) && !(nres.is_null()) { *nres = lua_gettop(L); @@ -345,18 +335,10 @@ pub unsafe fn luaL_loadbufferx( if !mode.is_null() { let modeb = CStr::from_ptr(mode).to_bytes(); if !chunk_is_text && !modeb.contains(&b'b') { - lua_pushfstring( - L, - cstr!("attempt to load a binary chunk (mode is '%s')"), - mode, - ); + lua_pushfstring(L, cstr!("attempt to load a binary chunk (mode is '%s')"), mode); return LUA_ERRSYNTAX; } else if chunk_is_text && !modeb.contains(&b't') { - lua_pushfstring( - L, - cstr!("attempt to load a text chunk (mode is '%s')"), - mode, - ); + lua_pushfstring(L, cstr!("attempt to load a text chunk (mode is '%s')"), mode); return LUA_ERRSYNTAX; } } @@ -397,12 +379,7 @@ pub unsafe fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer { res } -pub unsafe fn luaL_traceback( - L: *mut lua_State, - L1: *mut lua_State, - msg: *const c_char, - mut level: c_int, -) { +pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, mut level: c_int) { let mut ar: lua_Debug = mem::zeroed(); let top = lua_gettop(L); let numlevels = lua_stackdepth(L); @@ -494,12 +471,7 @@ pub unsafe fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_ch 0 } -pub unsafe fn luaL_requiref( - L: *mut lua_State, - modname: *const c_char, - openf: lua_CFunction, - glb: c_int, -) { +pub unsafe fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int) { luaL_checkstack(L, 3, cstr!("not enough stack slots available")); luaL_getsubtable(L, LUA_REGISTRYINDEX, cstr!("_LOADED")); if lua_getfield(L, -1, modname) == LUA_TNIL { diff --git a/mlua-sys/src/luau/lauxlib.rs b/mlua-sys/src/luau/lauxlib.rs index 9f4384c0..284cd3f2 100644 --- a/mlua-sys/src/luau/lauxlib.rs +++ b/mlua-sys/src/luau/lauxlib.rs @@ -3,9 +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_Integer, lua_Number, lua_State, lua_Unsigned, LUA_REGISTRYINDEX}; #[repr(C)] pub struct luaL_Reg { diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index cc2e6012..540dee46 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -84,12 +84,8 @@ pub type lua_Udestructor = unsafe extern "C-unwind" fn(*mut c_void); pub type lua_Destructor = unsafe extern "C-unwind" fn(L: *mut lua_State, *mut c_void); /// Type for memory-allocation functions. -pub type lua_Alloc = unsafe extern "C-unwind" fn( - ud: *mut c_void, - ptr: *mut c_void, - osize: usize, - nsize: usize, -) -> *mut c_void; +pub type lua_Alloc = + unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void; /// Returns Luau release version (eg. `0.xxx`). pub const fn luau_version() -> Option<&'static str> { @@ -426,12 +422,7 @@ pub unsafe fn lua_pushcclosure(L: *mut lua_State, f: lua_CFunction, nup: c_int) } #[inline(always)] -pub unsafe fn lua_pushcclosured( - L: *mut lua_State, - f: lua_CFunction, - debugname: *const c_char, - nup: c_int, -) { +pub unsafe fn lua_pushcclosured(L: *mut lua_State, f: lua_CFunction, debugname: *const c_char, nup: c_int) { lua_pushcclosurek(L, f, debugname, nup, None) } @@ -476,12 +467,7 @@ pub type lua_Coverage = unsafe extern "C-unwind" fn( extern "C-unwind" { pub fn lua_stackdepth(L: *mut lua_State) -> c_int; - pub fn lua_getinfo( - L: *mut lua_State, - level: c_int, - what: *const c_char, - ar: *mut lua_Debug, - ) -> c_int; + pub fn lua_getinfo(L: *mut lua_State, level: c_int, what: *const c_char, ar: *mut lua_Debug) -> c_int; pub fn lua_getargument(L: *mut lua_State, level: c_int, n: c_int) -> c_int; pub fn lua_getlocal(L: *mut lua_State, level: c_int, n: c_int) -> *const c_char; pub fn lua_setlocal(L: *mut lua_State, level: c_int, n: c_int) -> *const c_char; @@ -489,19 +475,9 @@ extern "C-unwind" { pub fn lua_setupvalue(L: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char; pub fn lua_singlestep(L: *mut lua_State, enabled: c_int); - pub fn lua_breakpoint( - L: *mut lua_State, - funcindex: c_int, - line: c_int, - enabled: c_int, - ) -> c_int; + pub fn lua_breakpoint(L: *mut lua_State, funcindex: c_int, line: c_int, enabled: c_int) -> c_int; - pub fn lua_getcoverage( - L: *mut lua_State, - funcindex: c_int, - context: *mut c_void, - callback: lua_Coverage, - ); + pub fn lua_getcoverage(L: *mut lua_State, funcindex: c_int, context: *mut c_void, callback: lua_Coverage); pub fn lua_debugtrace(L: *mut lua_State) -> *const c_char; } diff --git a/mlua-sys/src/macros.rs b/mlua-sys/src/macros.rs index df79a79f..263b4e76 100644 --- a/mlua-sys/src/macros.rs +++ b/mlua-sys/src/macros.rs @@ -1,7 +1,6 @@ #[allow(unused_macros)] macro_rules! cstr { ($s:expr) => { - concat!($s, "\0") as *const str as *const [::std::os::raw::c_char] - as *const ::std::os::raw::c_char + concat!($s, "\0") as *const str as *const [::std::os::raw::c_char] as *const ::std::os::raw::c_char }; } diff --git a/mlua_derive/src/from_lua.rs b/mlua_derive/src/from_lua.rs index 8d0b6836..dfbb6746 100644 --- a/mlua_derive/src/from_lua.rs +++ b/mlua_derive/src/from_lua.rs @@ -3,9 +3,7 @@ use quote::quote; use syn::{parse_macro_input, DeriveInput}; pub fn from_lua(input: TokenStream) -> TokenStream { - let DeriveInput { - ident, generics, .. - } = parse_macro_input!(input as DeriveInput); + let DeriveInput { ident, generics, .. } = parse_macro_input!(input as DeriveInput); let ident_str = ident.to_string(); let (impl_generics, ty_generics, _) = generics.split_for_impl(); diff --git a/mlua_derive/src/token.rs b/mlua_derive/src/token.rs index 9ef10d7a..19f3f05f 100644 --- a/mlua_derive/src/token.rs +++ b/mlua_derive/src/token.rs @@ -1,8 +1,6 @@ -use std::{ - cmp::{Eq, PartialEq}, - fmt::{self, Display, Formatter}, - vec::IntoIter, -}; +use std::cmp::{Eq, PartialEq}; +use std::fmt::{self, Display, Formatter}; +use std::vec::IntoIter; use itertools::Itertools; use once_cell::sync::Lazy; @@ -47,10 +45,7 @@ fn span_pos(span: &Span) -> (Pos, Pos) { return fallback_span_pos(span); } - ( - Pos::new(start.line, start.column), - Pos::new(end.line, end.column), - ) + (Pos::new(start.line, start.column), Pos::new(end.line, end.column)) } fn parse_pos(span: &Span) -> Option<(usize, usize)> { @@ -79,9 +74,7 @@ fn parse_pos(span: &Span) -> Option<(usize, usize)> { fn fallback_span_pos(span: &Span) -> (Pos, Pos) { let (start, end) = match parse_pos(span) { Some(v) => v, - None => proc_macro_error::abort_call_site!( - "Cannot retrieve span information; please use nightly" - ), + None => proc_macro_error::abort_call_site!("Cannot retrieve span information; please use nightly"), }; (Pos::new(1, start), Pos::new(1, end)) } diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..ac702a58 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,4 @@ +imports_granularity = "Module" +max_width = 110 +comment_width = 100 +wrap_comments = true diff --git a/src/chunk.rs b/src/chunk.rs index a7d7812e..b1c74b3d 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -299,8 +299,8 @@ impl<'a> Chunk<'a> { /// Sets the environment of the loaded chunk to the given value. /// - /// In Lua >=5.2 main chunks always have exactly one upvalue, and this upvalue is used as the `_ENV` - /// variable inside the chunk. By default this value is set to the global environment. + /// In Lua >=5.2 main chunks always have exactly one upvalue, and this upvalue is used as the + /// `_ENV` variable inside the chunk. By default this value is set to the global environment. /// /// Calling this method changes the `_ENV` upvalue to the value provided, and variables inside /// the chunk will refer to the given environment rather than the global one. @@ -450,19 +450,12 @@ impl<'a> Chunk<'a> { if self.detect_mode() == ChunkMode::Text { #[cfg(feature = "luau")] { - let data = self - .compiler - .get_or_insert_with(Default::default) - .compile(source); + let data = self.compiler.get_or_insert_with(Default::default).compile(source); self.source = Ok(Cow::Owned(data)); self.mode = Some(ChunkMode::Binary); } #[cfg(not(feature = "luau"))] - if let Ok(func) = self - .lua - .lock() - .load_chunk(None, None, None, source.as_ref()) - { + if let Ok(func) = self.lua.lock().load_chunk(None, None, None, source.as_ref()) { let data = func.dump(false); self.source = Ok(Cow::Owned(data)); self.mode = Some(ChunkMode::Binary); diff --git a/src/conversion.rs b/src/conversion.rs index 2defef3c..72c9fbbc 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -389,13 +389,13 @@ impl FromLua for StdString { let mut size = 0; let data = ffi::lua_tolstring(state, idx, &mut size); let bytes = slice::from_raw_parts(data as *const u8, size); - return str::from_utf8(bytes).map(|s| s.to_owned()).map_err(|e| { - Error::FromLuaConversionError { + return str::from_utf8(bytes) + .map(|s| s.to_owned()) + .map_err(|e| Error::FromLuaConversionError { from: "string", to: "String", message: Some(e.to_string()), - } - }); + }); } // Fallback to default Self::from_lua(lua.stack_value(idx), lua.lua()) @@ -603,15 +603,16 @@ macro_rules! lua_convert_int { if let Some(i) = lua.coerce_integer(value.clone())? { cast(i) } else { - cast(lua.coerce_number(value)?.ok_or_else(|| { - Error::FromLuaConversionError { - from: ty, - to: stringify!($x), - message: Some( - "expected number or string coercible to number".to_string(), - ), - } - })?) + cast( + lua.coerce_number(value)? + .ok_or_else(|| Error::FromLuaConversionError { + from: ty, + to: stringify!($x), + message: Some( + "expected number or string coercible to number".to_string(), + ), + })?, + ) } } }) @@ -684,9 +685,7 @@ where { #[inline] fn into_lua(self, lua: &Lua) -> Result { - Ok(Value::Table( - lua.create_sequence_from(self.iter().cloned())?, - )) + Ok(Value::Table(lua.create_sequence_from(self.iter().cloned())?)) } } @@ -819,9 +818,9 @@ impl FromLua for BTreeMap { impl IntoLua for HashSet { #[inline] fn into_lua(self, lua: &Lua) -> Result { - Ok(Value::Table(lua.create_table_from( - self.into_iter().map(|val| (val, true)), - )?)) + Ok(Value::Table( + lua.create_table_from(self.into_iter().map(|val| (val, true)))?, + )) } } @@ -830,10 +829,7 @@ impl FromLua for HashSet fn from_lua(value: Value, _: &Lua) -> Result { match value { Value::Table(table) if table.raw_len() > 0 => table.sequence_values().collect(), - Value::Table(table) => table - .pairs::() - .map(|res| res.map(|(k, _)| k)) - .collect(), + Value::Table(table) => table.pairs::().map(|res| res.map(|(k, _)| k)).collect(), _ => Err(Error::FromLuaConversionError { from: value.type_name(), to: "HashSet", @@ -846,9 +842,9 @@ impl FromLua for HashSet impl IntoLua for BTreeSet { #[inline] fn into_lua(self, lua: &Lua) -> Result { - Ok(Value::Table(lua.create_table_from( - self.into_iter().map(|val| (val, true)), - )?)) + Ok(Value::Table( + lua.create_table_from(self.into_iter().map(|val| (val, true)))?, + )) } } @@ -857,10 +853,7 @@ impl FromLua for BTreeSet { fn from_lua(value: Value, _: &Lua) -> Result { match value { Value::Table(table) if table.raw_len() > 0 => table.sequence_values().collect(), - Value::Table(table) => table - .pairs::() - .map(|res| res.map(|(k, _)| k)) - .collect(), + Value::Table(table) => table.pairs::().map(|res| res.map(|(k, _)| k)).collect(), _ => Err(Error::FromLuaConversionError { from: value.type_name(), to: "BTreeSet", diff --git a/src/error.rs b/src/error.rs index 8c563c28..b1635c49 100644 --- a/src/error.rs +++ b/src/error.rs @@ -324,7 +324,8 @@ impl StdError for Error { // An error type with a source error should either return that error via source or // include that source's error message in its own Display output, but never both. // https://blog.rust-lang.org/inside-rust/2021/07/01/What-the-error-handling-project-group-is-working-towards.html - // Given that we include source to fmt::Display implementation for `CallbackError`, this call returns nothing. + // Given that we include source to fmt::Display implementation for `CallbackError`, this call + // returns nothing. Error::CallbackError { .. } => None, Error::ExternalError(ref err) => err.source(), Error::WithContext { ref cause, .. } => match cause.as_ref() { diff --git a/src/function.rs b/src/function.rs index 3495236a..adbf9410 100644 --- a/src/function.rs +++ b/src/function.rs @@ -1,16 +1,13 @@ use std::cell::RefCell; -use std::mem; use std::os::raw::{c_int, c_void}; -use std::ptr; -use std::slice; +use std::{mem, ptr, slice}; use crate::error::{Error, Result}; use crate::state::Lua; use crate::table::Table; use crate::types::{Callback, MaybeSend, ValueRef}; use crate::util::{ - assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str, - StackGuard, + assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str, StackGuard, }; use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti, Value}; @@ -37,7 +34,8 @@ pub struct FunctionInfo { /// /// Always `None` for Luau. pub name_what: Option<&'static str>, - /// A string `Lua` if the function is a Lua function, `C` if it is a C function, `main` if it is the main part of a chunk. + /// A string `Lua` if the function is a Lua function, `C` if it is a C function, `main` if it is + /// the main part of a chunk. pub what: &'static str, /// Source of the chunk that created the function. pub source: Option, @@ -426,8 +424,8 @@ impl Function { /// Retrieves recorded coverage information about this Lua function including inner calls. /// - /// This function takes a callback as an argument and calls it providing [`CoverageInfo`] snapshot - /// per each executed inner function. + /// This function takes a callback as an argument and calls it providing [`CoverageInfo`] + /// snapshot per each executed inner function. /// /// Recording of coverage information is controlled by [`Compiler::set_coverage_level`] option. /// @@ -522,7 +520,8 @@ pub(crate) struct WrappedFunction(pub(crate) Callback<'static>); pub(crate) struct WrappedAsyncFunction(pub(crate) AsyncCallback<'static>); impl Function { - /// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`] trait. + /// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`] + /// trait. #[inline] pub fn wrap(func: F) -> impl IntoLua where @@ -546,15 +545,14 @@ impl Function { { let func = RefCell::new(func); WrappedFunction(Box::new(move |lua, nargs| unsafe { - let mut func = func - .try_borrow_mut() - .map_err(|_| Error::RecursiveMutCallback)?; + let mut func = func.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?; let args = A::from_stack_args(nargs, 1, None, lua)?; func(lua.lua(), args)?.push_into_stack_multi(lua) })) } - /// Wraps a Rust async function or closure, returning an opaque type that implements [`IntoLua`] trait. + /// Wraps a Rust async function or closure, returning an opaque type that implements [`IntoLua`] + /// trait. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] pub fn wrap_async(func: F) -> impl IntoLua @@ -588,9 +586,7 @@ impl IntoLua for WrappedFunction { impl IntoLua for WrappedAsyncFunction { #[inline] fn into_lua(self, lua: &Lua) -> Result { - lua.lock() - .create_async_callback(self.0) - .map(Value::Function) + lua.lock().create_async_callback(self.0).map(Value::Function) } } diff --git a/src/hook.rs b/src/hook.rs index 37f4b1e8..0d327445 100644 --- a/src/hook.rs +++ b/src/hook.rs @@ -55,11 +55,7 @@ impl<'a> Debug<'a> { } } - pub(crate) fn new_owned( - guard: ReentrantMutexGuard<'a, RawLua>, - _level: c_int, - ar: lua_Debug, - ) -> Self { + pub(crate) fn new_owned(guard: ReentrantMutexGuard<'a, RawLua>, _level: c_int, ar: lua_Debug) -> Self { Debug { lua: EitherLua::Owned(guard), ar: ActivationRecord::Owned(UnsafeCell::new(ar)), @@ -259,7 +255,8 @@ pub struct DebugSource<'a> { pub line_defined: Option, /// The line number where the definition of the function ends (not set by Luau). pub last_line_defined: Option, - /// A string `Lua` if the function is a Lua function, `C` if it is a C function, `main` if it is the main part of a chunk. + /// A string `Lua` if the function is a Lua function, `C` if it is a C function, `main` if it is + /// the main part of a chunk. pub what: &'static str, } @@ -267,20 +264,10 @@ pub struct DebugSource<'a> { pub struct DebugStack { pub num_ups: i32, /// Requires `feature = "lua54/lua53/lua52/luau"` - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luau" - ))] + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] pub num_params: i32, /// Requires `feature = "lua54/lua53/lua52/luau"` - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luau" - ))] + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] pub is_vararg: bool, } diff --git a/src/lib.rs b/src/lib.rs index ee8d51b6..5491ecc3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,9 +27,9 @@ //! //! # Serde support //! -//! The [`LuaSerdeExt`] trait implemented for [`Lua`] allows conversion from Rust types to Lua values -//! and vice versa using serde. Any user defined data type that implements [`serde::Serialize`] or -//! [`serde::Deserialize`] can be converted. +//! The [`LuaSerdeExt`] trait implemented for [`Lua`] allows conversion from Rust types to Lua +//! values and vice versa using serde. Any user defined data type that implements +//! [`serde::Serialize`] or [`serde::Deserialize`] can be converted. //! For convenience, additional functionality to handle `NULL` values and arrays is provided. //! //! The [`Value`] enum implements [`serde::Serialize`] trait to support serializing Lua values @@ -40,14 +40,14 @@ //! # Async/await support //! //! The [`create_async_function`] allows creating non-blocking functions that returns [`Future`]. -//! Lua code with async capabilities can be executed by [`call_async`] family of functions or polling -//! [`AsyncThread`] using any runtime (eg. Tokio). +//! Lua code with async capabilities can be executed by [`call_async`] family of functions or +//! polling [`AsyncThread`] using any runtime (eg. Tokio). //! //! Requires `feature = "async"`. //! //! # `Send` requirement -//! By default `mlua` is `!Send`. This can be changed by enabling `feature = "send"` that adds `Send` requirement -//! to [`Function`]s and [`UserData`]. +//! By default `mlua` is `!Send`. This can be changed by enabling `feature = "send"` that adds +//! `Send` requirement to [`Function`]s and [`UserData`]. //! //! [Lua programming language]: https://www.lua.org/ //! [`Lua`]: crate::Lua @@ -116,8 +116,8 @@ pub use crate::table::{Table, TableExt, TablePairs, TableSequence}; pub use crate::thread::{Thread, ThreadStatus}; pub use crate::types::{AppDataRef, AppDataRefMut, Integer, LightUserData, Number, RegistryKey}; pub use crate::userdata::{ - AnyUserData, AnyUserDataExt, MetaMethod, UserData, UserDataFields, UserDataMetatable, - UserDataMethods, UserDataRef, UserDataRefMut, UserDataRegistry, + AnyUserData, AnyUserDataExt, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, + UserDataRef, UserDataRefMut, UserDataRegistry, }; pub use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value}; @@ -137,9 +137,7 @@ pub use crate::thread::AsyncThread; #[cfg(feature = "serialize")] #[doc(inline)] -pub use crate::serde::{ - de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt, -}; +pub use crate::serde::{de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt}; #[cfg(feature = "serialize")] #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] @@ -190,8 +188,8 @@ extern crate mlua_derive; /// /// Other minor limitations: /// -/// - Certain escape codes in string literals don't work. -/// (Specifically: `\a`, `\b`, `\f`, `\v`, `\123` (octal escape codes), `\u`, and `\U`). +/// - Certain escape codes in string literals don't work. (Specifically: `\a`, `\b`, `\f`, `\v`, +/// `\123` (octal escape codes), `\u`, and `\U`). /// /// These are accepted: : `\\`, `\n`, `\t`, `\r`, `\xAB` (hex escape codes), and `\0`. /// @@ -255,7 +253,6 @@ pub use mlua_derive::FromLua; /// ... /// } /// ``` -/// #[cfg(any(feature = "module", docsrs))] #[cfg_attr(docsrs, doc(cfg(feature = "module")))] pub use mlua_derive::lua_module; diff --git a/src/luau/mod.rs b/src/luau/mod.rs index 139860b8..75d1a767 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -10,10 +10,7 @@ impl Lua { pub(crate) unsafe fn configure_luau(&self) -> Result<()> { let globals = self.globals(); - globals.raw_set( - "collectgarbage", - self.create_c_function(lua_collectgarbage)?, - )?; + globals.raw_set("collectgarbage", self.create_c_function(lua_collectgarbage)?)?; globals.raw_set("vector", self.create_c_function(lua_vector)?)?; // Set `_VERSION` global to include version number diff --git a/src/luau/package.rs b/src/luau/package.rs index cf3fe0ab..23e78c81 100644 --- a/src/luau/package.rs +++ b/src/luau/package.rs @@ -225,14 +225,13 @@ fn dylib_loader(lua: &Lua, modname: StdString) -> Result { let search_cpath = package.get::<_, StdString>("cpath").unwrap_or_default(); let find_symbol = |lib: &Library| unsafe { - if let Ok(entry) = lib.get::(format!("luaopen_{modname}\0").as_bytes()) - { + if let Ok(entry) = lib.get::(format!("luaopen_{modname}\0").as_bytes()) { return lua.create_c_function(*entry).map(Value::Function); } // Try all in one mode - if let Ok(entry) = lib.get::( - format!("luaopen_{}\0", modname.replace('.', "_")).as_bytes(), - ) { + if let Ok(entry) = + lib.get::(format!("luaopen_{}\0", modname.replace('.', "_")).as_bytes()) + { return lua.create_c_function(*entry).map(Value::Function); } "cannot find module entrypoint".into_lua(lua) diff --git a/src/macros.rs b/src/macros.rs index b3c991ba..5c487efb 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -10,8 +10,7 @@ macro_rules! bug_msg { macro_rules! cstr { ($s:expr) => { - concat!($s, "\0") as *const str as *const [::std::os::raw::c_char] - as *const ::std::os::raw::c_char + concat!($s, "\0") as *const str as *const [::std::os::raw::c_char] as *const ::std::os::raw::c_char }; } diff --git a/src/multi.rs b/src/multi.rs index 4f0c6632..443c2658 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -4,8 +4,7 @@ use std::os::raw::c_int; use std::result::Result as StdResult; use crate::error::Result; -use crate::state::Lua; -use crate::state::RawLua; +use crate::state::{Lua, RawLua}; use crate::util::check_stack; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil}; @@ -82,12 +81,7 @@ impl FromLuaMulti for T { } #[inline] - unsafe fn from_stack_args( - nargs: c_int, - i: usize, - to: Option<&str>, - lua: &RawLua, - ) -> Result { + unsafe fn from_stack_args(nargs: c_int, i: usize, to: Option<&str>, lua: &RawLua) -> Result { if nargs == 0 { return T::from_lua_arg(Nil, i, to, lua.lua()); } diff --git a/src/prelude.rs b/src/prelude.rs index c2929f79..b6168657 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,19 +2,17 @@ #[doc(no_inline)] pub use crate::{ - AnyUserData as LuaAnyUserData, AnyUserDataExt as LuaAnyUserDataExt, Chunk as LuaChunk, - Error as LuaError, ErrorContext as LuaErrorContext, ExternalError as LuaExternalError, - ExternalResult as LuaExternalResult, FromLua, FromLuaMulti, Function as LuaFunction, - FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, Integer as LuaInteger, IntoLua, - IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaOptions, MetaMethod as LuaMetaMethod, - MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, RegistryKey as LuaRegistryKey, - Result as LuaResult, StdLib as LuaStdLib, String as LuaString, Table as LuaTable, - TableExt as LuaTableExt, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, - Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData, - UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, + AnyUserData as LuaAnyUserData, AnyUserDataExt as LuaAnyUserDataExt, Chunk as LuaChunk, Error as LuaError, + ErrorContext as LuaErrorContext, ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, + FromLua, FromLuaMulti, Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, + Integer as LuaInteger, IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaOptions, + MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, + RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, String as LuaString, + Table as LuaTable, TableExt as LuaTableExt, TablePairs as LuaTablePairs, + TableSequence as LuaTableSequence, Thread as LuaThread, ThreadStatus as LuaThreadStatus, + UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, - UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, - Value as LuaValue, + UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, }; #[cfg(not(feature = "luau"))] @@ -32,6 +30,5 @@ pub use crate::AsyncThread as LuaAsyncThread; #[cfg(feature = "serialize")] #[doc(no_inline)] pub use crate::{ - DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, - SerializeOptions as LuaSerializeOptions, + DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, SerializeOptions as LuaSerializeOptions, }; diff --git a/src/serde/de.rs b/src/serde/de.rs index 542180f8..9c36c87e 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -108,11 +108,7 @@ impl Deserializer { } } - fn from_parts( - value: Value, - options: Options, - visited: Rc>>, - ) -> Self { + fn from_parts(value: Value, options: Options, visited: Rc>>) -> Self { Deserializer { value, options, @@ -267,10 +263,7 @@ impl<'de> serde::Deserializer<'de> for Deserializer { if deserializer.seq.count() == 0 { Ok(seq) } else { - Err(de::Error::invalid_length( - len, - &"fewer elements in the table", - )) + Err(de::Error::invalid_length(len, &"fewer elements in the table")) } } Value::UserData(ud) if ud.is_serializable() => { @@ -292,12 +285,7 @@ impl<'de> serde::Deserializer<'de> for Deserializer { } #[inline] - fn deserialize_tuple_struct( - self, - _name: &'static str, - _len: usize, - visitor: V, - ) -> Result + fn deserialize_tuple_struct(self, _name: &'static str, _len: usize, visitor: V) -> Result where V: de::Visitor<'de>, { @@ -454,8 +442,7 @@ impl<'de> de::SeqAccess<'de> for VecDeserializer { Some(&n) => { self.next += 1; let visited = Rc::clone(&self.visited); - let deserializer = - Deserializer::from_parts(Value::Number(n as _), self.options, visited); + let deserializer = Deserializer::from_parts(Value::Number(n as _), self.options, visited); seed.deserialize(deserializer).map(Some) } None => Ok(None), @@ -616,9 +603,7 @@ impl<'de> de::VariantAccess<'de> for VariantDeserializer { T: de::DeserializeSeed<'de>, { match self.value { - Some(value) => { - seed.deserialize(Deserializer::from_parts(value, self.options, self.visited)) - } + Some(value) => seed.deserialize(Deserializer::from_parts(value, self.options, self.visited)), None => Err(de::Error::invalid_type( de::Unexpected::UnitVariant, &"newtype variant", diff --git a/src/serde/mod.rs b/src/serde/mod.rs index dee3a81e..599960ed 100644 --- a/src/serde/mod.rs +++ b/src/serde/mod.rs @@ -2,7 +2,8 @@ use std::os::raw::c_void; -use serde::{de::DeserializeOwned, ser::Serialize}; +use serde::de::DeserializeOwned; +use serde::ser::Serialize; use crate::error::Result; use crate::private::Sealed; @@ -189,8 +190,7 @@ pub trait LuaSerdeExt: Sealed { /// } /// ``` #[allow(clippy::wrong_self_convention)] - fn from_value_with(&self, value: Value, options: de::Options) - -> Result; + fn from_value_with(&self, value: Value, options: de::Options) -> Result; } impl LuaSerdeExt for Lua { diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 315308cf..0267db78 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -96,8 +96,8 @@ impl Options { /// Sets [`detect_serde_json_arbitrary_precision`] option. /// - /// This option is used to serialize `serde_json::Number` with arbitrary precision to a Lua number. - /// Otherwise it will be serialized as an object (what serde does). + /// This option is used to serialize `serde_json::Number` with arbitrary precision to a Lua + /// number. Otherwise it will be serialized as an object (what serde does). /// /// This option is disabled by default. /// @@ -264,11 +264,7 @@ impl<'a> ser::Serializer for Serializer<'a> { } #[inline] - fn serialize_tuple_struct( - self, - name: &'static str, - len: usize, - ) -> Result { + fn serialize_tuple_struct(self, name: &'static str, len: usize) -> Result { #[cfg(feature = "luau")] if name == "Vector" && len == crate::types::Vector::SIZE { return Ok(SerializeSeq::new_vector(self.lua, self.options)); @@ -454,8 +450,7 @@ impl ser::SerializeTupleVariant for SerializeTupleVariant<'_> { where T: Serialize + ?Sized, { - self.table - .raw_push(self.lua.to_value_with(value, self.options)?) + self.table.raw_push(self.lua.to_value_with(value, self.options)?) } fn end(self) -> Result { @@ -489,10 +484,7 @@ impl ser::SerializeMap for SerializeMap<'_> { where T: Serialize + ?Sized, { - let key = mlua_expect!( - self.key.take(), - "serialize_value called before serialize_key" - ); + let key = mlua_expect!(self.key.take(), "serialize_value called before serialize_key"); let value = self.lua.to_value_with(value, self.options)?; self.table.raw_set(key, value) } diff --git a/src/state.rs b/src/state.rs index 2cba49f8..8eae6639 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,14 +1,12 @@ use std::any::TypeId; use std::cell::RefCell; // use std::collections::VecDeque; -use std::fmt; use std::marker::PhantomData; use std::ops::Deref; use std::os::raw::{c_int, c_void}; use std::panic::Location; -use std::rc::Rc; use std::result::Result as StdResult; -use std::{mem, ptr}; +use std::{fmt, mem, ptr}; use crate::chunk::{AsChunk, Chunk}; use crate::error::{Error, Result}; @@ -352,8 +350,9 @@ impl Lua { /// of the program's life. /// /// Dropping the returned reference will cause a memory leak. If this is not acceptable, - /// the reference should first be wrapped with the [`Lua::from_static`] function producing a `Lua`. - /// This `Lua` object can then be dropped which will properly release the allocated memory. + /// the reference should first be wrapped with the [`Lua::from_static`] function producing a + /// `Lua`. This `Lua` object can then be dropped which will properly release the allocated + /// memory. /// /// [`Lua::from_static`]: #method.from_static /// @@ -366,7 +365,8 @@ impl Lua { /// Constructs a `Lua` from a static reference to it. /// /// # Safety - /// This function is unsafe because improper use may lead to memory problems or undefined behavior. + /// This function is unsafe because improper use may lead to memory problems or undefined + /// behavior. /// /// FIXME: remove #[doc(hidden)] @@ -424,8 +424,8 @@ impl Lua { /// - Set all libraries to read-only /// - Set all builtin metatables to read-only /// - Set globals to read-only (and activates safeenv) - /// - Setup local environment table that performs writes locally and proxies reads - /// to the global environment. + /// - Setup local environment table that performs writes locally and proxies reads to the global + /// environment. /// /// # Examples /// @@ -482,7 +482,8 @@ impl Lua { /// erroring once an instruction limit has been reached. /// /// This method sets a hook function for the current thread of this Lua instance. - /// If you want to set a hook function for another thread (coroutine), use [`Thread::set_hook()`] instead. + /// If you want to set a hook function for another thread (coroutine), use + /// [`Thread::set_hook()`] instead. /// /// Please note you cannot have more than one hook function set at a time for this Lua instance. /// @@ -548,8 +549,8 @@ impl Lua { /// /// The provided interrupt function can error, and this error will be propagated through /// the Luau code that was executing at the time the interrupt was triggered. - /// Also this can be used to implement continuous execution limits by instructing Luau VM to yield - /// by returning [`VmState::Yield`]. + /// Also this can be used to implement continuous execution limits by instructing Luau VM to + /// yield by returning [`VmState::Yield`]. /// /// This is similar to [`Lua::set_hook`] but in more simplified form. /// @@ -589,6 +590,8 @@ impl Lua { where F: Fn(&Lua) -> Result + MaybeSend + 'static, { + use std::rc::Rc; + unsafe extern "C-unwind" fn interrupt_proc(state: *mut ffi::lua_State, gc: c_int) { if gc >= 0 { // We don't support GC interrupts since they cannot survive Lua exceptions @@ -597,8 +600,7 @@ impl Lua { let extra = ExtraData::get(state); let result = callback_error_ext(state, extra, move |_| { let interrupt_cb = (*extra).interrupt_callback.clone(); - let interrupt_cb = - mlua_expect!(interrupt_cb, "no interrupt callback set in interrupt_proc"); + let interrupt_cb = mlua_expect!(interrupt_cb, "no interrupt callback set in interrupt_proc"); if Rc::strong_count(&interrupt_cb) > 2 { return Ok(VmState::Continue); // Don't allow recursion } @@ -704,9 +706,10 @@ impl Lua { /// Gets information about the interpreter runtime stack. /// - /// This function returns [`Debug`] structure that can be used to get information about the function - /// executing at a given level. Level `0` is the current running function, whereas level `n+1` is the - /// function that has called level `n` (except for tail calls, which do not count in the stack). + /// This function returns [`Debug`] structure that can be used to get information about the + /// function executing at a given level. Level `0` is the current running function, whereas + /// level `n+1` is the function that has called level `n` (except for tail calls, which do + /// not count in the stack). /// /// [`Debug`]: crate::hook::Debug pub fn inspect_stack(&self, level: usize) -> Option { @@ -762,12 +765,7 @@ impl Lua { /// Returns true if the garbage collector is currently running automatically. /// /// Requires `feature = "lua54/lua53/lua52/luau"` - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luau" - ))] + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] pub fn gc_is_running(&self) -> bool { let lua = self.lock(); unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCISRUNNING, 0) != 0 } @@ -887,8 +885,7 @@ impl Lua { } #[cfg(feature = "lua54")] - let prev_mode = - unsafe { ffi::lua_gc(state, ffi::LUA_GCINC, pause, step_multiplier, step_size) }; + let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCINC, pause, step_multiplier, step_size) }; #[cfg(feature = "lua54")] match prev_mode { ffi::LUA_GCINC => GCMode::Incremental, @@ -910,8 +907,7 @@ impl Lua { pub fn gc_gen(&self, minor_multiplier: c_int, major_multiplier: c_int) -> GCMode { let lua = self.lock(); let state = lua.main_state; - let prev_mode = - unsafe { ffi::lua_gc(state, ffi::LUA_GCGEN, minor_multiplier, major_multiplier) }; + let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCGEN, minor_multiplier, major_multiplier) }; match prev_mode { ffi::LUA_GCGEN => GCMode::Generational, ffi::LUA_GCINC => GCMode::Incremental, @@ -1074,8 +1070,8 @@ impl Lua { /// intermediate Lua code. /// /// If the function returns `Ok`, the contained value will be converted to one or more Lua - /// values. For details on Rust-to-Lua conversions, refer to the [`IntoLua`] and [`IntoLuaMulti`] - /// traits. + /// values. For details on Rust-to-Lua conversions, refer to the [`IntoLua`] and + /// [`IntoLuaMulti`] traits. /// /// # Examples /// @@ -1137,9 +1133,7 @@ impl Lua { { let func = RefCell::new(func); self.create_function(move |lua, args| { - (*func - .try_borrow_mut() - .map_err(|_| Error::RecursiveMutCallback)?)(lua, args) + (*func.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?)(lua, args) }) } @@ -1158,10 +1152,10 @@ impl Lua { /// While executing the function Rust will poll Future and if the result is not ready, call /// `yield()` passing internal representation of a `Poll::Pending` value. /// - /// The function must be called inside Lua coroutine ([`Thread`]) to be able to suspend its execution. - /// An executor should be used to poll [`AsyncThread`] and mlua will take a provided Waker - /// in that case. Otherwise noop waker will be used if try to call the function outside of Rust - /// executors. + /// The function must be called inside Lua coroutine ([`Thread`]) to be able to suspend its + /// execution. An executor should be used to poll [`AsyncThread`] and mlua will take a + /// provided Waker in that case. Otherwise noop waker will be used if try to call the + /// function outside of Rust executors. /// /// The family of `call_async()` functions takes care about creating [`Thread`]. /// @@ -1277,10 +1271,7 @@ impl Lua { /// Registers a custom Rust type in Lua to use in userdata objects. /// /// This methods provides a way to add fields or methods to userdata objects of a type `T`. - pub fn register_userdata_type( - &self, - f: impl FnOnce(&mut UserDataRegistry), - ) -> Result<()> { + pub fn register_userdata_type(&self, f: impl FnOnce(&mut UserDataRegistry)) -> Result<()> { let mut registry = UserDataRegistry::new(); f(&mut registry); @@ -1376,8 +1367,9 @@ impl Lua { } } - /// Returns a handle to the active `Thread`. For calls to `Lua` this will be the main Lua thread, - /// for parameters given to a callback, this will be whatever Lua thread called the callback. + /// Returns a handle to the active `Thread`. For calls to `Lua` this will be the main Lua + /// thread, for parameters given to a callback, this will be whatever Lua thread called the + /// callback. pub fn current_thread(&self) -> Thread { let lua = self.lock(); let state = lua.state(); @@ -1645,9 +1637,9 @@ impl Lua { /// Removes a value from the Lua registry. /// /// You may call this function to manually remove a value placed in the registry with - /// [`Lua::create_registry_value`]. In addition to manual [`RegistryKey`] removal, you can also call - /// [`Lua::expire_registry_values`] to automatically remove values from the registry whose - /// [`RegistryKey`]s have been dropped. + /// [`Lua::create_registry_value`]. In addition to manual [`RegistryKey`] removal, you can also + /// call [`Lua::expire_registry_values`] to automatically remove values from the registry + /// whose [`RegistryKey`]s have been dropped. pub fn remove_registry_value(&self, key: RegistryKey) -> Result<()> { let lua = self.lock(); if !lua.owns_registry_value(&key) { @@ -1700,8 +1692,8 @@ impl Lua { Ok(()) } - /// Returns true if the given [`RegistryKey`] was created by a [`Lua`] which shares the underlying - /// main state with this [`Lua`] instance. + /// Returns true if the given [`RegistryKey`] was created by a [`Lua`] which shares the + /// underlying main state with this [`Lua`] instance. /// /// Other than this, methods that accept a [`RegistryKey`] will return /// [`Error::MismatchedRegistryKey`] if passed a [`RegistryKey`] that was not created with a @@ -1713,9 +1705,9 @@ impl Lua { /// Remove any registry values whose [`RegistryKey`]s have all been dropped. /// - /// Unlike normal handle values, [`RegistryKey`]s do not automatically remove themselves on Drop, - /// but you can call this method to remove any unreachable registry values not manually removed - /// by [`Lua::remove_registry_value`]. + /// Unlike normal handle values, [`RegistryKey`]s do not automatically remove themselves on + /// Drop, but you can call this method to remove any unreachable registry values not + /// manually removed by [`Lua::remove_registry_value`]. pub fn expire_registry_values(&self) { let lua = self.lock(); let state = lua.state(); @@ -1730,8 +1722,8 @@ 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. + /// 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. /// /// # Panics /// @@ -1770,7 +1762,8 @@ impl Lua { /// Returns: /// - `Ok(Some(old_data))` if the data object of type `T` was successfully replaced. /// - `Ok(None)` if the data object of type `T` was successfully inserted. - /// - `Err(data)` if the data object of type `T` was not inserted because the container is currently borrowed. + /// - `Err(data)` if the data object of type `T` was not inserted because the container is + /// currently borrowed. /// /// See [`Lua::set_app_data()`] for examples. pub fn try_set_app_data(&self, data: T) -> StdResult, T> { @@ -1779,12 +1772,13 @@ impl Lua { extra.app_data.try_insert(data) } - /// Gets a reference to an application data object stored by [`Lua::set_app_data()`] of type `T`. + /// Gets a reference to an application data object stored by [`Lua::set_app_data()`] of type + /// `T`. /// /// # Panics /// - /// Panics if the data object of type `T` is currently mutably borrowed. Multiple immutable reads - /// can be taken out at the same time. + /// Panics if the data object of type `T` is currently mutably borrowed. Multiple immutable + /// reads can be taken out at the same time. #[track_caller] pub fn app_data_ref(&self) -> Option> { let guard = self.lock_arc(); @@ -1792,7 +1786,8 @@ impl Lua { extra.app_data.borrow(Some(guard)) } - /// Gets a mutable reference to an application data object stored by [`Lua::set_app_data()`] of type `T`. + /// Gets a mutable reference to an application data object stored by [`Lua::set_app_data()`] of + /// type `T`. /// /// # Panics /// diff --git a/src/state/raw.rs b/src/state/raw.rs index 860a108c..1f05266b 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -18,15 +18,15 @@ use crate::string::String; use crate::table::Table; use crate::thread::Thread; use crate::types::{ - AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, - LightUserData, MaybeSend, ReentrantMutex, RegistryKey, SubtypeId, ValueRef, XRc, + AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData, + MaybeSend, ReentrantMutex, RegistryKey, SubtypeId, ValueRef, XRc, }; use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataRegistry, UserDataVariant}; use crate::util::{ assert_stack, check_stack, get_destructed_userdata_metatable, get_gc_userdata, get_main_state, get_userdata, init_error_registry, init_gc_metatable, init_userdata_metatable, pop_error, - push_gc_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, - short_type_name, StackGuard, WrappedFailure, + push_gc_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name, + StackGuard, WrappedFailure, }; use crate::value::{FromLuaMulti, IntoLua, MultiValue, Nil, Value}; @@ -220,9 +220,7 @@ impl RawLua { rawlua } - pub(super) unsafe fn try_from_ptr( - state: *mut ffi::lua_State, - ) -> Option>> { + pub(super) unsafe fn try_from_ptr(state: *mut ffi::lua_State) -> Option>> { match ExtraData::get(state) { extra if extra.is_null() => None, extra => Some(XRc::clone(&(*extra).lua().0)), @@ -261,10 +259,7 @@ impl RawLua { // If `package` library loaded into a safe lua state then disable C modules let curr_libs = (*self.extra.get()).libs; if is_safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) { - mlua_expect!( - self.lua().disable_c_modules(), - "Error during disabling C modules" - ); + mlua_expect!(self.lua().disable_c_modules(), "Error during disabling C modules"); } unsafe { (*self.extra.get()).libs |= libs }; @@ -273,10 +268,7 @@ impl RawLua { /// See [`Lua::try_set_app_data`] #[inline] - pub(crate) fn try_set_app_data( - &self, - data: T, - ) -> StdResult, T> { + pub(crate) fn try_set_app_data(&self, data: T) -> StdResult, T> { let extra = unsafe { &*self.extra.get() }; extra.app_data.try_insert(data) } @@ -400,11 +392,7 @@ impl RawLua { } /// See [`Lua::create_table_with_capacity`] - pub(crate) unsafe fn create_table_with_capacity( - &self, - narr: usize, - nrec: usize, - ) -> Result
{ + pub(crate) unsafe fn create_table_with_capacity(&self, narr: usize, nrec: usize) -> Result
{ if self.unlikely_memory_error() { push_table(self.ref_thread(), narr, nrec, false)?; return Ok(Table(self.pop_ref_thread())); @@ -587,9 +575,7 @@ impl RawLua { ffi::LUA_TBOOLEAN => Value::Boolean(ffi::lua_toboolean(state, idx) != 0), - ffi::LUA_TLIGHTUSERDATA => { - Value::LightUserData(LightUserData(ffi::lua_touserdata(state, idx))) - } + ffi::LUA_TLIGHTUSERDATA => Value::LightUserData(LightUserData(ffi::lua_touserdata(state, idx))), #[cfg(any(feature = "lua54", feature = "lua53"))] ffi::LUA_TNUMBER => { @@ -600,12 +586,7 @@ impl RawLua { } } - #[cfg(any( - feature = "lua52", - feature = "lua51", - feature = "luajit", - feature = "luau" - ))] + #[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))] ffi::LUA_TNUMBER => { use crate::types::Number; @@ -770,10 +751,7 @@ impl RawLua { }) } - pub(crate) unsafe fn make_any_userdata( - &self, - data: UserDataVariant, - ) -> Result + pub(crate) unsafe fn make_any_userdata(&self, data: UserDataVariant) -> Result where T: 'static, { @@ -806,12 +784,7 @@ impl RawLua { #[cfg(not(feature = "lua54"))] crate::util::push_userdata(state, data, protect)?; #[cfg(feature = "lua54")] - crate::util::push_userdata_uv( - state, - data, - crate::userdata::USER_VALUE_MAXSLOT as c_int, - protect, - )?; + crate::util::push_userdata_uv(state, data, crate::userdata::USER_VALUE_MAXSLOT as c_int, protect)?; ffi::lua_replace(state, -3); ffi::lua_setmetatable(state, -2); @@ -950,7 +923,8 @@ impl RawLua { ffi::lua_pop(state, 1); // All done } ffi::LUA_TNIL => { - rawset_field(state, metatable_index, "__index")?; // Set the new table as `__index` + // Set the new table as `__index` + rawset_field(state, metatable_index, "__index")?; } _ => { methods_index = Some(ffi::lua_absindex(state, -1)); @@ -963,10 +937,7 @@ impl RawLua { let extra_init = None; #[cfg(not(feature = "luau"))] let extra_init: Option Result<()>> = Some(|state| { - ffi::lua_pushcfunction( - state, - crate::util::userdata_destructor::>, - ); + ffi::lua_pushcfunction(state, crate::util::userdata_destructor::>); rawset_field(state, -2, "__gc") }); @@ -1024,10 +995,7 @@ impl RawLua { // Returns `TypeId` for the userdata ref, checking that it's registered and not destructed. // // Returns `None` if the userdata is registered but non-static. - pub(crate) unsafe fn get_userdata_ref_type_id( - &self, - vref: &ValueRef, - ) -> Result> { + pub(crate) unsafe fn get_userdata_ref_type_id(&self, vref: &ValueRef) -> Result> { self.get_userdata_type_id_inner(self.ref_thread(), vref.index) } @@ -1123,12 +1091,7 @@ impl RawLua { #[cfg(feature = "async")] pub(crate) fn create_async_callback(&self, func: AsyncCallback) -> Result { - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luau" - ))] + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] unsafe { if !(*self.extra.get()).libs.contains(StdLib::COROUTINE) { load_from_std_lib(self.main_state, StdLib::COROUTINE)?; @@ -1322,12 +1285,7 @@ unsafe fn load_from_std_lib(state: *mut ffi::lua_State, libs: StdLib) -> Result< #[cfg(feature = "luajit")] let _gc_guard = GcGuard::new(state); - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luau" - ))] + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] { if libs.contains(StdLib::COROUTINE) { requiref(state, ffi::LUA_COLIBNAME, ffi::luaopen_coroutine, 1)?; diff --git a/src/state/util.rs b/src/state/util.rs index 8c6a66b1..c1d64921 100644 --- a/src/state/util.rs +++ b/src/state/util.rs @@ -63,11 +63,7 @@ where } } - unsafe fn r#use( - &self, - state: *mut ffi::lua_State, - extra: *mut ExtraData, - ) -> *mut WrappedFailure { + unsafe fn r#use(&self, state: *mut ffi::lua_State, extra: *mut ExtraData) -> *mut WrappedFailure { let ref_thread = (*extra).ref_thread; match *self { PreallocatedFailure::New(ud) => { @@ -176,9 +172,7 @@ pub(super) unsafe fn ref_stack_pop(extra: *mut ExtraData) -> c_int { let top = extra.ref_stack_top; // It is a user error to create enough references to exhaust the Lua max stack size for // the ref thread. - panic!( - "cannot create a Lua reference, out of auxiliary stack space (used {top} slots)" - ); + panic!("cannot create a Lua reference, out of auxiliary stack space (used {top} slots)"); } extra.ref_stack_size += inc; } diff --git a/src/stdlib.rs b/src/stdlib.rs index e3630d44..c71d1497 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -8,12 +8,7 @@ impl StdLib { /// [`coroutine`](https://www.lua.org/manual/5.4/manual.html#6.2) library /// /// Requires `feature = "lua54/lua53/lua52/luau"` - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luau" - ))] + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] pub const COROUTINE: StdLib = StdLib(1); /// [`table`](https://www.lua.org/manual/5.4/manual.html#6.6) library diff --git a/src/table.rs b/src/table.rs index 478da12e..7f32d64e 100644 --- a/src/table.rs +++ b/src/table.rs @@ -272,8 +272,8 @@ impl Table { } } - /// Inserts element value at position `idx` to the table, shifting up the elements from `table[idx]`. - /// The worst case complexity is O(n), where n is the table length. + /// Inserts element value at position `idx` to the table, shifting up the elements from + /// `table[idx]`. The worst case complexity is O(n), where n is the table length. pub fn raw_insert(&self, idx: Integer, value: V) -> Result<()> { let size = self.raw_len() as Integer; if idx < 1 || idx > size + 1 { @@ -878,7 +878,8 @@ where pub trait TableExt: Sealed { /// Calls the table as function assuming it has `__call` metamethod. /// - /// The metamethod is called with the table as its first argument, followed by the passed arguments. + /// The metamethod is called with the table as its first argument, followed by the passed + /// arguments. fn call(&self, args: A) -> Result where A: IntoLuaMulti, @@ -886,7 +887,8 @@ pub trait TableExt: Sealed { /// Asynchronously calls the table as function assuming it has `__call` metamethod. /// - /// The metamethod is called with the table as its first argument, followed by the passed arguments. + /// The metamethod is called with the table as its first argument, followed by the passed + /// arguments. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] fn call_async(&self, args: A) -> impl Future> diff --git a/src/thread.rs b/src/thread.rs index db654963..af11f4ed 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -161,10 +161,7 @@ impl Thread { return Err(pop_error(thread_state, ret)); } check_stack(state, 3)?; - protect_lua!(state, 0, 1, |state| error_traceback_thread( - state, - thread_state - ))?; + protect_lua!(state, 0, 1, |state| error_traceback_thread(state, thread_state))?; return Err(pop_error(state, ret)); } diff --git a/src/types.rs b/src/types.rs index abd88af2..e09aca8d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -189,7 +189,8 @@ pub(crate) struct DestructedUserdata; /// /// This is a handle to a value stored inside the Lua registry. It is not automatically /// garbage collected on Drop, but it can be removed with [`Lua::remove_registry_value`], -/// and instances not manually removed can be garbage collected with [`Lua::expire_registry_values`]. +/// and instances not manually removed can be garbage collected with +/// [`Lua::expire_registry_values`]. /// /// Be warned, If you place this into Lua via a [`UserData`] type or a rust callback, it is *very /// easy* to accidentally cause reference cycles that the Lua garbage collector cannot resolve. diff --git a/src/types/app_data.rs b/src/types/app_data.rs index 3b6a4ab0..35cde1a0 100644 --- a/src/types/app_data.rs +++ b/src/types/app_data.rs @@ -6,9 +6,8 @@ use std::result::Result as StdResult; use rustc_hash::FxHashMap; -use crate::state::LuaGuard; - use super::MaybeSend; +use crate::state::LuaGuard; #[cfg(not(feature = "send"))] type Container = UnsafeCell>>>; @@ -56,10 +55,7 @@ impl AppData { } #[track_caller] - pub(crate) fn borrow_mut( - &self, - guard: Option, - ) -> Option> { + pub(crate) fn borrow_mut(&self, guard: Option) -> Option> { let data = unsafe { &*self.container.get() } .get(&TypeId::of::())? .borrow_mut(); diff --git a/src/userdata.rs b/src/userdata.rs index 855d4d55..fdb3f499 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -107,12 +107,7 @@ 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", - feature = "luajit52", - ))] + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52",))] Pairs, /// The `__ipairs` metamethod. /// @@ -148,7 +143,8 @@ pub enum MetaMethod { Close, /// The `__name`/`__type` metafield. /// - /// This is not a function, but it's value can be used by `tostring` and `typeof` built-in functions. + /// This is not a function, but it's value can be used by `tostring` and `typeof` built-in + /// functions. #[doc(hidden)] Type, } @@ -208,12 +204,7 @@ impl MetaMethod { MetaMethod::Call => "__call", MetaMethod::ToString => "__tostring", - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luajit52" - ))] + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] MetaMethod::Pairs => "__pairs", #[cfg(any(feature = "lua52", feature = "luajit52"))] MetaMethod::IPairs => "__ipairs", @@ -480,8 +471,8 @@ pub trait UserDataFields<'a, T> { /// Add a regular field getter as a method which accepts a `&T` as the parameter. /// - /// Regular field getters are implemented by overriding the `__index` metamethod and returning the - /// accessed field. This allows them to be used with the expected `userdata.field` syntax. + /// Regular field getters are implemented by overriding the `__index` metamethod and returning + /// the accessed field. This allows them to be used with the expected `userdata.field` syntax. /// /// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will /// be used as a fall-back if no regular field or method are found. @@ -492,11 +483,12 @@ pub trait UserDataFields<'a, T> { /// Add a regular field setter as a method which accepts a `&mut T` as the first parameter. /// - /// Regular field setters are implemented by overriding the `__newindex` metamethod and setting the - /// accessed field. This allows them to be used with the expected `userdata.field = value` syntax. + /// Regular field setters are implemented by overriding the `__newindex` metamethod and setting + /// the accessed field. This allows them to be used with the expected `userdata.field = value` + /// syntax. /// - /// If `add_meta_method` is used to set the `__newindex` metamethod, the `__newindex` metamethod will - /// be used as a fall-back if no regular field is found. + /// If `add_meta_method` is used to set the `__newindex` metamethod, the `__newindex` metamethod + /// will be used as a fall-back if no regular field is found. fn add_field_method_set(&mut self, name: impl ToString, method: M) where M: FnMut(&'a Lua, &mut T, A) -> Result<()> + MaybeSend + 'static, @@ -577,8 +569,8 @@ pub trait UserDataFields<'a, T> { /// # } /// ``` /// -/// Custom fields, methods and operators can be provided by implementing `add_fields` or `add_methods` -/// (refer to [`UserDataFields`] and [`UserDataMethods`] for more information): +/// Custom fields, methods and operators can be provided by implementing `add_fields` or +/// `add_methods` (refer to [`UserDataFields`] and [`UserDataMethods`] for more information): /// /// ``` /// # use mlua::{Lua, MetaMethod, Result, UserData, UserDataFields, UserDataMethods}; @@ -686,7 +678,8 @@ impl AnyUserData { } /// Takes the value out of this userdata. - /// Sets the special "destructed" metatable that prevents any further operations with this userdata. + /// Sets the special "destructed" metatable that prevents any further operations with this + /// userdata. /// /// Keeps associated user values unchanged (they will be collected by Lua's GC). pub fn take(&self) -> Result { @@ -1005,7 +998,8 @@ impl AnyUserData { Ok(false) } - /// Returns `true` if this `AnyUserData` is serializable (eg. was created using `create_ser_userdata`). + /// Returns `true` if this `AnyUserData` is serializable (eg. was created using + /// `create_ser_userdata`). #[cfg(feature = "serialize")] pub(crate) fn is_serializable(&self) -> bool { let lua = self.0.lua.lock(); @@ -1079,8 +1073,8 @@ impl UserDataMetatable { /// /// If the value is `Nil`, this will effectively remove the `key`. /// Access to restricted metamethods such as `__gc` or `__metatable` will cause an error. - /// Setting `__index` or `__newindex` metamethods is also restricted because their values are cached - /// for `mlua` internal usage. + /// Setting `__index` or `__newindex` metamethods is also restricted because their values are + /// cached for `mlua` internal usage. pub fn set(&self, key: impl AsRef, value: V) -> Result<()> { let key = MetaMethod::validate(key.as_ref())?; // `__index` and `__newindex` cannot be changed in runtime, because values are cached diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index 2ad4eb11..61d87e2d 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -9,8 +9,7 @@ use std::rc::Rc; use serde::ser::{Serialize, Serializer}; use crate::error::{Error, Result}; -use crate::state::RawLua; -use crate::state::{Lua, LuaGuard}; +use crate::state::{Lua, LuaGuard, RawLua}; use crate::userdata::AnyUserData; use crate::util::get_userdata; use crate::value::{FromLua, Value}; @@ -111,9 +110,7 @@ impl UserDataVariant { impl Serialize for UserDataVariant<()> { fn serialize(&self, serializer: S) -> std::result::Result { match self { - UserDataVariant::Default(_) => { - Err(serde::ser::Error::custom("cannot serialize ")) - } + UserDataVariant::Default(_) => Err(serde::ser::Error::custom("cannot serialize ")), UserDataVariant::Serializable(inner) => unsafe { let _ = self.try_borrow().map_err(serde::ser::Error::custom)?; (*inner.value.get()).serialize(serializer) diff --git a/src/userdata/ext.rs b/src/userdata/ext.rs index fd07192c..2156b5ef 100644 --- a/src/userdata/ext.rs +++ b/src/userdata/ext.rs @@ -16,7 +16,8 @@ pub trait AnyUserDataExt: Sealed { /// Calls the userdata as a function assuming it has `__call` metamethod. /// - /// The metamethod is called with the userdata as its first argument, followed by the passed arguments. + /// The metamethod is called with the userdata as its first argument, followed by the passed + /// arguments. fn call(&self, args: A) -> Result where A: IntoLuaMulti, @@ -24,7 +25,8 @@ pub trait AnyUserDataExt: Sealed { /// Asynchronously calls the userdata as a function assuming it has `__call` metamethod. /// - /// The metamethod is called with the userdata as its first argument, followed by the passed arguments. + /// The metamethod is called with the userdata as its first argument, followed by the passed + /// arguments. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] fn call_async(&self, args: A) -> impl Future> diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index 90282c10..eb6c02f2 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -106,9 +106,7 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { let method = RefCell::new(method); Box::new(move |rawlua, nargs| unsafe { - let mut method = method - .try_borrow_mut() - .map_err(|_| Error::RecursiveMutCallback)?; + let mut method = method.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?; if nargs == 0 { let err = Error::from_lua_conversion("missing argument", "userdata", None); try_self_arg!(Err(err)); @@ -142,9 +140,7 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { ($res:expr) => { match $res { Ok(res) => res, - Err(err) => { - return Box::pin(future::ready(Err(Error::bad_self_argument(&name, err)))) - } + Err(err) => return Box::pin(future::ready(Err(Error::bad_self_argument(&name, err)))), } }; } @@ -189,9 +185,7 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { ($res:expr) => { match $res { Ok(res) => res, - Err(err) => { - return Box::pin(future::ready(Err(Error::bad_self_argument(&name, err)))) - } + Err(err) => return Box::pin(future::ready(Err(Error::bad_self_argument(&name, err)))), } }; } @@ -345,8 +339,7 @@ impl<'a, T: 'static> UserDataFields<'a, T> for UserDataRegistry<'a, T> { A: FromLua, { let name = name.to_string(); - let callback = - Self::box_function_mut(&name, move |lua, (data, val)| function(lua, data, val)); + let callback = Self::box_function_mut(&name, move |lua, (data, val)| function(lua, data, val)); self.field_setters.push((name, callback)); } diff --git a/src/util/mod.rs b/src/util/mod.rs index 966a919a..e682d836 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -45,10 +45,7 @@ pub unsafe fn assert_stack(state: *mut ffi::lua_State, amount: c_int) { // TODO: This should only be triggered when there is a logic error in `mlua`. In the future, // when there is a way to be confident about stack safety and test it, this could be enabled // only when `cfg!(debug_assertions)` is true. - mlua_assert!( - ffi::lua_checkstack(state, amount) != 0, - "out of stack space" - ); + mlua_assert!(ffi::lua_checkstack(state, amount) != 0, "out of stack space"); } // Checks that Lua has enough free stack space and returns `Error::StackError` on failure. @@ -101,8 +98,8 @@ impl Drop for StackGuard { // Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way. // Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a // limited lua stack. `nargs` is the same as the the parameter to `lua_pcall`, and `nresults` is -// always `LUA_MULTRET`. Provided function must *not* panic, and since it will generally be lonjmping, -// should not contain any values that implements Drop. +// always `LUA_MULTRET`. Provided function must *not* panic, and since it will generally be +// longjmping, should not contain any values that implements Drop. // Internally uses 2 extra stack spaces, and does not call checkstack. pub unsafe fn protect_lua_call( state: *mut ffi::lua_State, @@ -133,8 +130,8 @@ pub unsafe fn protect_lua_call( // Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a // limited lua stack. `nargs` and `nresults` are similar to the parameters of `lua_pcall`, but the // given function return type is not the return value count, instead the inner function return -// values are assumed to match the `nresults` param. Provided function must *not* panic, and since it -// will generally be lonjmping, should not contain any values that implements Drop. +// values are assumed to match the `nresults` param. Provided function must *not* panic, and since +// it will generally be longjmping, should not contain any values that implements Drop. // Internally uses 3 extra stack spaces, and does not call checkstack. pub unsafe fn protect_lua_closure( state: *mut ffi::lua_State, @@ -232,8 +229,7 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error { Error::SyntaxError { // This seems terrible, but as far as I can tell, this is exactly what the // stock Lua REPL does. - incomplete_input: err_string.ends_with("") - || err_string.ends_with("''"), + incomplete_input: err_string.ends_with("") || err_string.ends_with("''"), message: err_string, } } @@ -283,12 +279,7 @@ pub unsafe fn push_buffer(state: *mut ffi::lua_State, b: &[u8], protect: bool) - // Uses 3 stack spaces, does not call checkstack. #[inline] -pub unsafe fn push_table( - state: *mut ffi::lua_State, - narr: usize, - nrec: usize, - protect: bool, -) -> Result<()> { +pub unsafe fn push_table(state: *mut ffi::lua_State, narr: usize, nrec: usize, protect: bool) -> Result<()> { let narr: c_int = narr.try_into().unwrap_or(c_int::MAX); let nrec: c_int = nrec.try_into().unwrap_or(c_int::MAX); if protect { @@ -380,11 +371,7 @@ pub unsafe fn take_userdata(state: *mut ffi::lua_State) -> T { // Pushes the userdata and attaches a metatable with __gc method. // Internally uses 3 stack spaces, does not call checkstack. -pub unsafe fn push_gc_userdata( - state: *mut ffi::lua_State, - t: T, - protect: bool, -) -> Result<()> { +pub unsafe fn push_gc_userdata(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<()> { push_userdata(state, t, protect)?; get_gc_metatable::(state); ffi::lua_setmetatable(state, -2); @@ -556,12 +543,12 @@ pub unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Re }) } -// Populates the given table with the appropriate members to be a userdata metatable for the given type. -// This function takes the given table at the `metatable` index, and adds an appropriate `__gc` member -// to it for the given type and a `__metatable` entry to protect the table from script access. -// The function also, if given a `field_getters` or `methods` tables, will create an `__index` metamethod -// (capturing previous one) to lookup in `field_getters` first, then `methods` and falling back to the -// captured `__index` if no matches found. +// Populates the given table with the appropriate members to be a userdata metatable for the given +// type. This function takes the given table at the `metatable` index, and adds an appropriate +// `__gc` member to it for the given type and a `__metatable` entry to protect the table from script +// access. The function also, if given a `field_getters` or `methods` tables, will create an +// `__index` metamethod (capturing previous one) to lookup in `field_getters` first, then `methods` +// and falling back to the captured `__index` if no matches found. // The same is also applicable for `__newindex` metamethod and `field_setters` table. // Internally uses 9 stack spaces and does not call checkstack. pub unsafe fn init_userdata_metatable( @@ -873,8 +860,7 @@ pub unsafe fn init_gc_metatable( pub unsafe fn get_gc_metatable(state: *mut ffi::lua_State) { let type_id = TypeId::of::(); - let ref_addr = - mlua_expect!(METATABLE_CACHE.get(&type_id), "gc metatable does not exist") as *const u8; + let ref_addr = mlua_expect!(METATABLE_CACHE.get(&type_id), "gc metatable does not exist") as *const u8; ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, ref_addr as *const c_void); } @@ -979,12 +965,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> { "__newindex", "__call", "__tostring", - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luajit52" - ))] + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] "__pairs", #[cfg(any(feature = "lua53", feature = "lua52", feature = "luajit52"))] "__ipairs", diff --git a/src/util/short_names.rs b/src/util/short_names.rs index 55713ec0..22623ed1 100644 --- a/src/util/short_names.rs +++ b/src/util/short_names.rs @@ -25,22 +25,17 @@ pub(crate) fn short_type_name() -> String { // Collapse everything up to the next special character, // then skip over it - if let Some(special_character_index) = rest_of_string - .find(|c: char| [' ', '<', '>', '(', ')', '[', ']', ',', ';'].contains(&c)) + if let Some(special_character_index) = + rest_of_string.find(|c: char| [' ', '<', '>', '(', ')', '[', ']', ',', ';'].contains(&c)) { - let segment_to_collapse = rest_of_string - .get(0..special_character_index) - .unwrap_or_default(); + let segment_to_collapse = rest_of_string.get(0..special_character_index).unwrap_or_default(); parsed_name += collapse_type_name(segment_to_collapse); // Insert the special character - let special_character = - &rest_of_string[special_character_index..=special_character_index]; + let special_character = &rest_of_string[special_character_index..=special_character_index]; parsed_name.push_str(special_character); match special_character { - ">" | ")" | "]" - if rest_of_string[special_character_index + 1..].starts_with("::") => - { + ">" | ")" | "]" if rest_of_string[special_character_index + 1..].starts_with("::") => { parsed_name.push_str("::"); // Move the index past the "::" index += special_character_index + 3; @@ -77,9 +72,6 @@ mod tests { short_type_name::>>(), "HashMap>" ); - assert_eq!( - short_type_name:: i32>(), - "dyn Fn(i32) -> i32" - ); + assert_eq!(short_type_name:: i32>(), "dyn Fn(i32) -> i32"); } } diff --git a/src/value.rs b/src/value.rs index 82dc8951..dd2289d3 100644 --- a/src/value.rs +++ b/src/value.rs @@ -116,8 +116,8 @@ impl Value { /// Converts the value to a generic C pointer. /// - /// The value can be a userdata, a table, a thread, a string, or a function; otherwise it returns NULL. - /// Different objects will give different pointers. + /// The value can be a userdata, a table, a thread, a string, or a function; otherwise it + /// returns NULL. Different objects will give different pointers. /// There is no way to convert the pointer back to its original value. /// /// Typically this function is used only for hashing and debug information. @@ -136,7 +136,8 @@ impl Value { /// Converts the value to a string. /// - /// If the value has a metatable with a `__tostring` method, then it will be called to get the result. + /// If the value has a metatable with a `__tostring` method, then it will be called to get the + /// result. pub fn to_string(&self) -> Result { match self { Value::Nil => Ok("nil".to_string()), @@ -814,10 +815,7 @@ impl MultiValue { } #[inline] - pub(crate) fn extend_from_values( - &mut self, - iter: impl IntoIterator>, - ) -> Result<()> { + pub(crate) fn extend_from_values(&mut self, iter: impl IntoIterator>) -> Result<()> { for value in iter { self.push_back(value?); } @@ -860,8 +858,8 @@ impl<'a> IntoIterator for &'a MultiValue { /// Trait for types convertible to any number of Lua values. /// -/// This is a generalization of `IntoLua`, allowing any number of resulting Lua values instead of just -/// one. Any type that implements `IntoLua` will automatically implement this trait. +/// This is a generalization of `IntoLua`, allowing any number of resulting Lua values instead of +/// just one. Any type that implements `IntoLua` will automatically implement this trait. pub trait IntoLuaMulti: Sized { /// Performs the conversion. fn into_lua_multi(self, lua: &Lua) -> Result; @@ -926,12 +924,7 @@ pub trait FromLuaMulti: Sized { /// Same as `from_lua_args` but for a number of values in the Lua stack. #[doc(hidden)] #[inline] - unsafe fn from_stack_args( - nargs: c_int, - i: usize, - to: Option<&str>, - lua: &RawLua, - ) -> Result { + unsafe fn from_stack_args(nargs: c_int, i: usize, to: Option<&str>, lua: &RawLua) -> Result { let _ = (i, to); Self::from_stack_multi(nargs, lua) } diff --git a/tests/async.rs b/tests/async.rs index 0fda52ff..782075ee 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -6,8 +6,8 @@ use std::time::Duration; use futures_util::stream::TryStreamExt; use mlua::{ - AnyUserDataExt, Error, Function, Lua, LuaOptions, MultiValue, Result, StdLib, Table, TableExt, - UserData, UserDataMethods, Value, + AnyUserDataExt, Error, Function, Lua, LuaOptions, MultiValue, Result, StdLib, Table, TableExt, UserData, + UserDataMethods, Value, }; #[cfg(not(target_arch = "wasm32"))] @@ -25,8 +25,7 @@ async fn sleep_ms(_ms: u64) { async fn test_async_function() -> Result<()> { let lua = Lua::new(); - let f = lua - .create_async_function(|_lua, (a, b, c): (i64, i64, i64)| async move { Ok((a + b) * c) })?; + let f = lua.create_async_function(|_lua, (a, b, c): (i64, i64, i64)| async move { Ok((a + b) * c) })?; lua.globals().set("f", f)?; let res: i64 = lua.load("f(1, 2, 3)").eval_async().await?; @@ -75,9 +74,7 @@ async fn test_async_call() -> Result<()> { match hello.call::<_, ()>("alex") { Err(Error::RuntimeError(_)) => {} - _ => panic!( - "non-async executing async function must fail on the yield stage with RuntimeError" - ), + _ => panic!("non-async executing async function must fail on the yield stage with RuntimeError"), }; assert_eq!(hello.call_async::<_, String>("alex").await?, "hello, alex!"); @@ -342,15 +339,9 @@ async fn test_async_table() -> Result<()> { })?; table.set("sleep", sleep)?; - assert_eq!( - table.call_async_method::<_, i64>("get_value", ()).await?, - 10 - ); + assert_eq!(table.call_async_method::<_, i64>("get_value", ()).await?, 10); table.call_async_method("set_value", 15).await?; - assert_eq!( - table.call_async_method::<_, i64>("get_value", ()).await?, - 15 - ); + assert_eq!(table.call_async_method::<_, i64>("get_value", ()).await?, 15); assert_eq!( table.call_async_function::<_, String>("sleep", 7).await?, "elapsed:7ms" @@ -411,17 +402,14 @@ async fn test_async_userdata() -> Result<()> { }); #[cfg(not(any(feature = "lua51", feature = "luau")))] - methods.add_async_meta_method( - mlua::MetaMethod::Index, - |_, data, key: String| async move { - sleep_ms(10).await; - match key.as_str() { - "ms" => Ok(Some(data.0 as f64)), - "s" => Ok(Some((data.0 as f64) / 1000.0)), - _ => Ok(None), - } - }, - ); + methods.add_async_meta_method(mlua::MetaMethod::Index, |_, data, key: String| async move { + sleep_ms(10).await; + match key.as_str() { + "ms" => Ok(Some(data.0 as f64)), + "s" => Ok(Some((data.0 as f64) / 1000.0)), + _ => Ok(None), + } + }); #[cfg(not(any(feature = "lua51", feature = "luau")))] methods.add_async_meta_method_mut( diff --git a/tests/chunk.rs b/tests/chunk.rs index a797064e..31e55a24 100644 --- a/tests/chunk.rs +++ b/tests/chunk.rs @@ -1,5 +1,4 @@ -use std::fs; -use std::io; +use std::{fs, io}; use mlua::{Lua, Result}; diff --git a/tests/conversion.rs b/tests/conversion.rs index a0e0b383..709b0ba8 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -5,8 +5,7 @@ use std::ffi::{CStr, CString}; use bstr::BString; use maplit::{btreemap, btreeset, hashmap, hashset}; use mlua::{ - AnyUserData, Error, Function, IntoLua, Lua, RegistryKey, Result, Table, Thread, UserDataRef, - Value, + AnyUserData, Error, Function, IntoLua, Lua, RegistryKey, Result, Table, Thread, UserDataRef, Value, }; #[test] @@ -142,10 +141,7 @@ fn test_registry_value_into_lua() -> Result<()> { // Check non-owned registry key let lua2 = Lua::new(); let r2 = lua2.create_registry_value("abc")?; - assert!(matches!( - f.call::<_, ()>(&r2), - Err(Error::MismatchedRegistryKey) - )); + assert!(matches!(f.call::<_, ()>(&r2), Err(Error::MismatchedRegistryKey))); Ok(()) } diff --git a/tests/error.rs b/tests/error.rs index 0bd224a4..5bfd9485 100644 --- a/tests/error.rs +++ b/tests/error.rs @@ -6,9 +6,8 @@ use mlua::{Error, ErrorContext, Lua, Result}; fn test_error_context() -> Result<()> { let lua = Lua::new(); - let func = lua.create_function(|_, ()| { - Err::<(), _>(Error::runtime("runtime error")).context("some context") - })?; + let func = + lua.create_function(|_, ()| Err::<(), _>(Error::runtime("runtime error")).context("some context"))?; lua.globals().set("func", func)?; let msg = lua @@ -33,12 +32,9 @@ fn test_error_context() -> Result<()> { // Rewrite context message and test `downcast_ref` let func3 = lua.create_function(|_, ()| { - Err::<(), _>(Error::external(io::Error::new( - io::ErrorKind::Other, - "other", - ))) - .context("some context") - .context("some new context") + Err::<(), _>(Error::external(io::Error::new(io::ErrorKind::Other, "other"))) + .context("some context") + .context("some new context") })?; let res = func3.call::<_, ()>(()).err().unwrap(); let Error::CallbackError { cause, .. } = &res else { diff --git a/tests/function.rs b/tests/function.rs index c7596bed..26d40e1f 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -43,10 +43,7 @@ fn test_bind() -> Result<()> { concat = concat.bind("bar")?; concat = concat.bind(("baz", "baf"))?; assert_eq!(concat.call::<_, String>(())?, "foobarbazbaf"); - assert_eq!( - concat.call::<_, String>(("hi", "wut"))?, - "foobarbazbafhiwut" - ); + assert_eq!(concat.call::<_, String>(("hi", "wut"))?, "foobarbazbafhiwut"); let mut concat2 = globals.get::<_, Function>("concat")?; concat2 = concat2.bind(())?; @@ -271,8 +268,7 @@ fn test_function_wrap() -> Result<()> { let lua = Lua::new(); - lua.globals() - .set("f", Function::wrap(|_, s: String| Ok(s)))?; + lua.globals().set("f", Function::wrap(|_, s: String| Ok(s)))?; lua.load(r#"assert(f("hello") == "hello")"#).exec().unwrap(); let mut _i = false; diff --git a/tests/hooks.rs b/tests/hooks.rs index d2049085..4186339b 100644 --- a/tests/hooks.rs +++ b/tests/hooks.rs @@ -75,15 +75,9 @@ fn test_function_calls() -> Result<()> { let output = output.lock().unwrap(); if cfg!(feature = "luajit") && lua.load("jit.version_num").eval::()? >= 20100 { - assert_eq!( - *output, - vec![(None, "main"), (Some("len".to_string()), "Lua")] - ); + assert_eq!(*output, vec![(None, "main"), (Some("len".to_string()), "Lua")]); } else { - assert_eq!( - *output, - vec![(None, "main"), (Some("len".to_string()), "C")] - ); + assert_eq!(*output, vec![(None, "main"), (Some("len".to_string()), "C")]); } Ok(()) @@ -97,10 +91,7 @@ fn test_error_within_hook() -> Result<()> { Err(Error::runtime("Something happened in there!")) }); - let err = lua - .load("x = 1") - .exec() - .expect_err("panic didn't propagate"); + let err = lua.load("x = 1").exec().expect_err("panic didn't propagate"); match err { Error::CallbackError { cause, .. } => match cause.deref() { @@ -153,14 +144,9 @@ fn test_limit_execution_instructions() -> Result<()> { fn test_hook_removal() -> Result<()> { let lua = Lua::new(); - lua.set_hook( - HookTriggers::new().every_nth_instruction(1), - |_lua, _debug| { - Err(Error::runtime( - "this hook should've been removed by this time", - )) - }, - ); + lua.set_hook(HookTriggers::new().every_nth_instruction(1), |_lua, _debug| { + Err(Error::runtime("this hook should've been removed by this time")) + }); assert!(lua.load("local x = 1").exec().is_err()); lua.remove_hook(); @@ -189,9 +175,10 @@ fn test_hook_swap_within_hook() -> Result<()> { .set_hook(HookTriggers::EVERY_LINE, move |lua, _debug| { lua.globals().set("ok", 1i64)?; TL_LUA.with(|tl| { - tl.borrow().as_ref().unwrap().set_hook( - HookTriggers::EVERY_LINE, - move |lua, _debug| { + tl.borrow() + .as_ref() + .unwrap() + .set_hook(HookTriggers::EVERY_LINE, move |lua, _debug| { lua.load( r#" if ok ~= nil then @@ -205,8 +192,7 @@ fn test_hook_swap_within_hook() -> Result<()> { tl.borrow().as_ref().unwrap().remove_hook(); }); Ok(()) - }, - ) + }) }); Ok(()) }) diff --git a/tests/luau.rs b/tests/luau.rs index 29af6cf2..db22c27a 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -7,17 +7,14 @@ use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; use mlua::{ - Compiler, CoverageInfo, Error, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, - Vector, VmState, + Compiler, CoverageInfo, Error, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, Vector, + VmState, }; #[test] fn test_version() -> Result<()> { let lua = Lua::new(); - assert!(lua - .globals() - .get::<_, String>("_VERSION")? - .starts_with("Luau 0.")); + assert!(lua.globals().get::<_, String>("_VERSION")?.starts_with("Luau 0.")); Ok(()) } @@ -189,9 +186,7 @@ fn test_vector_metatable() -> Result<()> { lua.set_vector_metatable(Some(vector_mt.clone())); lua.globals().set("Vector3", vector_mt)?; - let compiler = Compiler::new() - .set_vector_lib("Vector3") - .set_vector_ctor("new"); + let compiler = Compiler::new().set_vector_lib("Vector3").set_vector_ctor("new"); // Test vector methods (fastcall) lua.load( diff --git a/tests/memory.rs b/tests/memory.rs index 0f9e8d04..77016b80 100644 --- a/tests/memory.rs +++ b/tests/memory.rs @@ -68,12 +68,7 @@ fn test_gc_control() -> Result<()> { assert_eq!(lua.gc_inc(0, 0, 0), GCMode::Generational); } - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luau" - ))] + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] { assert!(lua.gc_is_running()); lua.gc_stop(); diff --git a/tests/serde.rs b/tests/serde.rs index 5dc4bd8f..f4bd67a9 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -4,8 +4,8 @@ use std::collections::HashMap; use std::error::Error as StdError; use mlua::{ - DeserializeOptions, Error, ExternalResult, Lua, LuaSerdeExt, Result as LuaResult, - SerializeOptions, UserData, Value, + DeserializeOptions, Error, ExternalResult, Lua, LuaSerdeExt, Result as LuaResult, SerializeOptions, + UserData, Value, }; use serde::{Deserialize, Serialize}; @@ -395,10 +395,7 @@ fn test_to_value_with_options() -> Result<(), Box> { unit: (), unitstruct: UnitStruct, }; - let data2 = lua.to_value_with( - &mydata, - SerializeOptions::new().serialize_none_to_null(false), - )?; + let data2 = lua.to_value_with(&mydata, SerializeOptions::new().serialize_none_to_null(false))?; globals.set("data2", data2)?; lua.load( r#" @@ -410,10 +407,7 @@ fn test_to_value_with_options() -> Result<(), Box> { .exec()?; // serialize_unit_to_null - let data3 = lua.to_value_with( - &mydata, - SerializeOptions::new().serialize_unit_to_null(false), - )?; + let data3 = lua.to_value_with(&mydata, SerializeOptions::new().serialize_unit_to_null(false))?; globals.set("data3", data3)?; lua.load( r#" diff --git a/tests/static.rs b/tests/static.rs index 7f6e0289..059f4ee8 100644 --- a/tests/static.rs +++ b/tests/static.rs @@ -83,16 +83,15 @@ async fn test_static_async() -> Result<()> { tokio::task::yield_now().await; } - let timer = - lua.create_async_function(|_, (i, n, f): (u64, u64, mlua::Function)| async move { - tokio::task::spawn_local(async move { - for _ in 0..n { - tokio::task::spawn_local(f.call_async::<(), ()>(())); - sleep_ms(i).await; - } - }); - Ok(()) - })?; + let timer = lua.create_async_function(|_, (i, n, f): (u64, u64, mlua::Function)| async move { + tokio::task::spawn_local(async move { + for _ in 0..n { + tokio::task::spawn_local(f.call_async::<(), ()>(())); + sleep_ms(i).await; + } + }); + Ok(()) + })?; lua.globals().set("timer", timer)?; { diff --git a/tests/string.rs b/tests/string.rs index 289e3ea7..3421c9cf 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -15,9 +15,7 @@ fn test_string_compare() { with_str("teststring", |t| assert_eq!(t, b"teststring".to_vec())); // Vec with_str("teststring", |t| assert_eq!(t, "teststring".to_string())); // String with_str("teststring", |t| assert_eq!(t, t)); // mlua::String - with_str("teststring", |t| { - assert_eq!(t, Cow::from(b"teststring".as_ref())) - }); // Cow (borrowed) + with_str("teststring", |t| assert_eq!(t, Cow::from(b"teststring".as_ref()))); // Cow (borrowed) with_str("bla", |t| assert_eq!(t, Cow::from(b"bla".to_vec()))); // Cow (owned) } @@ -40,14 +38,8 @@ fn test_string_views() -> Result<()> { let empty: String = globals.get("empty")?; assert_eq!(ok.to_str()?, "null bytes are valid utf-8, wh\0 knew?"); - assert_eq!( - ok.to_string_lossy(), - "null bytes are valid utf-8, wh\0 knew?" - ); - assert_eq!( - ok.as_bytes(), - &b"null bytes are valid utf-8, wh\0 knew?"[..] - ); + assert_eq!(ok.to_string_lossy(), "null bytes are valid utf-8, wh\0 knew?"); + assert_eq!(ok.as_bytes(), &b"null bytes are valid utf-8, wh\0 knew?"[..]); assert!(err.to_str().is_err()); assert_eq!(err.as_bytes(), &b"but \xff isn't :("[..]); diff --git a/tests/table.rs b/tests/table.rs index cdbb122c..52cbc3f4 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -45,17 +45,11 @@ fn test_table() -> Result<()> { assert_eq!(table1.len()?, 5); assert!(!table1.is_empty()); assert_eq!( - table1 - .clone() - .pairs() - .collect::>>()?, + table1.clone().pairs().collect::>>()?, vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)] ); assert_eq!( - table1 - .clone() - .sequence_values() - .collect::>>()?, + table1.clone().sequence_values().collect::>>()?, vec![1, 2, 3, 4, 5] ); assert_eq!(table1, [1, 2, 3, 4, 5]); @@ -63,10 +57,7 @@ fn test_table() -> Result<()> { assert_eq!(table2.len()?, 0); assert!(table2.is_empty()); assert_eq!( - table2 - .clone() - .pairs() - .collect::>>()?, + table2.clone().pairs().collect::>>()?, vec![] ); assert_eq!(table2, [0; 0]); @@ -81,29 +72,20 @@ fn test_table() -> Result<()> { globals.set("table4", lua.create_sequence_from(vec![1, 2, 3, 4, 5])?)?; let table4 = globals.get::<_, Table>("table4")?; assert_eq!( - table4 - .clone() - .pairs() - .collect::>>()?, + table4.clone().pairs().collect::>>()?, vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)] ); table4.raw_insert(4, 35)?; table4.raw_insert(7, 7)?; assert_eq!( - table4 - .clone() - .pairs() - .collect::>>()?, + table4.clone().pairs().collect::>>()?, vec![(1, 1), (2, 2), (3, 3), (4, 35), (5, 4), (6, 5), (7, 7)] ); table4.raw_remove(1)?; assert_eq!( - table4 - .clone() - .pairs() - .collect::>>()?, + table4.clone().pairs().collect::>>()?, vec![(1, 2), (2, 3), (3, 35), (4, 4), (5, 5), (6, 7)] ); @@ -448,10 +430,7 @@ fn test_table_call() -> Result<()> { // Test calling non-callable table let table2 = lua.create_table()?; - assert!(matches!( - table2.call::<_, ()>(()), - Err(Error::RuntimeError(_)) - )); + assert!(matches!(table2.call::<_, ()>(()), Err(Error::RuntimeError(_)))); Ok(()) } diff --git a/tests/tests.rs b/tests/tests.rs index 8284fc2f..a77a351b 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -8,8 +8,8 @@ use std::sync::Arc; use std::{error, f32, f64, fmt}; use mlua::{ - ChunkMode, Error, ExternalError, Function, Lua, LuaOptions, Nil, Result, StdLib, String, Table, - UserData, Value, Variadic, + ChunkMode, Error, ExternalError, Function, Lua, LuaOptions, Nil, Result, StdLib, String, Table, UserData, + Value, Variadic, }; #[cfg(not(feature = "luau"))] @@ -108,10 +108,7 @@ fn test_exec() -> Result<()> { .eval()?; println!("checkpoint"); assert!(module.contains_key("func")?); - assert_eq!( - module.get::<_, Function>("func")?.call::<_, String>(())?, - "hello" - ); + assert_eq!(module.get::<_, Function>("func")?.call::<_, String>(())?, "hello"); Ok(()) } @@ -128,10 +125,7 @@ fn test_eval() -> Result<()> { incomplete_input: true, .. }) => {} - r => panic!( - "expected SyntaxError with incomplete_input=true, got {:?}", - r - ), + r => panic!("expected SyntaxError with incomplete_input=true, got {:?}", r), } Ok(()) @@ -141,10 +135,7 @@ fn test_eval() -> Result<()> { fn test_load_mode() -> Result<()> { let lua = unsafe { Lua::unsafe_new() }; - assert_eq!( - lua.load("1 + 1").set_mode(ChunkMode::Text).eval::()?, - 2 - ); + assert_eq!(lua.load("1 + 1").set_mode(ChunkMode::Text).eval::()?, 2); match lua.load("1 + 1").set_mode(ChunkMode::Binary).exec() { Ok(_) => panic!("expected SyntaxError, got no error"), Err(Error::SyntaxError { message: msg, .. }) => { @@ -158,12 +149,7 @@ fn test_load_mode() -> Result<()> { #[cfg(feature = "luau")] let bytecode = mlua::Compiler::new().compile("return 1 + 1"); assert_eq!(lua.load(&bytecode).eval::()?, 2); - assert_eq!( - lua.load(&bytecode) - .set_mode(ChunkMode::Binary) - .eval::()?, - 2 - ); + assert_eq!(lua.load(&bytecode).set_mode(ChunkMode::Binary).eval::()?, 2); match lua.load(&bytecode).set_mode(ChunkMode::Text).exec() { Ok(_) => panic!("expected SyntaxError, got no error"), Err(Error::SyntaxError { message: msg, .. }) => { @@ -308,8 +294,7 @@ fn test_error() -> Result<()> { ) .exec()?; - let rust_error_function = - lua.create_function(|_, ()| -> Result<()> { Err(TestError.into_lua_err()) })?; + let rust_error_function = lua.create_function(|_, ()| -> Result<()> { Err(TestError.into_lua_err()) })?; globals.set("rust_error_function", rust_error_function)?; let no_error = globals.get::<_, Function>("no_error")?; @@ -338,10 +323,7 @@ fn test_error() -> Result<()> { let return_string_error = globals.get::<_, Function>("return_string_error")?; assert!(return_string_error.call::<_, Error>(()).is_ok()); - match lua - .load("if youre happy and you know it syntax error") - .exec() - { + match lua.load("if youre happy and you know it syntax error").exec() { Err(Error::SyntaxError { incomplete_input: false, .. @@ -374,15 +356,13 @@ fn test_error() -> Result<()> { fn test_panic() -> Result<()> { fn make_lua(options: LuaOptions) -> Result { let lua = Lua::new_with(StdLib::ALL_SAFE, options)?; - let rust_panic_function = - lua.create_function(|_, msg: Option| -> Result<()> { - if let Some(msg) = msg { - panic!("{}", msg) - } - panic!("rust panic") - })?; - lua.globals() - .set("rust_panic_function", rust_panic_function)?; + let rust_panic_function = lua.create_function(|_, msg: Option| -> Result<()> { + if let Some(msg) = msg { + panic!("{}", msg) + } + panic!("rust panic") + })?; + lua.globals().set("rust_panic_function", rust_panic_function)?; Ok(lua) } @@ -538,12 +518,7 @@ fn test_num_conversion() -> Result<()> { assert_eq!(lua.load("1.0").eval::()?, 1.0); #[cfg(any(feature = "lua54", feature = "lua53"))] assert_eq!(lua.load("1.0").eval::()?, "1.0"); - #[cfg(any( - feature = "lua52", - feature = "lua51", - feature = "luajit", - feature = "luau" - ))] + #[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))] assert_eq!(lua.load("1.0").eval::()?, "1"); assert_eq!(lua.load("1.5").eval::()?, 1); @@ -620,12 +595,7 @@ fn test_pcall_xpcall() -> Result<()> { assert_eq!(globals.get::<_, String>("pcall_error")?, "testerror"); assert_eq!(globals.get::<_, bool>("xpcall_statusr")?, false); - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luajit" - ))] + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit"))] assert_eq!( globals.get::<_, std::string::String>("xpcall_error")?, "testerror" @@ -645,9 +615,7 @@ fn test_pcall_xpcall() -> Result<()> { "#, ) .exec()?; - let _ = globals - .get::<_, Function>("xpcall_recursion")? - .call::<_, ()>(()); + let _ = globals.get::<_, Function>("xpcall_recursion")?.call::<_, ()>(()); Ok(()) } @@ -901,10 +869,7 @@ fn test_application_data() -> Result<()> { f.call(())?; assert_eq!(*lua.app_data_ref::<&str>().unwrap(), "test4"); - assert_eq!( - *lua.app_data_ref::>().unwrap(), - vec!["test2", "test3"] - ); + assert_eq!(*lua.app_data_ref::>().unwrap(), vec!["test2", "test3"]); lua.remove_app_data::>(); assert!(matches!(lua.app_data_ref::>(), None)); @@ -919,9 +884,7 @@ fn test_recursion() -> Result<()> { let f = lua.create_function(move |lua, i: i32| { if i < 64 { - lua.globals() - .get::<_, Function>("f")? - .call::<_, ()>(i + 1)?; + lua.globals().get::<_, Function>("f")?.call::<_, ()>(i + 1)?; } Ok(()) })?; @@ -961,8 +924,7 @@ fn test_too_many_arguments() -> Result<()> { fn test_too_many_recursions() -> Result<()> { let lua = Lua::new(); - let f = lua - .create_function(move |lua, ()| lua.globals().get::<_, Function>("f")?.call::<_, ()>(()))?; + let f = lua.create_function(move |lua, ()| lua.globals().get::<_, Function>("f")?.call::<_, ()>(()))?; lua.globals().set("f", &f)?; assert!(f.call::<_, ()>(()).is_err()); @@ -985,9 +947,7 @@ fn test_too_many_binds() -> Result<()> { let concat = globals.get::<_, Function>("f")?; assert!(concat.bind(Variadic::from_iter(1..1000000)).is_err()); - assert!(concat - .call::<_, ()>(Variadic::from_iter(1..1000000)) - .is_err()); + assert!(concat.call::<_, ()>(Variadic::from_iter(1..1000000)).is_err()); Ok(()) } @@ -1038,10 +998,7 @@ fn test_large_args() -> Result<()> { ) .eval()?; - assert_eq!( - f.call::<_, usize>((0..100).collect::>())?, - 4950 - ); + assert_eq!(f.call::<_, usize>((0..100).collect::>())?, 4950); Ok(()) } @@ -1110,12 +1067,7 @@ fn test_context_thread() -> Result<()> { ) .into_function()?; - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luajit52" - ))] + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] f.call::<_, ()>(lua.current_thread())?; #[cfg(any( @@ -1154,10 +1106,7 @@ fn test_context_thread_51() -> Result<()> { fn test_jit_version() -> Result<()> { let lua = Lua::new(); let jit: Table = lua.globals().get("jit")?; - assert!(jit - .get::<_, String>("version")? - .to_str()? - .contains("LuaJIT")); + assert!(jit.get::<_, String>("version")?.to_str()?.contains("LuaJIT")); Ok(()) } diff --git a/tests/userdata.rs b/tests/userdata.rs index ac026f90..491f1bb3 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -6,8 +6,8 @@ use std::sync::Arc; use std::sync::atomic::{AtomicI64, Ordering}; use mlua::{ - AnyUserData, AnyUserDataExt, Error, ExternalError, Function, Lua, MetaMethod, Nil, Result, - String, UserData, UserDataFields, UserDataMethods, UserDataRef, Value, Variadic, + AnyUserData, AnyUserDataExt, Error, ExternalError, Function, Lua, MetaMethod, Nil, Result, String, + UserData, UserDataFields, UserDataMethods, UserDataRef, Value, Variadic, }; #[test] @@ -118,15 +118,11 @@ fn test_metamethods() -> Result<()> { methods.add_method("get", |_, data, ()| Ok(data.0)); methods.add_meta_function( MetaMethod::Add, - |_, (lhs, rhs): (UserDataRef, UserDataRef)| { - Ok(MyUserData(lhs.0 + rhs.0)) - }, + |_, (lhs, rhs): (UserDataRef, UserDataRef)| Ok(MyUserData(lhs.0 + rhs.0)), ); methods.add_meta_function( MetaMethod::Sub, - |_, (lhs, rhs): (UserDataRef, UserDataRef)| { - Ok(MyUserData(lhs.0 - rhs.0)) - }, + |_, (lhs, rhs): (UserDataRef, UserDataRef)| Ok(MyUserData(lhs.0 - rhs.0)), ); methods.add_meta_function( MetaMethod::Eq, @@ -139,22 +135,16 @@ fn test_metamethods() -> Result<()> { Err("no such custom index".into_lua_err()) } }); - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luajit52" - ))] + #[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): (UserDataRef, i64)| { - let i = i + 1; - if i <= data.0 { - return Ok(mlua::Variadic::from_iter(vec![i, i])); - } - return Ok(mlua::Variadic::new()); - })?; + let stateless_iter = lua.create_function(|_, (data, i): (UserDataRef, i64)| { + let i = i + 1; + if i <= data.0 { + return Ok(mlua::Variadic::from_iter(vec![i, i])); + } + return Ok(mlua::Variadic::new()); + })?; Ok((stateless_iter, data.clone(), 0)) }); } @@ -172,12 +162,7 @@ fn test_metamethods() -> Result<()> { 10 ); - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luajit52" - ))] + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] let pairs_it = lua .load( r#" @@ -202,12 +187,7 @@ fn test_metamethods() -> Result<()> { assert_eq!(lua.load("userdata2.inner").eval::()?, 3); assert!(lua.load("userdata2.nonexist_field").eval::<()>().is_err()); - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luajit52" - ))] + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] assert_eq!(pairs_it.call::<_, i64>(())?, 28); let userdata2: Value = globals.get("userdata2")?; @@ -454,9 +434,7 @@ fn test_functions() -> Result<()> { impl UserData for MyUserData { fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { - methods.add_function("get_value", |_, ud: AnyUserData| { - Ok(ud.borrow::()?.0) - }); + methods.add_function("get_value", |_, ud: AnyUserData| Ok(ud.borrow::()?.0)); methods.add_function_mut("set_value", |_, (ud, value): (AnyUserData, i64)| { ud.borrow_mut::()?.0 = value; Ok(()) @@ -517,8 +495,7 @@ fn test_fields() -> Result<()> { // Use userdata "uservalue" storage fields.add_field_function_get("uval", |_, ud| ud.user_value::>()); - fields - .add_field_function_set("uval", |_, ud, s| ud.set_user_value::>(s)); + fields.add_field_function_set("uval", |_, ud, s| ud.set_user_value::>(s)); fields.add_meta_field(MetaMethod::Index, HashMap::from([("f", 321)])); fields.add_meta_field_with(MetaMethod::NewIndex, |lua| { @@ -597,8 +574,7 @@ fn test_metatable() -> Result<()> { let lua = Lua::new(); let globals = lua.globals(); globals.set("ud", MyUserData)?; - lua.load(r#"assert(ud:my_type_name() == "MyUserData")"#) - .exec()?; + lua.load(r#"assert(ud:my_type_name() == "MyUserData")"#).exec()?; #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] lua.load(r#"assert(tostring(ud):sub(1, 11) == "MyUserData:")"#) @@ -654,10 +630,7 @@ fn test_metatable() -> Result<()> { let ud = lua.create_userdata(MyUserData3)?; let metatable = ud.get_metatable()?; - assert_eq!( - metatable.get::(MetaMethod::Type)?.to_str()?, - "CustomName" - ); + assert_eq!(metatable.get::(MetaMethod::Type)?.to_str()?, "CustomName"); Ok(()) } @@ -740,8 +713,7 @@ fn test_any_userdata_wrap() -> Result<()> { reg.add_method("get", |_, this, ()| Ok(this.clone())); })?; - lua.globals() - .set("s", AnyUserData::wrap("hello".to_string()))?; + lua.globals().set("s", AnyUserData::wrap("hello".to_string()))?; lua.load( r#" assert(s:get() == "hello") @@ -854,8 +826,7 @@ fn test_userdata_derive() -> Result<()> { reg.add_function("val", |_, this: MyUserData| Ok(this.0)); })?; - lua.globals() - .set("ud", AnyUserData::wrap(MyUserData(123)))?; + lua.globals().set("ud", AnyUserData::wrap(MyUserData(123)))?; lua.load("assert(ud:val() == 123)").exec()?; // More complex struct where generics and where clause @@ -869,8 +840,7 @@ fn test_userdata_derive() -> Result<()> { reg.add_function("val", |_, this: MyUserData2<'static, i32>| Ok(*this.0)); })?; - lua.globals() - .set("ud", AnyUserData::wrap(MyUserData2(&321)))?; + lua.globals().set("ud", AnyUserData::wrap(MyUserData2(&321)))?; lua.load("assert(ud:val() == 321)").exec()?; Ok(()) diff --git a/tests/value.rs b/tests/value.rs index 7b060258..024c5c59 100644 --- a/tests/value.rs +++ b/tests/value.rs @@ -111,10 +111,7 @@ fn test_value_to_string() -> Result<()> { Value::Vector(mlua::Vector::new(10.0, 11.1, 12.2, 13.3)).to_string()?, "vector(10, 11.1, 12.2, 13.3)" ); - assert_eq!( - Value::String(lua.create_string("hello")?).to_string()?, - "hello" - ); + assert_eq!(Value::String(lua.create_string("hello")?).to_string()?, "hello"); let table: Value = lua.load("{}").eval()?; assert!(table.to_string()?.starts_with("table:")); @@ -189,9 +186,7 @@ fn test_value_conversions() -> Result<()> { assert_eq!(Value::Number(1.23).as_f64(), Some(1.23f64)); assert!(Value::String(lua.create_string("hello")?).is_string()); assert_eq!( - Value::String(lua.create_string("hello")?) - .as_string() - .unwrap(), + Value::String(lua.create_string("hello")?).as_string().unwrap(), "hello" ); assert_eq!( @@ -207,11 +202,9 @@ fn test_value_conversions() -> Result<()> { assert!(Value::Table(lua.create_table()?).is_table()); assert!(Value::Table(lua.create_table()?).as_table().is_some()); assert!(Value::Function(lua.create_function(|_, ()| Ok(())).unwrap()).is_function()); - assert!( - Value::Function(lua.create_function(|_, ()| Ok(())).unwrap()) - .as_function() - .is_some() - ); + assert!(Value::Function(lua.create_function(|_, ()| Ok(())).unwrap()) + .as_function() + .is_some()); assert!(Value::Thread(lua.create_thread(lua.load("function() end").eval()?)?).is_thread()); assert!( Value::Thread(lua.create_thread(lua.load("function() end").eval()?)?) From cd3f45f31f787c4908a35df0d59f24d1988b5a23 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 10 Jul 2024 22:46:37 +0100 Subject: [PATCH 125/635] Rust nightly rustfmt --- .github/workflows/main.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9468c02a..06f5354f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -234,9 +234,8 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@nightly with: - toolchain: stable components: rustfmt - run: cargo fmt -- --check From c715aec1f79bd00c8b0e1c94bea20438db9de2b2 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 10 Jul 2024 23:09:54 +0100 Subject: [PATCH 126/635] Do not consume `self` in `Table::pairs` and `Table::sequence_values` methods. --- src/serde/de.rs | 24 +++++++++---------- src/table.rs | 61 +++++++++++++++++++------------------------------ src/userdata.rs | 8 +++---- 3 files changed, 39 insertions(+), 54 deletions(-) diff --git a/src/serde/de.rs b/src/serde/de.rs index 9c36c87e..7993d702 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -302,7 +302,7 @@ impl<'de> serde::Deserializer<'de> for Deserializer { let _guard = RecursionGuard::new(&t, &self.visited); let mut deserializer = MapDeserializer { - pairs: MapPairs::new(t, self.options.sort_keys)?, + pairs: MapPairs::new(&t, self.options.sort_keys)?, value: None, options: self.options, visited: self.visited, @@ -383,13 +383,13 @@ impl<'de> serde::Deserializer<'de> for Deserializer { } } -struct SeqDeserializer { - seq: TableSequence, +struct SeqDeserializer<'a> { + seq: TableSequence<'a, Value>, options: Options, visited: Rc>>, } -impl<'de> de::SeqAccess<'de> for SeqDeserializer { +impl<'de> de::SeqAccess<'de> for SeqDeserializer<'_> { type Error = Error; fn next_element_seed(&mut self, seed: T) -> Result> @@ -454,13 +454,13 @@ impl<'de> de::SeqAccess<'de> for VecDeserializer { } } -pub(crate) enum MapPairs { - Iter(TablePairs), +pub(crate) enum MapPairs<'a> { + Iter(TablePairs<'a, Value, Value>), Vec(Vec<(Value, Value)>), } -impl MapPairs { - pub(crate) fn new(t: Table, sort_keys: bool) -> Result { +impl<'a> MapPairs<'a> { + pub(crate) fn new(t: &'a Table, sort_keys: bool) -> Result { if sort_keys { let mut pairs = t.pairs::().collect::>>()?; pairs.sort_by(|(a, _), (b, _)| b.cmp(a)); // reverse order as we pop values from the end @@ -485,7 +485,7 @@ impl MapPairs { } } -impl Iterator for MapPairs { +impl Iterator for MapPairs<'_> { type Item = Result<(Value, Value)>; fn next(&mut self) -> Option { @@ -496,15 +496,15 @@ impl Iterator for MapPairs { } } -struct MapDeserializer { - pairs: MapPairs, +struct MapDeserializer<'a> { + pairs: MapPairs<'a>, value: Option, options: Options, visited: Rc>>, processed: usize, } -impl<'de> de::MapAccess<'de> for MapDeserializer { +impl<'de> de::MapAccess<'de> for MapDeserializer<'_> { type Error = Error; fn next_key_seed(&mut self, seed: T) -> Result> diff --git a/src/table.rs b/src/table.rs index 7f32d64e..a491e1ab 100644 --- a/src/table.rs +++ b/src/table.rs @@ -13,6 +13,7 @@ use { use crate::error::{Error, Result}; use crate::function::Function; use crate::private::Sealed; +use crate::state::{LuaGuard, RawLua}; use crate::types::{Integer, ValueRef}; use crate::util::{assert_stack, check_stack, StackGuard}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Nil, Value}; @@ -580,18 +581,12 @@ impl Table { self.0.to_pointer() } - /// Consume this table and return an iterator over the pairs of the table. + /// Returns an iterator over the pairs of the table. /// /// This works like the Lua `pairs` function, but does not invoke the `__pairs` metamethod. /// /// The pairs are wrapped in a [`Result`], since they are lazily converted to `K` and `V` types. /// - /// # Note - /// - /// While this method consumes the `Table` object, it can not prevent code from mutating the - /// table while the iteration is in progress. Refer to the [Lua manual] for information about - /// the consequences of such mutation. - /// /// # Examples /// /// Iterate over all globals: @@ -613,9 +608,10 @@ impl Table { /// /// [`Result`]: crate::Result /// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next - pub fn pairs(self) -> TablePairs { + pub fn pairs(&self) -> TablePairs { TablePairs { - table: self.0, + guard: self.0.lua.lock(), + table: self, key: Some(Nil), _phantom: PhantomData, } @@ -649,18 +645,12 @@ impl Table { Ok(()) } - /// Consume this table and return an iterator over all values in the sequence part of the table. + /// Returns an iterator over all values in the sequence part of the table. /// /// The iterator will yield all values `t[1]`, `t[2]` and so on, until a `nil` value is /// encountered. This mirrors the behavior of Lua's `ipairs` function but does not invoke /// any metamethods. /// - /// # Note - /// - /// While this method consumes the `Table` object, it can not prevent code from mutating the - /// table while the iteration is in progress. Refer to the [Lua manual] for information about - /// the consequences of such mutation. - /// /// # Examples /// /// ``` @@ -687,20 +677,15 @@ impl Table { /// [`pairs`]: #method.pairs /// [`Result`]: crate::Result /// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next - pub fn sequence_values(self) -> TableSequence { + pub fn sequence_values(&self) -> TableSequence { TableSequence { - table: self.0, + guard: self.0.lua.lock(), + table: self, index: 1, _phantom: PhantomData, } } - #[doc(hidden)] - #[deprecated(since = "0.9.0", note = "use `sequence_values` instead")] - pub fn raw_sequence_values(self) -> TableSequence { - self.sequence_values() - } - #[cfg(feature = "serialize")] pub(crate) fn for_each_value(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()> where @@ -782,9 +767,8 @@ impl Table { ) -> fmt::Result { visited.insert(self.to_pointer()); - let t = self.clone(); // Collect key/value pairs into a vector so we can sort them - let mut pairs = t.pairs::().flatten().collect::>(); + let mut pairs = self.pairs::().flatten().collect::>(); // Sort keys pairs.sort_by(|(a, _), (b, _)| a.cmp(b)); if pairs.is_empty() { @@ -1111,7 +1095,7 @@ impl<'a> Serialize for SerializableTable<'a> { // Fast track self.table.for_each(process_pair) } else { - MapPairs::new(self.table.clone(), self.options.sort_keys) + MapPairs::new(self.table, self.options.sort_keys) .map_err(serde::ser::Error::custom)? .try_for_each(|kv| { let (key, value) = kv?; @@ -1128,13 +1112,14 @@ impl<'a> Serialize for SerializableTable<'a> { /// This struct is created by the [`Table::pairs`] method. /// /// [`Table::pairs`]: crate::Table::pairs -pub struct TablePairs { - table: ValueRef, +pub struct TablePairs<'a, K, V> { + guard: LuaGuard, + table: &'a Table, key: Option, _phantom: PhantomData<(K, V)>, } -impl Iterator for TablePairs +impl<'a, K, V> Iterator for TablePairs<'a, K, V> where K: FromLua, V: FromLua, @@ -1143,14 +1128,14 @@ where fn next(&mut self) -> Option { if let Some(prev_key) = self.key.take() { - let lua = self.table.lua.lock(); + let lua: &RawLua = &self.guard; let state = lua.state(); let res = (|| unsafe { let _sg = StackGuard::new(state); check_stack(state, 5)?; - lua.push_ref(&self.table); + lua.push_ref(&self.table.0); lua.push_value(&prev_key)?; // It must be safe to call `lua_next` unprotected as deleting a key from a table is @@ -1187,21 +1172,21 @@ where /// This struct is created by the [`Table::sequence_values`] method. /// /// [`Table::sequence_values`]: crate::Table::sequence_values -pub struct TableSequence { - // TODO: Use `&Table` - table: ValueRef, +pub struct TableSequence<'a, V> { + guard: LuaGuard, + table: &'a Table, index: Integer, _phantom: PhantomData, } -impl Iterator for TableSequence +impl<'a, V> Iterator for TableSequence<'a, V> where V: FromLua, { type Item = Result; fn next(&mut self) -> Option { - let lua = self.table.lua.lock(); + let lua: &RawLua = &self.guard; let state = lua.state(); unsafe { let _sg = StackGuard::new(state); @@ -1209,7 +1194,7 @@ where return Some(Err(err)); } - lua.push_ref(&self.table); + lua.push_ref(&self.table.0); match ffi::lua_rawgeti(state, -1, self.index) { ffi::LUA_TNIL => None, _ => { diff --git a/src/userdata.rs b/src/userdata.rs index fdb3f499..54f5e44e 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -1089,12 +1089,12 @@ impl UserDataMetatable { self.0.contains_key(MetaMethod::validate(key.as_ref())?) } - /// Consumes this metatable and returns an iterator over the pairs of the metatable. + /// Returns an iterator over the pairs of the metatable. /// /// The pairs are wrapped in a [`Result`], since they are lazily converted to `V` type. /// /// [`Result`]: crate::Result - pub fn pairs(self) -> UserDataMetatablePairs { + pub fn pairs(&self) -> UserDataMetatablePairs { UserDataMetatablePairs(self.0.pairs()) } } @@ -1107,9 +1107,9 @@ impl UserDataMetatable { /// /// [`UserData`]: crate::UserData /// [`UserDataMetatable::pairs`]: crate::UserDataMetatable::method.pairs -pub struct UserDataMetatablePairs(TablePairs); +pub struct UserDataMetatablePairs<'a, V>(TablePairs<'a, StdString, V>); -impl Iterator for UserDataMetatablePairs +impl<'a, V> Iterator for UserDataMetatablePairs<'a, V> where V: FromLua, { From d9941ef409f9c59950df3708ce8af8ad23fa1a05 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 10 Jul 2024 23:17:00 +0100 Subject: [PATCH 127/635] Take `&mut RegistryKey` in `Lua::replace_registry_value`. --- src/state.rs | 4 +++- src/types.rs | 11 +++++------ tests/tests.rs | 14 +++++++------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/state.rs b/src/state.rs index 8eae6639..12b3ca66 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1654,8 +1654,10 @@ impl Lua { /// Replaces a value in the Lua registry by its [`RegistryKey`]. /// + /// An identifier used in [`RegistryKey`] may possibly be changed to a new value. + /// /// See [`Lua::create_registry_value`] for more details. - pub fn replace_registry_value(&self, key: &RegistryKey, t: T) -> Result<()> { + pub fn replace_registry_value(&self, key: &mut RegistryKey, t: T) -> Result<()> { let lua = self.lock(); if !lua.owns_registry_value(key) { return Err(Error::MismatchedRegistryKey); diff --git a/src/types.rs b/src/types.rs index e09aca8d..8c6810ef 100644 --- a/src/types.rs +++ b/src/types.rs @@ -2,7 +2,6 @@ use std::cell::UnsafeCell; use std::hash::{Hash, Hasher}; use std::os::raw::{c_int, c_void}; use std::rc::Rc; -use std::sync::atomic::{AtomicI32, Ordering}; use std::sync::Arc; use std::{fmt, mem, ptr}; @@ -204,7 +203,7 @@ pub(crate) struct DestructedUserdata; /// [`AnyUserData::set_user_value`]: crate::AnyUserData::set_user_value /// [`AnyUserData::user_value`]: crate::AnyUserData::user_value pub struct RegistryKey { - pub(crate) registry_id: AtomicI32, + pub(crate) registry_id: i32, pub(crate) unref_list: Arc>>>, } @@ -245,7 +244,7 @@ impl RegistryKey { /// Creates a new instance of `RegistryKey` pub(crate) const fn new(id: c_int, unref_list: Arc>>>) -> Self { RegistryKey { - registry_id: AtomicI32::new(id), + registry_id: id, unref_list, } } @@ -253,13 +252,13 @@ impl RegistryKey { /// Returns the underlying Lua reference of this `RegistryKey` #[inline(always)] pub fn id(&self) -> c_int { - self.registry_id.load(Ordering::Relaxed) + self.registry_id } /// Sets the unique Lua reference key of this `RegistryKey` #[inline(always)] - pub(crate) fn set_id(&self, id: c_int) { - self.registry_id.store(id, Ordering::Relaxed); + pub(crate) fn set_id(&mut self, id: c_int) { + self.registry_id = id; } /// Destroys the `RegistryKey` without adding to the unref list diff --git a/tests/tests.rs b/tests/tests.rs index a77a351b..90085ece 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -735,18 +735,18 @@ fn test_drop_registry_value() -> Result<()> { fn test_replace_registry_value() -> Result<()> { let lua = Lua::new(); - let key = lua.create_registry_value::(42)?; - lua.replace_registry_value(&key, "new value")?; + let mut key = lua.create_registry_value::(42)?; + lua.replace_registry_value(&mut key, "new value")?; assert_eq!(lua.registry_value::(&key)?, "new value"); - lua.replace_registry_value(&key, Value::Nil)?; + lua.replace_registry_value(&mut key, Value::Nil)?; assert_eq!(lua.registry_value::(&key)?, Value::Nil); - lua.replace_registry_value(&key, 123)?; + lua.replace_registry_value(&mut key, 123)?; assert_eq!(lua.registry_value::(&key)?, 123); - let key2 = lua.create_registry_value(Value::Nil)?; - lua.replace_registry_value(&key2, Value::Nil)?; + let mut key2 = lua.create_registry_value(Value::Nil)?; + lua.replace_registry_value(&mut key2, Value::Nil)?; assert_eq!(lua.registry_value::(&key2)?, Value::Nil); - lua.replace_registry_value(&key2, "abc")?; + lua.replace_registry_value(&mut key2, "abc")?; assert_eq!(lua.registry_value::(&key2)?, "abc"); Ok(()) From d5173380e3c1eabdd8afdb8375d299878e8af24a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 29 Jul 2024 10:37:22 +0100 Subject: [PATCH 128/635] Split `util` module Refactor internal userdata types to switch to the new `TypeKey` trait --- Cargo.toml | 1 - src/state/extra.rs | 15 +- src/state/raw.rs | 30 +- src/state/util.rs | 6 +- src/util/error.rs | 432 +++++++++++++++++++++ src/util/mod.rs | 876 +++---------------------------------------- src/util/types.rs | 82 ++++ src/util/userdata.rs | 380 +++++++++++++++++++ 8 files changed, 969 insertions(+), 853 deletions(-) create mode 100644 src/util/error.rs create mode 100644 src/util/types.rs create mode 100644 src/util/userdata.rs diff --git a/Cargo.toml b/Cargo.toml index cbd6086e..7c6ac6a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,6 @@ unstable = [] [dependencies] mlua_derive = { version = "=0.9.3", optional = true, path = "mlua_derive" } bstr = { version = "1.0", features = ["std"], default-features = false } -once_cell = { version = "1.0" } num-traits = { version = "0.2.14" } rustc-hash = "2.0" futures-util = { version = "0.3", optional = true, default-features = false, features = ["std"] } diff --git a/src/state/extra.rs b/src/state/extra.rs index e3806857..741823b5 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -14,7 +14,7 @@ use crate::error::Result; use crate::state::RawLua; use crate::stdlib::StdLib; use crate::types::{AppData, ReentrantMutex, XRc, XWeak}; -use crate::util::{get_gc_metatable, push_gc_userdata, WrappedFailure}; +use crate::util::{get_internal_metatable, push_internal_userdata, TypeKey, WrappedFailure}; #[cfg(any(feature = "luau", doc))] use crate::chunk::Compiler; @@ -102,6 +102,15 @@ impl Drop for ExtraData { } } +static EXTRA_TYPE_KEY: u8 = 0; + +impl TypeKey for XRc> { + #[inline(always)] + fn type_key() -> *const c_void { + &EXTRA_TYPE_KEY as *const u8 as *const c_void + } +} + impl ExtraData { // Index of `error_traceback` function in auxiliary thread stack #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] @@ -120,7 +129,7 @@ impl ExtraData { ); let wrapped_failure_mt_ptr = { - get_gc_metatable::(state); + get_internal_metatable::(state); let ptr = ffi::lua_topointer(state, -1); ffi::lua_pop(state, 1); ptr @@ -213,7 +222,7 @@ impl ExtraData { return Ok(()); } - push_gc_userdata(state, XRc::clone(extra), true)?; + push_internal_userdata(state, XRc::clone(extra), true)?; protect_lua!(state, 1, 0, fn(state) { let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void; ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, extra_key); diff --git a/src/state/raw.rs b/src/state/raw.rs index 1f05266b..bd507041 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -23,9 +23,9 @@ use crate::types::{ }; use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataRegistry, UserDataVariant}; use crate::util::{ - assert_stack, check_stack, get_destructed_userdata_metatable, get_gc_userdata, get_main_state, - get_userdata, init_error_registry, init_gc_metatable, init_userdata_metatable, pop_error, - push_gc_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name, + assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state, + get_userdata, init_error_registry, init_internal_metatable, init_userdata_metatable, pop_error, + push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name, StackGuard, WrappedFailure, }; use crate::value::{FromLuaMulti, IntoLua, MultiValue, Nil, Value}; @@ -171,15 +171,15 @@ impl RawLua { // Create the internal metatables and store them in the registry // to prevent from being garbage collected. - init_gc_metatable::>>(state, None)?; - init_gc_metatable::(state, None)?; - init_gc_metatable::(state, None)?; + init_internal_metatable::>>(state, None)?; + init_internal_metatable::(state, None)?; + init_internal_metatable::(state, None)?; #[cfg(feature = "async")] { - init_gc_metatable::(state, None)?; - init_gc_metatable::(state, None)?; - init_gc_metatable::(state, None)?; - init_gc_metatable::>(state, None)?; + init_internal_metatable::(state, None)?; + init_internal_metatable::(state, None)?; + init_internal_metatable::(state, None)?; + init_internal_metatable::>(state, None)?; } // Init serde metatables @@ -550,7 +550,7 @@ impl RawLua { Value::UserData(ud) => self.push_ref(&ud.0), Value::Error(err) => { let protect = !self.unlikely_memory_error(); - push_gc_userdata(state, WrappedFailure::Error(*err.clone()), protect)?; + push_internal_userdata(state, WrappedFailure::Error(*err.clone()), protect)?; } } Ok(()) @@ -625,7 +625,7 @@ impl RawLua { ffi::LUA_TUSERDATA => { // If the userdata is `WrappedFailure`, process it as an error or panic. let failure_mt_ptr = (*self.extra.get()).wrapped_failure_mt_ptr; - match get_gc_userdata::(state, idx, failure_mt_ptr).as_mut() { + match get_internal_userdata::(state, idx, failure_mt_ptr).as_mut() { Some(WrappedFailure::Error(err)) => Value::Error(Box::new(err.clone())), Some(WrappedFailure::Panic(panic)) => { if let Some(panic) = panic.take() { @@ -1076,7 +1076,7 @@ impl RawLua { let func = mem::transmute::>(func); let extra = XRc::clone(&self.extra); let protect = !self.unlikely_memory_error(); - push_gc_userdata(state, CallbackUpvalue { data: func, extra }, protect)?; + push_internal_userdata(state, CallbackUpvalue { data: func, extra }, protect)?; if protect { protect_lua!(state, 1, 1, fn(state) { ffi::lua_pushcclosure(state, call_callback, 1); @@ -1115,7 +1115,7 @@ impl RawLua { let fut = func(rawlua, args); let extra = XRc::clone(&(*upvalue).extra); let protect = !rawlua.unlikely_memory_error(); - push_gc_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?; + push_internal_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?; if protect { protect_lua!(state, 1, 1, fn(state) { ffi::lua_pushcclosure(state, poll_future, 1); @@ -1176,7 +1176,7 @@ impl RawLua { let extra = XRc::clone(&self.extra); let protect = !self.unlikely_memory_error(); let upvalue = AsyncCallbackUpvalue { data: func, extra }; - push_gc_userdata(state, upvalue, protect)?; + push_internal_userdata(state, upvalue, protect)?; if protect { protect_lua!(state, 1, 1, fn(state) { ffi::lua_pushcclosure(state, call_callback, 1); diff --git a/src/state/util.rs b/src/state/util.rs index c1d64921..9d557340 100644 --- a/src/state/util.rs +++ b/src/state/util.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use crate::error::{Error, Result}; use crate::state::{ExtraData, RawLua}; -use crate::util::{self, get_gc_metatable, WrappedFailure}; +use crate::util::{self, get_internal_metatable, WrappedFailure}; const WRAPPED_FAILURE_POOL_SIZE: usize = 64; // const MULTIVALUE_POOL_SIZE: usize = 64; @@ -137,7 +137,7 @@ where wrapped_error, WrappedFailure::Error(Error::CallbackError { traceback, cause }), ); - get_gc_metatable::(state); + get_internal_metatable::(state); ffi::lua_setmetatable(state, -2); ffi::lua_error(state) @@ -145,7 +145,7 @@ where Err(p) => { let wrapped_panic = prealloc_failure.r#use(state, extra); ptr::write(wrapped_panic, WrappedFailure::Panic(Some(p))); - get_gc_metatable::(state); + get_internal_metatable::(state); ffi::lua_setmetatable(state, -2); ffi::lua_error(state) } diff --git a/src/util/error.rs b/src/util/error.rs new file mode 100644 index 00000000..0cd4abef --- /dev/null +++ b/src/util/error.rs @@ -0,0 +1,432 @@ +use std::any::Any; +use std::fmt::Write as _; +use std::mem::MaybeUninit; +use std::os::raw::{c_int, c_void}; +use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe}; +use std::ptr; +use std::sync::Arc; + +use crate::error::{Error, Result}; +use crate::memory::MemoryState; +use crate::util::{ + check_stack, get_internal_metatable, get_internal_userdata, init_internal_metatable, + push_internal_userdata, push_string, push_table, rawset_field, to_string, TypeKey, + DESTRUCTED_USERDATA_METATABLE, +}; + +static WRAPPED_FAILURE_TYPE_KEY: u8 = 0; + +pub(crate) enum WrappedFailure { + None, + Error(Error), + Panic(Option>), +} + +impl TypeKey for WrappedFailure { + #[inline(always)] + fn type_key() -> *const c_void { + &WRAPPED_FAILURE_TYPE_KEY as *const u8 as *const c_void + } +} + +impl WrappedFailure { + pub(crate) unsafe fn new_userdata(state: *mut ffi::lua_State) -> *mut Self { + #[cfg(feature = "luau")] + let ud = ffi::lua_newuserdata_t::(state); + #[cfg(not(feature = "luau"))] + let ud = ffi::lua_newuserdata(state, std::mem::size_of::()) as *mut Self; + ptr::write(ud, WrappedFailure::None); + ud + } +} + +// In the context of a lua callback, this will call the given function and if the given function +// returns an error, *or if the given function panics*, this will result in a call to `lua_error` (a +// longjmp). The error or panic is wrapped in such a way that when calling `pop_error` back on +// the Rust side, it will resume the panic. +// +// This function assumes the structure of the stack at the beginning of a callback, that the only +// elements on the stack are the arguments to the callback. +// +// This function uses some of the bottom of the stack for error handling, the given callback will be +// given the number of arguments available as an argument, and should return the number of returns +// as normal, but cannot assume that the arguments available start at 0. +unsafe fn callback_error(state: *mut ffi::lua_State, f: F) -> R +where + F: FnOnce(c_int) -> Result, +{ + let nargs = ffi::lua_gettop(state); + + // We need 2 extra stack spaces to store preallocated memory and error/panic metatable + let extra_stack = if nargs < 2 { 2 - nargs } else { 1 }; + ffi::luaL_checkstack( + state, + extra_stack, + cstr!("not enough stack space for callback error handling"), + ); + + // We cannot shadow Rust errors with Lua ones, we pre-allocate enough memory + // to store a wrapped error or panic *before* we proceed. + let ud = WrappedFailure::new_userdata(state); + ffi::lua_rotate(state, 1, 1); + + match catch_unwind(AssertUnwindSafe(|| f(nargs))) { + Ok(Ok(r)) => { + ffi::lua_remove(state, 1); + r + } + Ok(Err(err)) => { + ffi::lua_settop(state, 1); + + // Build `CallbackError` with traceback + let traceback = if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 { + ffi::luaL_traceback(state, state, ptr::null(), 0); + let traceback = to_string(state, -1); + ffi::lua_pop(state, 1); + traceback + } else { + "".to_string() + }; + let cause = Arc::new(err); + let wrapped_error = WrappedFailure::Error(Error::CallbackError { traceback, cause }); + ptr::write(ud, wrapped_error); + get_internal_metatable::(state); + ffi::lua_setmetatable(state, -2); + + ffi::lua_error(state) + } + Err(p) => { + ffi::lua_settop(state, 1); + ptr::write(ud, WrappedFailure::Panic(Some(p))); + get_internal_metatable::(state); + ffi::lua_setmetatable(state, -2); + ffi::lua_error(state) + } + } +} + +// Pops an error off of the stack and returns it. The specific behavior depends on the type of the +// error at the top of the stack: +// 1) If the error is actually a panic, this will continue the panic. +// 2) If the error on the top of the stack is actually an error, just returns it. +// 3) Otherwise, interprets the error as the appropriate lua error. +// Uses 2 stack spaces, does not call checkstack. +pub(crate) unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error { + mlua_debug_assert!( + err_code != ffi::LUA_OK && err_code != ffi::LUA_YIELD, + "pop_error called with non-error return code" + ); + + match get_internal_userdata::(state, -1, ptr::null()).as_mut() { + Some(WrappedFailure::Error(err)) => { + ffi::lua_pop(state, 1); + err.clone() + } + Some(WrappedFailure::Panic(panic)) => { + if let Some(p) = panic.take() { + resume_unwind(p); + } else { + Error::PreviouslyResumedPanic + } + } + _ => { + let err_string = to_string(state, -1); + ffi::lua_pop(state, 1); + + match err_code { + ffi::LUA_ERRRUN => Error::RuntimeError(err_string), + ffi::LUA_ERRSYNTAX => { + Error::SyntaxError { + // This seems terrible, but as far as I can tell, this is exactly what the + // stock Lua REPL does. + incomplete_input: err_string.ends_with("") || err_string.ends_with("''"), + message: err_string, + } + } + ffi::LUA_ERRERR => { + // This error is raised when the error handler raises an error too many times + // recursively, and continuing to trigger the error handler would cause a stack + // overflow. It is not very useful to differentiate between this and "ordinary" + // runtime errors, so we handle them the same way. + Error::RuntimeError(err_string) + } + ffi::LUA_ERRMEM => Error::MemoryError(err_string), + #[cfg(any(feature = "lua53", feature = "lua52"))] + ffi::LUA_ERRGCMM => Error::GarbageCollectorError(err_string), + _ => mlua_panic!("unrecognized lua error code"), + } + } + } +} + +// Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way. +// Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a +// limited lua stack. `nargs` is the same as the the parameter to `lua_pcall`, and `nresults` is +// always `LUA_MULTRET`. Provided function must *not* panic, and since it will generally be +// longjmping, should not contain any values that implements Drop. +// Internally uses 2 extra stack spaces, and does not call checkstack. +pub(crate) unsafe fn protect_lua_call( + state: *mut ffi::lua_State, + nargs: c_int, + f: unsafe extern "C-unwind" fn(*mut ffi::lua_State) -> c_int, +) -> Result<()> { + let stack_start = ffi::lua_gettop(state) - nargs; + + MemoryState::relax_limit_with(state, || { + ffi::lua_pushcfunction(state, error_traceback); + ffi::lua_pushcfunction(state, f); + }); + if nargs > 0 { + ffi::lua_rotate(state, stack_start + 1, 2); + } + + let ret = ffi::lua_pcall(state, nargs, ffi::LUA_MULTRET, stack_start + 1); + ffi::lua_remove(state, stack_start + 1); + + if ret == ffi::LUA_OK { + Ok(()) + } else { + Err(pop_error(state, ret)) + } +} + +// Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way. +// Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a +// limited lua stack. `nargs` and `nresults` are similar to the parameters of `lua_pcall`, but the +// given function return type is not the return value count, instead the inner function return +// values are assumed to match the `nresults` param. Provided function must *not* panic, and since +// it will generally be longjmping, should not contain any values that implements Drop. +// Internally uses 3 extra stack spaces, and does not call checkstack. +pub(crate) unsafe fn protect_lua_closure( + state: *mut ffi::lua_State, + nargs: c_int, + nresults: c_int, + f: F, +) -> Result +where + F: Fn(*mut ffi::lua_State) -> R, + R: Copy, +{ + struct Params { + function: F, + result: MaybeUninit, + nresults: c_int, + } + + unsafe extern "C-unwind" fn do_call(state: *mut ffi::lua_State) -> c_int + where + F: Fn(*mut ffi::lua_State) -> R, + R: Copy, + { + let params = ffi::lua_touserdata(state, -1) as *mut Params; + ffi::lua_pop(state, 1); + + (*params).result.write(((*params).function)(state)); + + if (*params).nresults == ffi::LUA_MULTRET { + ffi::lua_gettop(state) + } else { + (*params).nresults + } + } + + let stack_start = ffi::lua_gettop(state) - nargs; + + MemoryState::relax_limit_with(state, || { + ffi::lua_pushcfunction(state, error_traceback); + ffi::lua_pushcfunction(state, do_call::); + }); + if nargs > 0 { + ffi::lua_rotate(state, stack_start + 1, 2); + } + + let mut params = Params { + function: f, + result: MaybeUninit::uninit(), + nresults, + }; + + ffi::lua_pushlightuserdata(state, &mut params as *mut Params as *mut c_void); + let ret = ffi::lua_pcall(state, nargs + 1, nresults, stack_start + 1); + ffi::lua_remove(state, stack_start + 1); + + if ret == ffi::LUA_OK { + // `LUA_OK` is only returned when the `do_call` function has completed successfully, so + // `params.result` is definitely initialized. + Ok(params.result.assume_init()) + } else { + Err(pop_error(state, ret)) + } +} + +pub(crate) unsafe extern "C-unwind" fn error_traceback(state: *mut ffi::lua_State) -> c_int { + // Luau calls error handler for memory allocation errors, skip it + // See https://github.com/Roblox/luau/issues/880 + #[cfg(feature = "luau")] + if MemoryState::limit_reached(state) { + return 0; + } + + if ffi::lua_checkstack(state, 2) == 0 { + // If we don't have enough stack space to even check the error type, do + // nothing so we don't risk shadowing a rust panic. + return 1; + } + + if get_internal_userdata::(state, -1, ptr::null()).is_null() { + let s = ffi::luaL_tolstring(state, -1, ptr::null_mut()); + if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 { + ffi::luaL_traceback(state, state, s, 0); + ffi::lua_remove(state, -2); + } + } + + 1 +} + +// A variant of `error_traceback` that can safely inspect another (yielded) thread stack +pub(crate) unsafe fn error_traceback_thread(state: *mut ffi::lua_State, thread: *mut ffi::lua_State) { + // Move error object to the main thread to safely call `__tostring` metamethod if present + ffi::lua_xmove(thread, state, 1); + + if get_internal_userdata::(state, -1, ptr::null()).is_null() { + let s = ffi::luaL_tolstring(state, -1, ptr::null_mut()); + if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 { + ffi::luaL_traceback(state, thread, s, 0); + ffi::lua_remove(state, -2); + } + } +} + +// Initialize the error, panic, and destructed userdata metatables. +pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> { + check_stack(state, 7)?; + + // Create error and panic metatables + + static ERROR_PRINT_BUFFER_KEY: u8 = 0; + + unsafe extern "C-unwind" fn error_tostring(state: *mut ffi::lua_State) -> c_int { + callback_error(state, |_| { + check_stack(state, 3)?; + + let err_buf = match get_internal_userdata::(state, -1, ptr::null()).as_ref() { + Some(WrappedFailure::Error(error)) => { + let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void; + ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key); + let err_buf = ffi::lua_touserdata(state, -1) as *mut String; + ffi::lua_pop(state, 2); + + (*err_buf).clear(); + // Depending on how the API is used and what error types scripts are given, it may + // be possible to make this consume arbitrary amounts of memory (for example, some + // kind of recursive error structure?) + let _ = write!(&mut (*err_buf), "{error}"); + Ok(err_buf) + } + Some(WrappedFailure::Panic(Some(ref panic))) => { + let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void; + ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key); + let err_buf = ffi::lua_touserdata(state, -1) as *mut String; + (*err_buf).clear(); + ffi::lua_pop(state, 2); + + if let Some(msg) = panic.downcast_ref::<&str>() { + let _ = write!(&mut (*err_buf), "{msg}"); + } else if let Some(msg) = panic.downcast_ref::() { + let _ = write!(&mut (*err_buf), "{msg}"); + } else { + let _ = write!(&mut (*err_buf), ""); + }; + Ok(err_buf) + } + Some(WrappedFailure::Panic(None)) => Err(Error::PreviouslyResumedPanic), + _ => { + // I'm not sure whether this is possible to trigger without bugs in mlua? + Err(Error::UserDataTypeMismatch) + } + }?; + + push_string(state, (*err_buf).as_bytes(), true)?; + (*err_buf).clear(); + + Ok(1) + }) + } + + init_internal_metatable::( + state, + Some(|state| { + ffi::lua_pushcfunction(state, error_tostring); + rawset_field(state, -2, "__tostring") + }), + )?; + + // Create destructed userdata metatable + + unsafe extern "C-unwind" fn destructed_error(state: *mut ffi::lua_State) -> c_int { + callback_error(state, |_| Err(Error::CallbackDestructed)) + } + + push_table(state, 0, 26, true)?; + ffi::lua_pushcfunction(state, destructed_error); + for &method in &[ + "__add", + "__sub", + "__mul", + "__div", + "__mod", + "__pow", + "__unm", + #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] + "__idiv", + #[cfg(any(feature = "lua54", feature = "lua53"))] + "__band", + #[cfg(any(feature = "lua54", feature = "lua53"))] + "__bor", + #[cfg(any(feature = "lua54", feature = "lua53"))] + "__bxor", + #[cfg(any(feature = "lua54", feature = "lua53"))] + "__bnot", + #[cfg(any(feature = "lua54", feature = "lua53"))] + "__shl", + #[cfg(any(feature = "lua54", feature = "lua53"))] + "__shr", + "__concat", + "__len", + "__eq", + "__lt", + "__le", + "__index", + "__newindex", + "__call", + "__tostring", + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] + "__pairs", + #[cfg(any(feature = "lua53", feature = "lua52", feature = "luajit52"))] + "__ipairs", + #[cfg(feature = "luau")] + "__iter", + #[cfg(feature = "lua54")] + "__close", + ] { + ffi::lua_pushvalue(state, -1); + rawset_field(state, -3, method)?; + } + ffi::lua_pop(state, 1); + + protect_lua!(state, 1, 0, fn(state) { + let destructed_mt_key = &DESTRUCTED_USERDATA_METATABLE as *const u8 as *const c_void; + ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, destructed_mt_key); + })?; + + // Create error print buffer + init_internal_metatable::(state, None)?; + push_internal_userdata(state, String::new(), true)?; + protect_lua!(state, 1, 0, fn(state) { + let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void; + ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key); + })?; + + Ok(()) +} diff --git a/src/util/mod.rs b/src/util/mod.rs index e682d836..93519c27 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,47 +1,34 @@ -use std::any::{Any, TypeId}; use std::borrow::Cow; -use std::cell::UnsafeCell; use std::ffi::CStr; -use std::fmt::Write; -use std::mem::MaybeUninit; -use std::os::raw::{c_char, c_int, c_void}; -use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe}; -use std::rc::Rc; -use std::sync::Arc; +use std::os::raw::{c_char, c_int}; use std::{ptr, slice, str}; -use once_cell::sync::Lazy; -use rustc_hash::FxHashMap; - use crate::error::{Error, Result}; -use crate::memory::MemoryState; +pub(crate) use error::{ + error_traceback, error_traceback_thread, init_error_registry, pop_error, protect_lua_call, + protect_lua_closure, WrappedFailure, +}; pub(crate) use short_names::short_type_name; +pub(crate) use types::TypeKey; +pub(crate) use userdata::{ + get_destructed_userdata_metatable, get_internal_metatable, get_internal_userdata, get_userdata, + init_internal_metatable, init_userdata_metatable, push_internal_userdata, take_userdata, + DESTRUCTED_USERDATA_METATABLE, +}; + +#[cfg(not(feature = "lua54"))] +pub(crate) use userdata::push_userdata; +#[cfg(feature = "lua54")] +pub(crate) use userdata::push_userdata_uv; -static METATABLE_CACHE: Lazy> = Lazy::new(|| { - let mut map = FxHashMap::with_capacity_and_hasher(32, Default::default()); - - map.insert(TypeId::of::>>(), 0); - map.insert(TypeId::of::(), 0); - map.insert(TypeId::of::(), 0); - - #[cfg(feature = "async")] - { - map.insert(TypeId::of::(), 0); - map.insert(TypeId::of::(), 0); - map.insert(TypeId::of::(), 0); - map.insert(TypeId::of::>(), 0); - } - - map.insert(TypeId::of::(), 0); - map.insert(TypeId::of::(), 0); - map -}); +#[cfg(not(feature = "luau"))] +pub(crate) use userdata::userdata_destructor; // Checks that Lua has enough free stack space for future stack operations. On failure, this will // panic with an internal error message. #[inline] -pub unsafe fn assert_stack(state: *mut ffi::lua_State, amount: c_int) { +pub(crate) unsafe fn assert_stack(state: *mut ffi::lua_State, amount: c_int) { // TODO: This should only be triggered when there is a logic error in `mlua`. In the future, // when there is a way to be confident about stack safety and test it, this could be enabled // only when `cfg!(debug_assertions)` is true. @@ -50,7 +37,7 @@ pub unsafe fn assert_stack(state: *mut ffi::lua_State, amount: c_int) { // Checks that Lua has enough free stack space and returns `Error::StackError` on failure. #[inline] -pub unsafe fn check_stack(state: *mut ffi::lua_State, amount: c_int) -> Result<()> { +pub(crate) unsafe fn check_stack(state: *mut ffi::lua_State, amount: c_int) -> Result<()> { if ffi::lua_checkstack(state, amount) == 0 { Err(Error::StackError) } else { @@ -58,7 +45,7 @@ pub unsafe fn check_stack(state: *mut ffi::lua_State, amount: c_int) -> Result<( } } -pub struct StackGuard { +pub(crate) struct StackGuard { state: *mut ffi::lua_State, top: c_int, } @@ -68,7 +55,7 @@ impl StackGuard { // stack size and drop any extra elements. If the stack size at the end is *smaller* than at // the beginning, this is considered a fatal logic error and will result in a panic. #[inline] - pub unsafe fn new(state: *mut ffi::lua_State) -> StackGuard { + pub(crate) unsafe fn new(state: *mut ffi::lua_State) -> StackGuard { StackGuard { state, top: ffi::lua_gettop(state), @@ -76,7 +63,8 @@ impl StackGuard { } // Same as `new()`, but allows specifying the expected stack size at the end of the scope. - pub const fn with_top(state: *mut ffi::lua_State, top: c_int) -> StackGuard { + #[inline] + pub(crate) fn with_top(state: *mut ffi::lua_State, top: c_int) -> StackGuard { StackGuard { state, top } } } @@ -95,163 +83,9 @@ impl Drop for StackGuard { } } -// Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way. -// Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a -// limited lua stack. `nargs` is the same as the the parameter to `lua_pcall`, and `nresults` is -// always `LUA_MULTRET`. Provided function must *not* panic, and since it will generally be -// longjmping, should not contain any values that implements Drop. -// Internally uses 2 extra stack spaces, and does not call checkstack. -pub unsafe fn protect_lua_call( - state: *mut ffi::lua_State, - nargs: c_int, - f: unsafe extern "C-unwind" fn(*mut ffi::lua_State) -> c_int, -) -> Result<()> { - let stack_start = ffi::lua_gettop(state) - nargs; - - MemoryState::relax_limit_with(state, || { - ffi::lua_pushcfunction(state, error_traceback); - ffi::lua_pushcfunction(state, f); - }); - if nargs > 0 { - ffi::lua_rotate(state, stack_start + 1, 2); - } - - let ret = ffi::lua_pcall(state, nargs, ffi::LUA_MULTRET, stack_start + 1); - ffi::lua_remove(state, stack_start + 1); - - if ret == ffi::LUA_OK { - Ok(()) - } else { - Err(pop_error(state, ret)) - } -} - -// Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way. -// Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a -// limited lua stack. `nargs` and `nresults` are similar to the parameters of `lua_pcall`, but the -// given function return type is not the return value count, instead the inner function return -// values are assumed to match the `nresults` param. Provided function must *not* panic, and since -// it will generally be longjmping, should not contain any values that implements Drop. -// Internally uses 3 extra stack spaces, and does not call checkstack. -pub unsafe fn protect_lua_closure( - state: *mut ffi::lua_State, - nargs: c_int, - nresults: c_int, - f: F, -) -> Result -where - F: Fn(*mut ffi::lua_State) -> R, - R: Copy, -{ - struct Params { - function: F, - result: MaybeUninit, - nresults: c_int, - } - - unsafe extern "C-unwind" fn do_call(state: *mut ffi::lua_State) -> c_int - where - F: Fn(*mut ffi::lua_State) -> R, - R: Copy, - { - let params = ffi::lua_touserdata(state, -1) as *mut Params; - ffi::lua_pop(state, 1); - - (*params).result.write(((*params).function)(state)); - - if (*params).nresults == ffi::LUA_MULTRET { - ffi::lua_gettop(state) - } else { - (*params).nresults - } - } - - let stack_start = ffi::lua_gettop(state) - nargs; - - MemoryState::relax_limit_with(state, || { - ffi::lua_pushcfunction(state, error_traceback); - ffi::lua_pushcfunction(state, do_call::); - }); - if nargs > 0 { - ffi::lua_rotate(state, stack_start + 1, 2); - } - - let mut params = Params { - function: f, - result: MaybeUninit::uninit(), - nresults, - }; - - ffi::lua_pushlightuserdata(state, &mut params as *mut Params as *mut c_void); - let ret = ffi::lua_pcall(state, nargs + 1, nresults, stack_start + 1); - ffi::lua_remove(state, stack_start + 1); - - if ret == ffi::LUA_OK { - // `LUA_OK` is only returned when the `do_call` function has completed successfully, so - // `params.result` is definitely initialized. - Ok(params.result.assume_init()) - } else { - Err(pop_error(state, ret)) - } -} - -// Pops an error off of the stack and returns it. The specific behavior depends on the type of the -// error at the top of the stack: -// 1) If the error is actually a WrappedPanic, this will continue the panic. -// 2) If the error on the top of the stack is actually a WrappedError, just returns it. -// 3) Otherwise, interprets the error as the appropriate lua error. -// Uses 2 stack spaces, does not call checkstack. -pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error { - mlua_debug_assert!( - err_code != ffi::LUA_OK && err_code != ffi::LUA_YIELD, - "pop_error called with non-error return code" - ); - - match get_gc_userdata::(state, -1, ptr::null()).as_mut() { - Some(WrappedFailure::Error(err)) => { - ffi::lua_pop(state, 1); - err.clone() - } - Some(WrappedFailure::Panic(panic)) => { - if let Some(p) = panic.take() { - resume_unwind(p); - } else { - Error::PreviouslyResumedPanic - } - } - _ => { - let err_string = to_string(state, -1); - ffi::lua_pop(state, 1); - - match err_code { - ffi::LUA_ERRRUN => Error::RuntimeError(err_string), - ffi::LUA_ERRSYNTAX => { - Error::SyntaxError { - // This seems terrible, but as far as I can tell, this is exactly what the - // stock Lua REPL does. - incomplete_input: err_string.ends_with("") || err_string.ends_with("''"), - message: err_string, - } - } - ffi::LUA_ERRERR => { - // This error is raised when the error handler raises an error too many times - // recursively, and continuing to trigger the error handler would cause a stack - // overflow. It is not very useful to differentiate between this and "ordinary" - // runtime errors, so we handle them the same way. - Error::RuntimeError(err_string) - } - ffi::LUA_ERRMEM => Error::MemoryError(err_string), - #[cfg(any(feature = "lua53", feature = "lua52"))] - ffi::LUA_ERRGCMM => Error::GarbageCollectorError(err_string), - _ => mlua_panic!("unrecognized lua error code"), - } - } - } -} - // Uses 3 (or 1 if unprotected) stack spaces, does not call checkstack. #[inline(always)] -pub unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: bool) -> Result<()> { +pub(crate) unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: bool) -> Result<()> { // Always use protected mode if the string is too long if protect || s.len() > (1 << 30) { protect_lua!(state, 0, 1, |state| { @@ -266,7 +100,7 @@ pub unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: bool) - // Uses 3 stack spaces (when protect), does not call checkstack. #[cfg(feature = "luau")] #[inline(always)] -pub unsafe fn push_buffer(state: *mut ffi::lua_State, b: &[u8], protect: bool) -> Result<()> { +pub(crate) unsafe fn push_buffer(state: *mut ffi::lua_State, b: &[u8], protect: bool) -> Result<()> { let data = if protect { protect_lua!(state, 0, 1, |state| ffi::lua_newbuffer(state, b.len()))? } else { @@ -279,7 +113,12 @@ pub unsafe fn push_buffer(state: *mut ffi::lua_State, b: &[u8], protect: bool) - // Uses 3 stack spaces, does not call checkstack. #[inline] -pub unsafe fn push_table(state: *mut ffi::lua_State, narr: usize, nrec: usize, protect: bool) -> Result<()> { +pub(crate) unsafe fn push_table( + state: *mut ffi::lua_State, + narr: usize, + nrec: usize, + protect: bool, +) -> Result<()> { let narr: c_int = narr.try_into().unwrap_or(c_int::MAX); let nrec: c_int = nrec.try_into().unwrap_or(c_int::MAX); if protect { @@ -291,7 +130,7 @@ pub unsafe fn push_table(state: *mut ffi::lua_State, narr: usize, nrec: usize, p } // Uses 4 stack spaces, does not call checkstack. -pub unsafe fn rawset_field(state: *mut ffi::lua_State, table: c_int, field: &str) -> Result<()> { +pub(crate) unsafe fn rawset_field(state: *mut ffi::lua_State, table: c_int, field: &str) -> Result<()> { ffi::lua_pushvalue(state, table); protect_lua!(state, 2, 0, |state| { ffi::lua_pushlstring(state, field.as_ptr() as *const c_char, field.len()); @@ -300,437 +139,8 @@ pub unsafe fn rawset_field(state: *mut ffi::lua_State, table: c_int, field: &str }) } -// Internally uses 3 stack spaces, does not call checkstack. -#[inline] -pub unsafe fn push_userdata(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<()> { - #[cfg(not(feature = "luau"))] - let ud = if protect { - protect_lua!(state, 0, 1, |state| { - ffi::lua_newuserdata(state, std::mem::size_of::()) as *mut T - })? - } else { - ffi::lua_newuserdata(state, std::mem::size_of::()) as *mut T - }; - #[cfg(feature = "luau")] - let ud = if protect { - protect_lua!(state, 0, 1, |state| { ffi::lua_newuserdata_t::(state) })? - } else { - ffi::lua_newuserdata_t::(state) - }; - ptr::write(ud, t); - Ok(()) -} - -// Internally uses 3 stack spaces, does not call checkstack. -#[cfg(feature = "lua54")] -#[inline] -pub unsafe fn push_userdata_uv( - state: *mut ffi::lua_State, - t: T, - nuvalue: c_int, - protect: bool, -) -> Result<()> { - let ud = if protect { - protect_lua!(state, 0, 1, |state| { - ffi::lua_newuserdatauv(state, std::mem::size_of::(), nuvalue) as *mut T - })? - } else { - ffi::lua_newuserdatauv(state, std::mem::size_of::(), nuvalue) as *mut T - }; - ptr::write(ud, t); - Ok(()) -} - -#[inline] -pub unsafe fn get_userdata(state: *mut ffi::lua_State, index: c_int) -> *mut T { - let ud = ffi::lua_touserdata(state, index) as *mut T; - mlua_debug_assert!(!ud.is_null(), "userdata pointer is null"); - ud -} - -// Pops the userdata off of the top of the stack and returns it to rust, invalidating the lua -// userdata and gives it the special "destructed" userdata metatable. Userdata must not have been -// previously invalidated, and this method does not check for this. -// Uses 1 extra stack space and does not call checkstack. -pub unsafe fn take_userdata(state: *mut ffi::lua_State) -> T { - // We set the metatable of userdata on __gc to a special table with no __gc method and with - // metamethods that trigger an error on access. We do this so that it will not be double - // dropped, and also so that it cannot be used or identified as any particular userdata type - // after the first call to __gc. - get_destructed_userdata_metatable(state); - ffi::lua_setmetatable(state, -2); - let ud = get_userdata::(state, -1); - - // Update userdata tag to disable destructor and mark as destructed - #[cfg(feature = "luau")] - ffi::lua_setuserdatatag(state, -1, 1); - - ffi::lua_pop(state, 1); - ptr::read(ud) -} - -// Pushes the userdata and attaches a metatable with __gc method. -// Internally uses 3 stack spaces, does not call checkstack. -pub unsafe fn push_gc_userdata(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<()> { - push_userdata(state, t, protect)?; - get_gc_metatable::(state); - ffi::lua_setmetatable(state, -2); - Ok(()) -} - -// Uses 2 stack spaces, does not call checkstack -pub unsafe fn get_gc_userdata( - state: *mut ffi::lua_State, - index: c_int, - mt_ptr: *const c_void, -) -> *mut T { - let ud = ffi::lua_touserdata(state, index) as *mut T; - if ud.is_null() || ffi::lua_getmetatable(state, index) == 0 { - return ptr::null_mut(); - } - if !mt_ptr.is_null() { - let ud_mt_ptr = ffi::lua_topointer(state, -1); - ffi::lua_pop(state, 1); - if !ptr::eq(ud_mt_ptr, mt_ptr) { - return ptr::null_mut(); - } - } else { - get_gc_metatable::(state); - let res = ffi::lua_rawequal(state, -1, -2); - ffi::lua_pop(state, 2); - if res == 0 { - return ptr::null_mut(); - } - } - ud -} - -unsafe extern "C-unwind" fn lua_error_impl(state: *mut ffi::lua_State) -> c_int { - ffi::lua_error(state); -} - -unsafe extern "C-unwind" fn lua_isfunction_impl(state: *mut ffi::lua_State) -> c_int { - ffi::lua_pushboolean(state, ffi::lua_isfunction(state, -1)); - 1 -} - -unsafe extern "C-unwind" fn lua_istable_impl(state: *mut ffi::lua_State) -> c_int { - ffi::lua_pushboolean(state, ffi::lua_istable(state, -1)); - 1 -} - -unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()> { - let index_key = &USERDATA_METATABLE_INDEX as *const u8 as *const _; - if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, index_key) == ffi::LUA_TFUNCTION { - return Ok(()); - } - ffi::lua_pop(state, 1); - - // Create and cache `__index` generator - let code = cstr!( - r#" - local error, isfunction, istable = ... - return function (__index, field_getters, methods) - -- Common case: has field getters and index is a table - if field_getters ~= nil and methods == nil and istable(__index) then - return function (self, key) - local field_getter = field_getters[key] - if field_getter ~= nil then - return field_getter(self) - end - return __index[key] - end - end - - return function (self, key) - if field_getters ~= nil then - local field_getter = field_getters[key] - if field_getter ~= nil then - return field_getter(self) - end - end - - if methods ~= nil then - local method = methods[key] - if method ~= nil then - return method - end - end - - if isfunction(__index) then - return __index(self, key) - elseif __index == nil then - error("attempt to get an unknown field '"..key.."'") - else - return __index[key] - end - end - end - "# - ); - let code_len = CStr::from_ptr(code).to_bytes().len(); - protect_lua!(state, 0, 1, |state| { - let ret = ffi::luaL_loadbuffer(state, code, code_len, cstr!("__mlua_index")); - if ret != ffi::LUA_OK { - ffi::lua_error(state); - } - ffi::lua_pushcfunction(state, lua_error_impl); - ffi::lua_pushcfunction(state, lua_isfunction_impl); - ffi::lua_pushcfunction(state, lua_istable_impl); - ffi::lua_call(state, 3, 1); - - #[cfg(feature = "luau-jit")] - if ffi::luau_codegen_supported() != 0 { - ffi::luau_codegen_compile(state, -1); - } - - // Store in the registry - ffi::lua_pushvalue(state, -1); - ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, index_key); - }) -} - -pub unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result<()> { - let newindex_key = &USERDATA_METATABLE_NEWINDEX as *const u8 as *const _; - if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, newindex_key) == ffi::LUA_TFUNCTION { - return Ok(()); - } - ffi::lua_pop(state, 1); - - // Create and cache `__newindex` generator - let code = cstr!( - r#" - local error, isfunction = ... - return function (__newindex, field_setters) - return function (self, key, value) - if field_setters ~= nil then - local field_setter = field_setters[key] - if field_setter ~= nil then - field_setter(self, value) - return - end - end - - if isfunction(__newindex) then - __newindex(self, key, value) - elseif __newindex == nil then - error("attempt to set an unknown field '"..key.."'") - else - __newindex[key] = value - end - end - end - "# - ); - let code_len = CStr::from_ptr(code).to_bytes().len(); - protect_lua!(state, 0, 1, |state| { - let ret = ffi::luaL_loadbuffer(state, code, code_len, cstr!("__mlua_newindex")); - if ret != ffi::LUA_OK { - ffi::lua_error(state); - } - ffi::lua_pushcfunction(state, lua_error_impl); - ffi::lua_pushcfunction(state, lua_isfunction_impl); - ffi::lua_call(state, 2, 1); - - #[cfg(feature = "luau-jit")] - if ffi::luau_codegen_supported() != 0 { - ffi::luau_codegen_compile(state, -1); - } - - // Store in the registry - ffi::lua_pushvalue(state, -1); - ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, newindex_key); - }) -} - -// Populates the given table with the appropriate members to be a userdata metatable for the given -// type. This function takes the given table at the `metatable` index, and adds an appropriate -// `__gc` member to it for the given type and a `__metatable` entry to protect the table from script -// access. The function also, if given a `field_getters` or `methods` tables, will create an -// `__index` metamethod (capturing previous one) to lookup in `field_getters` first, then `methods` -// and falling back to the captured `__index` if no matches found. -// The same is also applicable for `__newindex` metamethod and `field_setters` table. -// Internally uses 9 stack spaces and does not call checkstack. -pub unsafe fn init_userdata_metatable( - state: *mut ffi::lua_State, - metatable: c_int, - field_getters: Option, - field_setters: Option, - methods: Option, - extra_init: Option Result<()>>, -) -> Result<()> { - ffi::lua_pushvalue(state, metatable); - - if field_getters.is_some() || methods.is_some() { - // Push `__index` generator function - init_userdata_metatable_index(state)?; - - push_string(state, b"__index", true)?; - let index_type = ffi::lua_rawget(state, -3); - match index_type { - ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => { - for &idx in &[field_getters, methods] { - if let Some(idx) = idx { - ffi::lua_pushvalue(state, idx); - } else { - ffi::lua_pushnil(state); - } - } - - // Generate `__index` - protect_lua!(state, 4, 1, fn(state) ffi::lua_call(state, 3, 1))?; - } - _ => mlua_panic!("improper __index type {}", index_type), - } - - rawset_field(state, -2, "__index")?; - } - - if let Some(field_setters) = field_setters { - // Push `__newindex` generator function - init_userdata_metatable_newindex(state)?; - - push_string(state, b"__newindex", true)?; - let newindex_type = ffi::lua_rawget(state, -3); - match newindex_type { - ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => { - ffi::lua_pushvalue(state, field_setters); - // Generate `__newindex` - protect_lua!(state, 3, 1, fn(state) ffi::lua_call(state, 2, 1))?; - } - _ => mlua_panic!("improper __newindex type {}", newindex_type), - } - - rawset_field(state, -2, "__newindex")?; - } - - // Additional initialization - if let Some(extra_init) = extra_init { - extra_init(state)?; - } - - ffi::lua_pushboolean(state, 0); - rawset_field(state, -2, "__metatable")?; - - ffi::lua_pop(state, 1); - - Ok(()) -} - -#[cfg(not(feature = "luau"))] -pub unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::lua_State) -> c_int { - // It's probably NOT a good idea to catch Rust panics in finalizer - // Lua 5.4 ignores it, other versions generates `LUA_ERRGCMM` without calling message handler - take_userdata::(state); - 0 -} - -// In the context of a lua callback, this will call the given function and if the given function -// returns an error, *or if the given function panics*, this will result in a call to `lua_error` (a -// longjmp). The error or panic is wrapped in such a way that when calling `pop_error` back on -// the Rust side, it will resume the panic. -// -// This function assumes the structure of the stack at the beginning of a callback, that the only -// elements on the stack are the arguments to the callback. -// -// This function uses some of the bottom of the stack for error handling, the given callback will be -// given the number of arguments available as an argument, and should return the number of returns -// as normal, but cannot assume that the arguments available start at 0. -pub unsafe fn callback_error(state: *mut ffi::lua_State, f: F) -> R -where - F: FnOnce(c_int) -> Result, -{ - let nargs = ffi::lua_gettop(state); - - // We need 2 extra stack spaces to store preallocated memory and error/panic metatable - let extra_stack = if nargs < 2 { 2 - nargs } else { 1 }; - ffi::luaL_checkstack( - state, - extra_stack, - cstr!("not enough stack space for callback error handling"), - ); - - // We cannot shadow Rust errors with Lua ones, we pre-allocate enough memory - // to store a wrapped error or panic *before* we proceed. - let ud = WrappedFailure::new_userdata(state); - ffi::lua_rotate(state, 1, 1); - - match catch_unwind(AssertUnwindSafe(|| f(nargs))) { - Ok(Ok(r)) => { - ffi::lua_remove(state, 1); - r - } - Ok(Err(err)) => { - ffi::lua_settop(state, 1); - - // Build `CallbackError` with traceback - let traceback = if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 { - ffi::luaL_traceback(state, state, ptr::null(), 0); - let traceback = to_string(state, -1); - ffi::lua_pop(state, 1); - traceback - } else { - "".to_string() - }; - let cause = Arc::new(err); - let wrapped_error = WrappedFailure::Error(Error::CallbackError { traceback, cause }); - ptr::write(ud, wrapped_error); - get_gc_metatable::(state); - ffi::lua_setmetatable(state, -2); - - ffi::lua_error(state) - } - Err(p) => { - ffi::lua_settop(state, 1); - ptr::write(ud, WrappedFailure::Panic(Some(p))); - get_gc_metatable::(state); - ffi::lua_setmetatable(state, -2); - ffi::lua_error(state) - } - } -} - -pub unsafe extern "C-unwind" fn error_traceback(state: *mut ffi::lua_State) -> c_int { - // Luau calls error handler for memory allocation errors, skip it - // See https://github.com/Roblox/luau/issues/880 - #[cfg(feature = "luau")] - if MemoryState::limit_reached(state) { - return 0; - } - - if ffi::lua_checkstack(state, 2) == 0 { - // If we don't have enough stack space to even check the error type, do - // nothing so we don't risk shadowing a rust panic. - return 1; - } - - if get_gc_userdata::(state, -1, ptr::null()).is_null() { - let s = ffi::luaL_tolstring(state, -1, ptr::null_mut()); - if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 { - ffi::luaL_traceback(state, state, s, 0); - ffi::lua_remove(state, -2); - } - } - - 1 -} - -// A variant of `error_traceback` that can safely inspect another (yielded) thread stack -pub unsafe fn error_traceback_thread(state: *mut ffi::lua_State, thread: *mut ffi::lua_State) { - // Move error object to the main thread to safely call `__tostring` metamethod if present - ffi::lua_xmove(thread, state, 1); - - if get_gc_userdata::(state, -1, ptr::null()).is_null() { - let s = ffi::luaL_tolstring(state, -1, ptr::null_mut()); - if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 { - ffi::luaL_traceback(state, thread, s, 0); - ffi::lua_remove(state, -2); - } - } -} - // A variant of `pcall` that does not allow Lua to catch Rust panics from `callback_error`. -pub unsafe extern "C-unwind" fn safe_pcall(state: *mut ffi::lua_State) -> c_int { +pub(crate) unsafe extern "C-unwind" fn safe_pcall(state: *mut ffi::lua_State) -> c_int { ffi::luaL_checkstack(state, 2, ptr::null()); let top = ffi::lua_gettop(state); @@ -744,9 +154,8 @@ pub unsafe extern "C-unwind" fn safe_pcall(state: *mut ffi::lua_State) -> c_int ffi::lua_insert(state, 1); ffi::lua_gettop(state) } else { - if let Some(WrappedFailure::Panic(_)) = - get_gc_userdata::(state, -1, ptr::null()).as_ref() - { + let wf_ud = get_internal_userdata::(state, -1, ptr::null()); + if let Some(WrappedFailure::Panic(_)) = wf_ud.as_ref() { ffi::lua_error(state); } ffi::lua_pushboolean(state, 0); @@ -756,13 +165,12 @@ pub unsafe extern "C-unwind" fn safe_pcall(state: *mut ffi::lua_State) -> c_int } // A variant of `xpcall` that does not allow Lua to catch Rust panics from `callback_error`. -pub unsafe extern "C-unwind" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int { +pub(crate) unsafe extern "C-unwind" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int { unsafe extern "C-unwind" fn xpcall_msgh(state: *mut ffi::lua_State) -> c_int { ffi::luaL_checkstack(state, 2, ptr::null()); - if let Some(WrappedFailure::Panic(_)) = - get_gc_userdata::(state, -1, ptr::null()).as_ref() - { + let wf_ud = get_internal_userdata::(state, -1, ptr::null()); + if let Some(WrappedFailure::Panic(_)) = wf_ud.as_ref() { 1 } else { ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1)); @@ -790,9 +198,8 @@ pub unsafe extern "C-unwind" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int ffi::lua_insert(state, 2); ffi::lua_gettop(state) - 1 } else { - if let Some(WrappedFailure::Panic(_)) = - get_gc_userdata::(state, -1, ptr::null()).as_ref() - { + let wf_ud = get_internal_userdata::(state, -1, ptr::null()); + if let Some(WrappedFailure::Panic(_)) = wf_ud.as_ref() { ffi::lua_error(state); } ffi::lua_pushboolean(state, 0); @@ -803,7 +210,7 @@ pub unsafe extern "C-unwind" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int // Returns Lua main thread for Lua >= 5.2 or checks that the passed thread is main for Lua 5.1. // Does not call lua_checkstack, uses 1 stack space. -pub unsafe fn get_main_state(state: *mut ffi::lua_State) -> Option<*mut ffi::lua_State> { +pub(crate) unsafe fn get_main_state(state: *mut ffi::lua_State) -> Option<*mut ffi::lua_State> { #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] { ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_MAINTHREAD); @@ -826,192 +233,6 @@ pub unsafe fn get_main_state(state: *mut ffi::lua_State) -> Option<*mut ffi::lua Some(ffi::lua_mainthread(state)) } -// Initialize the internal (with __gc method) metatable for a type T. -// Uses 6 stack spaces and calls checkstack. -pub unsafe fn init_gc_metatable( - state: *mut ffi::lua_State, - customize_fn: Option Result<()>>, -) -> Result<()> { - check_stack(state, 6)?; - - push_table(state, 0, 3, true)?; - - #[cfg(not(feature = "luau"))] - { - ffi::lua_pushcfunction(state, userdata_destructor::); - rawset_field(state, -2, "__gc")?; - } - - ffi::lua_pushboolean(state, 0); - rawset_field(state, -2, "__metatable")?; - - if let Some(f) = customize_fn { - f(state)?; - } - - let type_id = TypeId::of::(); - let ref_addr = &METATABLE_CACHE[&type_id] as *const u8; - protect_lua!(state, 1, 0, |state| { - ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, ref_addr as *const c_void); - })?; - - Ok(()) -} - -pub unsafe fn get_gc_metatable(state: *mut ffi::lua_State) { - let type_id = TypeId::of::(); - let ref_addr = mlua_expect!(METATABLE_CACHE.get(&type_id), "gc metatable does not exist") as *const u8; - ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, ref_addr as *const c_void); -} - -// Initialize the error, panic, and destructed userdata metatables. -pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> { - check_stack(state, 7)?; - - // Create error and panic metatables - - unsafe extern "C-unwind" fn error_tostring(state: *mut ffi::lua_State) -> c_int { - callback_error(state, |_| { - check_stack(state, 3)?; - - let err_buf = match get_gc_userdata::(state, -1, ptr::null()).as_ref() { - Some(WrappedFailure::Error(error)) => { - let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void; - ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key); - let err_buf = ffi::lua_touserdata(state, -1) as *mut String; - ffi::lua_pop(state, 2); - - (*err_buf).clear(); - // Depending on how the API is used and what error types scripts are given, it may - // be possible to make this consume arbitrary amounts of memory (for example, some - // kind of recursive error structure?) - let _ = write!(&mut (*err_buf), "{error}"); - Ok(err_buf) - } - Some(WrappedFailure::Panic(Some(ref panic))) => { - let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void; - ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key); - let err_buf = ffi::lua_touserdata(state, -1) as *mut String; - (*err_buf).clear(); - ffi::lua_pop(state, 2); - - if let Some(msg) = panic.downcast_ref::<&str>() { - let _ = write!(&mut (*err_buf), "{msg}"); - } else if let Some(msg) = panic.downcast_ref::() { - let _ = write!(&mut (*err_buf), "{msg}"); - } else { - let _ = write!(&mut (*err_buf), ""); - }; - Ok(err_buf) - } - Some(WrappedFailure::Panic(None)) => Err(Error::PreviouslyResumedPanic), - _ => { - // I'm not sure whether this is possible to trigger without bugs in mlua? - Err(Error::UserDataTypeMismatch) - } - }?; - - push_string(state, (*err_buf).as_bytes(), true)?; - (*err_buf).clear(); - - Ok(1) - }) - } - - init_gc_metatable::( - state, - Some(|state| { - ffi::lua_pushcfunction(state, error_tostring); - rawset_field(state, -2, "__tostring") - }), - )?; - - // Create destructed userdata metatable - - unsafe extern "C-unwind" fn destructed_error(state: *mut ffi::lua_State) -> c_int { - callback_error(state, |_| Err(Error::CallbackDestructed)) - } - - push_table(state, 0, 26, true)?; - ffi::lua_pushcfunction(state, destructed_error); - for &method in &[ - "__add", - "__sub", - "__mul", - "__div", - "__mod", - "__pow", - "__unm", - #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] - "__idiv", - #[cfg(any(feature = "lua54", feature = "lua53"))] - "__band", - #[cfg(any(feature = "lua54", feature = "lua53"))] - "__bor", - #[cfg(any(feature = "lua54", feature = "lua53"))] - "__bxor", - #[cfg(any(feature = "lua54", feature = "lua53"))] - "__bnot", - #[cfg(any(feature = "lua54", feature = "lua53"))] - "__shl", - #[cfg(any(feature = "lua54", feature = "lua53"))] - "__shr", - "__concat", - "__len", - "__eq", - "__lt", - "__le", - "__index", - "__newindex", - "__call", - "__tostring", - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] - "__pairs", - #[cfg(any(feature = "lua53", feature = "lua52", feature = "luajit52"))] - "__ipairs", - #[cfg(feature = "luau")] - "__iter", - #[cfg(feature = "lua54")] - "__close", - ] { - ffi::lua_pushvalue(state, -1); - rawset_field(state, -3, method)?; - } - ffi::lua_pop(state, 1); - - protect_lua!(state, 1, 0, fn(state) { - let destructed_mt_key = &DESTRUCTED_USERDATA_METATABLE as *const u8 as *const c_void; - ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, destructed_mt_key); - })?; - - // Create error print buffer - init_gc_metatable::(state, None)?; - push_gc_userdata(state, String::new(), true)?; - protect_lua!(state, 1, 0, fn(state) { - let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void; - ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key); - })?; - - Ok(()) -} - -pub(crate) enum WrappedFailure { - None, - Error(Error), - Panic(Option>), -} - -impl WrappedFailure { - pub(crate) unsafe fn new_userdata(state: *mut ffi::lua_State) -> *mut Self { - #[cfg(feature = "luau")] - let ud = ffi::lua_newuserdata_t::(state); - #[cfg(not(feature = "luau"))] - let ud = ffi::lua_newuserdata(state, std::mem::size_of::()) as *mut Self; - ptr::write(ud, WrappedFailure::None); - ud - } -} - // Converts the given lua value to a string in a reasonable format without causing a Lua error or // panicking. pub(crate) unsafe fn to_string(state: *mut ffi::lua_State, index: c_int) -> String { @@ -1060,11 +281,6 @@ pub(crate) unsafe fn to_string(state: *mut ffi::lua_State, index: c_int) -> Stri } } -pub(crate) unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_State) { - let key = &DESTRUCTED_USERDATA_METATABLE as *const u8 as *const c_void; - ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, key); -} - pub(crate) unsafe fn ptr_to_str<'a>(input: *const c_char) -> Option<&'a str> { if input.is_null() { return None; @@ -1086,9 +302,7 @@ pub(crate) fn linenumber_to_usize(n: c_int) -> Option { } } -static DESTRUCTED_USERDATA_METATABLE: u8 = 0; -static ERROR_PRINT_BUFFER_KEY: u8 = 0; -static USERDATA_METATABLE_INDEX: u8 = 0; -static USERDATA_METATABLE_NEWINDEX: u8 = 0; - +mod error; mod short_names; +mod types; +mod userdata; diff --git a/src/util/types.rs b/src/util/types.rs new file mode 100644 index 00000000..d8705411 --- /dev/null +++ b/src/util/types.rs @@ -0,0 +1,82 @@ +use std::any::Any; +use std::os::raw::c_void; + +use crate::types::{Callback, CallbackUpvalue}; + +#[cfg(feature = "async")] +use crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue}; + +pub(crate) trait TypeKey: Any { + fn type_key() -> *const c_void; +} + +static STRING_TYPE_KEY: u8 = 0; + +impl TypeKey for String { + #[inline(always)] + fn type_key() -> *const c_void { + &STRING_TYPE_KEY as *const u8 as *const c_void + } +} + +static CALLBACK_TYPE_KEY: u8 = 0; + +impl TypeKey for Callback<'static> { + #[inline(always)] + fn type_key() -> *const c_void { + &CALLBACK_TYPE_KEY as *const u8 as *const c_void + } +} + +static CALLBACK_UPVALUE_TYPE_KEY: u8 = 0; + +impl TypeKey for CallbackUpvalue { + #[inline(always)] + fn type_key() -> *const c_void { + &CALLBACK_UPVALUE_TYPE_KEY as *const u8 as *const c_void + } +} + +#[cfg(feature = "async")] +static ASYNC_CALLBACK_TYPE_KEY: u8 = 0; + +#[cfg(feature = "async")] +impl TypeKey for AsyncCallback<'static> { + #[inline(always)] + fn type_key() -> *const c_void { + &ASYNC_CALLBACK_TYPE_KEY as *const u8 as *const c_void + } +} + +#[cfg(feature = "async")] +static ASYNC_CALLBACK_UPVALUE_TYPE_KEY: u8 = 0; + +#[cfg(feature = "async")] +impl TypeKey for AsyncCallbackUpvalue { + #[inline(always)] + fn type_key() -> *const c_void { + &ASYNC_CALLBACK_UPVALUE_TYPE_KEY as *const u8 as *const c_void + } +} + +#[cfg(feature = "async")] +static ASYNC_POLL_UPVALUE_TYPE_KEY: u8 = 0; + +#[cfg(feature = "async")] +impl TypeKey for AsyncPollUpvalue { + #[inline(always)] + fn type_key() -> *const c_void { + &ASYNC_POLL_UPVALUE_TYPE_KEY as *const u8 as *const c_void + } +} + +#[cfg(feature = "async")] +static WAKER_TYPE_KEY: u8 = 0; + +#[cfg(feature = "async")] +impl TypeKey for Option { + #[inline(always)] + fn type_key() -> *const c_void { + &WAKER_TYPE_KEY as *const u8 as *const c_void + } +} diff --git a/src/util/userdata.rs b/src/util/userdata.rs new file mode 100644 index 00000000..59c00223 --- /dev/null +++ b/src/util/userdata.rs @@ -0,0 +1,380 @@ +use std::ffi::CStr; +use std::os::raw::{c_int, c_void}; +use std::{ptr, str}; + +use crate::error::Result; +use crate::util::{check_stack, push_string, push_table, rawset_field, TypeKey}; + +// Pushes the userdata and attaches a metatable with __gc method. +// Internally uses 3 stack spaces, does not call checkstack. +pub(crate) unsafe fn push_internal_userdata( + state: *mut ffi::lua_State, + t: T, + protect: bool, +) -> Result<()> { + push_userdata(state, t, protect)?; + get_internal_metatable::(state); + ffi::lua_setmetatable(state, -2); + Ok(()) +} + +#[track_caller] +pub(crate) unsafe fn get_internal_metatable(state: *mut ffi::lua_State) { + ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, T::type_key()); + debug_assert!(ffi::lua_isnil(state, -1) == 0, "internal metatable not found"); +} + +// Initialize the internal metatable for a type T (with __gc method). +// Uses 6 stack spaces and calls checkstack. +pub(crate) unsafe fn init_internal_metatable( + state: *mut ffi::lua_State, + customize_fn: Option Result<()>>, +) -> Result<()> { + check_stack(state, 6)?; + + push_table(state, 0, 3, true)?; + + #[cfg(not(feature = "luau"))] + { + ffi::lua_pushcfunction(state, userdata_destructor::); + rawset_field(state, -2, "__gc")?; + } + + ffi::lua_pushboolean(state, 0); + rawset_field(state, -2, "__metatable")?; + + if let Some(f) = customize_fn { + f(state)?; + } + + protect_lua!(state, 1, 0, |state| { + ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, T::type_key()); + })?; + + Ok(()) +} + +// Uses 2 stack spaces, does not call checkstack +pub(crate) unsafe fn get_internal_userdata( + state: *mut ffi::lua_State, + index: c_int, + type_mt_ptr: *const c_void, +) -> *mut T { + let ud = ffi::lua_touserdata(state, index) as *mut T; + if ud.is_null() || ffi::lua_getmetatable(state, index) == 0 { + return ptr::null_mut(); + } + if !type_mt_ptr.is_null() { + let ud_mt_ptr = ffi::lua_topointer(state, -1); + ffi::lua_pop(state, 1); + if ud_mt_ptr != type_mt_ptr { + return ptr::null_mut(); + } + } else { + get_internal_metatable::(state); + let res = ffi::lua_rawequal(state, -1, -2); + ffi::lua_pop(state, 2); + if res == 0 { + return ptr::null_mut(); + } + } + ud +} + +// Internally uses 3 stack spaces, does not call checkstack. +#[inline] +pub(crate) unsafe fn push_userdata(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<()> { + #[cfg(not(feature = "luau"))] + let ud = if protect { + protect_lua!(state, 0, 1, |state| { + ffi::lua_newuserdata(state, std::mem::size_of::()) as *mut T + })? + } else { + ffi::lua_newuserdata(state, std::mem::size_of::()) as *mut T + }; + #[cfg(feature = "luau")] + let ud = if protect { + protect_lua!(state, 0, 1, |state| { ffi::lua_newuserdata_t::(state) })? + } else { + ffi::lua_newuserdata_t::(state) + }; + ptr::write(ud, t); + Ok(()) +} + +// Internally uses 3 stack spaces, does not call checkstack. +#[cfg(feature = "lua54")] +#[inline] +pub(crate) unsafe fn push_userdata_uv( + state: *mut ffi::lua_State, + t: T, + nuvalue: c_int, + protect: bool, +) -> Result<()> { + let ud = if protect { + protect_lua!(state, 0, 1, |state| { + ffi::lua_newuserdatauv(state, std::mem::size_of::(), nuvalue) as *mut T + })? + } else { + ffi::lua_newuserdatauv(state, std::mem::size_of::(), nuvalue) as *mut T + }; + ptr::write(ud, t); + Ok(()) +} + +#[inline] +pub(crate) unsafe fn get_userdata(state: *mut ffi::lua_State, index: c_int) -> *mut T { + let ud = ffi::lua_touserdata(state, index) as *mut T; + mlua_debug_assert!(!ud.is_null(), "userdata pointer is null"); + ud +} + +// Pops the userdata off of the top of the stack and returns it to rust, invalidating the lua +// userdata and gives it the special "destructed" userdata metatable. Userdata must not have been +// previously invalidated, and this method does not check for this. +// Uses 1 extra stack space and does not call checkstack. +pub(crate) unsafe fn take_userdata(state: *mut ffi::lua_State) -> T { + // We set the metatable of userdata on __gc to a special table with no __gc method and with + // metamethods that trigger an error on access. We do this so that it will not be double + // dropped, and also so that it cannot be used or identified as any particular userdata type + // after the first call to __gc. + get_destructed_userdata_metatable(state); + ffi::lua_setmetatable(state, -2); + let ud = get_userdata::(state, -1); + + // Update userdata tag to disable destructor and mark as destructed + #[cfg(feature = "luau")] + ffi::lua_setuserdatatag(state, -1, 1); + + ffi::lua_pop(state, 1); + ptr::read(ud) +} + +pub(crate) unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_State) { + let key = &DESTRUCTED_USERDATA_METATABLE as *const u8 as *const c_void; + ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, key); +} + +// Populates the given table with the appropriate members to be a userdata metatable for the given +// type. This function takes the given table at the `metatable` index, and adds an appropriate +// `__gc` member to it for the given type and a `__metatable` entry to protect the table from script +// access. The function also, if given a `field_getters` or `methods` tables, will create an +// `__index` metamethod (capturing previous one) to lookup in `field_getters` first, then `methods` +// and falling back to the captured `__index` if no matches found. +// The same is also applicable for `__newindex` metamethod and `field_setters` table. +// Internally uses 9 stack spaces and does not call checkstack. +pub(crate) unsafe fn init_userdata_metatable( + state: *mut ffi::lua_State, + metatable: c_int, + field_getters: Option, + field_setters: Option, + methods: Option, + extra_init: Option Result<()>>, +) -> Result<()> { + ffi::lua_pushvalue(state, metatable); + + if field_getters.is_some() || methods.is_some() { + // Push `__index` generator function + init_userdata_metatable_index(state)?; + + push_string(state, b"__index", true)?; + let index_type = ffi::lua_rawget(state, -3); + match index_type { + ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => { + for &idx in &[field_getters, methods] { + if let Some(idx) = idx { + ffi::lua_pushvalue(state, idx); + } else { + ffi::lua_pushnil(state); + } + } + + // Generate `__index` + protect_lua!(state, 4, 1, fn(state) ffi::lua_call(state, 3, 1))?; + } + _ => mlua_panic!("improper __index type {}", index_type), + } + + rawset_field(state, -2, "__index")?; + } + + if let Some(field_setters) = field_setters { + // Push `__newindex` generator function + init_userdata_metatable_newindex(state)?; + + push_string(state, b"__newindex", true)?; + let newindex_type = ffi::lua_rawget(state, -3); + match newindex_type { + ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => { + ffi::lua_pushvalue(state, field_setters); + // Generate `__newindex` + protect_lua!(state, 3, 1, fn(state) ffi::lua_call(state, 2, 1))?; + } + _ => mlua_panic!("improper __newindex type {}", newindex_type), + } + + rawset_field(state, -2, "__newindex")?; + } + + // Additional initialization + if let Some(extra_init) = extra_init { + extra_init(state)?; + } + + ffi::lua_pushboolean(state, 0); + rawset_field(state, -2, "__metatable")?; + + ffi::lua_pop(state, 1); + + Ok(()) +} + +unsafe extern "C-unwind" fn lua_error_impl(state: *mut ffi::lua_State) -> c_int { + ffi::lua_error(state); +} + +unsafe extern "C-unwind" fn lua_isfunction_impl(state: *mut ffi::lua_State) -> c_int { + ffi::lua_pushboolean(state, ffi::lua_isfunction(state, -1)); + 1 +} + +unsafe extern "C-unwind" fn lua_istable_impl(state: *mut ffi::lua_State) -> c_int { + ffi::lua_pushboolean(state, ffi::lua_istable(state, -1)); + 1 +} + +unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()> { + let index_key = &USERDATA_METATABLE_INDEX as *const u8 as *const _; + if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, index_key) == ffi::LUA_TFUNCTION { + return Ok(()); + } + ffi::lua_pop(state, 1); + + // Create and cache `__index` generator + let code = cstr!( + r#" + local error, isfunction, istable = ... + return function (__index, field_getters, methods) + -- Common case: has field getters and index is a table + if field_getters ~= nil and methods == nil and istable(__index) then + return function (self, key) + local field_getter = field_getters[key] + if field_getter ~= nil then + return field_getter(self) + end + return __index[key] + end + end + + return function (self, key) + if field_getters ~= nil then + local field_getter = field_getters[key] + if field_getter ~= nil then + return field_getter(self) + end + end + + if methods ~= nil then + local method = methods[key] + if method ~= nil then + return method + end + end + + if isfunction(__index) then + return __index(self, key) + elseif __index == nil then + error("attempt to get an unknown field '"..key.."'") + else + return __index[key] + end + end + end + "# + ); + let code_len = CStr::from_ptr(code).to_bytes().len(); + protect_lua!(state, 0, 1, |state| { + let ret = ffi::luaL_loadbuffer(state, code, code_len, cstr!("__mlua_index")); + if ret != ffi::LUA_OK { + ffi::lua_error(state); + } + ffi::lua_pushcfunction(state, lua_error_impl); + ffi::lua_pushcfunction(state, lua_isfunction_impl); + ffi::lua_pushcfunction(state, lua_istable_impl); + ffi::lua_call(state, 3, 1); + + #[cfg(feature = "luau-jit")] + if ffi::luau_codegen_supported() != 0 { + ffi::luau_codegen_compile(state, -1); + } + + // Store in the registry + ffi::lua_pushvalue(state, -1); + ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, index_key); + }) +} + +unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result<()> { + let newindex_key = &USERDATA_METATABLE_NEWINDEX as *const u8 as *const _; + if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, newindex_key) == ffi::LUA_TFUNCTION { + return Ok(()); + } + ffi::lua_pop(state, 1); + + // Create and cache `__newindex` generator + let code = cstr!( + r#" + local error, isfunction = ... + return function (__newindex, field_setters) + return function (self, key, value) + if field_setters ~= nil then + local field_setter = field_setters[key] + if field_setter ~= nil then + field_setter(self, value) + return + end + end + + if isfunction(__newindex) then + __newindex(self, key, value) + elseif __newindex == nil then + error("attempt to set an unknown field '"..key.."'") + else + __newindex[key] = value + end + end + end + "# + ); + let code_len = CStr::from_ptr(code).to_bytes().len(); + protect_lua!(state, 0, 1, |state| { + let ret = ffi::luaL_loadbuffer(state, code, code_len, cstr!("__mlua_newindex")); + if ret != ffi::LUA_OK { + ffi::lua_error(state); + } + ffi::lua_pushcfunction(state, lua_error_impl); + ffi::lua_pushcfunction(state, lua_isfunction_impl); + ffi::lua_call(state, 2, 1); + + #[cfg(feature = "luau-jit")] + if ffi::luau_codegen_supported() != 0 { + ffi::luau_codegen_compile(state, -1); + } + + // Store in the registry + ffi::lua_pushvalue(state, -1); + ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, newindex_key); + }) +} + +#[cfg(not(feature = "luau"))] +pub(crate) unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::lua_State) -> c_int { + // It's probably NOT a good idea to catch Rust panics in finalizer + // Lua 5.4 ignores it, other versions generates `LUA_ERRGCMM` without calling message handler + take_userdata::(state); + 0 +} + +pub(crate) static DESTRUCTED_USERDATA_METATABLE: u8 = 0; +static USERDATA_METATABLE_INDEX: u8 = 0; +static USERDATA_METATABLE_NEWINDEX: u8 = 0; From bba644e83f46f2ec6c55abdfab651cd64e272d68 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 29 Jul 2024 10:49:34 +0100 Subject: [PATCH 129/635] Fix compilation --- src/state/extra.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/state/extra.rs b/src/state/extra.rs index 741823b5..4b4fe4df 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -93,11 +93,12 @@ pub(crate) struct ExtraData { impl Drop for ExtraData { fn drop(&mut self) { - #[cfg(feature = "module")] unsafe { - self.inner.assume_init_drop(); + #[cfg(feature = "module")] + self.lua.assume_init_drop(); + + self.weak.assume_init_drop(); } - unsafe { self.weak.assume_init_drop() }; *self.registry_unref_list.lock() = None; } } From 94415065c08eac27e3234ed6c6bd180936d2282f Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 30 Jul 2024 23:59:29 +0100 Subject: [PATCH 130/635] Fix Lua String soundness when borrowing `&str` or `&[u8]`. Make borrowing using new `BorrowedStr` and `BorrowedBytes` types that holds strong reference to Lua. --- examples/async_http_server.rs | 4 +- src/conversion.rs | 10 +- src/lib.rs | 2 +- src/serde/de.rs | 4 +- src/string.rs | 185 ++++++++++++++++++++++++++++------ src/value.rs | 18 ++-- tests/conversion.rs | 2 +- tests/string.rs | 10 +- 8 files changed, 179 insertions(+), 56 deletions(-) diff --git a/examples/async_http_server.rs b/examples/async_http_server.rs index 030006fb..1cd5ef57 100644 --- a/examples/async_http_server.rs +++ b/examples/async_http_server.rs @@ -68,14 +68,14 @@ impl hyper::service::Service> for Svc { if let Some(headers) = lua_resp.get::<_, Option
>("headers")? { for pair in headers.pairs::() { let (h, v) = pair?; - resp = resp.header(&h, v.as_bytes()); + resp = resp.header(&h, &*v.as_bytes()); } } // Set body let body = lua_resp .get::<_, Option>("body")? - .map(|b| Full::new(Bytes::copy_from_slice(b.as_bytes())).boxed()) + .map(|b| Full::new(Bytes::copy_from_slice(&b.as_bytes())).boxed()) .unwrap_or_else(|| Empty::::new().boxed()); Ok(resp.body(body).unwrap()) diff --git a/src/conversion.rs b/src/conversion.rs index 72c9fbbc..0e77f50b 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -464,7 +464,7 @@ impl FromLua for CString { message: Some("expected string or number".to_string()), })?; - match CStr::from_bytes_with_nul(string.as_bytes_with_nul()) { + match CStr::from_bytes_with_nul(&string.as_bytes_with_nul()) { Ok(s) => Ok(s.into()), Err(_) => Err(Error::FromLuaConversionError { from: ty, @@ -500,7 +500,7 @@ impl FromLua for BString { fn from_lua(value: Value, lua: &Lua) -> Result { let ty = value.type_name(); match value { - Value::String(s) => Ok(s.as_bytes().into()), + Value::String(s) => Ok((*s.as_bytes()).into()), #[cfg(feature = "luau")] Value::UserData(ud) if ud.1 == crate::types::SubtypeId::Buffer => unsafe { let lua = ud.0.lua.lock(); @@ -509,15 +509,15 @@ impl FromLua for BString { mlua_assert!(!buf.is_null(), "invalid Luau buffer"); Ok(slice::from_raw_parts(buf as *const u8, size).into()) }, - _ => Ok(lua + _ => Ok((*lua .coerce_string(value)? .ok_or_else(|| Error::FromLuaConversionError { from: ty, to: "BString", message: Some("expected string or number".to_string()), })? - .as_bytes() - .into()), + .as_bytes()) + .into()), } } diff --git a/src/lib.rs b/src/lib.rs index 5491ecc3..f21ef6e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,7 +111,7 @@ pub use crate::multi::Variadic; pub use crate::state::{GCMode, Lua, LuaOptions}; // pub use crate::scope::Scope; pub use crate::stdlib::StdLib; -pub use crate::string::String; +pub use crate::string::{BorrowedBytes, BorrowedStr, String}; pub use crate::table::{Table, TableExt, TablePairs, TableSequence}; pub use crate::thread::{Thread, ThreadStatus}; pub use crate::types::{AppDataRef, AppDataRefMut, Integer, LightUserData, Number, RegistryKey}; diff --git a/src/serde/de.rs b/src/serde/de.rs index 7993d702..82e2abf6 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -135,8 +135,8 @@ impl<'de> serde::Deserializer<'de> for Deserializer { #[cfg(feature = "luau")] Value::Vector(_) => self.deserialize_seq(visitor), Value::String(s) => match s.to_str() { - Ok(s) => visitor.visit_str(s), - Err(_) => visitor.visit_bytes(s.as_bytes()), + Ok(s) => visitor.visit_str(&s), + Err(_) => visitor.visit_bytes(&s.as_bytes()), }, Value::Table(ref t) if t.raw_len() > 0 || t.is_array() => self.deserialize_seq(visitor), Value::Table(_) => self.deserialize_map(visitor), diff --git a/src/string.rs b/src/string.rs index ac026f12..58f7dc08 100644 --- a/src/string.rs +++ b/src/string.rs @@ -1,8 +1,9 @@ -use std::borrow::{Borrow, Cow}; +use std::borrow::Borrow; use std::hash::{Hash, Hasher}; +use std::ops::Deref; use std::os::raw::c_void; use std::string::String as StdString; -use std::{fmt, slice, str}; +use std::{cmp, fmt, slice, str}; #[cfg(feature = "serialize")] use { @@ -11,6 +12,7 @@ use { }; use crate::error::{Error, Result}; +use crate::state::LuaGuard; use crate::types::ValueRef; /// Handle to an internal Lua string. @@ -20,7 +22,7 @@ use crate::types::ValueRef; pub struct String(pub(crate) ValueRef); impl String { - /// Get a `&str` slice if the Lua string is valid UTF-8. + /// Get a [`BorrowedStr`] if the Lua string is valid UTF-8. /// /// # Examples /// @@ -39,15 +41,17 @@ impl String { /// # } /// ``` #[inline] - pub fn to_str(&self) -> Result<&str> { - str::from_utf8(self.as_bytes()).map_err(|e| Error::FromLuaConversionError { + pub fn to_str(&self) -> Result { + let BorrowedBytes(bytes, guard) = self.as_bytes(); + let s = str::from_utf8(bytes).map_err(|e| Error::FromLuaConversionError { from: "string", to: "&str", message: Some(e.to_string()), - }) + })?; + Ok(BorrowedStr(s, guard)) } - /// Converts this string to a [`Cow`]. + /// Converts this string to a [`StdString`]. /// /// Any non-Unicode sequences are replaced with [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD]. /// @@ -66,8 +70,8 @@ impl String { /// # } /// ``` #[inline] - pub fn to_string_lossy(&self) -> Cow<'_, str> { - StdString::from_utf8_lossy(self.as_bytes()) + pub fn to_string_lossy(&self) -> StdString { + StdString::from_utf8_lossy(&self.as_bytes()).into_owned() } /// Get the bytes that make up this string. @@ -88,13 +92,18 @@ impl String { /// # } /// ``` #[inline] - pub fn as_bytes(&self) -> &[u8] { - let nulled = self.as_bytes_with_nul(); - &nulled[..nulled.len() - 1] + pub fn as_bytes(&self) -> BorrowedBytes { + let (bytes, guard) = unsafe { self.to_slice() }; + BorrowedBytes(&bytes[..bytes.len() - 1], guard) } /// Get the bytes that make up this string, including the trailing nul byte. - pub fn as_bytes_with_nul(&self) -> &[u8] { + pub fn as_bytes_with_nul(&self) -> BorrowedBytes { + let (bytes, guard) = unsafe { self.to_slice() }; + BorrowedBytes(bytes, guard) + } + + unsafe fn to_slice(&self) -> (&[u8], LuaGuard) { let lua = self.0.lua.lock(); let ref_thread = lua.ref_thread(); unsafe { @@ -108,7 +117,7 @@ impl String { // string type let data = ffi::lua_tolstring(ref_thread, self.0.index, &mut size); - slice::from_raw_parts(data as *const u8, size + 1) + (slice::from_raw_parts(data as *const u8, size + 1), lua) } } @@ -127,7 +136,7 @@ impl fmt::Debug for String { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let bytes = self.as_bytes(); // Check if the string is valid utf8 - if let Ok(s) = str::from_utf8(bytes) { + if let Ok(s) = str::from_utf8(&bytes) { return s.fmt(f); } @@ -152,22 +161,9 @@ impl fmt::Debug for String { } } -impl AsRef<[u8]> for String { - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} - -impl Borrow<[u8]> for String { - fn borrow(&self) -> &[u8] { - self.as_bytes() - } -} - // Lua strings are basically &[u8] slices, so implement PartialEq for anything resembling that. // -// This makes our `String` comparable with `Vec`, `[u8]`, `&str`, `String` and `mlua::String` -// itself. +// This makes our `String` comparable with `Vec`, `[u8]`, `&str` and `String`. // // The only downside is that this disallows a comparison with `Cow`, as that only implements // `AsRef`, which collides with this impl. Requiring `AsRef` would fix that, but limit us @@ -181,6 +177,18 @@ where } } +impl PartialEq for String { + fn eq(&self, other: &String) -> bool { + self.as_bytes() == other.as_bytes() + } +} + +impl PartialEq<&String> for String { + fn eq(&self, other: &&String) -> bool { + self.as_bytes() == other.as_bytes() + } +} + impl Eq for String {} impl Hash for String { @@ -196,12 +204,127 @@ impl Serialize for String { S: Serializer, { match self.to_str() { - Ok(s) => serializer.serialize_str(s), - Err(_) => serializer.serialize_bytes(self.as_bytes()), + Ok(s) => serializer.serialize_str(&s), + Err(_) => serializer.serialize_bytes(&self.as_bytes()), } } } +/// A borrowed string (`&str`) that holds a strong reference to the Lua state. +pub struct BorrowedStr<'a>(&'a str, #[allow(unused)] LuaGuard); + +impl Deref for BorrowedStr<'_> { + type Target = str; + + #[inline(always)] + fn deref(&self) -> &str { + self.0 + } +} + +impl Borrow for BorrowedStr<'_> { + #[inline(always)] + fn borrow(&self) -> &str { + self.0 + } +} + +impl AsRef for BorrowedStr<'_> { + #[inline(always)] + fn as_ref(&self) -> &str { + self.0 + } +} + +impl fmt::Display for BorrowedStr<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl fmt::Debug for BorrowedStr<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl PartialEq for BorrowedStr<'_> +where + T: AsRef, +{ + fn eq(&self, other: &T) -> bool { + self.0 == other.as_ref() + } +} + +impl PartialOrd for BorrowedStr<'_> +where + T: AsRef, +{ + fn partial_cmp(&self, other: &T) -> Option { + self.0.partial_cmp(other.as_ref()) + } +} + +/// A borrowed byte slice (`&[u8]`) that holds a strong reference to the Lua state. +pub struct BorrowedBytes<'a>(&'a [u8], #[allow(unused)] LuaGuard); + +impl Deref for BorrowedBytes<'_> { + type Target = [u8]; + + #[inline(always)] + fn deref(&self) -> &[u8] { + self.0 + } +} + +impl Borrow<[u8]> for BorrowedBytes<'_> { + #[inline(always)] + fn borrow(&self) -> &[u8] { + self.0 + } +} + +impl AsRef<[u8]> for BorrowedBytes<'_> { + #[inline(always)] + fn as_ref(&self) -> &[u8] { + self.0 + } +} + +impl fmt::Debug for BorrowedBytes<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl PartialEq for BorrowedBytes<'_> +where + T: AsRef<[u8]>, +{ + fn eq(&self, other: &T) -> bool { + self.0 == other.as_ref() + } +} + +impl PartialOrd for BorrowedBytes<'_> +where + T: AsRef<[u8]>, +{ + fn partial_cmp(&self, other: &T) -> Option { + self.0.partial_cmp(other.as_ref()) + } +} + +impl<'a> IntoIterator for BorrowedBytes<'a> { + type Item = &'a u8; + type IntoIter = slice::Iter<'a, u8>; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + #[cfg(test)] mod assertions { use super::*; diff --git a/src/value.rs b/src/value.rs index dd2289d3..fe2a27d3 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,4 +1,3 @@ -use std::borrow::Cow; use std::cmp::Ordering; use std::collections::{vec_deque, HashSet, VecDeque}; use std::ops::{Deref, DerefMut}; @@ -12,7 +11,7 @@ use num_traits::FromPrimitive; use crate::error::{Error, Result}; use crate::function::Function; use crate::state::{Lua, RawLua}; -use crate::string::String; +use crate::string::{BorrowedStr, String}; use crate::table::Table; use crate::thread::Thread; use crate::types::{Integer, LightUserData, Number, SubtypeId}; @@ -330,19 +329,20 @@ impl Value { } } - /// Cast the value to [`str`]. + /// Cast the value to [`BorrowedStr`]. /// - /// If the value is a Lua [`String`], try to convert it to [`str`] or return `None` otherwise. + /// If the value is a Lua [`String`], try to convert it to [`BorrowedStr`] or return `None` + /// otherwise. #[inline] - pub fn as_str(&self) -> Option<&str> { + pub fn as_str(&self) -> Option { self.as_string().and_then(|s| s.to_str().ok()) } - /// Cast the value to [`Cow`]. + /// Cast the value to [`StdString`]. /// - /// If the value is a Lua [`String`], converts it to [`Cow`] or returns `None` otherwise. + /// If the value is a Lua [`String`], converts it to [`StdString`] or returns `None` otherwise. #[inline] - pub fn as_string_lossy(&self) -> Option> { + pub fn as_string_lossy(&self) -> Option { self.as_string().map(|s| s.to_string_lossy()) } @@ -478,7 +478,7 @@ impl Value { (Value::Integer(_) | Value::Number(_), _) => Ordering::Less, (_, Value::Integer(_) | Value::Number(_)) => Ordering::Greater, // String - (Value::String(a), Value::String(b)) => a.as_bytes().cmp(b.as_bytes()), + (Value::String(a), Value::String(b)) => a.as_bytes().cmp(&b.as_bytes()), (Value::String(_), _) => Ordering::Less, (_, Value::String(_)) => Ordering::Greater, // Other variants can be randomly ordered diff --git a/tests/conversion.rs b/tests/conversion.rs index 709b0ba8..3618111e 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -121,7 +121,7 @@ fn test_registry_value_into_lua() -> Result<()> { let r = lua.create_registry_value(&s)?; let value1 = lua.pack(&r)?; let value2 = lua.pack(r)?; - assert_eq!(value1.as_str(), Some("hello, world")); + assert_eq!(value1.as_str().as_deref(), Some("hello, world")); assert_eq!(value2.to_pointer(), value2.to_pointer()); // Push into stack diff --git a/tests/string.rs b/tests/string.rs index 3421c9cf..a986c29c 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -67,11 +67,11 @@ fn test_string_hash() -> Result<()> { let set: HashSet = lua.load(r#"{"hello", "world", "abc", 321}"#).eval()?; assert_eq!(set.len(), 4); - assert!(set.contains(b"hello".as_ref())); - assert!(set.contains(b"world".as_ref())); - assert!(set.contains(b"abc".as_ref())); - assert!(set.contains(b"321".as_ref())); - assert!(!set.contains(b"Hello".as_ref())); + assert!(set.contains(&lua.create_string("hello")?)); + assert!(set.contains(&lua.create_string("world")?)); + assert!(set.contains(&lua.create_string("abc")?)); + assert!(set.contains(&lua.create_string("321")?)); + assert!(!set.contains(&lua.create_string("Hello")?)); Ok(()) } From 2f8755dcc795dac0b24e25992977fc6fa3792a21 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 31 Jul 2024 10:57:26 +0100 Subject: [PATCH 131/635] Update README --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index ac04b0fc..da803a0c 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,7 @@ [Benchmarks]: https://github.com/khvzak/script-bench-rs [FAQ]: FAQ.md -> **Note** -> -> See v0.9 [release notes](https://github.com/khvzak/mlua/blob/main/docs/release_notes/v0.9.md). +# The main branch is the v0.10, development version of `mlua`. Please see the [v0.9](https://github.com/mlua-rs/mlua/tree/v0.9) branch for the stable versions of `mlua`. `mlua` is bindings to [Lua](https://www.lua.org) programming language for Rust with a goal to provide _safe_ (as far as it's possible), high level, easy to use, practical and flexible API. From a86d6ab330983f88fe8defd80aa0e297edb5c8fb Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 31 Jul 2024 11:13:48 +0100 Subject: [PATCH 132/635] Mark `LightUserData` as Send+Sync (`send` feature flag) --- src/types.rs | 5 +++++ src/value.rs | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/src/types.rs b/src/types.rs index 8c6810ef..9060bfac 100644 --- a/src/types.rs +++ b/src/types.rs @@ -41,6 +41,11 @@ pub(crate) enum SubtypeId { #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct LightUserData(pub *mut c_void); +#[cfg(feature = "send")] +unsafe impl Send for LightUserData {} +#[cfg(feature = "send")] +unsafe impl Sync for LightUserData {} + pub(crate) type Callback<'a> = Box Result + 'static>; pub(crate) struct Upvalue { diff --git a/src/value.rs b/src/value.rs index fe2a27d3..2429910a 100644 --- a/src/value.rs +++ b/src/value.rs @@ -934,6 +934,13 @@ pub trait FromLuaMulti: Sized { mod assertions { use super::*; + #[cfg(not(feature = "send"))] static_assertions::assert_not_impl_any!(Value: Send); + #[cfg(not(feature = "send"))] static_assertions::assert_not_impl_any!(MultiValue: Send); + + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(Value: Send, Sync); + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(MultiValue: Send, Sync); } From 833790967b31046c2981697f37fba445dcc482f6 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 31 Jul 2024 12:10:23 +0100 Subject: [PATCH 133/635] Update async examples (use `send` feature) --- Cargo.toml | 5 +-- examples/async_http_server.rs | 69 ++++++++++------------------------- examples/async_tcp_server.rs | 25 +++---------- src/thread.rs | 2 +- 4 files changed, 28 insertions(+), 73 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7c6ac6a6..626d5a03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,6 @@ libloading = { version = "0.8", optional = true } [dev-dependencies] trybuild = "1.0" -futures = "0.3.5" hyper = { version = "1.2", features = ["full"] } hyper-util = { version = "0.1.3", features = ["full"] } http-body-util = "0.1.1" @@ -101,11 +100,11 @@ required-features = ["async", "serialize", "macros"] [[example]] name = "async_http_server" -required-features = ["async", "macros"] +required-features = ["async", "macros", "send"] [[example]] name = "async_tcp_server" -required-features = ["async", "macros"] +required-features = ["async", "macros", "send"] [[example]] name = "guided_tour" diff --git a/examples/async_http_server.rs b/examples/async_http_server.rs index 1cd5ef57..4ac13a69 100644 --- a/examples/async_http_server.rs +++ b/examples/async_http_server.rs @@ -1,22 +1,17 @@ use std::convert::Infallible; use std::future::Future; use std::net::SocketAddr; -use std::rc::Rc; +use std::pin::Pin; -use futures::future::LocalBoxFuture; use http_body_util::combinators::BoxBody; use http_body_util::{BodyExt as _, Empty, Full}; use hyper::body::{Bytes, Incoming}; +use hyper::server::conn::http1; use hyper::{Request, Response}; use hyper_util::rt::TokioIo; -use hyper_util::server::conn::auto::Builder as ServerConnBuilder; use tokio::net::TcpListener; -use tokio::task::LocalSet; -use mlua::{ - chunk, Error as LuaError, Function, Lua, RegistryKey, String as LuaString, Table, UserData, - UserDataMethods, -}; +use mlua::{chunk, Error as LuaError, Function, Lua, String as LuaString, Table, UserData, UserDataMethods}; /// Wrapper around incoming request that implements UserData struct LuaRequest(SocketAddr, Request); @@ -32,33 +27,26 @@ impl UserData for LuaRequest { /// Service that handles incoming requests #[derive(Clone)] pub struct Svc { - lua: Rc, - handler: Rc, + handler: Function, peer_addr: SocketAddr, } impl Svc { - pub fn new(lua: Rc, handler: Rc, peer_addr: SocketAddr) -> Self { - Self { - lua, - handler, - peer_addr, - } + pub fn new(handler: Function, peer_addr: SocketAddr) -> Self { + Self { handler, peer_addr } } } impl hyper::service::Service> for Svc { type Response = Response>; type Error = LuaError; - type Future = LocalBoxFuture<'static, Result>; + type Future = Pin> + Send>>; fn call(&self, req: Request) -> Self::Future { // If handler returns an error then generate 5xx response - let lua = self.lua.clone(); - let handler_key = self.handler.clone(); + let handler = self.handler.clone(); let lua_req = LuaRequest(self.peer_addr, req); Box::pin(async move { - let handler: Function = lua.registry_value(&handler_key)?; match handler.call_async::<_, Table>(lua_req).await { Ok(lua_resp) => { let status = lua_resp.get::<_, Option>("status")?.unwrap_or(200); @@ -94,10 +82,10 @@ impl hyper::service::Service> for Svc { #[tokio::main(flavor = "current_thread")] async fn main() { - let lua = Rc::new(Lua::new()); + let lua = Lua::new(); // Create Lua handler function - let handler: RegistryKey = lua + let handler = lua .load(chunk! { function(req) return { @@ -111,15 +99,13 @@ async fn main() { } end }) - .eval() + .eval::() .expect("Failed to create Lua handler"); - let handler = Rc::new(handler); let listen_addr = "127.0.0.1:3000"; let listener = TcpListener::bind(listen_addr).await.unwrap(); println!("Listening on http://{listen_addr}"); - let local = LocalSet::new(); loop { let (stream, peer_addr) = match listener.accept().await { Ok(x) => x, @@ -129,29 +115,14 @@ async fn main() { } }; - let svc = Svc::new(lua.clone(), handler.clone(), peer_addr); - local - .run_until(async move { - let result = ServerConnBuilder::new(LocalExec) - .http1() - .serve_connection(TokioIo::new(stream), svc) - .await; - if let Err(err) = result { - eprintln!("Error serving connection: {err:?}"); - } - }) - .await; - } -} - -#[derive(Clone, Copy, Debug)] -struct LocalExec; - -impl hyper::rt::Executor for LocalExec -where - F: Future + 'static, // not requiring `Send` -{ - fn execute(&self, fut: F) { - tokio::task::spawn_local(fut); + let svc = Svc::new(handler.clone(), peer_addr); + tokio::task::spawn(async move { + if let Err(err) = http1::Builder::new() + .serve_connection(TokioIo::new(stream), svc) + .await + { + eprintln!("Error serving connection: {:?}", err); + } + }); } } diff --git a/examples/async_tcp_server.rs b/examples/async_tcp_server.rs index a6b65f7c..419d6025 100644 --- a/examples/async_tcp_server.rs +++ b/examples/async_tcp_server.rs @@ -1,12 +1,10 @@ use std::io; use std::net::SocketAddr; -use std::rc::Rc; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream}; -use tokio::task; -use mlua::{chunk, Function, Lua, RegistryKey, String as LuaString, UserData, UserDataMethods}; +use mlua::{chunk, Function, Lua, String as LuaString, UserData, UserDataMethods}; struct LuaTcpStream(TcpStream); @@ -33,14 +31,12 @@ impl UserData for LuaTcpStream { } } -async fn run_server(lua: Lua, handler: RegistryKey) -> io::Result<()> { +async fn run_server(handler: Function) -> io::Result<()> { let addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); let listener = TcpListener::bind(addr).await.expect("cannot bind addr"); println!("Listening on {}", addr); - let lua = Rc::new(lua); - let handler = Rc::new(handler); loop { let (stream, _) = match listener.accept().await { Ok(res) => res, @@ -48,11 +44,8 @@ async fn run_server(lua: Lua, handler: RegistryKey) -> io::Result<()> { Err(err) => return Err(err), }; - let lua = lua.clone(); let handler = handler.clone(); - task::spawn_local(async move { - let handler: Function = lua.registry_value(&handler).expect("cannot get Lua handler"); - + tokio::task::spawn(async move { let stream = LuaTcpStream(stream); if let Err(err) = handler.call_async::<_, ()>(stream).await { eprintln!("{}", err); @@ -66,7 +59,7 @@ async fn main() { let lua = Lua::new(); // Create Lua handler function - let handler_fn = lua + let handler = lua .load(chunk! { function(stream) local peer_addr = stream:peer_addr() @@ -88,15 +81,7 @@ async fn main() { .eval::() .expect("cannot create Lua handler"); - // Store it in the Registry - let handler = lua - .create_registry_value(handler_fn) - .expect("cannot store Lua handler"); - - task::LocalSet::new() - .run_until(run_server(lua, handler)) - .await - .expect("cannot run server") + run_server(handler).await.expect("cannot run server") } fn is_transient_error(e: &io::Error) -> bool { diff --git a/src/thread.rs b/src/thread.rs index af11f4ed..9ff0cc45 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -276,7 +276,7 @@ impl Thread { /// /// ``` /// # use mlua::{Lua, Result, Thread}; - /// use futures::stream::TryStreamExt; + /// use futures_util::stream::TryStreamExt; /// # #[tokio::main] /// # async fn main() -> Result<()> { /// # let lua = Lua::new(); From 5acf9d758dc007addd7ec5a0dea7380f876e0590 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 31 Jul 2024 13:10:39 +0100 Subject: [PATCH 134/635] Update CHANGELOG for v0.10.0-beta.1 --- CHANGELOG.md | 9 +++++++++ Cargo.toml | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91574951..1d1f8bc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## v0.10.0-beta.1 + +- Dropped `'lua` lifetime (subtypes now store a weak reference to Lua) +- Removed (experimental) owned types (they no longer needed) +- Make Lua types truly `Send` and `Sync` (when enabling `send` feature flag) +- Removed `UserData` impl for Rc/Arc types ("any" userdata functions can be used instead) +- `Lua::replace_registry_value` takes `&mut RegistryKey` +- `Lua::scope` temporary disabled (will be re-added in the next release) + ## v0.9.9 - Minimal Luau updated to 0.629 diff --git a/Cargo.toml b/Cargo.toml index 626d5a03..760205a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.10.0" # remember to update mlua_derive +version = "0.10.0-beta.1" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.71" edition = "2021" From b7d170ab9b620a875a75399bd9d7e992b1c66779 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 31 Jul 2024 22:34:45 +0100 Subject: [PATCH 135/635] Refactor `ThreadStatus`: - Add `ThreadStatus::Running` - Replace `ThreadStatus::Unresumable` with `ThreadStatus::Finished` Change `Error::CoroutineInactive` to `Error::CoroutineUnresumable` --- src/error.rs | 10 ++++----- src/thread.rs | 60 ++++++++++++++++++++++++------------------------- tests/luau.rs | 2 +- tests/thread.rs | 16 ++++++------- 4 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/error.rs b/src/error.rs index b1635c49..2f4d7305 100644 --- a/src/error.rs +++ b/src/error.rs @@ -98,17 +98,17 @@ pub enum Error { /// A string containing more detailed error information. message: Option, }, - /// [`Thread::resume`] was called on an inactive coroutine. + /// [`Thread::resume`] was called on an unresumable coroutine. /// - /// A coroutine is inactive if its main function has returned or if an error has occurred inside - /// the coroutine. Already running coroutines are also marked as inactive (unresumable). + /// A coroutine is unresumable if its main function has returned or if an error has occurred inside + /// the coroutine. Already running coroutines are also marked as unresumable. /// /// [`Thread::status`] can be used to check if the coroutine can be resumed without causing this /// error. /// /// [`Thread::resume`]: crate::Thread::resume /// [`Thread::status`]: crate::Thread::status - CoroutineInactive, + CoroutineUnresumable, /// An [`AnyUserData`] is not the expected type in a borrow. /// /// This error can only happen when manually using [`AnyUserData`], or when implementing @@ -259,7 +259,7 @@ impl fmt::Display for Error { Some(ref message) => write!(fmt, " ({message})"), } } - Error::CoroutineInactive => write!(fmt, "cannot resume inactive coroutine"), + Error::CoroutineUnresumable => write!(fmt, "coroutine is non-resumable"), Error::UserDataTypeMismatch => write!(fmt, "userdata is not expected type"), Error::UserDataDestructed => write!(fmt, "userdata has been destructed"), Error::UserDataBorrowError => write!(fmt, "error borrowing userdata"), diff --git a/src/thread.rs b/src/thread.rs index 9ff0cc45..4802ef5d 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -30,14 +30,14 @@ use { /// Status of a Lua thread (coroutine). #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum ThreadStatus { - /// The thread was just created, or is suspended because it has called `coroutine.yield`. + /// The thread was just created or is suspended (yielded). /// /// If a thread is in this state, it can be resumed by calling [`Thread::resume`]. - /// - /// [`Thread::resume`]: crate::Thread::resume Resumable, - /// Either the thread has finished executing, or the thread is currently running. - Unresumable, + /// The thread is currently running. + Running, + /// The thread has finished executing. + Finished, /// The thread has raised a Lua error during execution. Error, } @@ -108,7 +108,7 @@ impl Thread { /// /// // The coroutine has now returned, so `resume` will fail /// match thread.resume::<_, u32>(()) { - /// Err(Error::CoroutineInactive) => {}, + /// Err(Error::CoroutineUnresumable) => {}, /// unexpected => panic!("unexpected result {:?}", unexpected), /// } /// # Ok(()) @@ -120,9 +120,8 @@ impl Thread { R: FromLuaMulti, { let lua = self.0.lua.lock(); - - if unsafe { self.status_unprotected() } != ThreadStatus::Resumable { - return Err(Error::CoroutineInactive); + if self.status_inner(&lua) != ThreadStatus::Resumable { + return Err(Error::CoroutineUnresumable); } let state = lua.state(); @@ -170,25 +169,23 @@ impl Thread { /// Gets the status of the thread. pub fn status(&self) -> ThreadStatus { - let _guard = self.0.lua.lock(); - unsafe { self.status_unprotected() } + self.status_inner(&self.0.lua.lock()) } - /// Gets the status of the thread without locking the Lua state. - pub(crate) unsafe fn status_unprotected(&self) -> ThreadStatus { + /// Gets the status of the thread (internal implementation). + pub(crate) fn status_inner(&self, lua: &RawLua) -> ThreadStatus { let thread_state = self.state(); - // FIXME: skip double lock - if thread_state == self.0.lua.lock().state() { - // The coroutine is currently running - return ThreadStatus::Unresumable; + if thread_state == lua.state() { + // The thread is currently running + return ThreadStatus::Running; } - let status = ffi::lua_status(thread_state); + let status = unsafe { ffi::lua_status(thread_state) }; if status != ffi::LUA_OK && status != ffi::LUA_YIELD { ThreadStatus::Error - } else if status == ffi::LUA_YIELD || ffi::lua_gettop(thread_state) > 0 { + } else if status == ffi::LUA_YIELD || unsafe { ffi::lua_gettop(thread_state) > 0 } { ThreadStatus::Resumable } else { - ThreadStatus::Unresumable + ThreadStatus::Finished } } @@ -226,10 +223,11 @@ impl Thread { #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "luau"))))] pub fn reset(&self, func: crate::function::Function) -> Result<()> { let lua = self.0.lua.lock(); - let thread_state = self.state(); - if thread_state == lua.state() { + if self.status_inner(&lua) == ThreadStatus::Running { return Err(Error::runtime("cannot reset a running thread")); } + + let thread_state = self.state(); unsafe { #[cfg(all(feature = "lua54", not(feature = "vendored")))] let status = ffi::lua_resetthread(thread_state); @@ -398,7 +396,7 @@ impl Drop for AsyncThread { // For Lua 5.4 this also closes all pending to-be-closed variables if !lua.recycle_thread(&mut self.thread) { #[cfg(feature = "lua54")] - if self.thread.status_unprotected() == ThreadStatus::Error { + if self.thread.status_inner(&lua) == ThreadStatus::Error { #[cfg(not(feature = "vendored"))] ffi::lua_resetthread(self.thread.state()); #[cfg(feature = "vendored")] @@ -416,13 +414,13 @@ impl Stream for AsyncThread { fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let lua = self.thread.0.lua.lock(); + if self.thread.status_inner(&lua) != ThreadStatus::Resumable { + return Poll::Ready(None); + } + let state = lua.state(); let thread_state = self.thread.state(); unsafe { - if self.thread.status_unprotected() != ThreadStatus::Resumable { - return Poll::Ready(None); - } - let _sg = StackGuard::new(state); let _thread_sg = StackGuard::with_top(thread_state, 0); let _wg = WakerGuard::new(&lua, cx.waker()); @@ -454,13 +452,13 @@ impl Future for AsyncThread { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let lua = self.thread.0.lua.lock(); + if self.thread.status_inner(&lua) != ThreadStatus::Resumable { + return Poll::Ready(Err(Error::CoroutineUnresumable)); + } + let state = lua.state(); let thread_state = self.thread.state(); unsafe { - if self.thread.status_unprotected() != ThreadStatus::Resumable { - return Poll::Ready(Err(Error::CoroutineInactive)); - } - let _sg = StackGuard::new(state); let _thread_sg = StackGuard::with_top(thread_state, 0); let _wg = WakerGuard::new(&lua, cx.waker()); diff --git a/tests/luau.rs b/tests/luau.rs index db22c27a..557a2e34 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -362,7 +362,7 @@ fn test_interrupts() -> Result<()> { let result: i32 = co.resume(())?; assert_eq!(result, 6); assert_eq!(yield_count.load(Ordering::Relaxed), 7); - assert_eq!(co.status(), ThreadStatus::Unresumable); + assert_eq!(co.status(), ThreadStatus::Finished); // // Test errors in interrupts diff --git a/tests/thread.rs b/tests/thread.rs index 81f14e01..9b93eb04 100644 --- a/tests/thread.rs +++ b/tests/thread.rs @@ -31,7 +31,7 @@ fn test_thread() -> Result<()> { assert_eq!(thread.resume::<_, i64>(3)?, 6); assert_eq!(thread.status(), ThreadStatus::Resumable); assert_eq!(thread.resume::<_, i64>(4)?, 10); - assert_eq!(thread.status(), ThreadStatus::Unresumable); + assert_eq!(thread.status(), ThreadStatus::Finished); let accumulate = lua.create_thread( lua.load( @@ -85,17 +85,17 @@ fn test_thread() -> Result<()> { assert_eq!(thread.resume::<_, u32>(43)?, 987); match thread.resume::<_, u32>(()) { - Err(Error::CoroutineInactive) => {} + Err(Error::CoroutineUnresumable) => {} Err(_) => panic!("resuming dead coroutine error is not CoroutineInactive kind"), _ => panic!("resuming dead coroutine did not return error"), } // Already running thread must be unresumable let thread = lua.create_thread(lua.create_function(|lua, ()| { - assert_eq!(lua.current_thread().status(), ThreadStatus::Unresumable); + assert_eq!(lua.current_thread().status(), ThreadStatus::Running); let result = lua.current_thread().resume::<_, ()>(()); assert!( - matches!(result, Err(Error::CoroutineInactive)), + matches!(result, Err(Error::CoroutineUnresumable)), "unexpected result: {result:?}", ); Ok(()) @@ -128,7 +128,7 @@ fn test_thread_reset() -> Result<()> { assert_eq!(thread.status(), ThreadStatus::Resumable); assert_eq!(Arc::strong_count(&arc), 2); thread.resume::<_, ()>(())?; - assert_eq!(thread.status(), ThreadStatus::Unresumable); + assert_eq!(thread.status(), ThreadStatus::Finished); thread.reset(func.clone())?; lua.gc_collect()?; assert_eq!(Arc::strong_count(&arc), 1); @@ -147,11 +147,11 @@ fn test_thread_reset() -> Result<()> { // It's became possible to force reset thread by popping error object assert!(matches!( thread.status(), - ThreadStatus::Unresumable | ThreadStatus::Error + ThreadStatus::Finished | ThreadStatus::Error )); // Would pass in 5.4.4 - // assert!(thread.reset(func.clone()).is_ok()); - // assert_eq!(thread.status(), ThreadStatus::Resumable); + assert!(thread.reset(func.clone()).is_ok()); + assert_eq!(thread.status(), ThreadStatus::Resumable); } #[cfg(any(feature = "lua54", feature = "luau"))] { From 3641c9895963af0f50c5706dc26dd829b73b656b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 31 Jul 2024 23:42:43 +0100 Subject: [PATCH 136/635] Prepare for Rust 2024 edition (see rust-lang/rust#123748) Replace `IntoLua(Multi)` generic with positional arg (impl trait) where possible This allow to shorten syntax from `a.get::<_, T>` to `a.get::` --- benches/benchmark.rs | 18 ++--- benches/serde.rs | 4 +- examples/async_http_server.rs | 8 +-- examples/async_tcp_server.rs | 2 +- examples/guided_tour.rs | 12 ++-- src/chunk.rs | 9 ++- src/function.rs | 15 ++--- src/lib.rs | 1 - src/luau/package.rs | 4 +- src/state.rs | 17 ++--- src/state/raw.rs | 9 ++- src/table.rs | 68 ++++++++----------- src/thread.rs | 20 +++--- src/userdata.rs | 10 +-- src/userdata/ext.rs | 46 +++++-------- src/userdata/registry.rs | 4 +- tests/async.rs | 48 ++++++------- tests/byte_string.rs | 48 ++++++------- tests/chunk.rs | 2 +- tests/conversion.rs | 44 ++++++------ tests/error.rs | 4 +- tests/function.rs | 58 ++++++++-------- tests/hooks.rs | 4 +- tests/luau.rs | 48 ++++++------- tests/memory.rs | 8 +-- tests/static.rs | 8 +-- tests/table.rs | 60 ++++++++--------- tests/tests.rs | 122 +++++++++++++++++----------------- tests/thread.rs | 38 +++++------ tests/types.rs | 4 +- tests/userdata.rs | 48 ++++++------- 31 files changed, 378 insertions(+), 413 deletions(-) diff --git a/benches/benchmark.rs b/benches/benchmark.rs index da67f1d5..4a0f44bc 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -74,7 +74,7 @@ fn table_get_set(c: &mut Criterion) { .enumerate() { table.raw_set(s, i).unwrap(); - assert_eq!(table.raw_get::<_, usize>(s).unwrap(), i); + assert_eq!(table.raw_get::(s).unwrap(), i); } }, BatchSize::SmallInput, @@ -153,7 +153,7 @@ fn function_call_sum(c: &mut Criterion) { b.iter_batched( || collect_gc_twice(&lua), |_| { - assert_eq!(sum.call::<_, i64>((10, 20, 30)).unwrap(), 0); + assert_eq!(sum.call::((10, 20, 30)).unwrap(), 0); }, BatchSize::SmallInput, ); @@ -172,7 +172,7 @@ fn function_call_lua_sum(c: &mut Criterion) { b.iter_batched( || collect_gc_twice(&lua), |_| { - assert_eq!(sum.call::<_, i64>((10, 20, 30)).unwrap(), 0); + assert_eq!(sum.call::((10, 20, 30)).unwrap(), 0); }, BatchSize::SmallInput, ); @@ -195,7 +195,7 @@ fn function_call_concat(c: &mut Criterion) { }, |i| { assert_eq!( - concat.call::<_, LuaString>(("num:", i)).unwrap(), + concat.call::(("num:", i)).unwrap(), format!("num:{i}") ); }, @@ -221,7 +221,7 @@ fn function_call_lua_concat(c: &mut Criterion) { }, |i| { assert_eq!( - concat.call::<_, LuaString>(("num:", i)).unwrap(), + concat.call::(("num:", i)).unwrap(), format!("num:{i}") ); }, @@ -246,7 +246,7 @@ fn function_async_call_sum(c: &mut Criterion) { b.to_async(rt).iter_batched( || collect_gc_twice(&lua), |_| async { - assert_eq!(sum.call_async::<_, i64>((10, 20, 30)).await.unwrap(), 0); + assert_eq!(sum.call_async::((10, 20, 30)).await.unwrap(), 0); }, BatchSize::SmallInput, ); @@ -319,7 +319,7 @@ fn userdata_call_index(c: &mut Criterion) { b.iter_batched( || collect_gc_twice(&lua), |_| { - assert_eq!(index.call::<_, LuaString>(&ud).unwrap(), "test"); + assert_eq!(index.call::(&ud).unwrap(), "test"); }, BatchSize::SmallInput, ); @@ -349,7 +349,7 @@ fn userdata_call_method(c: &mut Criterion) { i.fetch_add(1, Ordering::Relaxed) }, |i| { - assert_eq!(method.call::<_, usize>((&ud, i)).unwrap(), 123 + i); + assert_eq!(method.call::((&ud, i)).unwrap(), 123 + i); }, BatchSize::SmallInput, ); @@ -384,7 +384,7 @@ fn userdata_async_call_method(c: &mut Criterion) { (method.clone(), ud.clone(), i.fetch_add(1, Ordering::Relaxed)) }, |(method, ud, i)| async move { - assert_eq!(method.call_async::<_, usize>((ud, i)).await.unwrap(), 123 + i); + assert_eq!(method.call_async::((ud, i)).await.unwrap(), 123 + i); }, BatchSize::SmallInput, ); diff --git a/benches/serde.rs b/benches/serde.rs index 2ff193d2..1dc26c08 100644 --- a/benches/serde.rs +++ b/benches/serde.rs @@ -37,7 +37,7 @@ fn encode_json(c: &mut Criterion) { b.iter_batched( || collect_gc_twice(&lua), |_| { - encode.call::<_, LuaString>(&table).unwrap(); + encode.call::(&table).unwrap(); }, BatchSize::SmallInput, ); @@ -69,7 +69,7 @@ fn decode_json(c: &mut Criterion) { b.iter_batched( || collect_gc_twice(&lua), |_| { - decode.call::<_, LuaTable>(json).unwrap(); + decode.call::(json).unwrap(); }, BatchSize::SmallInput, ); diff --git a/examples/async_http_server.rs b/examples/async_http_server.rs index 4ac13a69..5eb8c970 100644 --- a/examples/async_http_server.rs +++ b/examples/async_http_server.rs @@ -47,13 +47,13 @@ impl hyper::service::Service> for Svc { let handler = self.handler.clone(); let lua_req = LuaRequest(self.peer_addr, req); Box::pin(async move { - match handler.call_async::<_, Table>(lua_req).await { + match handler.call_async::
(lua_req).await { Ok(lua_resp) => { - let status = lua_resp.get::<_, Option>("status")?.unwrap_or(200); + let status = lua_resp.get::>("status")?.unwrap_or(200); let mut resp = Response::builder().status(status); // Set headers - if let Some(headers) = lua_resp.get::<_, Option
>("headers")? { + if let Some(headers) = lua_resp.get::>("headers")? { for pair in headers.pairs::() { let (h, v) = pair?; resp = resp.header(&h, &*v.as_bytes()); @@ -62,7 +62,7 @@ impl hyper::service::Service> for Svc { // Set body let body = lua_resp - .get::<_, Option>("body")? + .get::>("body")? .map(|b| Full::new(Bytes::copy_from_slice(&b.as_bytes())).boxed()) .unwrap_or_else(|| Empty::::new().boxed()); diff --git a/examples/async_tcp_server.rs b/examples/async_tcp_server.rs index 419d6025..676482d9 100644 --- a/examples/async_tcp_server.rs +++ b/examples/async_tcp_server.rs @@ -47,7 +47,7 @@ async fn run_server(handler: Function) -> io::Result<()> { let handler = handler.clone(); tokio::task::spawn(async move { let stream = LuaTcpStream(stream); - if let Err(err) = handler.call_async::<_, ()>(stream).await { + if let Err(err) = handler.call_async::<()>(stream).await { eprintln!("{}", err); } }); diff --git a/examples/guided_tour.rs b/examples/guided_tour.rs index 2a84d34a..26706e8d 100644 --- a/examples/guided_tour.rs +++ b/examples/guided_tour.rs @@ -17,8 +17,8 @@ fn main() -> Result<()> { globals.set("string_var", "hello")?; globals.set("int_var", 42)?; - assert_eq!(globals.get::<_, String>("string_var")?, "hello"); - assert_eq!(globals.get::<_, i64>("int_var")?, 42); + assert_eq!(globals.get::("string_var")?, "hello"); + assert_eq!(globals.get::("int_var")?, 42); // You can load and evaluate Lua code. The returned type of `Lua::load` is a builder // that allows you to change settings before running Lua code. Here, we are using it to set @@ -32,7 +32,7 @@ fn main() -> Result<()> { ) .set_name("example code") .exec()?; - assert_eq!(globals.get::<_, String>("global")?, "foobar"); + assert_eq!(globals.get::("global")?, "foobar"); assert_eq!(lua.load("1 + 1").eval::()?, 2); assert_eq!(lua.load("false == false").eval::()?, true); @@ -85,16 +85,16 @@ fn main() -> Result<()> { // You can load Lua functions let print: Function = globals.get("print")?; - print.call::<_, ()>("hello from rust")?; + print.call::<()>("hello from rust")?; // This API generally handles variadic using tuples. This is one way to call a function with // multiple parameters: - print.call::<_, ()>(("hello", "again", "from", "rust"))?; + print.call::<()>(("hello", "again", "from", "rust"))?; // But, you can also pass variadic arguments with the `Variadic` type. - print.call::<_, ()>(Variadic::from_iter( + print.call::<()>(Variadic::from_iter( ["hello", "yet", "again", "from", "rust"].iter().cloned(), ))?; diff --git a/src/chunk.rs b/src/chunk.rs index b1c74b3d..5e521df3 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -308,7 +308,7 @@ impl<'a> Chunk<'a> { /// All global variables (including the standard library!) are looked up in `_ENV`, so it may be /// necessary to populate the environment in order for scripts using custom environments to be /// useful. - pub fn set_environment(mut self, env: V) -> Self { + pub fn set_environment(mut self, env: impl IntoLua) -> Self { let lua = self.lua.lock(); let lua = lua.lua(); self.env = env @@ -343,7 +343,7 @@ impl<'a> Chunk<'a> { /// /// This is equivalent to calling the chunk function with no arguments and no return values. pub fn exec(self) -> Result<()> { - self.call::<_, ()>(())?; + self.call::<()>(())?; Ok(()) } @@ -404,7 +404,7 @@ impl<'a> Chunk<'a> { /// Load the chunk function and call it with the given arguments. /// /// This is equivalent to `into_function` and calling the resulting function. - pub fn call(self, args: A) -> Result { + pub fn call(self, args: impl IntoLuaMulti) -> Result { self.into_function()?.call(args) } @@ -417,9 +417,8 @@ impl<'a> Chunk<'a> { /// [`call`]: #method.call #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub async fn call_async(self, args: A) -> Result + pub async fn call_async(self, args: impl IntoLuaMulti) -> Result where - A: IntoLuaMulti, R: FromLuaMulti, { self.into_function()?.call_async(args).await diff --git a/src/function.rs b/src/function.rs index adbf9410..8c4aaa73 100644 --- a/src/function.rs +++ b/src/function.rs @@ -75,7 +75,7 @@ impl Function { /// /// let tostring: Function = globals.get("tostring")?; /// - /// assert_eq!(tostring.call::<_, String>(123)?, "123"); + /// assert_eq!(tostring.call::(123)?, "123"); /// /// # Ok(()) /// # } @@ -94,12 +94,12 @@ impl Function { /// end /// "#).eval()?; /// - /// assert_eq!(sum.call::<_, u32>((3, 4))?, 3 + 4); + /// assert_eq!(sum.call::((3, 4))?, 3 + 4); /// /// # Ok(()) /// # } /// ``` - pub fn call(&self, args: A) -> Result { + pub fn call(&self, args: impl IntoLuaMulti) -> Result { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { @@ -153,9 +153,8 @@ impl Function { /// [`AsyncThread`]: crate::AsyncThread #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub fn call_async(&self, args: A) -> impl Future> + pub fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> where - A: IntoLuaMulti, R: FromLuaMulti, { let lua = self.0.lua.lock(); @@ -188,15 +187,15 @@ impl Function { /// "#).eval()?; /// /// let bound_a = sum.bind(1)?; - /// assert_eq!(bound_a.call::<_, u32>(2)?, 1 + 2); + /// assert_eq!(bound_a.call::(2)?, 1 + 2); /// /// let bound_a_and_b = sum.bind(13)?.bind(57)?; - /// assert_eq!(bound_a_and_b.call::<_, u32>(())?, 13 + 57); + /// assert_eq!(bound_a_and_b.call::(())?, 13 + 57); /// /// # Ok(()) /// # } /// ``` - pub fn bind(&self, args: A) -> Result { + pub fn bind(&self, args: impl IntoLuaMulti) -> Result { unsafe extern "C-unwind" fn args_wrapper_impl(state: *mut ffi::lua_State) -> c_int { let nargs = ffi::lua_gettop(state); let nbinds = ffi::lua_tointeger(state, ffi::lua_upvalueindex(1)) as c_int; diff --git a/src/lib.rs b/src/lib.rs index f21ef6e1..61a06f26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,7 +73,6 @@ // Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any* // warnings at all. -#![doc(test(attr(warn(warnings))))] // FIXME: Remove this when rust-lang/rust#123748 is fixed #![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] diff --git a/src/luau/package.rs b/src/luau/package.rs index 23e78c81..94f1e12b 100644 --- a/src/luau/package.rs +++ b/src/luau/package.rs @@ -194,7 +194,7 @@ fn lua_loader(lua: &Lua, modname: StdString) -> Result { let key = lua.app_data_ref::().unwrap(); lua.registry_value::
(&key.0) }?; - let search_path = package.get::<_, StdString>("path").unwrap_or_default(); + let search_path = package.get::("path").unwrap_or_default(); if let Some(file_path) = package_searchpath(&modname, &search_path, false) { match fs::read(&file_path) { @@ -222,7 +222,7 @@ fn dylib_loader(lua: &Lua, modname: StdString) -> Result { let key = lua.app_data_ref::().unwrap(); lua.registry_value::
(&key.0) }?; - let search_cpath = package.get::<_, StdString>("cpath").unwrap_or_default(); + let search_cpath = package.get::("cpath").unwrap_or_default(); let find_symbol = |lib: &Library| unsafe { if let Ok(entry) = lib.get::(format!("luaopen_{modname}\0").as_bytes()) { diff --git a/src/state.rs b/src/state.rs index 12b3ca66..fef8f39a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -436,11 +436,11 @@ impl Lua { /// /// lua.sandbox(true)?; /// lua.load("var = 123").exec()?; - /// assert_eq!(lua.globals().get::<_, u32>("var")?, 123); + /// assert_eq!(lua.globals().get::("var")?, 123); /// /// // Restore the global environment (clear changes made in sandbox) /// lua.sandbox(false)?; - /// assert_eq!(lua.globals().get::<_, Option>("var")?, None); + /// assert_eq!(lua.globals().get::>("var")?, None); /// # Ok(()) /// # } /// ``` @@ -1497,7 +1497,7 @@ impl Lua { /// Converts a value that implements `IntoLua` into a `Value` instance. #[inline] - pub fn pack(&self, t: T) -> Result { + pub fn pack(&self, t: impl IntoLua) -> Result { t.into_lua(self) } @@ -1509,7 +1509,7 @@ impl Lua { /// Converts a value that implements `IntoLuaMulti` into a `MultiValue` instance. #[inline] - pub fn pack_multi(&self, t: T) -> Result { + pub fn pack_multi(&self, t: impl IntoLuaMulti) -> Result { t.into_lua_multi(self) } @@ -1523,10 +1523,7 @@ impl Lua { /// /// This value will be available to rust from all `Lua` instances which share the same main /// state. - pub fn set_named_registry_value(&self, name: &str, t: T) -> Result<()> - where - T: IntoLua, - { + pub fn set_named_registry_value(&self, name: &str, t: impl IntoLua) -> Result<()> { let lua = self.lock(); let state = lua.state(); unsafe { @@ -1575,7 +1572,7 @@ impl Lua { /// Be warned, garbage collection of values held inside the registry is not automatic, see /// [`RegistryKey`] for more details. /// However, dropped [`RegistryKey`]s automatically reused to store new values. - pub fn create_registry_value(&self, t: T) -> Result { + pub fn create_registry_value(&self, t: impl IntoLua) -> Result { let lua = self.lock(); let state = lua.state(); unsafe { @@ -1657,7 +1654,7 @@ impl Lua { /// An identifier used in [`RegistryKey`] may possibly be changed to a new value. /// /// See [`Lua::create_registry_value`] for more details. - pub fn replace_registry_value(&self, key: &mut RegistryKey, t: T) -> Result<()> { + pub fn replace_registry_value(&self, key: &mut RegistryKey, t: impl IntoLua) -> Result<()> { let lua = self.lock(); if !lua.owns_registry_value(key) { return Err(Error::MismatchedRegistryKey); diff --git a/src/state/raw.rs b/src/state/raw.rs index bd507041..fccf2540 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -3,7 +3,6 @@ use std::cell::{Cell, UnsafeCell}; use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_int, c_void}; use std::panic::resume_unwind; -use std::rc::Rc; use std::result::Result as StdResult; use std::sync::Arc; use std::{mem, ptr}; @@ -362,7 +361,7 @@ impl RawLua { callback_error_ext(state, extra, move |_| { let hook_cb = (*extra).hook_callback.clone(); let hook_cb = mlua_expect!(hook_cb, "no hook callback set in hook_proc"); - if Rc::strong_count(&hook_cb) > 2 { + if std::rc::Rc::strong_count(&hook_cb) > 2 { return Ok(()); // Don't allow recursion } let rawlua = (*extra).raw_lua(); @@ -372,7 +371,7 @@ impl RawLua { }) } - (*self.extra.get()).hook_callback = Some(Rc::new(callback)); + (*self.extra.get()).hook_callback = Some(std::rc::Rc::new(callback)); (*self.extra.get()).hook_thread = state; // Mark for what thread the hook is set ffi::lua_sethook(state, Some(hook_proc), triggers.mask(), triggers.count()); } @@ -1198,12 +1197,12 @@ impl RawLua { } let lua = self.lua(); - let coroutine = lua.globals().get::<_, Table>("coroutine")?; + let coroutine = lua.globals().get::
("coroutine")?; let env = lua.create_table_with_capacity(0, 3)?; env.set("get_poll", get_poll)?; // Cache `yield` function - env.set("yield", coroutine.get::<_, Function>("yield")?)?; + env.set("yield", coroutine.get::("yield")?)?; unsafe { env.set("unpack", lua.create_c_function(unpack)?)?; } diff --git a/src/table.rs b/src/table.rs index a491e1ab..8dfdf5f2 100644 --- a/src/table.rs +++ b/src/table.rs @@ -59,7 +59,7 @@ impl Table { /// ``` /// /// [`raw_set`]: #method.raw_set - pub fn set(&self, key: K, value: V) -> Result<()> { + pub fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { // Fast track if !self.has_metatable() { return self.raw_set(key, value); @@ -102,7 +102,7 @@ impl Table { /// ``` /// /// [`raw_get`]: #method.raw_get - pub fn get(&self, key: K) -> Result { + pub fn get(&self, key: impl IntoLua) -> Result { // Fast track if !self.has_metatable() { return self.raw_get(key); @@ -125,14 +125,14 @@ impl Table { /// Checks whether the table contains a non-nil value for `key`. /// /// This might invoke the `__index` metamethod. - pub fn contains_key(&self, key: K) -> Result { - Ok(self.get::<_, Value>(key)? != Value::Nil) + pub fn contains_key(&self, key: impl IntoLua) -> Result { + Ok(self.get::(key)? != Value::Nil) } /// Appends a value to the back of the table. /// /// This might invoke the `__len` and `__newindex` metamethods. - pub fn push(&self, value: V) -> Result<()> { + pub fn push(&self, value: impl IntoLua) -> Result<()> { // Fast track if !self.has_metatable() { return self.raw_push(value); @@ -220,12 +220,12 @@ impl Table { // If self does not define it, then check the other table. if let Some(mt) = self.get_metatable() { if mt.contains_key("__eq")? { - return mt.get::<_, Function>("__eq")?.call((self, other)); + return mt.get::("__eq")?.call((self, other)); } } if let Some(mt) = other.get_metatable() { if mt.contains_key("__eq")? { - return mt.get::<_, Function>("__eq")?.call((self, other)); + return mt.get::("__eq")?.call((self, other)); } } @@ -233,7 +233,7 @@ impl Table { } /// Sets a key-value pair without invoking metamethods. - pub fn raw_set(&self, key: K, value: V) -> Result<()> { + pub fn raw_set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { #[cfg(feature = "luau")] self.check_readonly_write()?; @@ -258,7 +258,7 @@ impl Table { } /// Gets the value associated to `key` without invoking metamethods. - pub fn raw_get(&self, key: K) -> Result { + pub fn raw_get(&self, key: impl IntoLua) -> Result { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { @@ -275,7 +275,7 @@ impl Table { /// Inserts element value at position `idx` to the table, shifting up the elements from /// `table[idx]`. The worst case complexity is O(n), where n is the table length. - pub fn raw_insert(&self, idx: Integer, value: V) -> Result<()> { + pub fn raw_insert(&self, idx: Integer, value: impl IntoLua) -> Result<()> { let size = self.raw_len() as Integer; if idx < 1 || idx > size + 1 { return Err(Error::runtime("index out of bounds")); @@ -301,7 +301,7 @@ impl Table { } /// Appends a value to the back of the table without invoking metamethods. - pub fn raw_push(&self, value: V) -> Result<()> { + pub fn raw_push(&self, value: impl IntoLua) -> Result<()> { #[cfg(feature = "luau")] self.check_readonly_write()?; @@ -357,7 +357,7 @@ impl Table { /// where n is the table length. /// /// For other key types this is equivalent to setting `table[key] = nil`. - pub fn raw_remove(&self, key: K) -> Result<()> { + pub fn raw_remove(&self, key: impl IntoLua) -> Result<()> { let lua = self.0.lua.lock(); let state = lua.state(); let key = key.into_lua(lua.lua())?; @@ -710,7 +710,7 @@ impl Table { /// Sets element value at position `idx` without invoking metamethods. #[doc(hidden)] - pub fn raw_seti(&self, idx: usize, value: V) -> Result<()> { + pub fn raw_seti(&self, idx: usize, value: impl IntoLua) -> Result<()> { #[cfg(feature = "luau")] self.check_readonly_write()?; @@ -864,9 +864,8 @@ pub trait TableExt: Sealed { /// /// The metamethod is called with the table as its first argument, followed by the passed /// arguments. - fn call(&self, args: A) -> Result + fn call(&self, args: impl IntoLuaMulti) -> Result where - A: IntoLuaMulti, R: FromLuaMulti; /// Asynchronously calls the table as function assuming it has `__call` metamethod. @@ -875,9 +874,8 @@ pub trait TableExt: Sealed { /// arguments. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async(&self, args: A) -> impl Future> + fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> where - A: IntoLuaMulti, R: FromLuaMulti; /// Gets the function associated to `key` from the table and executes it, @@ -887,9 +885,8 @@ pub trait TableExt: Sealed { /// `table.get::<_, Function>(key)?.call((table.clone(), arg1, ..., argN))` /// /// This might invoke the `__index` metamethod. - fn call_method(&self, name: &str, args: A) -> Result + fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result where - A: IntoLuaMulti, R: FromLuaMulti; /// Gets the function associated to `key` from the table and executes it, @@ -899,9 +896,8 @@ pub trait TableExt: Sealed { /// `table.get::<_, Function>(key)?.call(args)` /// /// This might invoke the `__index` metamethod. - fn call_function(&self, name: &str, args: A) -> Result + fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result where - A: IntoLuaMulti, R: FromLuaMulti; /// Gets the function associated to `key` from the table and asynchronously executes it, @@ -912,9 +908,8 @@ pub trait TableExt: Sealed { /// This might invoke the `__index` metamethod. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_method(&self, name: &str, args: A) -> impl Future> + fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> where - A: IntoLuaMulti, R: FromLuaMulti; /// Gets the function associated to `key` from the table and asynchronously executes it, @@ -925,16 +920,14 @@ pub trait TableExt: Sealed { /// This might invoke the `__index` metamethod. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_function(&self, name: &str, args: A) -> impl Future> + fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> where - A: IntoLuaMulti, R: FromLuaMulti; } impl TableExt for Table { - fn call(&self, args: A) -> Result + fn call(&self, args: impl IntoLuaMulti) -> Result where - A: IntoLuaMulti, R: FromLuaMulti, { // Convert table to a function and call via pcall that respects the `__call` metamethod. @@ -942,9 +935,8 @@ impl TableExt for Table { } #[cfg(feature = "async")] - fn call_async(&self, args: A) -> impl Future> + fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> where - A: IntoLuaMulti, R: FromLuaMulti, { let lua = self.0.lua.lock(); @@ -955,41 +947,37 @@ impl TableExt for Table { } } - fn call_method(&self, name: &str, args: A) -> Result + fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result where - A: IntoLuaMulti, R: FromLuaMulti, { - self.get::<_, Function>(name)?.call((self, args)) + self.get::(name)?.call((self, args)) } - fn call_function(&self, name: &str, args: A) -> Result + fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result where - A: IntoLuaMulti, R: FromLuaMulti, { - self.get::<_, Function>(name)?.call(args) + self.get::(name)?.call(args) } #[cfg(feature = "async")] - fn call_async_method(&self, name: &str, args: A) -> impl Future> + fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> where - A: IntoLuaMulti, R: FromLuaMulti, { self.call_async_function(name, (self, args)) } #[cfg(feature = "async")] - fn call_async_function(&self, name: &str, args: A) -> impl Future> + fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> where - A: IntoLuaMulti, R: FromLuaMulti, { let lua = self.0.lua.lock(); let args = args.into_lua_multi(lua.lua()); async move { - let func = self.get::<_, Function>(name)?; + let func = self.get::(name)?; func.call_async(args?).await } } diff --git a/src/thread.rs b/src/thread.rs index 4802ef5d..8c9b9d3f 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -103,20 +103,19 @@ impl Thread { /// end) /// "#).eval()?; /// - /// assert_eq!(thread.resume::<_, u32>(42)?, 123); - /// assert_eq!(thread.resume::<_, u32>(43)?, 987); + /// assert_eq!(thread.resume::(42)?, 123); + /// assert_eq!(thread.resume::(43)?, 987); /// /// // The coroutine has now returned, so `resume` will fail - /// match thread.resume::<_, u32>(()) { + /// match thread.resume::(()) { /// Err(Error::CoroutineUnresumable) => {}, /// unexpected => panic!("unexpected result {:?}", unexpected), /// } /// # Ok(()) /// # } /// ``` - pub fn resume(&self, args: A) -> Result + pub fn resume(&self, args: impl IntoLuaMulti) -> Result where - A: IntoLuaMulti, R: FromLuaMulti, { let lua = self.0.lua.lock(); @@ -141,7 +140,7 @@ impl Thread { /// Resumes execution of this thread. /// /// It's similar to `resume()` but leaves `nresults` values on the thread stack. - unsafe fn resume_inner(&self, args: A) -> Result { + unsafe fn resume_inner(&self, args: impl IntoLuaMulti) -> Result { let lua = self.0.lua.lock(); let state = lua.state(); let thread_state = self.state(); @@ -288,7 +287,7 @@ impl Thread { /// end) /// "#).eval()?; /// - /// let mut stream = thread.into_async::<_, i64>(1); + /// let mut stream = thread.into_async::(1); /// let mut sum = 0; /// while let Some(n) = stream.try_next().await? { /// sum += n; @@ -301,9 +300,8 @@ impl Thread { /// ``` #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub fn into_async(self, args: A) -> AsyncThread + pub fn into_async(self, args: impl IntoLuaMulti) -> AsyncThread where - A: IntoLuaMulti, R: FromLuaMulti, { let lua = self.0.lua.lock(); @@ -334,14 +332,14 @@ impl Thread { /// let lua = Lua::new(); /// let thread = lua.create_thread(lua.create_function(|lua2, ()| { /// lua2.load("var = 123").exec()?; - /// assert_eq!(lua2.globals().get::<_, u32>("var")?, 123); + /// assert_eq!(lua2.globals().get::("var")?, 123); /// Ok(()) /// })?)?; /// thread.sandbox()?; /// thread.resume(())?; /// /// // The global environment should be unchanged - /// assert_eq!(lua.globals().get::<_, Option>("var")?, None); + /// assert_eq!(lua.globals().get::>("var")?, None); /// # Ok(()) /// # } /// ``` diff --git a/src/userdata.rs b/src/userdata.rs index 54f5e44e..d2f48acd 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -710,7 +710,7 @@ impl AnyUserData { /// [`user_value`]: #method.user_value /// [`set_nth_user_value`]: #method.set_nth_user_value #[inline] - pub fn set_user_value(&self, v: V) -> Result<()> { + pub fn set_user_value(&self, v: impl IntoLua) -> Result<()> { self.set_nth_user_value(1, v) } @@ -741,7 +741,7 @@ impl AnyUserData { /// For other Lua versions this functionality is provided using a wrapping table. /// /// [`nth_user_value`]: #method.nth_user_value - pub fn set_nth_user_value(&self, n: usize, v: V) -> Result<()> { + pub fn set_nth_user_value(&self, n: usize, v: impl IntoLua) -> Result<()> { if n < 1 || n > u16::MAX as usize { return Err(Error::runtime("user value index out of bounds")); } @@ -840,7 +840,7 @@ impl AnyUserData { /// The value can be retrieved with [`named_user_value`]. /// /// [`named_user_value`]: #method.named_user_value - pub fn set_named_user_value(&self, name: &str, v: V) -> Result<()> { + pub fn set_named_user_value(&self, name: &str, v: impl IntoLua) -> Result<()> { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { @@ -992,7 +992,7 @@ impl AnyUserData { } if mt.contains_key("__eq")? { - return mt.get::<_, Function>("__eq")?.call((self, other)); + return mt.get::("__eq")?.call((self, other)); } Ok(false) @@ -1075,7 +1075,7 @@ impl UserDataMetatable { /// Access to restricted metamethods such as `__gc` or `__metatable` will cause an error. /// Setting `__index` or `__newindex` metamethods is also restricted because their values are /// cached for `mlua` internal usage. - pub fn set(&self, key: impl AsRef, value: V) -> Result<()> { + pub fn set(&self, key: impl AsRef, value: impl IntoLua) -> Result<()> { let key = MetaMethod::validate(key.as_ref())?; // `__index` and `__newindex` cannot be changed in runtime, because values are cached if key == MetaMethod::Index || key == MetaMethod::NewIndex { diff --git a/src/userdata/ext.rs b/src/userdata/ext.rs index 2156b5ef..42a257e9 100644 --- a/src/userdata/ext.rs +++ b/src/userdata/ext.rs @@ -9,18 +9,17 @@ use std::future::Future; /// An extension trait for [`AnyUserData`] that provides a variety of convenient functionality. pub trait AnyUserDataExt: Sealed { /// Gets the value associated to `key` from the userdata, assuming it has `__index` metamethod. - fn get(&self, key: K) -> Result; + fn get(&self, key: impl IntoLua) -> Result; /// Sets the value associated to `key` in the userdata, assuming it has `__newindex` metamethod. - fn set(&self, key: K, value: V) -> Result<()>; + fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()>; /// Calls the userdata as a function assuming it has `__call` metamethod. /// /// The metamethod is called with the userdata as its first argument, followed by the passed /// arguments. - fn call(&self, args: A) -> Result + fn call(&self, args: impl IntoLuaMulti) -> Result where - A: IntoLuaMulti, R: FromLuaMulti; /// Asynchronously calls the userdata as a function assuming it has `__call` metamethod. @@ -29,16 +28,14 @@ pub trait AnyUserDataExt: Sealed { /// arguments. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async(&self, args: A) -> impl Future> + fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> where - A: IntoLuaMulti, R: FromLuaMulti; /// Calls the userdata method, assuming it has `__index` metamethod /// and a function associated to `name`. - fn call_method(&self, name: &str, args: A) -> Result + fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result where - A: IntoLuaMulti, R: FromLuaMulti; /// Gets the function associated to `key` from the table and asynchronously executes it, @@ -49,9 +46,8 @@ pub trait AnyUserDataExt: Sealed { /// This might invoke the `__index` metamethod. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_method(&self, name: &str, args: A) -> impl Future> + fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> where - A: IntoLuaMulti, R: FromLuaMulti; /// Gets the function associated to `key` from the table and executes it, @@ -61,9 +57,8 @@ pub trait AnyUserDataExt: Sealed { /// `table.get::<_, Function>(key)?.call(args)` /// /// This might invoke the `__index` metamethod. - fn call_function(&self, name: &str, args: A) -> Result + fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result where - A: IntoLuaMulti, R: FromLuaMulti; /// Gets the function associated to `key` from the table and asynchronously executes it, @@ -74,14 +69,13 @@ pub trait AnyUserDataExt: Sealed { /// This might invoke the `__index` metamethod. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_function(&self, name: &str, args: A) -> impl Future> + fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> where - A: IntoLuaMulti, R: FromLuaMulti; } impl AnyUserDataExt for AnyUserData { - fn get(&self, key: K) -> Result { + fn get(&self, key: impl IntoLua) -> Result { let metatable = self.get_metatable()?; match metatable.get::(MetaMethod::Index)? { Value::Table(table) => table.raw_get(key), @@ -90,7 +84,7 @@ impl AnyUserDataExt for AnyUserData { } } - fn set(&self, key: K, value: V) -> Result<()> { + fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { let metatable = self.get_metatable()?; match metatable.get::(MetaMethod::NewIndex)? { Value::Table(table) => table.raw_set(key, value), @@ -99,9 +93,8 @@ impl AnyUserDataExt for AnyUserData { } } - fn call(&self, args: A) -> Result + fn call(&self, args: impl IntoLuaMulti) -> Result where - A: IntoLuaMulti, R: FromLuaMulti, { let metatable = self.get_metatable()?; @@ -112,9 +105,8 @@ impl AnyUserDataExt for AnyUserData { } #[cfg(feature = "async")] - fn call_async(&self, args: A) -> impl Future> + fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> where - A: IntoLuaMulti, R: FromLuaMulti, { let lua = self.0.lua.lock(); @@ -128,26 +120,23 @@ impl AnyUserDataExt for AnyUserData { } } - fn call_method(&self, name: &str, args: A) -> Result + fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result where - A: IntoLuaMulti, R: FromLuaMulti, { self.call_function(name, (self, args)) } #[cfg(feature = "async")] - fn call_async_method(&self, name: &str, args: A) -> impl Future> + fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> where - A: IntoLuaMulti, R: FromLuaMulti, { self.call_async_function(name, (self, args)) } - fn call_function(&self, name: &str, args: A) -> Result + fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result where - A: IntoLuaMulti, R: FromLuaMulti, { match self.get(name)? { @@ -160,15 +149,14 @@ impl AnyUserDataExt for AnyUserData { } #[cfg(feature = "async")] - fn call_async_function(&self, name: &str, args: A) -> impl Future> + fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> where - A: IntoLuaMulti, R: FromLuaMulti, { let lua = self.0.lua.lock(); let args = args.into_lua_multi(lua.lua()); async move { - match self.get::<_, Value>(name)? { + match self.get::(name)? { Value::Function(func) => func.call_async(args?).await, val => { let msg = format!("attempt to call a {} value", val.type_name()); diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index eb6c02f2..9c7bf033 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -267,9 +267,7 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { }) } - pub(crate) fn check_meta_field(lua: &Lua, name: &str, value: V) -> Result - where - V: IntoLua, + pub(crate) fn check_meta_field(lua: &Lua, name: &str, value: impl IntoLua) -> Result { let value = value.into_lua(lua)?; if name == MetaMethod::Index || name == MetaMethod::NewIndex { diff --git a/tests/async.rs b/tests/async.rs index 782075ee..871ebf0a 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -72,16 +72,16 @@ async fn test_async_call() -> Result<()> { Ok(format!("hello, {}!", name)) })?; - match hello.call::<_, ()>("alex") { + match hello.call::<()>("alex") { Err(Error::RuntimeError(_)) => {} _ => panic!("non-async executing async function must fail on the yield stage with RuntimeError"), }; - assert_eq!(hello.call_async::<_, String>("alex").await?, "hello, alex!"); + assert_eq!(hello.call_async::("alex").await?, "hello, alex!"); // Executing non-async functions using async call is allowed let sum = lua.create_function(|_lua, (a, b): (i64, i64)| return Ok(a + b))?; - assert_eq!(sum.call_async::<_, i64>((5, 1)).await?, 6); + assert_eq!(sum.call_async::((5, 1)).await?, 6); Ok(()) } @@ -95,7 +95,7 @@ async fn test_async_call_many_returns() -> Result<()> { Ok(("a", "b", "c", 1)) })?; - let vals = hello.call_async::<_, MultiValue>(()).await?; + let vals = hello.call_async::(()).await?; assert_eq!(vals.len(), 4); assert_eq!(vals[0].to_string()?, "a"); assert_eq!(vals[1].to_string()?, "b"); @@ -158,7 +158,7 @@ async fn test_async_handle_yield() -> Result<()> { "#, ) .eval::()?; - assert_eq!(min.call_async::<_, i64>((-1, 1)).await?, -1); + assert_eq!(min.call_async::((-1, 1)).await?, -1); Ok(()) } @@ -227,15 +227,15 @@ async fn test_async_lua54_to_be_closed() -> Result<()> { let f = lua.load(code).into_function()?; // Test close using call_async - let _ = f.call_async::<_, ()>(()).await; - assert_eq!(globals.get::<_, usize>("close_count")?, 1); + let _ = f.call_async::<()>(()).await; + assert_eq!(globals.get::("close_count")?, 1); // Don't close by default when awaiting async threads let co = lua.create_thread(f.clone())?; - let _ = co.clone().into_async::<_, ()>(()).await; - assert_eq!(globals.get::<_, usize>("close_count")?, 1); + let _ = co.clone().into_async::<()>(()).await; + assert_eq!(globals.get::("close_count")?, 1); let _ = co.reset(f); - assert_eq!(globals.get::<_, usize>("close_count")?, 2); + assert_eq!(globals.get::("close_count")?, 2); Ok(()) } @@ -259,7 +259,7 @@ async fn test_async_thread_stream() -> Result<()> { .eval()?, )?; - let mut stream = thread.into_async::<_, i64>(1); + let mut stream = thread.into_async::(1); let mut sum = 0; while let Some(n) = stream.try_next().await? { sum += n; @@ -307,7 +307,7 @@ fn test_async_thread_capture() -> Result<()> { let thread = lua.create_thread(f)?; // After first resume, `v: Value` is captured in the coroutine - thread.resume::<_, ()>("abc").unwrap(); + thread.resume::<()>("abc").unwrap(); drop(thread); Ok(()) @@ -323,7 +323,7 @@ async fn test_async_table() -> Result<()> { let get_value = lua.create_async_function(|_, table: Table| async move { sleep_ms(10).await; - table.get::<_, i64>("val") + table.get::("val") })?; table.set("get_value", get_value)?; @@ -339,11 +339,11 @@ async fn test_async_table() -> Result<()> { })?; table.set("sleep", sleep)?; - assert_eq!(table.call_async_method::<_, i64>("get_value", ()).await?, 10); - table.call_async_method("set_value", 15).await?; - assert_eq!(table.call_async_method::<_, i64>("get_value", ()).await?, 15); + assert_eq!(table.call_async_method::("get_value", ()).await?, 10); + table.call_async_method::<()>("set_value", 15).await?; + assert_eq!(table.call_async_method::("get_value", ()).await?, 15); assert_eq!( - table.call_async_function::<_, String>("sleep", 7).await?, + table.call_async_function::("sleep", 7).await?, "elapsed:7ms" ); @@ -365,9 +365,9 @@ async fn test_async_thread_pool() -> Result<()> { Ok(format!("elapsed:{}ms", n)) })?; - assert!(error_f.call_async::<_, ()>(()).await.is_err()); + assert!(error_f.call_async::<()>(()).await.is_err()); // Next call should use cached thread - assert_eq!(sleep.call_async::<_, String>(3).await?, "elapsed:3ms"); + assert_eq!(sleep.call_async::(3).await?, "elapsed:3ms"); Ok(()) } @@ -460,13 +460,13 @@ async fn test_async_userdata() -> Result<()> { .exec_async() .await?; - userdata.call_async_method("set_value", 24).await?; + userdata.call_async_method::<()>("set_value", 24).await?; let n: u64 = userdata.call_async_method("get_value", ()).await?; assert_eq!(n, 24); - userdata.call_async_function("sleep", 15).await?; + userdata.call_async_function::<()>("sleep", 15).await?; #[cfg(not(any(feature = "lua51", feature = "luau")))] - assert_eq!(userdata.call_async::<_, String>(()).await?, "elapsed:24ms"); + assert_eq!(userdata.call_async::(()).await?, "elapsed:24ms"); Ok(()) } @@ -485,7 +485,7 @@ async fn test_async_thread_error() -> Result<()> { let result = lua .load("function x(...) error(...) end x(...)") .set_name("chunk") - .call_async::<_, ()>(MyUserData) + .call_async::<()>(MyUserData) .await; assert!( matches!(result, Err(Error::RuntimeError(cause)) if cause.contains("myuserdata error")), @@ -510,7 +510,7 @@ async fn test_async_terminate() -> Result<()> { } })?; - let _ = tokio::time::timeout(Duration::from_millis(30), func.call_async::<_, ()>(())).await; + let _ = tokio::time::timeout(Duration::from_millis(30), func.call_async::<()>(())).await; lua.gc_collect()?; assert!(mutex.try_lock().is_ok()); diff --git a/tests/byte_string.rs b/tests/byte_string.rs index 48be2d9b..76e43e14 100644 --- a/tests/byte_string.rs +++ b/tests/byte_string.rs @@ -22,38 +22,38 @@ fn test_byte_string_round_trip() -> Result<()> { let globals = lua.globals(); - let isi = globals.get::<_, BString>("invalid_sequence_identifier")?; + let isi = globals.get::("invalid_sequence_identifier")?; assert_eq!(isi, [0xa0, 0xa1].as_ref()); - let i2os2 = globals.get::<_, BString>("invalid_2_octet_sequence_2nd")?; + let i2os2 = globals.get::("invalid_2_octet_sequence_2nd")?; assert_eq!(i2os2, [0xc3, 0x28].as_ref()); - let i3os2 = globals.get::<_, BString>("invalid_3_octet_sequence_2nd")?; + let i3os2 = globals.get::("invalid_3_octet_sequence_2nd")?; assert_eq!(i3os2, [0xe2, 0x28, 0xa1].as_ref()); - let i3os3 = globals.get::<_, BString>("invalid_3_octet_sequence_3rd")?; + let i3os3 = globals.get::("invalid_3_octet_sequence_3rd")?; assert_eq!(i3os3, [0xe2, 0x82, 0x28].as_ref()); - let i4os2 = globals.get::<_, BString>("invalid_4_octet_sequence_2nd")?; + let i4os2 = globals.get::("invalid_4_octet_sequence_2nd")?; assert_eq!(i4os2, [0xf0, 0x28, 0x8c, 0xbc].as_ref()); - let i4os3 = globals.get::<_, BString>("invalid_4_octet_sequence_3rd")?; + let i4os3 = globals.get::("invalid_4_octet_sequence_3rd")?; assert_eq!(i4os3, [0xf0, 0x90, 0x28, 0xbc].as_ref()); - let i4os4 = globals.get::<_, BString>("invalid_4_octet_sequence_4th")?; + let i4os4 = globals.get::("invalid_4_octet_sequence_4th")?; assert_eq!(i4os4, [0xf0, 0x28, 0x8c, 0x28].as_ref()); - let aas = globals.get::<_, BString>("an_actual_string")?; + let aas = globals.get::("an_actual_string")?; assert_eq!(aas, b"Hello, world!".as_ref()); - globals.set::<_, &BStr>("bstr_invalid_sequence_identifier", isi.as_ref())?; - globals.set::<_, &BStr>("bstr_invalid_2_octet_sequence_2nd", i2os2.as_ref())?; - globals.set::<_, &BStr>("bstr_invalid_3_octet_sequence_2nd", i3os2.as_ref())?; - globals.set::<_, &BStr>("bstr_invalid_3_octet_sequence_3rd", i3os3.as_ref())?; - globals.set::<_, &BStr>("bstr_invalid_4_octet_sequence_2nd", i4os2.as_ref())?; - globals.set::<_, &BStr>("bstr_invalid_4_octet_sequence_3rd", i4os3.as_ref())?; - globals.set::<_, &BStr>("bstr_invalid_4_octet_sequence_4th", i4os4.as_ref())?; - globals.set::<_, &BStr>("bstr_an_actual_string", aas.as_ref())?; + globals.set("bstr_invalid_sequence_identifier", isi.as_ref() as &BStr)?; + globals.set("bstr_invalid_2_octet_sequence_2nd", i2os2.as_ref() as &BStr)?; + globals.set("bstr_invalid_3_octet_sequence_2nd", i3os2.as_ref() as &BStr)?; + globals.set("bstr_invalid_3_octet_sequence_3rd", i3os3.as_ref() as &BStr)?; + globals.set("bstr_invalid_4_octet_sequence_2nd", i4os2.as_ref() as &BStr)?; + globals.set("bstr_invalid_4_octet_sequence_3rd", i4os3.as_ref() as &BStr)?; + globals.set("bstr_invalid_4_octet_sequence_4th", i4os4.as_ref() as &BStr)?; + globals.set("bstr_an_actual_string", aas.as_ref() as &BStr)?; lua.load( r#" @@ -69,14 +69,14 @@ fn test_byte_string_round_trip() -> Result<()> { ) .exec()?; - globals.set::<_, BString>("bstring_invalid_sequence_identifier", isi)?; - globals.set::<_, BString>("bstring_invalid_2_octet_sequence_2nd", i2os2)?; - globals.set::<_, BString>("bstring_invalid_3_octet_sequence_2nd", i3os2)?; - globals.set::<_, BString>("bstring_invalid_3_octet_sequence_3rd", i3os3)?; - globals.set::<_, BString>("bstring_invalid_4_octet_sequence_2nd", i4os2)?; - globals.set::<_, BString>("bstring_invalid_4_octet_sequence_3rd", i4os3)?; - globals.set::<_, BString>("bstring_invalid_4_octet_sequence_4th", i4os4)?; - globals.set::<_, BString>("bstring_an_actual_string", aas)?; + globals.set("bstring_invalid_sequence_identifier", isi)?; + globals.set("bstring_invalid_2_octet_sequence_2nd", i2os2)?; + globals.set("bstring_invalid_3_octet_sequence_2nd", i3os2)?; + globals.set("bstring_invalid_3_octet_sequence_3rd", i3os3)?; + globals.set("bstring_invalid_4_octet_sequence_2nd", i4os2)?; + globals.set("bstring_invalid_4_octet_sequence_3rd", i4os3)?; + globals.set("bstring_invalid_4_octet_sequence_4th", i4os4)?; + globals.set("bstring_an_actual_string", aas)?; lua.load( r#" diff --git a/tests/chunk.rs b/tests/chunk.rs index 31e55a24..403a6c56 100644 --- a/tests/chunk.rs +++ b/tests/chunk.rs @@ -64,7 +64,7 @@ fn test_chunk_macro() -> Result<()> { }) .exec()?; - assert_eq!(lua.globals().get::<_, i32>("s")?, 321); + assert_eq!(lua.globals().get::("s")?, 321); Ok(()) } diff --git a/tests/conversion.rs b/tests/conversion.rs index 3618111e..5684c285 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -20,7 +20,7 @@ fn test_value_into_lua() -> Result<()> { // Push into stack let table = lua.create_table()?; table.set("v", &v)?; - assert_eq!(v, table.get::<_, Value>("v")?); + assert_eq!(v, table.get::("v")?); Ok(()) } @@ -37,7 +37,7 @@ fn test_string_into_lua() -> Result<()> { // Push into stack let table = lua.create_table()?; table.set("s", &s)?; - assert_eq!(s, table.get::<_, String>("s")?); + assert_eq!(s, table.get::("s")?); Ok(()) } @@ -53,8 +53,8 @@ fn test_table_into_lua() -> Result<()> { // Push into stack let f = lua.create_function(|_, (t, s): (Table, String)| t.set("s", s))?; - f.call((&t, "hello"))?; - assert_eq!("hello", t.get::<_, String>("s")?); + f.call::<()>((&t, "hello"))?; + assert_eq!("hello", t.get::("s")?); Ok(()) } @@ -71,7 +71,7 @@ fn test_function_into_lua() -> Result<()> { // Push into stack let table = lua.create_table()?; table.set("f", &f)?; - assert_eq!(f, table.get::<_, Function>("f")?); + assert_eq!(f, table.get::("f")?); Ok(()) } @@ -89,7 +89,7 @@ fn test_thread_into_lua() -> Result<()> { // Push into stack let table = lua.create_table()?; table.set("th", &th)?; - assert_eq!(th, table.get::<_, Thread>("th")?); + assert_eq!(th, table.get::("th")?); Ok(()) } @@ -106,8 +106,8 @@ fn test_anyuserdata_into_lua() -> Result<()> { // Push into stack let table = lua.create_table()?; table.set("ud", &ud)?; - assert_eq!(ud, table.get::<_, AnyUserData>("ud")?); - assert_eq!("hello", *table.get::<_, UserDataRef>("ud")?); + assert_eq!(ud, table.get::("ud")?); + assert_eq!("hello", *table.get::>("ud")?); Ok(()) } @@ -128,20 +128,20 @@ fn test_registry_value_into_lua() -> Result<()> { let t = lua.create_table()?; let r = lua.create_registry_value(&t)?; let f = lua.create_function(|_, (t, k, v): (Table, Value, Value)| t.set(k, v))?; - f.call((&r, "hello", "world"))?; - f.call((r, "welcome", "to the jungle"))?; - assert_eq!(t.get::<_, String>("hello")?, "world"); - assert_eq!(t.get::<_, String>("welcome")?, "to the jungle"); + f.call::<()>((&r, "hello", "world"))?; + f.call::<()>((r, "welcome", "to the jungle"))?; + assert_eq!(t.get::("hello")?, "world"); + assert_eq!(t.get::("welcome")?, "to the jungle"); // Try to set nil registry key let r_nil = lua.create_registry_value(Value::Nil)?; t.set("hello", &r_nil)?; - assert_eq!(t.get::<_, Value>("hello")?, Value::Nil); + assert_eq!(t.get::("hello")?, Value::Nil); // Check non-owned registry key let lua2 = Lua::new(); let r2 = lua2.create_registry_value("abc")?; - assert!(matches!(f.call::<_, ()>(&r2), Err(Error::MismatchedRegistryKey))); + assert!(matches!(f.call::<()>(&r2), Err(Error::MismatchedRegistryKey))); Ok(()) } @@ -152,7 +152,7 @@ fn test_registry_key_from_lua() -> Result<()> { let fkey = lua.load("function() return 1 end").eval::()?; let f = lua.registry_value::(&fkey)?; - assert_eq!(f.call::<_, i32>(())?, 1); + assert_eq!(f.call::(())?, 1); Ok(()) } @@ -285,7 +285,7 @@ fn test_conv_array() -> Result<()> { let v2: [i32; 3] = lua.globals().get("v")?; assert_eq!(v, v2); - let v2 = lua.globals().get::<_, [i32; 4]>("v"); + let v2 = lua.globals().get::<[i32; 4]>("v"); assert!(matches!(v2, Err(Error::FromLuaConversionError { .. }))); Ok(()) @@ -307,10 +307,10 @@ fn test_bstring_from_lua() -> Result<()> { // Test from stack let f = lua.create_function(|_, bstr: BString| Ok(bstr))?; - let bstr = f.call::<_, BString>("hello, world")?; + let bstr = f.call::("hello, world")?; assert_eq!(bstr, "hello, world"); - let bstr = f.call::<_, BString>(-43.22)?; + let bstr = f.call::(-43.22)?; assert_eq!(bstr, "-43.22"); Ok(()) @@ -328,7 +328,7 @@ fn test_bstring_from_lua_buffer() -> Result<()> { // Test from stack let f = lua.create_function(|_, bstr: BString| Ok(bstr))?; let buf = lua.create_buffer("hello, world")?; - let bstr = f.call::<_, BString>(buf)?; + let bstr = f.call::(buf)?; assert_eq!(bstr, "hello, world"); Ok(()) @@ -345,9 +345,9 @@ fn test_option_into_from_lua() -> Result<()> { // Push into stack / get from stack let f = lua.create_function(|_, v: Option| Ok(v))?; - assert_eq!(f.call::<_, Option>(Some(42))?, Some(42)); - assert_eq!(f.call::<_, Option>(Option::::None)?, None); - assert_eq!(f.call::<_, Option>(())?, None); + assert_eq!(f.call::>(Some(42))?, Some(42)); + assert_eq!(f.call::>(Option::::None)?, None); + assert_eq!(f.call::>(())?, None); Ok(()) } diff --git a/tests/error.rs b/tests/error.rs index 5bfd9485..922b83c6 100644 --- a/tests/error.rs +++ b/tests/error.rs @@ -18,7 +18,7 @@ fn test_error_context() -> Result<()> { let func2 = lua.create_function(|lua, ()| { lua.globals() - .get::<_, String>("nonextant") + .get::("nonextant") .with_context(|_| "failed to find global") })?; lua.globals().set("func2", func2)?; @@ -36,7 +36,7 @@ fn test_error_context() -> Result<()> { .context("some context") .context("some new context") })?; - let res = func3.call::<_, ()>(()).err().unwrap(); + let res = func3.call::<()>(()).err().unwrap(); let Error::CallbackError { cause, .. } = &res else { unreachable!() }; diff --git a/tests/function.rs b/tests/function.rs index 26d40e1f..2735e655 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -14,8 +14,8 @@ fn test_function() -> Result<()> { ) .exec()?; - let concat = globals.get::<_, Function>("concat")?; - assert_eq!(concat.call::<_, String>(("foo", "bar"))?, "foobar"); + let concat = globals.get::("concat")?; + assert_eq!(concat.call::(("foo", "bar"))?, "foobar"); Ok(()) } @@ -38,17 +38,17 @@ fn test_bind() -> Result<()> { ) .exec()?; - let mut concat = globals.get::<_, Function>("concat")?; + let mut concat = globals.get::("concat")?; concat = concat.bind("foo")?; concat = concat.bind("bar")?; concat = concat.bind(("baz", "baf"))?; - assert_eq!(concat.call::<_, String>(())?, "foobarbazbaf"); - assert_eq!(concat.call::<_, String>(("hi", "wut"))?, "foobarbazbafhiwut"); + assert_eq!(concat.call::(())?, "foobarbazbaf"); + assert_eq!(concat.call::(("hi", "wut"))?, "foobarbazbafhiwut"); - let mut concat2 = globals.get::<_, Function>("concat")?; + let mut concat2 = globals.get::("concat")?; concat2 = concat2.bind(())?; - assert_eq!(concat2.call::<_, String>(())?, ""); - assert_eq!(concat2.call::<_, String>(("ab", "cd"))?, "abcd"); + assert_eq!(concat2.call::(())?, ""); + assert_eq!(concat2.call::(("ab", "cd"))?, "abcd"); Ok(()) } @@ -70,11 +70,11 @@ fn test_rust_function() -> Result<()> { ) .exec()?; - let lua_function = globals.get::<_, Function>("lua_function")?; + let lua_function = globals.get::("lua_function")?; let rust_function = lua.create_function(|_, ()| Ok("hello"))?; globals.set("rust_function", rust_function)?; - assert_eq!(lua_function.call::<_, String>(())?, "hello"); + assert_eq!(lua_function.call::(())?, "hello"); Ok(()) } @@ -90,8 +90,8 @@ fn test_c_function() -> Result<()> { } let func = unsafe { lua.create_c_function(c_function)? }; - func.call(())?; - assert_eq!(lua.globals().get::<_, bool>("c_function")?, true); + func.call::<()>(())?; + assert_eq!(lua.globals().get::("c_function")?, true); Ok(()) } @@ -106,7 +106,7 @@ fn test_dump() -> Result<()> { .eval::()?; let concat = lua.load(&concat_lua.dump(false)).into_function()?; - assert_eq!(concat.call::<_, String>(("foo", "bar"))?, "foobar"); + assert_eq!(concat.call::(("foo", "bar"))?, "foobar"); Ok(()) } @@ -134,14 +134,14 @@ fn test_function_environment() -> Result<()> { ) .eval::()?; let lua_func2 = lua.load("return hello").into_function()?; - assert_eq!(lua_func.call::<_, String>(())?, "global"); + assert_eq!(lua_func.call::(())?, "global"); assert_eq!(lua_func.environment(), Some(lua.globals())); // Test changing the environment let env = lua.create_table_from([("hello", "local")])?; assert!(lua_func.set_environment(env.clone())?); - assert_eq!(lua_func.call::<_, String>(())?, "local"); - assert_eq!(lua_func2.call::<_, String>(())?, "global"); + assert_eq!(lua_func.call::(())?, "local"); + assert_eq!(lua_func2.call::(())?, "global"); // More complex case lua.load( @@ -154,11 +154,11 @@ fn test_function_environment() -> Result<()> { "#, ) .exec()?; - let lucky = lua.globals().get::<_, Function>("lucky")?; - assert_eq!(lucky.call::<_, String>(())?, "number is 15"); - let new_env = lua.globals().get::<_, Table>("new_env")?; + let lucky = lua.globals().get::("lucky")?; + assert_eq!(lucky.call::(())?, "number is 15"); + let new_env = lua.globals().get::
("new_env")?; lucky.set_environment(new_env)?; - assert_eq!(lucky.call::<_, String>(())?, "15"); + assert_eq!(lucky.call::(())?, "15"); // Test inheritance let lua_func2 = lua @@ -166,7 +166,7 @@ fn test_function_environment() -> Result<()> { .eval::()?; assert!(lua_func2.set_environment(env.clone())?); lua.gc_collect()?; - assert_eq!(lua_func2.call::<_, String>(())?, "local"); + assert_eq!(lua_func2.call::(())?, "local"); Ok(()) } @@ -186,8 +186,8 @@ fn test_function_info() -> Result<()> { .set_name("source1") .exec()?; - let function1 = globals.get::<_, Function>("function1")?; - let function2 = function1.call::<_, Function>(())?; + let function1 = globals.get::("function1")?; + let function2 = function1.call::(())?; let function3 = lua.create_function(|_, ()| Ok(()))?; let function1_info = function1.info(); @@ -218,7 +218,7 @@ fn test_function_info() -> Result<()> { assert_eq!(function3_info.last_line_defined, None); assert_eq!(function3_info.what, "C"); - let print_info = globals.get::<_, Function>("print")?.info(); + let print_info = globals.get::("print")?.info(); #[cfg(feature = "luau")] assert_eq!(print_info.name.as_deref(), Some("print")); assert_eq!(print_info.source.as_deref(), Some("=[C]")); @@ -233,7 +233,7 @@ fn test_function_pointer() -> Result<()> { let lua = Lua::new(); let func1 = lua.load("return function() end").into_function()?; - let func2 = func1.call::<_, Function>(())?; + let func2 = func1.call::(())?; assert_eq!(func1.to_pointer(), func1.clone().to_pointer()); assert_ne!(func1.to_pointer(), func2.to_pointer()); @@ -251,8 +251,8 @@ fn test_function_deep_clone() -> Result<()> { let func2 = func1.deep_clone(); assert_ne!(func1.to_pointer(), func2.to_pointer()); - assert_eq!(func1.call::<_, i32>(())?, 2); - assert_eq!(func2.call::<_, i32>(())?, 3); + assert_eq!(func1.call::(())?, 2); + assert_eq!(func2.call::(())?, 3); // Check that for Rust functions deep_clone is just a clone let rust_func = lua.create_function(|_, ()| Ok(42))?; @@ -276,10 +276,10 @@ fn test_function_wrap() -> Result<()> { "f", Function::wrap_mut(move |lua, ()| { _i = true; - lua.globals().get::<_, Function>("f")?.call::<_, ()>(()) + lua.globals().get::("f")?.call::<()>(()) }), )?; - match lua.globals().get::<_, Function>("f")?.call::<_, ()>(()) { + match lua.globals().get::("f")?.call::<()>(()) { Err(Error::CallbackError { ref cause, .. }) => match *cause.as_ref() { Error::CallbackError { ref cause, .. } => match *cause.as_ref() { Error::RecursiveMutCallback { .. } => {} diff --git a/tests/hooks.rs b/tests/hooks.rs index 4186339b..a7b3ff47 100644 --- a/tests/hooks.rs +++ b/tests/hooks.rs @@ -209,7 +209,7 @@ fn test_hook_swap_within_hook() -> Result<()> { "#, ) .exec()?; - assert_eq!(lua.globals().get::<_, i64>("ok")?, 2); + assert_eq!(lua.globals().get::("ok")?, 2); Ok(()) }) } @@ -237,7 +237,7 @@ fn test_hook_threads() -> Result<()> { Ok(()) }); - co.resume(())?; + co.resume::<()>(())?; lua.remove_hook(); let output = output.lock().unwrap(); diff --git a/tests/luau.rs b/tests/luau.rs index 557a2e34..e8d4bee0 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -14,7 +14,7 @@ use mlua::{ #[test] fn test_version() -> Result<()> { let lua = Lua::new(); - assert!(lua.globals().get::<_, String>("_VERSION")?.starts_with("Luau 0.")); + assert!(lua.globals().get::("_VERSION")?.starts_with("Luau 0.")); Ok(()) } @@ -22,8 +22,8 @@ fn test_version() -> Result<()> { fn test_require() -> Result<()> { // Ensure that require() is not available if package module is not loaded let mut lua = Lua::new_with(StdLib::NONE, LuaOptions::default())?; - assert!(lua.globals().get::<_, Option>("require")?.is_none()); - assert!(lua.globals().get::<_, Option>("package")?.is_none()); + assert!(lua.globals().get::>("require")?.is_none()); + assert!(lua.globals().get::>("package")?.is_none()); if cfg!(target_arch = "wasm32") { // TODO: figure out why emscripten fails on file operations @@ -46,7 +46,7 @@ fn test_require() -> Result<()> { )?; lua.globals() - .get::<_, Table>("package")? + .get::
("package")? .set("path", temp_dir.path().join("?.luau").to_string_lossy())?; lua.load( @@ -70,7 +70,7 @@ fn test_require() -> Result<()> { // Require binary module in safe mode lua.globals() - .get::<_, Table>("package")? + .get::
("package")? .set("cpath", temp_dir.path().join("?.so").to_string_lossy())?; fs::write(temp_dir.path().join("dylib.so"), "")?; match lua.load("require('dylib')").exec() { @@ -246,25 +246,25 @@ fn test_sandbox() -> Result<()> { lua.load("global = 123").exec()?; let n: i32 = lua.load("return global").eval()?; assert_eq!(n, 123); - assert_eq!(lua.globals().get::<_, Option>("global")?, Some(123)); + assert_eq!(lua.globals().get::>("global")?, Some(123)); // Threads should inherit "main" globals - let f = lua.create_function(|lua, ()| lua.globals().get::<_, i32>("global"))?; + let f = lua.create_function(|lua, ()| lua.globals().get::("global"))?; let co = lua.create_thread(f.clone())?; - assert_eq!(co.resume::<_, Option>(())?, Some(123)); + assert_eq!(co.resume::>(())?, Some(123)); // Sandboxed threads should also inherit "main" globals let co = lua.create_thread(f)?; co.sandbox()?; - assert_eq!(co.resume::<_, Option>(())?, Some(123)); + assert_eq!(co.resume::>(())?, Some(123)); lua.sandbox(false)?; // Previously set variable `global` should be cleared now - assert_eq!(lua.globals().get::<_, Option>("global")?, None); + assert_eq!(lua.globals().get::>("global")?, None); // Readonly flags should be cleared as well - let table = lua.globals().get::<_, Table>("table")?; + let table = lua.globals().get::
("table")?; table.set("test", "test")?; Ok(()) @@ -278,10 +278,10 @@ fn test_sandbox_nolibs() -> Result<()> { lua.load("global = 123").exec()?; let n: i32 = lua.load("return global").eval()?; assert_eq!(n, 123); - assert_eq!(lua.globals().get::<_, Option>("global")?, Some(123)); + assert_eq!(lua.globals().get::>("global")?, Some(123)); lua.sandbox(false)?; - assert_eq!(lua.globals().get::<_, Option>("global")?, None); + assert_eq!(lua.globals().get::>("global")?, None); Ok(()) } @@ -293,20 +293,20 @@ fn test_sandbox_threads() -> Result<()> { let f = lua.create_function(|lua, v: Value| lua.globals().set("global", v))?; let co = lua.create_thread(f.clone())?; - co.resume(321)?; + co.resume::<()>(321)?; // The main state should see the `global` variable (as the thread is not sandboxed) - assert_eq!(lua.globals().get::<_, Option>("global")?, Some(321)); + assert_eq!(lua.globals().get::>("global")?, Some(321)); let co = lua.create_thread(f.clone())?; co.sandbox()?; - co.resume(123)?; + co.resume::<()>(123)?; // The main state should see the previous `global` value (as the thread is sandboxed) - assert_eq!(lua.globals().get::<_, Option>("global")?, Some(321)); + assert_eq!(lua.globals().get::>("global")?, Some(321)); // Try to reset the (sandboxed) thread co.reset(f)?; - co.resume(111)?; - assert_eq!(lua.globals().get::<_, Option>("global")?, Some(111)); + co.resume::<()>(111)?; + assert_eq!(lua.globals().get::>("global")?, Some(111)); Ok(()) } @@ -331,7 +331,7 @@ fn test_interrupts() -> Result<()> { "#, ) .into_function()?; - f.call(())?; + f.call::<()>(())?; assert!(interrupts_count.load(Ordering::Relaxed) > 0); @@ -357,7 +357,7 @@ fn test_interrupts() -> Result<()> { ) .into_function()?, )?; - co.resume(())?; + co.resume::<()>(())?; assert_eq!(co.status(), ThreadStatus::Resumable); let result: i32 = co.resume(())?; assert_eq!(result, 6); @@ -368,7 +368,7 @@ fn test_interrupts() -> Result<()> { // Test errors in interrupts // lua.set_interrupt(|_| Err(Error::runtime("error from interrupt"))); - match f.call::<_, ()>(()) { + match f.call::<()>(()) { Err(Error::CallbackError { cause, .. }) => match *cause { Error::RuntimeError(ref m) if m == "error from interrupt" => {} ref e => panic!("expected RuntimeError with a specific message, got {:?}", e), @@ -407,7 +407,7 @@ fn test_coverage() -> Result<()> { ) .into_function()?; - f.call(())?; + f.call::<()>(())?; let mut report = Vec::new(); f.coverage(|cov| { @@ -475,7 +475,7 @@ fn test_buffer() -> Result<()> { // Check that we can pass buffer type to Lua let func = lua.create_function(|_, buf: Value| return buf.to_string())?; - assert!(func.call::<_, String>(buf1)?.starts_with("buffer:")); + assert!(func.call::(buf1)?.starts_with("buffer:")); Ok(()) } diff --git a/tests/memory.rs b/tests/memory.rs index 77016b80..e765e06c 100644 --- a/tests/memory.rs +++ b/tests/memory.rs @@ -15,7 +15,7 @@ fn test_memory_limit() -> Result<()> { let f = lua .load("local t = {}; for i = 1,10000 do t[i] = i end") .into_function()?; - f.call::<_, ()>(()).expect("should trigger no memory limit"); + f.call::<()>(()).expect("should trigger no memory limit"); if cfg!(feature = "luajit") && lua.set_memory_limit(0).is_err() { // seems this luajit version does not support memory limit @@ -23,13 +23,13 @@ fn test_memory_limit() -> Result<()> { } lua.set_memory_limit(initial_memory + 10000)?; - match f.call::<_, ()>(()) { + match f.call::<()>(()) { Err(Error::MemoryError(_)) => {} something_else => panic!("did not trigger memory error: {:?}", something_else), }; lua.set_memory_limit(0)?; - f.call::<_, ()>(()).expect("should trigger no memory limit"); + f.call::<()>(()).expect("should trigger no memory limit"); Ok(()) } @@ -49,7 +49,7 @@ fn test_memory_limit_thread() -> Result<()> { lua.set_memory_limit(lua.used_memory() + 10000)?; let thread = lua.create_thread(f)?; - match thread.resume::<_, ()>(()) { + match thread.resume::<()>(()) { Err(Error::MemoryError(_)) => {} something_else => panic!("did not trigger memory error: {:?}", something_else), }; diff --git a/tests/static.rs b/tests/static.rs index 059f4ee8..aeb7eca7 100644 --- a/tests/static.rs +++ b/tests/static.rs @@ -18,7 +18,7 @@ fn test_static_lua() -> Result<()> { }) })?; - f.call(lua.create_table()?)?; + f.call::<()>(lua.create_table()?)?; drop(f); lua.gc_collect()?; @@ -50,13 +50,13 @@ fn test_static_lua_coroutine() -> Result<()> { })?; let co = lua.create_thread(f)?; - co.resume::<_, ()>(lua.create_table()?)?; + co.resume::<()>(lua.create_table()?)?; drop(co); lua.gc_collect()?; TABLE.with(|t| { assert_eq!( - t.borrow().as_ref().unwrap().get::<_, String>(1i32).unwrap(), + t.borrow().as_ref().unwrap().get::(1i32).unwrap(), "hello".to_string() ); *t.borrow_mut() = None; @@ -86,7 +86,7 @@ async fn test_static_async() -> Result<()> { let timer = lua.create_async_function(|_, (i, n, f): (u64, u64, mlua::Function)| async move { tokio::task::spawn_local(async move { for _ in 0..n { - tokio::task::spawn_local(f.call_async::<(), ()>(())); + tokio::task::spawn_local(f.call_async::<()>(())); sleep_ms(i).await; } }); diff --git a/tests/table.rs b/tests/table.rs index 52cbc3f4..30b51d90 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -7,8 +7,8 @@ fn test_globals_set_get() -> Result<()> { let globals = lua.globals(); globals.set("foo", "bar")?; globals.set("baz", "baf")?; - assert_eq!(globals.get::<_, String>("foo")?, "bar"); - assert_eq!(globals.get::<_, String>("baz")?, "baf"); + assert_eq!(globals.get::("foo")?, "bar"); + assert_eq!(globals.get::("baz")?, "baf"); Ok(()) } @@ -26,8 +26,8 @@ fn test_table() -> Result<()> { table1.set("foo", "bar")?; table2.set("baz", "baf")?; - assert_eq!(table2.get::<_, String>("foo")?, "bar"); - assert_eq!(table1.get::<_, String>("baz")?, "baf"); + assert_eq!(table2.get::("foo")?, "bar"); + assert_eq!(table1.get::("baz")?, "baf"); lua.load( r#" @@ -38,9 +38,9 @@ fn test_table() -> Result<()> { ) .exec()?; - let table1 = globals.get::<_, Table>("table1")?; - let table2 = globals.get::<_, Table>("table2")?; - let table3 = globals.get::<_, Table>("table3")?; + let table1 = globals.get::
("table1")?; + let table2 = globals.get::
("table2")?; + let table3 = globals.get::
("table3")?; assert_eq!(table1.len()?, 5); assert!(!table1.is_empty()); @@ -70,7 +70,7 @@ fn test_table() -> Result<()> { ); globals.set("table4", lua.create_sequence_from(vec![1, 2, 3, 4, 5])?)?; - let table4 = globals.get::<_, Table>("table4")?; + let table4 = globals.get::
("table4")?; assert_eq!( table4.clone().pairs().collect::>>()?, vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)] @@ -180,7 +180,7 @@ fn test_table_clear() -> Result<()> { t2.clear()?; assert_eq!(t2.raw_len(), 0); assert!(t2.is_empty()); - assert_eq!(t2.raw_get::<_, Value>("a")?, Value::Nil); + assert_eq!(t2.raw_get::("a")?, Value::Nil); assert_ne!(t2.get_metatable(), None); Ok(()) @@ -192,9 +192,9 @@ fn test_table_sequence_from() -> Result<()> { let get_table = lua.create_function(|_, t: Table| Ok(t))?; - assert_eq!(get_table.call::<_, Table>(vec![1, 2, 3])?, [1, 2, 3]); - assert_eq!(get_table.call::<_, Table>([4, 5, 6])?, [4, 5, 6]); - assert_eq!(get_table.call::<_, Table>([7, 8, 9].as_slice())?, [7, 8, 9]); + assert_eq!(get_table.call::
(vec![1, 2, 3])?, [1, 2, 3]); + assert_eq!(get_table.call::
([4, 5, 6])?, [4, 5, 6]); + assert_eq!(get_table.call::
([7, 8, 9].as_slice())?, [7, 8, 9]); Ok(()) } @@ -284,13 +284,13 @@ fn test_table_scope() -> Result<()> { // Make sure that table gets do not borrow the table, but instead just borrow lua. let tin; { - let touter = globals.get::<_, Table>("touter")?; - tin = touter.get::<_, Table>("tin")?; + let touter = globals.get::
("touter")?; + tin = touter.get::
("tin")?; } - assert_eq!(tin.get::<_, i64>(1)?, 1); - assert_eq!(tin.get::<_, i64>(2)?, 2); - assert_eq!(tin.get::<_, i64>(3)?, 3); + assert_eq!(tin.get::(1)?, 1); + assert_eq!(tin.get::(2)?, 2); + assert_eq!(tin.get::(3)?, 3); Ok(()) } @@ -303,13 +303,13 @@ fn test_metatable() -> Result<()> { let metatable = lua.create_table()?; metatable.set("__index", lua.create_function(|_, ()| Ok("index_value"))?)?; table.set_metatable(Some(metatable)); - assert_eq!(table.get::<_, String>("any_key")?, "index_value"); - match table.raw_get::<_, Value>("any_key")? { + assert_eq!(table.get::("any_key")?, "index_value"); + match table.raw_get::("any_key")? { Nil => {} _ => panic!(), } table.set_metatable(None); - match table.get::<_, Value>("any_key")? { + match table.get::("any_key")? { Nil => {} _ => panic!(), }; @@ -336,10 +336,10 @@ fn test_table_eq() -> Result<()> { ) .exec()?; - let table1 = globals.get::<_, Table>("table1")?; - let table2 = globals.get::<_, Table>("table2")?; - let table3 = globals.get::<_, Table>("table3")?; - let table4 = globals.get::<_, Table>("table4")?; + let table1 = globals.get::
("table1")?; + let table2 = globals.get::
("table2")?; + let table3 = globals.get::
("table3")?; + let table4 = globals.get::
("table4")?; assert!(table1 != table2); assert!(!table1.equals(&table2)?); @@ -389,10 +389,10 @@ fn test_table_error() -> Result<()> { let bad_table: Table = globals.get("table")?; assert!(bad_table.set(1, 1).is_err()); - assert!(bad_table.get::<_, i32>(1).is_err()); + assert!(bad_table.get::(1).is_err()); assert!(bad_table.len().is_err()); assert!(bad_table.raw_set(1, 1).is_ok()); - assert!(bad_table.raw_get::<_, i32>(1).is_ok()); + assert!(bad_table.raw_get::(1).is_ok()); assert_eq!(bad_table.raw_len(), 1); Ok(()) @@ -424,13 +424,13 @@ fn test_table_call() -> Result<()> { let table: Table = lua.globals().get("table")?; - assert_eq!(table.call::<_, String>("b")?, "call_2"); - assert_eq!(table.call_function::<_, String>("func", "a")?, "func_a"); - assert_eq!(table.call_method::<_, String>("method", "a")?, "method_1"); + assert_eq!(table.call::("b")?, "call_2"); + assert_eq!(table.call_function::("func", "a")?, "func_a"); + assert_eq!(table.call_method::("method", "a")?, "method_1"); // Test calling non-callable table let table2 = lua.create_table()?; - assert!(matches!(table2.call::<_, ()>(()), Err(Error::RuntimeError(_)))); + assert!(matches!(table2.call::<()>(()), Err(Error::RuntimeError(_)))); Ok(()) } diff --git a/tests/tests.rs b/tests/tests.rs index 90085ece..e49f9367 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -52,7 +52,7 @@ fn test_safety() -> Result<()> { // Test safety rules after dynamically loading `package` library let lua = Lua::new_with(StdLib::NONE, LuaOptions::default())?; - assert!(lua.globals().get::<_, Option>("require")?.is_none()); + assert!(lua.globals().get::>("require")?.is_none()); lua.load_std_libs(StdLib::PACKAGE)?; match lua.load(r#"package.loadlib()"#).exec() { Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() { @@ -91,7 +91,7 @@ fn test_exec() -> Result<()> { "#, ) .exec()?; - assert_eq!(globals.get::<_, String>("res")?, "foobar"); + assert_eq!(globals.get::("res")?, "foobar"); let module: Table = lua .load( @@ -108,7 +108,7 @@ fn test_exec() -> Result<()> { .eval()?; println!("checkpoint"); assert!(module.contains_key("func")?); - assert_eq!(module.get::<_, Function>("func")?.call::<_, String>(())?, "hello"); + assert_eq!(module.get::("func")?.call::(())?, "hello"); Ok(()) } @@ -179,13 +179,13 @@ fn test_lua_multi() -> Result<()> { .exec()?; let globals = lua.globals(); - let concat = globals.get::<_, Function>("concat")?; - let mreturn = globals.get::<_, Function>("mreturn")?; + let concat = globals.get::("concat")?; + let mreturn = globals.get::("mreturn")?; - assert_eq!(concat.call::<_, String>(("foo", "bar"))?, "foobar"); - let (a, b) = mreturn.call::<_, (u64, u64)>(())?; + assert_eq!(concat.call::(("foo", "bar"))?, "foobar"); + let (a, b) = mreturn.call::<(u64, u64)>(())?; assert_eq!((a, b), (1, 2)); - let (a, b, v) = mreturn.call::<_, (u64, u64, Variadic)>(())?; + let (a, b, v) = mreturn.call::<(u64, u64, Variadic)>(())?; assert_eq!((a, b), (1, 2)); assert_eq!(v[..], [3, 4, 5, 6]); @@ -207,10 +207,10 @@ fn test_coercion() -> Result<()> { .exec()?; let globals = lua.globals(); - assert_eq!(globals.get::<_, String>("int")?, "123"); - assert_eq!(globals.get::<_, i32>("str")?, 123); - assert_eq!(globals.get::<_, i32>("num")?, 123); - assert!(globals.get::<_, String>("func").is_err()); + assert_eq!(globals.get::("int")?, "123"); + assert_eq!(globals.get::("str")?, 123); + assert_eq!(globals.get::("num")?, 123); + assert!(globals.get::("func").is_err()); Ok(()) } @@ -297,31 +297,31 @@ fn test_error() -> Result<()> { let rust_error_function = lua.create_function(|_, ()| -> Result<()> { Err(TestError.into_lua_err()) })?; globals.set("rust_error_function", rust_error_function)?; - let no_error = globals.get::<_, Function>("no_error")?; - assert!(no_error.call::<_, ()>(()).is_ok()); + let no_error = globals.get::("no_error")?; + assert!(no_error.call::<()>(()).is_ok()); - let lua_error = globals.get::<_, Function>("lua_error")?; - match lua_error.call::<_, ()>(()) { + let lua_error = globals.get::("lua_error")?; + match lua_error.call::<()>(()) { Err(Error::RuntimeError(_)) => {} Err(e) => panic!("error is not RuntimeError kind, got {:?}", e), _ => panic!("error not returned"), } - let rust_error = globals.get::<_, Function>("rust_error")?; - match rust_error.call::<_, ()>(()) { + let rust_error = globals.get::("rust_error")?; + match rust_error.call::<()>(()) { Err(Error::CallbackError { .. }) => {} Err(e) => panic!("error is not CallbackError kind, got {:?}", e), _ => panic!("error not returned"), } - let return_error = globals.get::<_, Function>("return_error")?; - match return_error.call::<_, Value>(()) { + let return_error = globals.get::("return_error")?; + match return_error.call::(()) { Ok(Value::Error(_)) => {} _ => panic!("Value::Error not returned"), } - let return_string_error = globals.get::<_, Function>("return_string_error")?; - assert!(return_string_error.call::<_, Error>(()).is_ok()); + let return_string_error = globals.get::("return_string_error")?; + assert!(return_string_error.call::(()).is_ok()); match lua.load("if youre happy and you know it syntax error").exec() { Err(Error::SyntaxError { @@ -340,13 +340,13 @@ fn test_error() -> Result<()> { _ => panic!("error not returned"), } - let test_pcall = globals.get::<_, Function>("test_pcall")?; - test_pcall.call::<_, ()>(())?; + let test_pcall = globals.get::("test_pcall")?; + test_pcall.call::<()>(())?; #[cfg(not(target_arch = "wasm32"))] { - let understand_recursion = globals.get::<_, Function>("understand_recursion")?; - assert!(understand_recursion.call::<_, ()>(()).is_err()); + let understand_recursion = globals.get::("understand_recursion")?; + assert!(understand_recursion.call::<()>(()).is_err()); } Ok(()) @@ -411,7 +411,7 @@ fn test_panic() -> Result<()> { Err(_) => {} }; - assert!(lua.globals().get::<_, Value>("err")? == Value::Nil); + assert!(lua.globals().get::("err")? == Value::Nil); match lua.load("tostring(err)").exec() { Ok(_) => panic!("no error was detected"), Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() { @@ -591,18 +591,18 @@ fn test_pcall_xpcall() -> Result<()> { ) .exec()?; - assert_eq!(globals.get::<_, bool>("pcall_status")?, false); - assert_eq!(globals.get::<_, String>("pcall_error")?, "testerror"); + assert_eq!(globals.get::("pcall_status")?, false); + assert_eq!(globals.get::("pcall_error")?, "testerror"); - assert_eq!(globals.get::<_, bool>("xpcall_statusr")?, false); + assert_eq!(globals.get::("xpcall_statusr")?, false); #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit"))] assert_eq!( - globals.get::<_, std::string::String>("xpcall_error")?, + globals.get::("xpcall_error")?, "testerror" ); #[cfg(feature = "lua51")] assert!(globals - .get::<_, String>("xpcall_error")? + .get::("xpcall_error")? .to_str()? .ends_with(": testerror")); @@ -615,7 +615,7 @@ fn test_pcall_xpcall() -> Result<()> { "#, ) .exec()?; - let _ = globals.get::<_, Function>("xpcall_recursion")?.call::<_, ()>(()); + let _ = globals.get::("xpcall_recursion")?.call::<()>(()); Ok(()) } @@ -632,7 +632,7 @@ fn test_recursive_mut_callback_error() -> Result<()> { // Produce a mutable reference let r = v.as_mut().unwrap(); // Whoops, this will recurse into the function and produce another mutable reference! - lua.globals().get::<_, Function>("f")?.call::<_, ()>(true)?; + lua.globals().get::("f")?.call::<()>(true)?; println!("Should not get here, mutable aliasing has occurred!"); println!("value at {:p}", r as *mut _); println!("value is {}", r); @@ -641,7 +641,7 @@ fn test_recursive_mut_callback_error() -> Result<()> { Ok(()) })?; lua.globals().set("f", f)?; - match lua.globals().get::<_, Function>("f")?.call::<_, ()>(false) { + match lua.globals().get::("f")?.call::<()>(false) { Err(Error::CallbackError { ref cause, .. }) => match *cause.as_ref() { Error::CallbackError { ref cause, .. } => match *cause.as_ref() { Error::RecursiveMutCallback { .. } => {} @@ -672,13 +672,13 @@ fn test_set_metatable_nil() -> Result<()> { fn test_named_registry_value() -> Result<()> { let lua = Lua::new(); - lua.set_named_registry_value::("test", 42)?; + lua.set_named_registry_value("test", 42)?; let f = lua.create_function(move |lua, ()| { assert_eq!(lua.named_registry_value::("test")?, 42); Ok(()) })?; - f.call::<_, ()>(())?; + f.call::<()>(())?; lua.unset_named_registry_value("test")?; match lua.named_registry_value("test")? { @@ -693,7 +693,7 @@ fn test_named_registry_value() -> Result<()> { fn test_registry_value() -> Result<()> { let lua = Lua::new(); - let mut r = Some(lua.create_registry_value::(42)?); + let mut r = Some(lua.create_registry_value(42)?); let f = lua.create_function_mut(move |lua, ()| { if let Some(r) = r.take() { assert_eq!(lua.registry_value::(&r)?, 42); @@ -704,7 +704,7 @@ fn test_registry_value() -> Result<()> { Ok(()) })?; - f.call::<_, ()>(())?; + f.call::<()>(())?; Ok(()) } @@ -735,7 +735,7 @@ fn test_drop_registry_value() -> Result<()> { fn test_replace_registry_value() -> Result<()> { let lua = Lua::new(); - let mut key = lua.create_registry_value::(42)?; + let mut key = lua.create_registry_value(42)?; lua.replace_registry_value(&mut key, "new value")?; assert_eq!(lua.registry_value::(&key)?, "new value"); lua.replace_registry_value(&mut key, Value::Nil)?; @@ -866,7 +866,7 @@ fn test_application_data() -> Result<()> { Ok(()) })?; - f.call(())?; + f.call::<()>(())?; assert_eq!(*lua.app_data_ref::<&str>().unwrap(), "test4"); assert_eq!(*lua.app_data_ref::>().unwrap(), vec!["test2", "test3"]); @@ -884,13 +884,13 @@ fn test_recursion() -> Result<()> { let f = lua.create_function(move |lua, i: i32| { if i < 64 { - lua.globals().get::<_, Function>("f")?.call::<_, ()>(i + 1)?; + lua.globals().get::("f")?.call::<()>(i + 1)?; } Ok(()) })?; lua.globals().set("f", &f)?; - f.call::<_, ()>(1)?; + f.call::<()>(1)?; Ok(()) } @@ -900,7 +900,7 @@ fn test_recursion() -> Result<()> { fn test_too_many_returns() -> Result<()> { let lua = Lua::new(); let f = lua.create_function(|_, ()| Ok(Variadic::from_iter(1..1000000)))?; - assert!(f.call::<_, Variadic>(()).is_err()); + assert!(f.call::>(()).is_err()); Ok(()) } @@ -912,8 +912,8 @@ fn test_too_many_arguments() -> Result<()> { let args = Variadic::from_iter(1..1000000); assert!(lua .globals() - .get::<_, Function>("test")? - .call::<_, ()>(args) + .get::("test")? + .call::<()>(args) .is_err()); Ok(()) } @@ -924,10 +924,10 @@ fn test_too_many_arguments() -> Result<()> { fn test_too_many_recursions() -> Result<()> { let lua = Lua::new(); - let f = lua.create_function(move |lua, ()| lua.globals().get::<_, Function>("f")?.call::<_, ()>(()))?; + let f = lua.create_function(move |lua, ()| lua.globals().get::("f")?.call::<()>(()))?; lua.globals().set("f", &f)?; - assert!(f.call::<_, ()>(()).is_err()); + assert!(f.call::<()>(()).is_err()); Ok(()) } @@ -945,9 +945,9 @@ fn test_too_many_binds() -> Result<()> { ) .exec()?; - let concat = globals.get::<_, Function>("f")?; + let concat = globals.get::("f")?; assert!(concat.bind(Variadic::from_iter(1..1000000)).is_err()); - assert!(concat.call::<_, ()>(Variadic::from_iter(1..1000000)).is_err()); + assert!(concat.call::<()>(Variadic::from_iter(1..1000000)).is_err()); Ok(()) } @@ -998,7 +998,7 @@ fn test_large_args() -> Result<()> { ) .eval()?; - assert_eq!(f.call::<_, usize>((0..100).collect::>())?, 4950); + assert_eq!(f.call::((0..100).collect::>())?, 4950); Ok(()) } @@ -1014,7 +1014,7 @@ fn test_large_args_ref() -> Result<()> { Ok(()) })?; - f.call::<_, ()>((0..100).map(|i| i.to_string()).collect::>())?; + f.call::<()>((0..100).map(|i| i.to_string()).collect::>())?; Ok(()) } @@ -1068,14 +1068,14 @@ fn test_context_thread() -> Result<()> { .into_function()?; #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] - f.call::<_, ()>(lua.current_thread())?; + f.call::<()>(lua.current_thread())?; #[cfg(any( feature = "lua51", all(feature = "luajit", not(feature = "luajit52")), feature = "luau" ))] - f.call::<_, ()>(Nil)?; + f.call::<()>(Nil)?; Ok(()) } @@ -1096,7 +1096,7 @@ fn test_context_thread_51() -> Result<()> { .eval()?, )?; - thread.resume::<_, ()>(thread.clone())?; + thread.resume::<()>(thread.clone())?; Ok(()) } @@ -1106,7 +1106,7 @@ fn test_context_thread_51() -> Result<()> { fn test_jit_version() -> Result<()> { let lua = Lua::new(); let jit: Table = lua.globals().get("jit")?; - assert!(jit.get::<_, String>("version")?.to_str()?.contains("LuaJIT")); + assert!(jit.get::("version")?.to_str()?.contains("LuaJIT")); Ok(()) } @@ -1124,7 +1124,7 @@ fn test_load_from_function() -> Result<()> { })?; let t: Table = lua.load_from_function("my_module", func.clone())?; - assert_eq!(t.get::<_, String>("__name")?, "my_module"); + assert_eq!(t.get::("__name")?, "my_module"); assert_eq!(i.load(Ordering::Relaxed), 1); let _: Value = lua.load_from_function("my_module", func.clone())?; @@ -1188,7 +1188,7 @@ fn test_multi_states() -> Result<()> { let f = lua.create_function(|_, g: Option| { if let Some(g) = g { - g.call(())?; + g.call::<()>(())?; } Ok(()) })?; @@ -1282,17 +1282,17 @@ fn test_multi_thread() -> Result<()> { std::thread::scope(|s| { s.spawn(|| { for _ in 0..5 { - func.call::<_, ()>(()).unwrap(); + func.call::<()>(()).unwrap(); } }); s.spawn(|| { for _ in 0..5 { - func.call::<_, ()>(()).unwrap(); + func.call::<()>(()).unwrap(); } }); }); - assert_eq!(lua.globals().get::<_, i32>("i")?, 10); + assert_eq!(lua.globals().get::("i")?, 10); Ok(()) } diff --git a/tests/thread.rs b/tests/thread.rs index 9b93eb04..7ece2b56 100644 --- a/tests/thread.rs +++ b/tests/thread.rs @@ -22,15 +22,15 @@ fn test_thread() -> Result<()> { )?; assert_eq!(thread.status(), ThreadStatus::Resumable); - assert_eq!(thread.resume::<_, i64>(0)?, 0); + assert_eq!(thread.resume::(0)?, 0); assert_eq!(thread.status(), ThreadStatus::Resumable); - assert_eq!(thread.resume::<_, i64>(1)?, 1); + assert_eq!(thread.resume::(1)?, 1); assert_eq!(thread.status(), ThreadStatus::Resumable); - assert_eq!(thread.resume::<_, i64>(2)?, 3); + assert_eq!(thread.resume::(2)?, 3); assert_eq!(thread.status(), ThreadStatus::Resumable); - assert_eq!(thread.resume::<_, i64>(3)?, 6); + assert_eq!(thread.resume::(3)?, 6); assert_eq!(thread.status(), ThreadStatus::Resumable); - assert_eq!(thread.resume::<_, i64>(4)?, 10); + assert_eq!(thread.resume::(4)?, 10); assert_eq!(thread.status(), ThreadStatus::Finished); let accumulate = lua.create_thread( @@ -47,11 +47,11 @@ fn test_thread() -> Result<()> { )?; for i in 0..4 { - accumulate.resume::<_, ()>(i)?; + accumulate.resume::<()>(i)?; } - assert_eq!(accumulate.resume::<_, i64>(4)?, 10); + assert_eq!(accumulate.resume::(4)?, 10); assert_eq!(accumulate.status(), ThreadStatus::Resumable); - assert!(accumulate.resume::<_, ()>("error").is_err()); + assert!(accumulate.resume::<()>("error").is_err()); assert_eq!(accumulate.status(), ThreadStatus::Error); let thread = lua @@ -66,7 +66,7 @@ fn test_thread() -> Result<()> { ) .eval::()?; assert_eq!(thread.status(), ThreadStatus::Resumable); - assert_eq!(thread.resume::<_, i64>(())?, 42); + assert_eq!(thread.resume::(())?, 42); let thread: Thread = lua .load( @@ -81,10 +81,10 @@ fn test_thread() -> Result<()> { ) .eval()?; - assert_eq!(thread.resume::<_, u32>(42)?, 123); - assert_eq!(thread.resume::<_, u32>(43)?, 987); + assert_eq!(thread.resume::(42)?, 123); + assert_eq!(thread.resume::(43)?, 987); - match thread.resume::<_, u32>(()) { + match thread.resume::(()) { Err(Error::CoroutineUnresumable) => {} Err(_) => panic!("resuming dead coroutine error is not CoroutineInactive kind"), _ => panic!("resuming dead coroutine did not return error"), @@ -93,14 +93,14 @@ fn test_thread() -> Result<()> { // Already running thread must be unresumable let thread = lua.create_thread(lua.create_function(|lua, ()| { assert_eq!(lua.current_thread().status(), ThreadStatus::Running); - let result = lua.current_thread().resume::<_, ()>(()); + let result = lua.current_thread().resume::<()>(()); assert!( matches!(result, Err(Error::CoroutineUnresumable)), "unexpected result: {result:?}", ); Ok(()) })?)?; - let result = thread.resume::<_, ()>(()); + let result = thread.resume::<()>(()); assert!(result.is_ok(), "unexpected result: {result:?}"); Ok(()) @@ -124,10 +124,10 @@ fn test_thread_reset() -> Result<()> { for _ in 0..2 { assert_eq!(thread.status(), ThreadStatus::Resumable); - let _ = thread.resume::<_, AnyUserData>(MyUserData(arc.clone()))?; + let _ = thread.resume::(MyUserData(arc.clone()))?; assert_eq!(thread.status(), ThreadStatus::Resumable); assert_eq!(Arc::strong_count(&arc), 2); - thread.resume::<_, ()>(())?; + thread.resume::<()>(())?; assert_eq!(thread.status(), ThreadStatus::Finished); thread.reset(func.clone())?; lua.gc_collect()?; @@ -137,7 +137,7 @@ fn test_thread_reset() -> Result<()> { // Check for errors let func: Function = lua.load(r#"function(ud) error("test error") end"#).eval()?; let thread = lua.create_thread(func.clone())?; - let _ = thread.resume::<_, AnyUserData>(MyUserData(arc.clone())); + let _ = thread.resume::(MyUserData(arc.clone())); assert_eq!(thread.status(), ThreadStatus::Error); assert_eq!(Arc::strong_count(&arc), 2); #[cfg(feature = "lua54")] @@ -165,7 +165,7 @@ fn test_thread_reset() -> Result<()> { this.reset(lua.create_function(|_, ()| Ok(()))?)?; Ok(()) })?)?; - let result = thread.resume::<_, ()>(()); + let result = thread.resume::<()>(()); assert!( matches!(result, Err(Error::CallbackError{ ref cause, ..}) if matches!(cause.as_ref(), Error::RuntimeError(ref err) @@ -197,7 +197,7 @@ fn test_coroutine_from_closure() -> Result<()> { .load("coroutine.create(function(...) return main(unpack(arg)) end)") .eval()?; - thrd.resume::<_, ()>(())?; + thrd.resume::<()>(())?; Ok(()) } diff --git a/tests/types.rs b/tests/types.rs index 72f9484f..ffe39607 100644 --- a/tests/types.rs +++ b/tests/types.rs @@ -17,8 +17,8 @@ fn test_lightuserdata() -> Result<()> { .exec()?; let res = globals - .get::<_, Function>("id")? - .call::<_, LightUserData>(LightUserData(42 as *mut c_void))?; + .get::("id")? + .call::(LightUserData(42 as *mut c_void))?; assert_eq!(res, LightUserData(42 as *mut c_void)); diff --git a/tests/userdata.rs b/tests/userdata.rs index 491f1bb3..f925e20f 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -63,13 +63,13 @@ fn test_methods() -> Result<()> { "#, ) .exec()?; - let get = globals.get::<_, Function>("get_it")?; - let set = globals.get::<_, Function>("set_it")?; - assert_eq!(get.call::<_, i64>(())?, 42); + let get = globals.get::("get_it")?; + let set = globals.get::("set_it")?; + assert_eq!(get.call::(())?, 42); userdata.borrow_mut::()?.0 = 64; - assert_eq!(get.call::<_, i64>(())?, 64); - set.call::<_, ()>(100)?; - assert_eq!(get.call::<_, i64>(())?, 100); + assert_eq!(get.call::(())?, 64); + set.call::<()>(100)?; + assert_eq!(get.call::(())?, 100); Ok(()) } @@ -188,7 +188,7 @@ fn test_metamethods() -> Result<()> { 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); + assert_eq!(pairs_it.call::(())?, 28); let userdata2: Value = globals.get("userdata2")?; let userdata3: Value = globals.get("userdata3")?; @@ -463,15 +463,15 @@ fn test_functions() -> Result<()> { "#, ) .exec()?; - let get = globals.get::<_, Function>("get_it")?; - let set = globals.get::<_, Function>("set_it")?; - let get_constant = globals.get::<_, Function>("get_constant")?; - assert_eq!(get.call::<_, i64>(())?, 42); + let get = globals.get::("get_it")?; + let set = globals.get::("set_it")?; + let get_constant = globals.get::("get_constant")?; + assert_eq!(get.call::(())?, 42); userdata.borrow_mut::()?.0 = 64; - assert_eq!(get.call::<_, i64>(())?, 64); - set.call::<_, ()>(100)?; - assert_eq!(get.call::<_, i64>(())?, 100); - assert_eq!(get_constant.call::<_, i64>(())?, 7); + assert_eq!(get.call::(())?, 64); + set.call::<()>(100)?; + assert_eq!(get.call::(())?, 100); + assert_eq!(get_constant.call::(())?, 7); Ok(()) } @@ -495,7 +495,7 @@ fn test_fields() -> Result<()> { // Use userdata "uservalue" storage fields.add_field_function_get("uval", |_, ud| ud.user_value::>()); - fields.add_field_function_set("uval", |_, ud, s| ud.set_user_value::>(s)); + fields.add_field_function_set("uval", |_, ud, s: Option| ud.set_user_value(s)); fields.add_meta_field(MetaMethod::Index, HashMap::from([("f", 321)])); fields.add_meta_field_with(MetaMethod::NewIndex, |lua| { @@ -752,19 +752,19 @@ fn test_userdata_ext() -> Result<()> { let ud = lua.create_userdata(MyUserData(123))?; - assert_eq!(ud.get::<_, u32>("n")?, 123); + assert_eq!(ud.get::("n")?, 123); ud.set("n", 321)?; - assert_eq!(ud.get::<_, u32>("n")?, 321); - assert_eq!(ud.get::<_, Option>("non-existent")?, None); - match ud.set::<_, u32>("non-existent", 123) { + assert_eq!(ud.get::("n")?, 321); + assert_eq!(ud.get::>("non-existent")?, None); + match ud.set("non-existent", 123) { Err(Error::RuntimeError(_)) => {} r => panic!("expected RuntimeError, got {r:?}"), } - assert_eq!(ud.call::<_, String>(())?, "called"); + assert_eq!(ud.call::(())?, "called"); - ud.call_method("add", 2)?; - assert_eq!(ud.get::<_, u32>("n")?, 323); + ud.call_method::<()>("add", 2)?; + assert_eq!(ud.get::("n")?, 323); Ok(()) } @@ -782,7 +782,7 @@ fn test_userdata_method_errors() -> Result<()> { let lua = Lua::new(); let ud = lua.create_userdata(MyUserData(123))?; - let res = ud.call_function::<_, ()>("get_value", ()); + let res = ud.call_function::<()>("get_value", ()); let Err(Error::CallbackError { cause, .. }) = res else { panic!("expected CallbackError, got {res:?}"); }; From 4082b354fe7855b7738e21f9903a5665e9362bbd Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 31 Jul 2024 23:51:20 +0100 Subject: [PATCH 137/635] clippy --- benches/benchmark.rs | 10 ++-------- src/error.rs | 4 ++-- src/luau/package.rs | 2 +- src/state/extra.rs | 1 + src/string.rs | 2 +- src/table.rs | 4 ++-- src/userdata/registry.rs | 3 +-- tests/tests.rs | 11 ++--------- 8 files changed, 12 insertions(+), 25 deletions(-) diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 4a0f44bc..5c86bcb5 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -194,10 +194,7 @@ fn function_call_concat(c: &mut Criterion) { i.fetch_add(1, Ordering::Relaxed) }, |i| { - assert_eq!( - concat.call::(("num:", i)).unwrap(), - format!("num:{i}") - ); + assert_eq!(concat.call::(("num:", i)).unwrap(), format!("num:{i}")); }, BatchSize::SmallInput, ); @@ -220,10 +217,7 @@ fn function_call_lua_concat(c: &mut Criterion) { i.fetch_add(1, Ordering::Relaxed) }, |i| { - assert_eq!( - concat.call::(("num:", i)).unwrap(), - format!("num:{i}") - ); + assert_eq!(concat.call::(("num:", i)).unwrap(), format!("num:{i}")); }, BatchSize::SmallInput, ); diff --git a/src/error.rs b/src/error.rs index 2f4d7305..7c25f036 100644 --- a/src/error.rs +++ b/src/error.rs @@ -100,8 +100,8 @@ pub enum Error { }, /// [`Thread::resume`] was called on an unresumable coroutine. /// - /// A coroutine is unresumable if its main function has returned or if an error has occurred inside - /// the coroutine. Already running coroutines are also marked as unresumable. + /// A coroutine is unresumable if its main function has returned or if an error has occurred + /// inside the coroutine. Already running coroutines are also marked as unresumable. /// /// [`Thread::status`] can be used to check if the coroutine can be resumed without causing this /// error. diff --git a/src/luau/package.rs b/src/luau/package.rs index 94f1e12b..62252f9e 100644 --- a/src/luau/package.rs +++ b/src/luau/package.rs @@ -201,7 +201,7 @@ fn lua_loader(lua: &Lua, modname: StdString) -> Result { Ok(buf) => { return lua .load(&buf) - .set_name(&format!("={}", file_path.display())) + .set_name(format!("={}", file_path.display())) .set_mode(ChunkMode::Text) .into_function() .map(Value::Function); diff --git a/src/state/extra.rs b/src/state/extra.rs index 4b4fe4df..d3724e67 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -143,6 +143,7 @@ impl ExtraData { assert_eq!(ffi::lua_gettop(ref_thread), Self::ERROR_TRACEBACK_IDX); } + #[allow(clippy::arc_with_non_send_sync)] let extra = XRc::new(UnsafeCell::new(ExtraData { lua: MaybeUninit::uninit(), weak: MaybeUninit::uninit(), diff --git a/src/string.rs b/src/string.rs index 58f7dc08..1bdd4071 100644 --- a/src/string.rs +++ b/src/string.rs @@ -321,7 +321,7 @@ impl<'a> IntoIterator for BorrowedBytes<'a> { type IntoIter = slice::Iter<'a, u8>; fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() + self.0.iter() } } diff --git a/src/table.rs b/src/table.rs index 8dfdf5f2..e74ffbe5 100644 --- a/src/table.rs +++ b/src/table.rs @@ -1134,7 +1134,7 @@ where Ok(Some(( key.clone(), K::from_lua(key, lua.lua())?, - V::from_stack(-1, &lua)?, + V::from_stack(-1, lua)?, ))) } else { Ok(None) @@ -1187,7 +1187,7 @@ where ffi::LUA_TNIL => None, _ => { self.index += 1; - Some(V::from_stack(-1, &lua)) + Some(V::from_stack(-1, lua)) } } } diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index 9c7bf033..45d301af 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -267,8 +267,7 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { }) } - pub(crate) fn check_meta_field(lua: &Lua, name: &str, value: impl IntoLua) -> Result - { + pub(crate) fn check_meta_field(lua: &Lua, name: &str, value: impl IntoLua) -> Result { let value = value.into_lua(lua)?; if name == MetaMethod::Index || name == MetaMethod::NewIndex { match value { diff --git a/tests/tests.rs b/tests/tests.rs index e49f9367..36cc35fa 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -596,10 +596,7 @@ fn test_pcall_xpcall() -> Result<()> { assert_eq!(globals.get::("xpcall_statusr")?, false); #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit"))] - assert_eq!( - globals.get::("xpcall_error")?, - "testerror" - ); + assert_eq!(globals.get::("xpcall_error")?, "testerror"); #[cfg(feature = "lua51")] assert!(globals .get::("xpcall_error")? @@ -910,11 +907,7 @@ fn test_too_many_arguments() -> Result<()> { let lua = Lua::new(); lua.load("function test(...) end").exec()?; let args = Variadic::from_iter(1..1000000); - assert!(lua - .globals() - .get::("test")? - .call::<()>(args) - .is_err()); + assert!(lua.globals().get::("test")?.call::<()>(args).is_err()); Ok(()) } From c117a4c1af3c4177644b44309c31c408759b0fa1 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 4 Aug 2024 18:55:17 +0100 Subject: [PATCH 138/635] Fix loading (fetching) stdlib modules when using `require` in Luau. Fixes #435. --- src/luau/package.rs | 30 ++++++++++++++++-------------- tests/luau.rs | 11 +++++++++++ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/luau/package.rs b/src/luau/package.rs index 62252f9e..e0401881 100644 --- a/src/luau/package.rs +++ b/src/luau/package.rs @@ -9,7 +9,6 @@ use crate::chunk::ChunkMode; use crate::error::Result; use crate::state::Lua; use crate::table::Table; -use crate::types::RegistryKey; use crate::value::{IntoLua, Value}; #[cfg(unix)] @@ -27,9 +26,6 @@ const TARGET_MLUA_LUAU_ABI_VERSION: u32 = 1; #[used] pub static MLUA_LUAU_ABI_VERSION: u32 = TARGET_MLUA_LUAU_ABI_VERSION; -// We keep reference to the `package` table in registry under this key -struct PackageKey(RegistryKey); - // We keep reference to the loaded dylibs in application data #[cfg(unix)] struct LoadedDylibs(FxHashMap); @@ -51,9 +47,8 @@ impl std::ops::DerefMut for LoadedDylibs { } pub(crate) fn register_package_module(lua: &Lua) -> Result<()> { - // Create the package table and store it in app_data for later use (bypassing globals lookup) + // Create the package table let package = lua.create_table()?; - lua.set_app_data(PackageKey(lua.create_registry_value(&package)?)); // Set `package.path` let mut search_path = env::var("LUAU_PATH") @@ -81,9 +76,15 @@ pub(crate) fn register_package_module(lua: &Lua) -> Result<()> { } // Set `package.loaded` (table with a list of loaded modules) - let loaded = lua.create_table()?; - package.raw_set("loaded", &loaded)?; - lua.set_named_registry_value("_LOADED", loaded)?; + let loaded = if let Ok(Some(loaded)) = lua.named_registry_value::>("_LOADED") { + package.raw_set("loaded", &loaded)?; + loaded + } else { + let loaded = lua.create_table()?; + package.raw_set("loaded", &loaded)?; + lua.set_named_registry_value("_LOADED", &loaded)?; + loaded + }; // Set `package.loaders` let loaders = lua.create_sequence_from([lua.create_function(lua_loader)?])?; @@ -97,7 +98,8 @@ pub(crate) fn register_package_module(lua: &Lua) -> Result<()> { // Register the module and `require` function in globals let globals = lua.globals(); - globals.raw_set("package", package)?; + globals.raw_set("package", &package)?; + loaded.raw_set("package", package)?; globals.raw_set("require", unsafe { lua.create_c_function(lua_require)? })?; Ok(()) @@ -191,8 +193,8 @@ fn package_searchpath(name: &str, search_path: &str, try_prefix: bool) -> Option /// Tries to load a lua (text) file fn lua_loader(lua: &Lua, modname: StdString) -> Result { let package = { - let key = lua.app_data_ref::().unwrap(); - lua.registry_value::
(&key.0) + let loaded = lua.named_registry_value::
("_LOADED")?; + loaded.raw_get::
("package") }?; let search_path = package.get::("path").unwrap_or_default(); @@ -219,8 +221,8 @@ fn lua_loader(lua: &Lua, modname: StdString) -> Result { #[cfg(unix)] fn dylib_loader(lua: &Lua, modname: StdString) -> Result { let package = { - let key = lua.app_data_ref::().unwrap(); - lua.registry_value::
(&key.0) + let loaded = lua.named_registry_value::
("_LOADED")?; + loaded.raw_get::
("package") }?; let search_cpath = package.get::("cpath").unwrap_or_default(); diff --git a/tests/luau.rs b/tests/luau.rs index e8d4bee0..37218508 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -33,6 +33,17 @@ fn test_require() -> Result<()> { lua = Lua::new(); + // Check that require() can load stdlib modules (including `package`) + lua.load( + r#" + local math = require("math") + assert(math == _G.math, "math module does not match _G.math") + local package = require("package") + assert(package == _G.package, "package module does not match _G.package") + "#, + ) + .exec()?; + let temp_dir = tempfile::tempdir().unwrap(); fs::write( temp_dir.path().join("module.luau"), From 8e14b6e40b9369450fc65beacc0fc4aa43062d64 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 4 Aug 2024 22:22:02 +0100 Subject: [PATCH 139/635] Fix tests --- src/table.rs | 4 +- src/userdata/ext.rs | 2 +- tests/compile.rs | 1 - .../compile/async_any_userdata_method.stderr | 21 +- tests/compile/lua_norefunwindsafe.stderr | 85 +++++--- tests/compile/non_send.rs | 2 +- tests/compile/ref_nounwindsafe.stderr | 198 +++++++++++++----- tests/compile/scope_callback_capture.rs | 2 +- tests/compile/scope_callback_capture.stderr | 29 +-- tests/compile/scope_callback_inner.rs | 2 +- tests/compile/scope_callback_inner.stderr | 47 +---- tests/compile/scope_callback_outer.rs | 2 +- tests/compile/scope_callback_outer.stderr | 35 +--- tests/compile/scope_invariance.rs | 2 +- tests/compile/scope_invariance.stderr | 30 +-- tests/compile/scope_mutable_aliasing.stderr | 12 +- tests/compile/scope_userdata_borrow.stderr | 16 +- tests/compile/static_callback_args.rs | 32 --- tests/compile/static_callback_args.stderr | 31 --- tests/compile/userdata_borrow.rs | 4 +- 20 files changed, 245 insertions(+), 312 deletions(-) delete mode 100644 tests/compile/static_callback_args.rs delete mode 100644 tests/compile/static_callback_args.stderr diff --git a/src/table.rs b/src/table.rs index e74ffbe5..80c62283 100644 --- a/src/table.rs +++ b/src/table.rs @@ -882,7 +882,7 @@ pub trait TableExt: Sealed { /// passing the table itself along with `args` as function arguments. /// /// This is a shortcut for - /// `table.get::<_, Function>(key)?.call((table.clone(), arg1, ..., argN))` + /// `table.get::(key)?.call((table.clone(), arg1, ..., argN))` /// /// This might invoke the `__index` metamethod. fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result @@ -893,7 +893,7 @@ pub trait TableExt: Sealed { /// passing `args` as function arguments. /// /// This is a shortcut for - /// `table.get::<_, Function>(key)?.call(args)` + /// `table.get::(key)?.call(args)` /// /// This might invoke the `__index` metamethod. fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result diff --git a/src/userdata/ext.rs b/src/userdata/ext.rs index 42a257e9..b2588778 100644 --- a/src/userdata/ext.rs +++ b/src/userdata/ext.rs @@ -54,7 +54,7 @@ pub trait AnyUserDataExt: Sealed { /// passing `args` as function arguments. /// /// This is a shortcut for - /// `table.get::<_, Function>(key)?.call(args)` + /// `table.get::(key)?.call(args)` /// /// This might invoke the `__index` metamethod. fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result diff --git a/tests/compile.rs b/tests/compile.rs index 5a1a6262..f237cad2 100644 --- a/tests/compile.rs +++ b/tests/compile.rs @@ -12,7 +12,6 @@ fn test_compilation() { t.compile_fail("tests/compile/scope_invariance.rs"); t.compile_fail("tests/compile/scope_mutable_aliasing.rs"); t.compile_fail("tests/compile/scope_userdata_borrow.rs"); - t.compile_fail("tests/compile/static_callback_args.rs"); #[cfg(feature = "async")] { diff --git a/tests/compile/async_any_userdata_method.stderr b/tests/compile/async_any_userdata_method.stderr index f1b5e179..a33b5b6e 100644 --- a/tests/compile/async_any_userdata_method.stderr +++ b/tests/compile/async_any_userdata_method.stderr @@ -1,10 +1,18 @@ +error[E0596]: cannot borrow `s` as mutable, as it is a captured variable in a `Fn` closure + --> tests/compile/async_any_userdata_method.rs:9:58 + | +9 | reg.add_async_method("t", |_, this: &String, ()| async { + | ^^^^^ cannot borrow as mutable +10 | s = this; + | - mutable borrow occurs due to use of `s` in closure + error: lifetime may not live long enough --> tests/compile/async_any_userdata_method.rs:9:58 | 9 | reg.add_async_method("t", |_, this: &String, ()| async { | ___________________________________----------------------_^ | | | | - | | | return type of closure `{async block@$DIR/tests/compile/async_any_userdata_method.rs:9:58: 12:10}` contains a lifetime `'2` + | | | return type of closure `{async block@$DIR/tests/compile/async_any_userdata_method.rs:9:58: 9:63}` contains a lifetime `'2` | | lifetime `'1` represents this closure's body 10 | | s = this; 11 | | Ok(()) @@ -13,17 +21,6 @@ error: lifetime may not live long enough | = note: closure implements `Fn`, so references to captured variables can't escape the closure -error[E0596]: cannot borrow `s` as mutable, as it is a captured variable in a `Fn` closure - --> tests/compile/async_any_userdata_method.rs:9:58 - | -9 | reg.add_async_method("t", |_, this: &String, ()| async { - | __________________________________________________________^ -10 | | s = this; - | | - mutable borrow occurs due to use of `s` in closure -11 | | Ok(()) -12 | | }); - | |_________^ cannot borrow as mutable - error[E0597]: `s` does not live long enough --> tests/compile/async_any_userdata_method.rs:8:21 | diff --git a/tests/compile/lua_norefunwindsafe.stderr b/tests/compile/lua_norefunwindsafe.stderr index 9771dd11..a4410fec 100644 --- a/tests/compile/lua_norefunwindsafe.stderr +++ b/tests/compile/lua_norefunwindsafe.stderr @@ -1,51 +1,70 @@ -error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary +error[E0277]: the type `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary --> tests/compile/lua_norefunwindsafe.rs:7:18 | 7 | catch_unwind(|| lua.create_table().unwrap()); - | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary | | | required by a bound introduced by this call | - = help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell` -note: required because it appears within the type `ArcInner>` - --> $RUST/alloc/src/sync.rs + = help: within `mlua::types::sync::inner::ReentrantMutex`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<*mut lua_State>`, which is required by `{closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:20}: UnwindSafe` +note: required because it appears within the type `Cell<*mut lua_State>` + --> $RUST/core/src/cell.rs | - | struct ArcInner { - | ^^^^^^^^ -note: required because it appears within the type `PhantomData>>` - --> $RUST/core/src/marker.rs + | pub struct Cell { + | ^^^^ +note: required because it appears within the type `mlua::state::raw::RawLua` + --> src/state/raw.rs | - | pub struct PhantomData; - | ^^^^^^^^^^^ -note: required because it appears within the type `Arc>` - --> $RUST/alloc/src/sync.rs + | pub struct RawLua { + | ^^^^^^ +note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex` + --> src/types/sync.rs | - | pub struct Arc< + | pub(crate) struct ReentrantMutex(T); + | ^^^^^^^^^^^^^^ + = note: required for `Rc>` to implement `RefUnwindSafe` +note: required because it appears within the type `Lua` + --> src/state.rs + | + | pub struct Lua(XRc>); | ^^^ -note: required because it appears within the type `LuaInner` - --> src/lua.rs + = note: required for `&Lua` to implement `UnwindSafe` +note: required because it's used within this closure + --> tests/compile/lua_norefunwindsafe.rs:7:18 | - | pub struct LuaInner { - | ^^^^^^^^ -note: required because it appears within the type `ArcInner` - --> $RUST/alloc/src/sync.rs +7 | catch_unwind(|| lua.create_table().unwrap()); + | ^^ +note: required by a bound in `std::panic::catch_unwind` + --> $RUST/std/src/panic.rs | - | struct ArcInner { - | ^^^^^^^^ -note: required because it appears within the type `PhantomData>` - --> $RUST/core/src/marker.rs + | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { + | ^^^^^^^^^^ required by this bound in `catch_unwind` + +error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + --> tests/compile/lua_norefunwindsafe.rs:7:18 + | +7 | catch_unwind(|| lua.create_table().unwrap()); + | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + | | + | required by a bound introduced by this call | - | pub struct PhantomData; - | ^^^^^^^^^^^ -note: required because it appears within the type `Arc` - --> $RUST/alloc/src/sync.rs + = help: the trait `RefUnwindSafe` is not implemented for `UnsafeCell`, which is required by `{closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:20}: UnwindSafe` + = note: required for `Rc>` to implement `RefUnwindSafe` +note: required because it appears within the type `mlua::state::raw::RawLua` + --> src/state/raw.rs | - | pub struct Arc< - | ^^^ + | pub struct RawLua { + | ^^^^^^ +note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex` + --> src/types/sync.rs + | + | pub(crate) struct ReentrantMutex(T); + | ^^^^^^^^^^^^^^ + = note: required for `Rc>` to implement `RefUnwindSafe` note: required because it appears within the type `Lua` - --> src/lua.rs + --> src/state.rs | - | pub struct Lua(Arc); + | pub struct Lua(XRc>); | ^^^ = note: required for `&Lua` to implement `UnwindSafe` note: required because it's used within this closure @@ -53,7 +72,7 @@ note: required because it's used within this closure | 7 | catch_unwind(|| lua.create_table().unwrap()); | ^^ -note: required by a bound in `catch_unwind` +note: required by a bound in `std::panic::catch_unwind` --> $RUST/std/src/panic.rs | | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { diff --git a/tests/compile/non_send.rs b/tests/compile/non_send.rs index fe030a5c..cd21f445 100644 --- a/tests/compile/non_send.rs +++ b/tests/compile/non_send.rs @@ -11,7 +11,7 @@ fn main() -> Result<()> { lua.create_function(move |_, ()| { Ok(data.get()) })? - .call::<_, i32>(())?; + .call::(())?; Ok(()) } diff --git a/tests/compile/ref_nounwindsafe.stderr b/tests/compile/ref_nounwindsafe.stderr index 342dc7b4..4c82de1b 100644 --- a/tests/compile/ref_nounwindsafe.stderr +++ b/tests/compile/ref_nounwindsafe.stderr @@ -1,69 +1,167 @@ -error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary +error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary --> tests/compile/ref_nounwindsafe.rs:8:18 | 8 | catch_unwind(move || table.set("a", "b").unwrap()); - | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary | | | required by a bound introduced by this call | - = help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell` -note: required because it appears within the type `ArcInner>` - --> $RUST/alloc/src/sync.rs - | - | struct ArcInner { - | ^^^^^^^^ -note: required because it appears within the type `PhantomData>>` - --> $RUST/core/src/marker.rs - | - | pub struct PhantomData; - | ^^^^^^^^^^^ -note: required because it appears within the type `Arc>` - --> $RUST/alloc/src/sync.rs - | - | pub struct Arc< - | ^^^ -note: required because it appears within the type `LuaInner` - --> src/lua.rs - | - | pub struct LuaInner { - | ^^^^^^^^ -note: required because it appears within the type `ArcInner` - --> $RUST/alloc/src/sync.rs - | - | struct ArcInner { - | ^^^^^^^^ -note: required because it appears within the type `PhantomData>` - --> $RUST/core/src/marker.rs - | - | pub struct PhantomData; - | ^^^^^^^^^^^ -note: required because it appears within the type `Arc` - --> $RUST/alloc/src/sync.rs - | - | pub struct Arc< - | ^^^ -note: required because it appears within the type `Lua` - --> src/lua.rs - | - | pub struct Lua(Arc); - | ^^^ - = note: required for `&Lua` to implement `UnwindSafe` -note: required because it appears within the type `LuaRef<'_>` + = help: within `rc::RcBox>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell`, which is required by `{closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:25}: UnwindSafe` +note: required because it appears within the type `Cell` + --> $RUST/core/src/cell.rs + | + | pub struct Cell { + | ^^^^ +note: required because it appears within the type `rc::RcBox>` + --> $RUST/alloc/src/rc.rs + | + | struct RcBox { + | ^^^^^ + = note: required for `NonNull>>` to implement `UnwindSafe` +note: required because it appears within the type `std::rc::Weak>` + --> $RUST/alloc/src/rc.rs + | + | pub struct Weak< + | ^^^^ +note: required because it appears within the type `mlua::state::WeakLua` + --> src/state.rs + | + | pub(crate) struct WeakLua(XWeak>); + | ^^^^^^^ +note: required because it appears within the type `mlua::types::ValueRef` + --> src/types.rs + | + | pub(crate) struct ValueRef { + | ^^^^^^^^ +note: required because it appears within the type `LuaTable` + --> src/table.rs + | + | pub struct Table(pub(crate) ValueRef); + | ^^^^^ +note: required because it's used within this closure + --> tests/compile/ref_nounwindsafe.rs:8:18 + | +8 | catch_unwind(move || table.set("a", "b").unwrap()); + | ^^^^^^^ +note: required by a bound in `std::panic::catch_unwind` + --> $RUST/std/src/panic.rs + | + | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { + | ^^^^^^^^^^ required by this bound in `catch_unwind` + +error[E0277]: the type `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + --> tests/compile/ref_nounwindsafe.rs:8:18 + | +8 | catch_unwind(move || table.set("a", "b").unwrap()); + | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + | | + | required by a bound introduced by this call + | + = help: within `rc::RcBox>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<*mut lua_State>`, which is required by `{closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:25}: UnwindSafe` +note: required because it appears within the type `Cell<*mut lua_State>` + --> $RUST/core/src/cell.rs + | + | pub struct Cell { + | ^^^^ +note: required because it appears within the type `mlua::state::raw::RawLua` + --> src/state/raw.rs + | + | pub struct RawLua { + | ^^^^^^ +note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex` + --> src/types/sync.rs + | + | pub(crate) struct ReentrantMutex(T); + | ^^^^^^^^^^^^^^ +note: required because it appears within the type `rc::RcBox>` + --> $RUST/alloc/src/rc.rs + | + | struct RcBox { + | ^^^^^ + = note: required for `NonNull>>` to implement `UnwindSafe` +note: required because it appears within the type `std::rc::Weak>` + --> $RUST/alloc/src/rc.rs + | + | pub struct Weak< + | ^^^^ +note: required because it appears within the type `mlua::state::WeakLua` + --> src/state.rs + | + | pub(crate) struct WeakLua(XWeak>); + | ^^^^^^^ +note: required because it appears within the type `mlua::types::ValueRef` + --> src/types.rs + | + | pub(crate) struct ValueRef { + | ^^^^^^^^ +note: required because it appears within the type `LuaTable` + --> src/table.rs + | + | pub struct Table(pub(crate) ValueRef); + | ^^^^^ +note: required because it's used within this closure + --> tests/compile/ref_nounwindsafe.rs:8:18 + | +8 | catch_unwind(move || table.set("a", "b").unwrap()); + | ^^^^^^^ +note: required by a bound in `std::panic::catch_unwind` + --> $RUST/std/src/panic.rs + | + | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { + | ^^^^^^^^^^ required by this bound in `catch_unwind` + +error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + --> tests/compile/ref_nounwindsafe.rs:8:18 + | +8 | catch_unwind(move || table.set("a", "b").unwrap()); + | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + | | + | required by a bound introduced by this call + | + = help: the trait `RefUnwindSafe` is not implemented for `UnsafeCell`, which is required by `{closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:25}: UnwindSafe` + = note: required for `Rc>` to implement `RefUnwindSafe` +note: required because it appears within the type `mlua::state::raw::RawLua` + --> src/state/raw.rs + | + | pub struct RawLua { + | ^^^^^^ +note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex` + --> src/types/sync.rs + | + | pub(crate) struct ReentrantMutex(T); + | ^^^^^^^^^^^^^^ +note: required because it appears within the type `rc::RcBox>` + --> $RUST/alloc/src/rc.rs + | + | struct RcBox { + | ^^^^^ + = note: required for `NonNull>>` to implement `UnwindSafe` +note: required because it appears within the type `std::rc::Weak>` + --> $RUST/alloc/src/rc.rs + | + | pub struct Weak< + | ^^^^ +note: required because it appears within the type `mlua::state::WeakLua` + --> src/state.rs + | + | pub(crate) struct WeakLua(XWeak>); + | ^^^^^^^ +note: required because it appears within the type `mlua::types::ValueRef` --> src/types.rs | - | pub(crate) struct LuaRef<'lua> { - | ^^^^^^ -note: required because it appears within the type `Table<'_>` + | pub(crate) struct ValueRef { + | ^^^^^^^^ +note: required because it appears within the type `LuaTable` --> src/table.rs | - | pub struct Table<'lua>(pub(crate) LuaRef<'lua>); + | pub struct Table(pub(crate) ValueRef); | ^^^^^ note: required because it's used within this closure --> tests/compile/ref_nounwindsafe.rs:8:18 | 8 | catch_unwind(move || table.set("a", "b").unwrap()); | ^^^^^^^ -note: required by a bound in `catch_unwind` +note: required by a bound in `std::panic::catch_unwind` --> $RUST/std/src/panic.rs | | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { diff --git a/tests/compile/scope_callback_capture.rs b/tests/compile/scope_callback_capture.rs index 927c36d7..8f0d0b07 100644 --- a/tests/compile/scope_callback_capture.rs +++ b/tests/compile/scope_callback_capture.rs @@ -12,7 +12,7 @@ fn main() { inner = Some(t); Ok(()) })?; - f.call::<_, ()>(lua.create_table()?)?; + f.call::<()>(lua.create_table()?)?; Ok(()) }); } diff --git a/tests/compile/scope_callback_capture.stderr b/tests/compile/scope_callback_capture.stderr index 5c8914b3..2b641480 100644 --- a/tests/compile/scope_callback_capture.stderr +++ b/tests/compile/scope_callback_capture.stderr @@ -1,26 +1,5 @@ -warning: unused variable: `old` - --> $DIR/scope_callback_capture.rs:9:29 +error[E0599]: no method named `scope` found for struct `Lua` in the current scope + --> tests/compile/scope_callback_capture.rs:5:9 | -9 | if let Some(old) = inner.take() { - | ^^^ help: if this is intentional, prefix it with an underscore: `_old` - | - = note: `#[warn(unused_variables)]` on by default - -error[E0521]: borrowed data escapes outside of closure - --> $DIR/scope_callback_capture.rs:7:17 - | -5 | lua.scope(|scope| { - | ----- - | | - | `scope` declared here, outside of the closure body - | `scope` is a reference that is only valid in the closure body -6 | let mut inner: Option
= None; -7 | let f = scope - | _________________^ -8 | | .create_function_mut(move |_, t: Table| { -9 | | if let Some(old) = inner.take() { -10 | | // Access old callback `Lua`. -... | -13 | | Ok(()) -14 | | })?; - | |______________^ `scope` escapes the closure body here +5 | lua.scope(|scope| { + | ----^^^^^ method not found in `Lua` diff --git a/tests/compile/scope_callback_inner.rs b/tests/compile/scope_callback_inner.rs index 037c6acb..b7958b47 100644 --- a/tests/compile/scope_callback_inner.rs +++ b/tests/compile/scope_callback_inner.rs @@ -9,7 +9,7 @@ fn main() { inner = Some(t); Ok(()) })?; - f.call::<_, ()>(lua.create_table()?)?; + f.call::<()>(lua.create_table()?)?; Ok(()) }); } diff --git a/tests/compile/scope_callback_inner.stderr b/tests/compile/scope_callback_inner.stderr index 55c38b94..5fb9a570 100644 --- a/tests/compile/scope_callback_inner.stderr +++ b/tests/compile/scope_callback_inner.stderr @@ -1,42 +1,5 @@ -error[E0521]: borrowed data escapes outside of closure - --> tests/compile/scope_callback_inner.rs:7:17 - | -5 | lua.scope(|scope| { - | ----- - | | - | `scope` declared here, outside of the closure body - | `scope` is a reference that is only valid in the closure body -6 | let mut inner: Option
= None; -7 | let f = scope - | _________________^ -8 | | .create_function_mut(|_, t: Table| { -9 | | inner = Some(t); -10 | | Ok(()) -11 | | })?; - | |______________^ `scope` escapes the closure body here - -error[E0373]: closure may outlive the current function, but it borrows `inner`, which is owned by the current function - --> tests/compile/scope_callback_inner.rs:8:34 - | -5 | lua.scope(|scope| { - | ----- has type `&mlua::Scope<'_, '2>` -... -8 | .create_function_mut(|_, t: Table| { - | ^^^^^^^^^^^^^ may outlive borrowed value `inner` -9 | inner = Some(t); - | ----- `inner` is borrowed here - | -note: function requires argument type to outlive `'2` - --> tests/compile/scope_callback_inner.rs:7:17 - | -7 | let f = scope - | _________________^ -8 | | .create_function_mut(|_, t: Table| { -9 | | inner = Some(t); -10 | | Ok(()) -11 | | })?; - | |______________^ -help: to force the closure to take ownership of `inner` (and any other referenced variables), use the `move` keyword - | -8 | .create_function_mut(move |_, t: Table| { - | ++++ +error[E0599]: no method named `scope` found for struct `Lua` in the current scope + --> tests/compile/scope_callback_inner.rs:5:9 + | +5 | lua.scope(|scope| { + | ----^^^^^ method not found in `Lua` diff --git a/tests/compile/scope_callback_outer.rs b/tests/compile/scope_callback_outer.rs index 7c9974ea..8aa09868 100644 --- a/tests/compile/scope_callback_outer.rs +++ b/tests/compile/scope_callback_outer.rs @@ -9,7 +9,7 @@ fn main() { outer = Some(t); Ok(()) })?; - f.call::<_, ()>(lua.create_table()?)?; + f.call::<()>(lua.create_table()?)?; Ok(()) }); } diff --git a/tests/compile/scope_callback_outer.stderr b/tests/compile/scope_callback_outer.stderr index 2a15e4a6..dfafdaee 100644 --- a/tests/compile/scope_callback_outer.stderr +++ b/tests/compile/scope_callback_outer.stderr @@ -1,30 +1,5 @@ -error[E0521]: borrowed data escapes outside of closure - --> $DIR/scope_callback_outer.rs:7:17 - | -6 | lua.scope(|scope| { - | ----- - | | - | `scope` declared here, outside of the closure body - | `scope` is a reference that is only valid in the closure body -7 | let f = scope - | _________________^ -8 | | .create_function_mut(|_, t: Table| { -9 | | outer = Some(t); -10 | | Ok(()) -11 | | })?; - | |______________^ `scope` escapes the closure body here - -error[E0597]: `outer` does not live long enough - --> $DIR/scope_callback_outer.rs:9:17 - | -6 | lua.scope(|scope| { - | ------- value captured here -... -9 | outer = Some(t); - | ^^^^^ borrowed value does not live long enough -... -15 | } - | - - | | - | `outer` dropped here while still borrowed - | borrow might be used here, when `outer` is dropped and runs the destructor for type `Option>` +error[E0599]: no method named `scope` found for struct `Lua` in the current scope + --> tests/compile/scope_callback_outer.rs:6:9 + | +6 | lua.scope(|scope| { + | ----^^^^^ method not found in `Lua` diff --git a/tests/compile/scope_invariance.rs b/tests/compile/scope_invariance.rs index e4f4ea75..abf44bb9 100644 --- a/tests/compile/scope_invariance.rs +++ b/tests/compile/scope_invariance.rs @@ -18,6 +18,6 @@ fn main() { })? }; - f.call::<_, ()>(()) + f.call::<()>(()) }); } diff --git a/tests/compile/scope_invariance.stderr b/tests/compile/scope_invariance.stderr index 75a058d0..40ed6850 100644 --- a/tests/compile/scope_invariance.stderr +++ b/tests/compile/scope_invariance.stderr @@ -1,25 +1,5 @@ -error[E0373]: closure may outlive the current function, but it borrows `test.field`, which is owned by the current function - --> tests/compile/scope_invariance.rs:14:38 - | -9 | lua.scope(|scope| { - | ----- has type `&mlua::Scope<'_, '1>` -... -14 | .create_function_mut(|_, ()| { - | ^^^^^^^ may outlive borrowed value `test.field` -15 | test.field = 42; - | ---------- `test.field` is borrowed here - | -note: function requires argument type to outlive `'1` - --> tests/compile/scope_invariance.rs:13:13 - | -13 | / scope -14 | | .create_function_mut(|_, ()| { -15 | | test.field = 42; -16 | | //~^ error: `test` does not live long enough -17 | | Ok(()) -18 | | })? - | |__________________^ -help: to force the closure to take ownership of `test.field` (and any other referenced variables), use the `move` keyword - | -14 | .create_function_mut(move |_, ()| { - | ++++ +error[E0599]: no method named `scope` found for struct `Lua` in the current scope + --> tests/compile/scope_invariance.rs:9:9 + | +9 | lua.scope(|scope| { + | ----^^^^^ method not found in `Lua` diff --git a/tests/compile/scope_mutable_aliasing.stderr b/tests/compile/scope_mutable_aliasing.stderr index c661826f..adbb63b5 100644 --- a/tests/compile/scope_mutable_aliasing.stderr +++ b/tests/compile/scope_mutable_aliasing.stderr @@ -1,9 +1,5 @@ -error[E0499]: cannot borrow `i` as mutable more than once at a time - --> $DIR/scope_mutable_aliasing.rs:12:61 +error[E0599]: no method named `scope` found for struct `Lua` in the current scope + --> tests/compile/scope_mutable_aliasing.rs:10:9 | -11 | let _a = scope.create_nonstatic_userdata(MyUserData(&mut i)).unwrap(); - | ------ first mutable borrow occurs here -12 | let _b = scope.create_nonstatic_userdata(MyUserData(&mut i)).unwrap(); - | ------------------------- ^^^^^^ second mutable borrow occurs here - | | - | first borrow later used by call +10 | lua.scope(|scope| { + | ----^^^^^ method not found in `Lua` diff --git a/tests/compile/scope_userdata_borrow.stderr b/tests/compile/scope_userdata_borrow.stderr index 132acddc..87abf7b8 100644 --- a/tests/compile/scope_userdata_borrow.stderr +++ b/tests/compile/scope_userdata_borrow.stderr @@ -1,15 +1,5 @@ -error[E0597]: `ibad` does not live long enough - --> tests/compile/scope_userdata_borrow.rs:15:56 +error[E0599]: no method named `scope` found for struct `Lua` in the current scope + --> tests/compile/scope_userdata_borrow.rs:11:9 | 11 | lua.scope(|scope| { - | ----- has type `&mlua::Scope<'_, '1>` -... -14 | let ibad = 42; - | ---- binding `ibad` declared here -15 | scope.create_nonstatic_userdata(MyUserData(&ibad)).unwrap(); - | -------------------------------------------^^^^^-- - | | | - | | borrowed value does not live long enough - | argument requires that `ibad` is borrowed for `'1` -16 | }; - | - `ibad` dropped here while still borrowed + | ----^^^^^ method not found in `Lua` diff --git a/tests/compile/static_callback_args.rs b/tests/compile/static_callback_args.rs deleted file mode 100644 index 66dbf8b2..00000000 --- a/tests/compile/static_callback_args.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::cell::RefCell; - -use mlua::{Lua, Result, Table}; - -fn main() -> Result<()> { - thread_local! { - static BAD_TIME: RefCell>> = RefCell::new(None); - } - - let lua = Lua::new(); - - lua.create_function(|_, table: Table| { - BAD_TIME.with(|bt| { - *bt.borrow_mut() = Some(table); - }); - Ok(()) - })? - .call::<_, ()>(lua.create_table()?)?; - - // In debug, this will panic with a reference leak before getting to the next part but - // it segfaults anyway. - drop(lua); - - BAD_TIME.with(|bt| { - println!( - "you're gonna have a bad time: {}", - bt.borrow().as_ref().unwrap().len().unwrap() - ); - }); - - Ok(()) -} diff --git a/tests/compile/static_callback_args.stderr b/tests/compile/static_callback_args.stderr deleted file mode 100644 index 2bf660b9..00000000 --- a/tests/compile/static_callback_args.stderr +++ /dev/null @@ -1,31 +0,0 @@ -error[E0597]: `lua` does not live long enough - --> tests/compile/static_callback_args.rs:12:5 - | -10 | let lua = Lua::new(); - | --- binding `lua` declared here -11 | -12 | lua.create_function(|_, table: Table| { - | ^^^ borrowed value does not live long enough -13 | / BAD_TIME.with(|bt| { -14 | | *bt.borrow_mut() = Some(table); -15 | | }); - | |__________- argument requires that `lua` is borrowed for `'static` -... -32 | } - | - `lua` dropped here while still borrowed - -error[E0505]: cannot move out of `lua` because it is borrowed - --> tests/compile/static_callback_args.rs:22:10 - | -10 | let lua = Lua::new(); - | --- binding `lua` declared here -11 | -12 | lua.create_function(|_, table: Table| { - | --- borrow of `lua` occurs here -13 | / BAD_TIME.with(|bt| { -14 | | *bt.borrow_mut() = Some(table); -15 | | }); - | |__________- argument requires that `lua` is borrowed for `'static` -... -22 | drop(lua); - | ^^^ move out of `lua` occurs here diff --git a/tests/compile/userdata_borrow.rs b/tests/compile/userdata_borrow.rs index 26eb3c77..d29e5275 100644 --- a/tests/compile/userdata_borrow.rs +++ b/tests/compile/userdata_borrow.rs @@ -9,9 +9,9 @@ fn main() -> Result<()> { impl UserData for MyUserData {}; let _userdata_ref; { - let touter = globals.get::<_, Table>("touter")?; + let touter = globals.get::
("touter")?; touter.set("userdata", lua.create_userdata(MyUserData)?)?; - let userdata = touter.get::<_, AnyUserData>("userdata")?; + let userdata = touter.get::("userdata")?; _userdata_ref = userdata.borrow::(); //~^ error: `userdata` does not live long enough } From ac6a3914265cd4925fc6ec135d1f4569efc75d9a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 5 Aug 2024 14:57:15 +0100 Subject: [PATCH 140/635] Remove `Lua::into_static` and `Lua::from_static` (undocumented). --- src/state.rs | 30 ------------ tests/static.rs | 122 ------------------------------------------------ 2 files changed, 152 deletions(-) delete mode 100644 tests/static.rs diff --git a/src/state.rs b/src/state.rs index fef8f39a..9734a601 100644 --- a/src/state.rs +++ b/src/state.rs @@ -344,36 +344,6 @@ impl Lua { loaded.raw_set(modname, Nil) } - /// Consumes and leaks `Lua` object, returning a static reference `&'static Lua`. - /// - /// This function is useful when the `Lua` object is supposed to live for the remainder - /// of the program's life. - /// - /// Dropping the returned reference will cause a memory leak. If this is not acceptable, - /// the reference should first be wrapped with the [`Lua::from_static`] function producing a - /// `Lua`. This `Lua` object can then be dropped which will properly release the allocated - /// memory. - /// - /// [`Lua::from_static`]: #method.from_static - /// - /// FIXME: remove - #[doc(hidden)] - pub fn into_static(self) -> &'static Self { - Box::leak(Box::new(self)) - } - - /// Constructs a `Lua` from a static reference to it. - /// - /// # Safety - /// This function is unsafe because improper use may lead to memory problems or undefined - /// behavior. - /// - /// FIXME: remove - #[doc(hidden)] - pub unsafe fn from_static(lua: &'static Lua) -> Self { - *Box::from_raw(lua as *const Lua as *mut Lua) - } - // Executes module entrypoint function, which returns only one Value. // The returned value then pushed onto the stack. #[doc(hidden)] diff --git a/tests/static.rs b/tests/static.rs deleted file mode 100644 index aeb7eca7..00000000 --- a/tests/static.rs +++ /dev/null @@ -1,122 +0,0 @@ -use std::cell::RefCell; - -use mlua::{Lua, Result, Table}; - -#[test] -fn test_static_lua() -> Result<()> { - let lua = Lua::new().into_static(); - - thread_local! { - static TABLE: RefCell> = RefCell::new(None); - } - - let f = lua.create_function(|_, table: Table| { - TABLE.with(|t| { - table.raw_insert(1, "hello")?; - *t.borrow_mut() = Some(table); - Ok(()) - }) - })?; - - f.call::<()>(lua.create_table()?)?; - drop(f); - lua.gc_collect()?; - - TABLE.with(|t| { - assert!(t.borrow().as_ref().unwrap().len().unwrap() == 1); - *t.borrow_mut() = None; - }); - - // Consume the Lua instance - unsafe { Lua::from_static(lua) }; - - Ok(()) -} - -#[test] -fn test_static_lua_coroutine() -> Result<()> { - let lua = Lua::new().into_static(); - - thread_local! { - static TABLE: RefCell> = RefCell::new(None); - } - - let f = lua.create_function(|_, table: Table| { - TABLE.with(|t| { - table.raw_insert(1, "hello")?; - *t.borrow_mut() = Some(table); - Ok(()) - }) - })?; - - let co = lua.create_thread(f)?; - co.resume::<()>(lua.create_table()?)?; - drop(co); - lua.gc_collect()?; - - TABLE.with(|t| { - assert_eq!( - t.borrow().as_ref().unwrap().get::(1i32).unwrap(), - "hello".to_string() - ); - *t.borrow_mut() = None; - }); - - // Consume the Lua instance - unsafe { Lua::from_static(lua) }; - - Ok(()) -} - -#[cfg(feature = "async")] -#[tokio::test] -async fn test_static_async() -> Result<()> { - let lua = Lua::new().into_static(); - - #[cfg(not(target_arch = "wasm32"))] - async fn sleep_ms(ms: u64) { - tokio::time::sleep(std::time::Duration::from_millis(ms)).await; - } - - #[cfg(target_arch = "wasm32")] - async fn sleep_ms(_ms: u64) { - tokio::task::yield_now().await; - } - - let timer = lua.create_async_function(|_, (i, n, f): (u64, u64, mlua::Function)| async move { - tokio::task::spawn_local(async move { - for _ in 0..n { - tokio::task::spawn_local(f.call_async::<()>(())); - sleep_ms(i).await; - } - }); - Ok(()) - })?; - lua.globals().set("timer", timer)?; - - { - let local_set = tokio::task::LocalSet::new(); - local_set - .run_until( - lua.load( - r#" - local cnt = 0 - timer(1, 100, function() - cnt = cnt + 1 - if cnt % 10 == 0 then - collectgarbage() - end - end) - "#, - ) - .exec_async(), - ) - .await?; - local_set.await; - } - - // Consume the Lua instance - unsafe { Lua::from_static(lua) }; - - Ok(()) -} From aa47324ee9c09fda894b0142314cafa9e92fed01 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 5 Aug 2024 21:34:51 +0100 Subject: [PATCH 141/635] Use pool for MultiValue container --- Cargo.toml | 2 +- src/multi.rs | 8 ++--- src/state/extra.rs | 7 +--- src/state/raw.rs | 19 ----------- src/state/util.rs | 1 - src/value.rs | 81 ++++++++++++++++++++++------------------------ 6 files changed, 45 insertions(+), 73 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 760205a6..042260fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "mlua" version = "0.10.0-beta.1" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] -rust-version = "1.71" +rust-version = "1.79.0" edition = "2021" repository = "https://github.com/khvzak/mlua" documentation = "https://docs.rs/mlua" diff --git a/src/multi.rs b/src/multi.rs index 443c2658..f62f15a3 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -49,7 +49,7 @@ impl IntoLuaMulti for StdResult<(), E> { impl IntoLuaMulti for T { #[inline] fn into_lua_multi(self, lua: &Lua) -> Result { - let mut v = MultiValue::with_lua_and_capacity(lua, 1); + let mut v = MultiValue::with_capacity(1); v.push_back(self.into_lua(lua)?); Ok(v) } @@ -177,7 +177,7 @@ impl DerefMut for Variadic { impl IntoLuaMulti for Variadic { #[inline] fn into_lua_multi(self, lua: &Lua) -> Result { - let mut values = MultiValue::with_lua_and_capacity(lua, self.0.len()); + let mut values = MultiValue::with_capacity(self.0.len()); values.extend_from_values(self.0.into_iter().map(|val| val.into_lua(lua)))?; Ok(values) } @@ -198,8 +198,8 @@ macro_rules! impl_tuple { () => ( impl IntoLuaMulti for () { #[inline] - fn into_lua_multi(self, lua: &Lua) -> Result { - Ok(MultiValue::with_lua_and_capacity(lua, 0)) + fn into_lua_multi(self, _: &Lua) -> Result { + Ok(MultiValue::new()) } #[inline] diff --git a/src/state/extra.rs b/src/state/extra.rs index d3724e67..e8bbc646 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -1,10 +1,9 @@ use std::any::TypeId; use std::cell::UnsafeCell; -use std::rc::Rc; -// use std::collections::VecDeque; use std::mem::{self, MaybeUninit}; use std::os::raw::{c_int, c_void}; use std::ptr; +use std::rc::Rc; use std::sync::Arc; use parking_lot::Mutex; @@ -28,7 +27,6 @@ use super::{Lua, WeakLua}; static EXTRA_REGISTRY_KEY: u8 = 0; const WRAPPED_FAILURE_POOL_SIZE: usize = 64; -// const MULTIVALUE_POOL_SIZE: usize = 64; const REF_STACK_RESERVE: c_int = 1; /// Data associated with the Lua state. @@ -61,8 +59,6 @@ pub(crate) struct ExtraData { // Pool of `WrappedFailure` enums in the ref thread (as userdata) pub(super) wrapped_failure_pool: Vec, - // Pool of `MultiValue` containers - // multivalue_pool: Vec>, // Pool of `Thread`s (coroutines) for async execution #[cfg(feature = "async")] pub(super) thread_pool: Vec, @@ -162,7 +158,6 @@ impl ExtraData { ref_stack_top: ffi::lua_gettop(ref_thread), ref_free: Vec::new(), wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_SIZE), - // multivalue_pool: Vec::with_capacity(MULTIVALUE_POOL_SIZE), #[cfg(feature = "async")] thread_pool: Vec::new(), wrapped_failure_mt_ptr, diff --git a/src/state/raw.rs b/src/state/raw.rs index fccf2540..959ccd64 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -496,25 +496,6 @@ impl RawLua { false } - // FIXME - // #[inline] - // pub(crate) fn pop_multivalue_from_pool(&self) -> Option> { - // let extra = unsafe { &mut *self.extra.get() }; - // extra.multivalue_pool.pop() - // } - - // FIXME - // #[inline] - // pub(crate) fn push_multivalue_to_pool(&self, mut multivalue: VecDeque) { - // let extra = unsafe { &mut *self.extra.get() }; - // if extra.multivalue_pool.len() < MULTIVALUE_POOL_SIZE { - // multivalue.clear(); - // extra - // .multivalue_pool - // .push(unsafe { mem::transmute(multivalue) }); - // } - // } - /// Pushes a value that implements `IntoLua` onto the Lua stack. /// /// Uses 2 stack spaces, does not call checkstack. diff --git a/src/state/util.rs b/src/state/util.rs index 9d557340..bb082c72 100644 --- a/src/state/util.rs +++ b/src/state/util.rs @@ -8,7 +8,6 @@ use crate::state::{ExtraData, RawLua}; use crate::util::{self, get_internal_metatable, WrappedFailure}; const WRAPPED_FAILURE_POOL_SIZE: usize = 64; -// const MULTIVALUE_POOL_SIZE: usize = 64; pub(super) struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State); diff --git a/src/value.rs b/src/value.rs index 2429910a..6f603199 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::cmp::Ordering; use std::collections::{vec_deque, HashSet, VecDeque}; use std::ops::{Deref, DerefMut}; @@ -23,7 +24,7 @@ use { crate::table::SerializableTable, rustc_hash::FxHashSet, serde::ser::{self, Serialize, Serializer}, - std::{cell::RefCell, rc::Rc, result::Result as StdResult}, + std::{rc::Rc, result::Result as StdResult}, }; /// A dynamically typed Lua value. The `String`, `Table`, `Function`, `Thread`, and `UserData` @@ -744,21 +745,25 @@ pub trait FromLua: Sized { } } +// Per-thread size of the VecDeque pool for MultiValue container. +const MULTIVALUE_POOL_SIZE: usize = 32; + +thread_local! { + static MULTIVALUE_POOL: RefCell>> = const { RefCell::new(Vec::new()) }; +} + /// Multiple Lua values used for both argument passing and also for multiple return values. #[derive(Debug, Clone)] -pub struct MultiValue { - deque: VecDeque, - // FIXME - // lua: Option<&'static Lua>, -} +pub struct MultiValue(VecDeque); impl Drop for MultiValue { fn drop(&mut self) { - // FIXME - // if let Some(lua) = self.lua { - // let vec = mem::take(&mut self.deque); - // lua.push_multivalue_to_pool(vec); - // } + MULTIVALUE_POOL.with_borrow_mut(|pool| { + if pool.len() < MULTIVALUE_POOL_SIZE { + self.0.clear(); + pool.push(mem::take(&mut self.0)); + } + }); } } @@ -774,44 +779,38 @@ impl Deref for MultiValue { #[inline] fn deref(&self) -> &Self::Target { - &self.deque + &self.0 } } impl DerefMut for MultiValue { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.deque + &mut self.0 } } impl MultiValue { /// Creates an empty `MultiValue` containing no values. - pub const fn new() -> MultiValue { - MultiValue { - deque: VecDeque::new(), - // lua: None, - } + pub fn new() -> MultiValue { + Self::with_capacity(0) } /// Similar to `new` but can reuse previously used container with allocated capacity. #[inline] - pub(crate) fn with_lua_and_capacity(_lua: &Lua, capacity: usize) -> MultiValue { - // FIXME - // let deque = lua - // .pop_multivalue_from_pool() - // .map(|mut deque| { - // if capacity > 0 { - // deque.reserve(capacity); - // } - // deque - // }) - // .unwrap_or_else(|| VecDeque::with_capacity(capacity)); - let deque = VecDeque::with_capacity(capacity); - MultiValue { - deque, - // lua: Some(lua), - } + pub(crate) fn with_capacity(capacity: usize) -> MultiValue { + let deque = MULTIVALUE_POOL.with_borrow_mut(|pool| { + pool.pop().map_or_else( + || VecDeque::with_capacity(capacity), + |mut deque| { + if capacity > 0 { + deque.reserve(capacity); + } + deque + }, + ) + }); + MultiValue(deque) } #[inline] @@ -826,11 +825,9 @@ impl MultiValue { impl FromIterator for MultiValue { #[inline] fn from_iter>(iter: I) -> Self { - let deque = VecDeque::from_iter(iter); - MultiValue { - deque, - // lua: None, - } + let mut multi_value = MultiValue::new(); + multi_value.extend(iter); + multi_value } } @@ -840,7 +837,7 @@ impl IntoIterator for MultiValue { #[inline] fn into_iter(mut self) -> Self::IntoIter { - let deque = mem::take(&mut self.deque); + let deque = mem::take(&mut self.0); mem::forget(self); deque.into_iter() } @@ -852,7 +849,7 @@ impl<'a> IntoIterator for &'a MultiValue { #[inline] fn into_iter(self) -> Self::IntoIter { - self.deque.iter() + self.0.iter() } } @@ -910,7 +907,7 @@ pub trait FromLuaMulti: Sized { #[doc(hidden)] #[inline] unsafe fn from_stack_multi(nvals: c_int, lua: &RawLua) -> Result { - let mut values = MultiValue::with_lua_and_capacity(lua.lua(), nvals as usize); + let mut values = MultiValue::with_capacity(nvals as usize); for idx in 0..nvals { values.push_back(lua.stack_value(-nvals + idx)); } From f0a995a35721fe7010b03ab2e5e7762e935c058a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 5 Aug 2024 23:36:17 +0100 Subject: [PATCH 142/635] Make `MultiValue::with_capacity` public --- src/value.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/value.rs b/src/value.rs index 6f603199..3fbe5159 100644 --- a/src/value.rs +++ b/src/value.rs @@ -792,13 +792,13 @@ impl DerefMut for MultiValue { impl MultiValue { /// Creates an empty `MultiValue` containing no values. + #[inline] pub fn new() -> MultiValue { Self::with_capacity(0) } - /// Similar to `new` but can reuse previously used container with allocated capacity. - #[inline] - pub(crate) fn with_capacity(capacity: usize) -> MultiValue { + /// Creates an empty `MultiValue` container with space for at least `capacity` elements. + pub fn with_capacity(capacity: usize) -> MultiValue { let deque = MULTIVALUE_POOL.with_borrow_mut(|pool| { pool.pop().map_or_else( || VecDeque::with_capacity(capacity), From 10999babe0fc936552437b50fa0aa8bf6f4cf4e8 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 6 Aug 2024 01:32:20 +0100 Subject: [PATCH 143/635] Replace `MultiValue::extend_from_values` with `from_lua_iter` --- src/multi.rs | 4 +--- src/value.rs | 8 +++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/multi.rs b/src/multi.rs index f62f15a3..8488f61d 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -177,9 +177,7 @@ impl DerefMut for Variadic { impl IntoLuaMulti for Variadic { #[inline] fn into_lua_multi(self, lua: &Lua) -> Result { - let mut values = MultiValue::with_capacity(self.0.len()); - values.extend_from_values(self.0.into_iter().map(|val| val.into_lua(lua)))?; - Ok(values) + MultiValue::from_lua_iter(lua, self) } } diff --git a/src/value.rs b/src/value.rs index 3fbe5159..5505d728 100644 --- a/src/value.rs +++ b/src/value.rs @@ -814,11 +814,13 @@ impl MultiValue { } #[inline] - pub(crate) fn extend_from_values(&mut self, iter: impl IntoIterator>) -> Result<()> { + pub(crate) fn from_lua_iter(lua: &Lua, iter: impl IntoIterator) -> Result { + let iter = iter.into_iter(); + let mut multi_value = MultiValue::with_capacity(iter.size_hint().0); for value in iter { - self.push_back(value?); + multi_value.push_back(value.into_lua(lua)?); } - Ok(()) + Ok(multi_value) } } From 0c08cdaf7c296b0c7bcc165f8a1a38055287f8d2 Mon Sep 17 00:00:00 2001 From: Sven Niederberger <73159570+s-nie@users.noreply.github.com> Date: Tue, 6 Aug 2024 21:04:17 +0200 Subject: [PATCH 144/635] Reduce compile time contribution of `next_key_seed` and `next_value_seed` (#436) * factor out common code * changelog entry --- CHANGELOG.md | 1 + src/serde/de.rs | 43 ++++++++++++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d1f8bc7..9c607b11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Removed `UserData` impl for Rc/Arc types ("any" userdata functions can be used instead) - `Lua::replace_registry_value` takes `&mut RegistryKey` - `Lua::scope` temporary disabled (will be re-added in the next release) +- Reduced the compile time contribution of `next_key_seed` and `next_value_seed`. ## v0.9.9 diff --git a/src/serde/de.rs b/src/serde/de.rs index 82e2abf6..24b651f1 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -504,13 +504,8 @@ struct MapDeserializer<'a> { processed: usize, } -impl<'de> de::MapAccess<'de> for MapDeserializer<'_> { - type Error = Error; - - fn next_key_seed(&mut self, seed: T) -> Result> - where - T: de::DeserializeSeed<'de>, - { +impl<'a> MapDeserializer<'a> { + fn next_key_deserializer(&mut self) -> Result> { loop { match self.pairs.next() { Some(item) => { @@ -526,25 +521,47 @@ impl<'de> de::MapAccess<'de> for MapDeserializer<'_> { self.value = Some(value); let visited = Rc::clone(&self.visited); let key_de = Deserializer::from_parts(key, self.options, visited); - return seed.deserialize(key_de).map(Some); + return Ok(Some(key_de)); } None => return Ok(None), } } } - fn next_value_seed(&mut self, seed: T) -> Result - where - T: de::DeserializeSeed<'de>, - { + fn next_value_deserializer(&mut self) -> Result { match self.value.take() { Some(value) => { let visited = Rc::clone(&self.visited); - seed.deserialize(Deserializer::from_parts(value, self.options, visited)) + Ok(Deserializer::from_parts(value, self.options, visited)) } None => Err(de::Error::custom("value is missing")), } } +} + +impl<'de> de::MapAccess<'de> for MapDeserializer<'_> { + type Error = Error; + + fn next_key_seed(&mut self, seed: T) -> Result> + where + T: de::DeserializeSeed<'de>, + { + match self.next_key_deserializer() { + Ok(Some(key_de)) => seed.deserialize(key_de).map(Some), + Ok(None) => Ok(None), + Err(error) => Err(error), + } + } + + fn next_value_seed(&mut self, seed: T) -> Result + where + T: de::DeserializeSeed<'de>, + { + match self.next_value_deserializer() { + Ok(value_de) => seed.deserialize(value_de), + Err(error) => Err(error), + } + } fn size_hint(&self) -> Option { match self.pairs.size_hint() { From c58f67b140f09315855a2a67de8e1b05c96e5fe7 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 10 Aug 2024 17:55:04 +0100 Subject: [PATCH 145/635] Add `MaybeSend` requirement to Lua futures --- examples/async_tcp_server.rs | 6 +++--- src/function.rs | 8 +++----- src/lib.rs | 3 ++- src/state.rs | 16 +++++++++++----- src/state/raw.rs | 2 +- src/thread.rs | 4 ++++ src/types.rs | 13 ++++++++++--- src/userdata.rs | 12 ++++++------ src/userdata/registry.rs | 37 +++++++++++++++++------------------- tests/async.rs | 5 +++-- 10 files changed, 60 insertions(+), 46 deletions(-) diff --git a/examples/async_tcp_server.rs b/examples/async_tcp_server.rs index 676482d9..7ceb3120 100644 --- a/examples/async_tcp_server.rs +++ b/examples/async_tcp_server.rs @@ -4,7 +4,7 @@ use std::net::SocketAddr; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream}; -use mlua::{chunk, Function, Lua, String as LuaString, UserData, UserDataMethods}; +use mlua::{chunk, BString, Function, Lua, UserData, UserDataMethods}; struct LuaTcpStream(TcpStream); @@ -19,8 +19,8 @@ impl UserData for LuaTcpStream { lua.create_string(&buf) }); - methods.add_async_method_mut("write", |_, this, data: LuaString| async move { - let n = this.0.write(&data.as_bytes()).await?; + methods.add_async_method_mut("write", |_, this, data: BString| async move { + let n = this.0.write(&data).await?; Ok(n) }); diff --git a/src/function.rs b/src/function.rs index 8c4aaa73..2d3e37f1 100644 --- a/src/function.rs +++ b/src/function.rs @@ -559,17 +559,15 @@ impl Function { A: FromLuaMulti, R: IntoLuaMulti, F: Fn(&Lua, A) -> FR + MaybeSend + 'static, - FR: Future> + 'static, + FR: Future> + MaybeSend + 'static, { - WrappedAsyncFunction(Box::new(move |rawlua, args| unsafe { - let lua = rawlua.lua(); + WrappedAsyncFunction(Box::new(move |lua, args| unsafe { let args = match A::from_lua_args(args, 1, None, lua) { Ok(args) => args, Err(e) => return Box::pin(future::ready(Err(e))), }; let fut = func(lua, args); - let weak = rawlua.weak().clone(); - Box::pin(async move { fut.await?.push_into_stack_multi(&weak.lock()) }) + Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) })) } } diff --git a/src/lib.rs b/src/lib.rs index 61a06f26..5f5985b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,6 +100,7 @@ mod value; pub mod prelude; +pub use bstr::BString; pub use ffi::{self, lua_CFunction, lua_State}; pub use crate::chunk::{AsChunk, Chunk, ChunkMode}; @@ -113,7 +114,7 @@ pub use crate::stdlib::StdLib; pub use crate::string::{BorrowedBytes, BorrowedStr, String}; pub use crate::table::{Table, TableExt, TablePairs, TableSequence}; pub use crate::thread::{Thread, ThreadStatus}; -pub use crate::types::{AppDataRef, AppDataRefMut, Integer, LightUserData, Number, RegistryKey}; +pub use crate::types::{AppDataRef, AppDataRefMut, Integer, LightUserData, MaybeSend, Number, RegistryKey}; pub use crate::userdata::{ AnyUserData, AnyUserDataExt, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef, UserDataRefMut, UserDataRegistry, diff --git a/src/state.rs b/src/state.rs index 9734a601..ae479d2d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,6 +1,5 @@ use std::any::TypeId; use std::cell::RefCell; -// use std::collections::VecDeque; use std::marker::PhantomData; use std::ops::Deref; use std::os::raw::{c_int, c_void}; @@ -1163,17 +1162,16 @@ impl Lua { 'lua: 'a, F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, - FR: Future> + 'a, + FR: Future> + MaybeSend + 'a, R: IntoLuaMulti, { - (self.lock()).create_async_callback(Box::new(move |rawlua, args| unsafe { - let lua = rawlua.lua(); + (self.lock()).create_async_callback(Box::new(move |lua, args| unsafe { let args = match A::from_lua_args(args, 1, None, lua) { Ok(args) => args, Err(e) => return Box::pin(future::ready(Err(e))), }; let fut = func(lua, args); - Box::pin(async move { fut.await?.push_into_stack_multi(rawlua) }) + Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) })) } @@ -1840,6 +1838,14 @@ impl Lua { pub(crate) fn weak(&self) -> WeakLua { WeakLua(XRc::downgrade(&self.0)) } + + /// Returns a handle to the unprotected Lua state without any synchronization. + /// + /// This is useful where we know that the lock is already held by the caller. + #[inline(always)] + pub(crate) unsafe fn raw_lua(&self) -> &RawLua { + &*self.0.data_ptr() + } } impl WeakLua { diff --git a/src/state/raw.rs b/src/state/raw.rs index 959ccd64..45c35ad9 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -1092,7 +1092,7 @@ impl RawLua { let args = MultiValue::from_stack_multi(nargs, rawlua)?; let func = &*(*upvalue).data; - let fut = func(rawlua, args); + let fut = func(rawlua.lua(), args); let extra = XRc::clone(&(*upvalue).extra); let protect = !rawlua.unlikely_memory_error(); push_internal_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?; diff --git a/src/thread.rs b/src/thread.rs index 8c9b9d3f..b11d0a82 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -528,4 +528,8 @@ mod assertions { static_assertions::assert_not_impl_any!(Thread: Send); #[cfg(feature = "send")] static_assertions::assert_impl_all!(Thread: Send, Sync); + #[cfg(all(feature = "async", not(feature = "send")))] + static_assertions::assert_not_impl_any!(AsyncThread<()>: Send); + #[cfg(all(feature = "async", feature = "send"))] + static_assertions::assert_impl_all!(AsyncThread<()>: Send, Sync); } diff --git a/src/types.rs b/src/types.rs index 9060bfac..8462a092 100644 --- a/src/types.rs +++ b/src/types.rs @@ -13,7 +13,13 @@ use crate::hook::Debug; use crate::state::{ExtraData, Lua, RawLua, WeakLua}; #[cfg(feature = "async")] -use {crate::value::MultiValue, futures_util::future::LocalBoxFuture}; +use crate::value::MultiValue; + +#[cfg(all(feature = "async", feature = "send"))] +pub(crate) type BoxFuture<'a, T> = futures_util::future::BoxFuture<'a, T>; + +#[cfg(all(feature = "async", not(feature = "send")))] +pub(crate) type BoxFuture<'a, T> = futures_util::future::LocalBoxFuture<'a, T>; #[cfg(all(feature = "luau", feature = "serialize"))] use serde::ser::{Serialize, SerializeTupleStruct, Serializer}; @@ -57,13 +63,13 @@ pub(crate) type CallbackUpvalue = Upvalue>; #[cfg(feature = "async")] pub(crate) type AsyncCallback<'a> = - Box LocalBoxFuture<'a, Result> + 'static>; + Box BoxFuture<'a, Result> + 'static>; #[cfg(feature = "async")] pub(crate) type AsyncCallbackUpvalue = Upvalue>; #[cfg(feature = "async")] -pub(crate) type AsyncPollUpvalue = Upvalue>>; +pub(crate) type AsyncPollUpvalue = Upvalue>>; /// Type to set next Luau VM action after executing interrupt function. #[cfg(any(feature = "luau", doc))] @@ -91,6 +97,7 @@ pub(crate) type WarnCallback = Box Result<()> + Send #[cfg(all(not(feature = "send"), feature = "lua54"))] pub(crate) type WarnCallback = Box Result<()>>; +/// A trait that adds `Send` requirement if `send` feature is enabled. #[cfg(feature = "send")] pub trait MaybeSend: Send {} #[cfg(feature = "send")] diff --git a/src/userdata.rs b/src/userdata.rs index d2f48acd..2ebe46bc 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -287,7 +287,7 @@ pub trait UserDataMethods<'a, T> { T: 'static, M: Fn(&'a Lua, &'a T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + 'a, + MR: Future> + MaybeSend + 'a, R: IntoLuaMulti; /// Add an async method which accepts a `&mut T` as the first parameter and returns Future. @@ -304,7 +304,7 @@ pub trait UserDataMethods<'a, T> { T: 'static, M: Fn(&'a Lua, &'a mut T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + 'a, + MR: Future> + MaybeSend + 'a, R: IntoLuaMulti; /// Add a regular method as a function which accepts generic arguments, the first argument will @@ -348,7 +348,7 @@ pub trait UserDataMethods<'a, T> { where F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, - FR: Future> + 'a, + FR: Future> + MaybeSend + 'a, R: IntoLuaMulti; /// Add a metamethod which accepts a `&T` as the first parameter. @@ -393,7 +393,7 @@ pub trait UserDataMethods<'a, T> { T: 'static, M: Fn(&'a Lua, &'a T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + 'a, + MR: Future> + MaybeSend + 'a, R: IntoLuaMulti; /// Add an async metamethod which accepts a `&mut T` as the first parameter and returns Future. @@ -410,7 +410,7 @@ pub trait UserDataMethods<'a, T> { T: 'static, M: Fn(&'a Lua, &'a mut T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + 'a, + MR: Future> + MaybeSend + 'a, R: IntoLuaMulti; /// Add a metamethod which accepts generic arguments. @@ -448,7 +448,7 @@ pub trait UserDataMethods<'a, T> { where F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, - FR: Future> + 'a, + FR: Future> + MaybeSend + 'a, R: IntoLuaMulti; } diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index 45d301af..de31f307 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -132,7 +132,7 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { where M: Fn(&'a Lua, &'a T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + 'a, + MR: Future> + MaybeSend + 'a, R: IntoLuaMulti, { let name = get_function_name::(name); @@ -145,15 +145,14 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { }; } - Box::new(move |rawlua, mut args| unsafe { + Box::new(move |lua, mut args| unsafe { let this = args .pop_front() .ok_or_else(|| Error::from_lua_conversion("missing argument", "userdata", None)); - let lua = rawlua.lua(); let this = try_self_arg!(AnyUserData::from_lua(try_self_arg!(this), lua)); let args = A::from_lua_args(args, 2, Some(&name), lua); - let (ref_thread, index) = (rawlua.ref_thread(), this.0.index); + let (ref_thread, index) = (lua.raw_lua().ref_thread(), this.0.index); match try_self_arg!(this.type_id()) { Some(id) if id == TypeId::of::() => { let ud = try_self_arg!(borrow_userdata_ref::(ref_thread, index)); @@ -162,7 +161,7 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { Err(e) => return Box::pin(future::ready(Err(e))), }; let fut = method(lua, ud.get_ref(), args); - Box::pin(async move { fut.await?.push_into_stack_multi(rawlua) }) + Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) } _ => { let err = Error::bad_self_argument(&name, Error::UserDataTypeMismatch); @@ -177,7 +176,7 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { where M: Fn(&'a Lua, &'a mut T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + 'a, + MR: Future> + MaybeSend + 'a, R: IntoLuaMulti, { let name = get_function_name::(name); @@ -190,15 +189,14 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { }; } - Box::new(move |rawlua, mut args| unsafe { + Box::new(move |lua, mut args| unsafe { let this = args .pop_front() .ok_or_else(|| Error::from_lua_conversion("missing argument", "userdata", None)); - let lua = rawlua.lua(); let this = try_self_arg!(AnyUserData::from_lua(try_self_arg!(this), lua)); let args = A::from_lua_args(args, 2, Some(&name), lua); - let (ref_thread, index) = (rawlua.ref_thread(), this.0.index); + let (ref_thread, index) = (lua.raw_lua().ref_thread(), this.0.index); match try_self_arg!(this.type_id()) { Some(id) if id == TypeId::of::() => { let mut ud = try_self_arg!(borrow_userdata_mut::(ref_thread, index)); @@ -207,7 +205,7 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { Err(e) => return Box::pin(future::ready(Err(e))), }; let fut = method(lua, ud.get_mut(), args); - Box::pin(async move { fut.await?.push_into_stack_multi(rawlua) }) + Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) } _ => { let err = Error::bad_self_argument(&name, Error::UserDataTypeMismatch); @@ -252,18 +250,17 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { where F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, - FR: Future> + 'a, + FR: Future> + MaybeSend + 'a, R: IntoLuaMulti, { let name = get_function_name::(name); - Box::new(move |rawlua, args| unsafe { - let lua = rawlua.lua(); + Box::new(move |lua, args| unsafe { let args = match A::from_lua_args(args, 1, Some(&name), lua) { Ok(args) => args, Err(e) => return Box::pin(future::ready(Err(e))), }; let fut = function(lua, args); - Box::pin(async move { fut.await?.push_into_stack_multi(rawlua) }) + Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) }) } @@ -397,7 +394,7 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { where M: Fn(&'a Lua, &'a T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + 'a, + MR: Future> + MaybeSend + 'a, R: IntoLuaMulti, { let name = name.to_string(); @@ -410,7 +407,7 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { where M: Fn(&'a Lua, &'a mut T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + 'a, + MR: Future> + MaybeSend + 'a, R: IntoLuaMulti, { let name = name.to_string(); @@ -445,7 +442,7 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { where F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, - FR: Future> + 'a, + FR: Future> + MaybeSend + 'a, R: IntoLuaMulti, { let name = name.to_string(); @@ -480,7 +477,7 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { where M: Fn(&'a Lua, &'a T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + 'a, + MR: Future> + MaybeSend + 'a, R: IntoLuaMulti, { let name = name.to_string(); @@ -493,7 +490,7 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { where M: Fn(&'a Lua, &'a mut T, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + 'a, + MR: Future> + MaybeSend + 'a, R: IntoLuaMulti, { let name = name.to_string(); @@ -528,7 +525,7 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { where F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, - FR: Future> + 'a, + FR: Future> + MaybeSend + 'a, R: IntoLuaMulti, { let name = name.to_string(); diff --git a/tests/async.rs b/tests/async.rs index 871ebf0a..038d14fb 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -1,9 +1,10 @@ #![cfg(feature = "async")] -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use std::time::Duration; use futures_util::stream::TryStreamExt; +use tokio::sync::Mutex; use mlua::{ AnyUserDataExt, Error, Function, Lua, LuaOptions, MultiValue, Result, StdLib, Table, TableExt, UserData, @@ -504,7 +505,7 @@ async fn test_async_terminate() -> Result<()> { let func = lua.create_async_function(move |_, ()| { let mutex = mutex2.clone(); async move { - let _guard = mutex.lock(); + let _guard = mutex.lock().await; sleep_ms(100).await; Ok(()) } From 26b9bdb362a543d706ebbb5436ccb5d594efde36 Mon Sep 17 00:00:00 2001 From: Sven Niederberger <73159570+s-nie@users.noreply.github.com> Date: Wed, 21 Aug 2024 13:06:49 +0200 Subject: [PATCH 146/635] `serde_userdata`: Remove `map_err` to reduce compile time impact (#441) --- CHANGELOG.md | 1 + src/serde/de.rs | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c607b11..0c27db22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - `Lua::replace_registry_value` takes `&mut RegistryKey` - `Lua::scope` temporary disabled (will be re-added in the next release) - Reduced the compile time contribution of `next_key_seed` and `next_value_seed`. +- Reduced the compile time contribution of `serde_userdata`. ## v0.9.9 diff --git a/src/serde/de.rs b/src/serde/de.rs index 24b651f1..0d72d83b 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -719,6 +719,11 @@ fn serde_userdata( ud: AnyUserData, f: impl FnOnce(serde_value::Value) -> std::result::Result, ) -> Result { - let value = serde_value::to_value(ud).map_err(|err| Error::SerializeError(err.to_string()))?; - f(value).map_err(|err| Error::DeserializeError(err.to_string())) + match serde_value::to_value(ud) { + Ok(value) => match f(value) { + Ok(r) => Ok(r), + Err(error) => Err(Error::DeserializeError(error.to_string())), + }, + Err(error) => Err(Error::SerializeError(error.to_string())), + } } From d2e87943ac018e32fdde2a7822201aa79d142fef Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 10 Aug 2024 22:55:29 +0100 Subject: [PATCH 147/635] Skip extra lock when resuming thread --- src/thread.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index b11d0a82..dfc685d6 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -129,7 +129,7 @@ impl Thread { let _sg = StackGuard::new(state); let _thread_sg = StackGuard::with_top(thread_state, 0); - let nresults = self.resume_inner(args)?; + let nresults = self.resume_inner(&lua, args)?; check_stack(state, nresults + 1)?; ffi::lua_xmove(thread_state, state, nresults); @@ -140,8 +140,7 @@ impl Thread { /// Resumes execution of this thread. /// /// It's similar to `resume()` but leaves `nresults` values on the thread stack. - unsafe fn resume_inner(&self, args: impl IntoLuaMulti) -> Result { - let lua = self.0.lua.lock(); + unsafe fn resume_inner(&self, lua: &RawLua, args: impl IntoLuaMulti) -> Result { let state = lua.state(); let thread_state = self.state(); @@ -426,9 +425,9 @@ impl Stream for AsyncThread { // This is safe as we are not moving the whole struct let this = self.get_unchecked_mut(); let nresults = if let Some(args) = this.init_args.take() { - this.thread.resume_inner(args?)? + this.thread.resume_inner(&lua, args?)? } else { - this.thread.resume_inner(())? + this.thread.resume_inner(&lua, ())? }; if nresults == 1 && is_poll_pending(thread_state) { @@ -464,9 +463,9 @@ impl Future for AsyncThread { // This is safe as we are not moving the whole struct let this = self.get_unchecked_mut(); let nresults = if let Some(args) = this.init_args.take() { - this.thread.resume_inner(args?)? + this.thread.resume_inner(&lua, args?)? } else { - this.thread.resume_inner(())? + this.thread.resume_inner(&lua, ())? }; if nresults == 1 && is_poll_pending(thread_state) { From 8092f009305cc60ac4f66249a4afd8396f4c1e45 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 10 Aug 2024 22:57:42 +0100 Subject: [PATCH 148/635] Do not require Lua to be alive when dropping `AsyncThread` --- src/thread.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index dfc685d6..d5cdb353 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -388,16 +388,17 @@ impl AsyncThread { impl Drop for AsyncThread { fn drop(&mut self) { if self.recycle { - unsafe { - let lua = self.thread.0.lua.lock(); - // For Lua 5.4 this also closes all pending to-be-closed variables - if !lua.recycle_thread(&mut self.thread) { - #[cfg(feature = "lua54")] - if self.thread.status_inner(&lua) == ThreadStatus::Error { - #[cfg(not(feature = "vendored"))] - ffi::lua_resetthread(self.thread.state()); - #[cfg(feature = "vendored")] - ffi::lua_closethread(self.thread.state(), lua.state()); + if let Some(lua) = self.thread.0.lua.try_lock() { + unsafe { + // For Lua 5.4 this also closes all pending to-be-closed variables + if !lua.recycle_thread(&mut self.thread) { + #[cfg(feature = "lua54")] + if self.thread.status_inner(&lua) == ThreadStatus::Error { + #[cfg(not(feature = "vendored"))] + ffi::lua_resetthread(self.thread.state()); + #[cfg(feature = "vendored")] + ffi::lua_closethread(self.thread.state(), lua.state()); + } } } } From 9931709ecda99daa16444cacf22fa0e89b9bd78c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 23 Aug 2024 00:05:24 +0100 Subject: [PATCH 149/635] Remove explicit lifetime from `UserDataMethods` and `UserDataFields` traits. Pass `'static` arguments to async functions and require `'static` Future. (in future we can use async closures to make it more elegant). --- Cargo.toml | 2 +- benches/benchmark.rs | 6 +- examples/async_http_client.rs | 4 +- examples/async_http_server.rs | 2 +- examples/async_tcp_server.rs | 8 +- examples/guided_tour.rs | 2 +- examples/userdata.rs | 4 +- src/function.rs | 13 +- src/state.rs | 27 ++-- src/state/raw.rs | 5 +- src/types.rs | 12 +- src/userdata.rs | 76 +++++----- src/userdata/cell.rs | 251 +++++++++++++++------------------- src/userdata/lock.rs | 93 +++++++++++++ src/userdata/registry.rs | 211 ++++++++++++++-------------- src/util/types.rs | 4 +- tests/async.rs | 8 +- tests/userdata.rs | 36 ++--- 18 files changed, 403 insertions(+), 361 deletions(-) create mode 100644 src/userdata/lock.rs diff --git a/Cargo.toml b/Cargo.toml index 042260fd..3936a4b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ luau-vector4 = ["luau", "ffi/luau-vector4"] vendored = ["ffi/vendored"] module = ["dep:mlua_derive", "ffi/module"] async = ["dep:futures-util"] -send = [] +send = ["parking_lot/send_guard"] serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value"] macros = ["mlua_derive/macros"] unstable = [] diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 5c86bcb5..b5bb2b38 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -297,7 +297,7 @@ fn userdata_create(c: &mut Criterion) { fn userdata_call_index(c: &mut Criterion) { struct UserData(#[allow(unused)] i64); impl LuaUserData for UserData { - fn add_methods<'a, M: LuaUserDataMethods<'a, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_meta_method(LuaMetaMethod::Index, move |_, _, key: LuaString| Ok(key)); } } @@ -323,7 +323,7 @@ fn userdata_call_index(c: &mut Criterion) { fn userdata_call_method(c: &mut Criterion) { struct UserData(i64); impl LuaUserData for UserData { - fn add_methods<'a, M: LuaUserDataMethods<'a, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_method("add", |_, this, i: i64| Ok(this.0 + i)); } } @@ -353,7 +353,7 @@ fn userdata_call_method(c: &mut Criterion) { fn userdata_async_call_method(c: &mut Criterion) { struct UserData(i64); impl LuaUserData for UserData { - fn add_methods<'a, M: LuaUserDataMethods<'a, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_async_method("add", |_, this, i: i64| async move { task::yield_now().await; Ok(this.0 + i) diff --git a/examples/async_http_client.rs b/examples/async_http_client.rs index ca4eac9a..86568127 100644 --- a/examples/async_http_client.rs +++ b/examples/async_http_client.rs @@ -10,9 +10,9 @@ use mlua::{chunk, ExternalResult, Lua, Result, UserData, UserDataMethods}; struct BodyReader(Incoming); impl UserData for BodyReader { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { // Every call returns a next chunk - methods.add_async_method_mut("read", |lua, reader, ()| async move { + methods.add_async_method_mut("read", |lua, mut reader, ()| async move { if let Some(bytes) = reader.0.frame().await { if let Some(bytes) = bytes.into_lua_err()?.data_ref() { return Some(lua.create_string(&bytes)).transpose(); diff --git a/examples/async_http_server.rs b/examples/async_http_server.rs index 5eb8c970..244a8e4e 100644 --- a/examples/async_http_server.rs +++ b/examples/async_http_server.rs @@ -17,7 +17,7 @@ use mlua::{chunk, Error as LuaError, Function, Lua, String as LuaString, Table, struct LuaRequest(SocketAddr, Request); impl UserData for LuaRequest { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_method("remote_addr", |_, req, ()| Ok((req.0).to_string())); methods.add_method("method", |_, req, ()| Ok((req.1).method().to_string())); methods.add_method("path", |_, req, ()| Ok(req.1.uri().path().to_string())); diff --git a/examples/async_tcp_server.rs b/examples/async_tcp_server.rs index 7ceb3120..7d96dbe2 100644 --- a/examples/async_tcp_server.rs +++ b/examples/async_tcp_server.rs @@ -9,22 +9,22 @@ use mlua::{chunk, BString, Function, Lua, UserData, UserDataMethods}; struct LuaTcpStream(TcpStream); impl UserData for LuaTcpStream { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_method("peer_addr", |_, this, ()| Ok(this.0.peer_addr()?.to_string())); - methods.add_async_method_mut("read", |lua, this, size| async move { + methods.add_async_method_mut("read", |lua, mut this, size| async move { let mut buf = vec![0; size]; let n = this.0.read(&mut buf).await?; buf.truncate(n); lua.create_string(&buf) }); - methods.add_async_method_mut("write", |_, this, data: BString| async move { + methods.add_async_method_mut("write", |_, mut this, data: BString| async move { let n = this.0.write(&data).await?; Ok(n) }); - methods.add_async_method_mut("close", |_, this, ()| async move { + methods.add_async_method_mut("close", |_, mut this, ()| async move { this.0.shutdown().await?; Ok(()) }); diff --git a/examples/guided_tour.rs b/examples/guided_tour.rs index 26706e8d..dd4c649a 100644 --- a/examples/guided_tour.rs +++ b/examples/guided_tour.rs @@ -162,7 +162,7 @@ fn main() -> Result<()> { } impl UserData for Vec2 { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_method("magnitude", |_, vec, ()| { let mag_squared = vec.0 * vec.0 + vec.1 * vec.1; Ok(mag_squared.sqrt()) diff --git a/examples/userdata.rs b/examples/userdata.rs index 8823dfd5..a5cc8ba4 100644 --- a/examples/userdata.rs +++ b/examples/userdata.rs @@ -7,7 +7,7 @@ struct Rectangle { } impl UserData for Rectangle { - fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) { + fn add_fields>(fields: &mut F) { fields.add_field_method_get("length", |_, this| Ok(this.length)); fields.add_field_method_set("length", |_, this, val| { this.length = val; @@ -20,7 +20,7 @@ impl UserData for Rectangle { }); } - fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_method("area", |_, this, ()| Ok(this.length * this.width)); methods.add_method("diagonal", |_, this, ()| { Ok((this.length.pow(2) as f64 + this.width.pow(2) as f64).sqrt()) diff --git a/src/function.rs b/src/function.rs index 2d3e37f1..95cb43a7 100644 --- a/src/function.rs +++ b/src/function.rs @@ -513,10 +513,10 @@ impl PartialEq for Function { } } -pub(crate) struct WrappedFunction(pub(crate) Callback<'static>); +pub(crate) struct WrappedFunction(pub(crate) Callback); #[cfg(feature = "async")] -pub(crate) struct WrappedAsyncFunction(pub(crate) AsyncCallback<'static>); +pub(crate) struct WrappedAsyncFunction(pub(crate) AsyncCallback); impl Function { /// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`] @@ -558,15 +558,16 @@ impl Function { where A: FromLuaMulti, R: IntoLuaMulti, - F: Fn(&Lua, A) -> FR + MaybeSend + 'static, + F: Fn(Lua, A) -> FR + MaybeSend + 'static, FR: Future> + MaybeSend + 'static, { - WrappedAsyncFunction(Box::new(move |lua, args| unsafe { - let args = match A::from_lua_args(args, 1, None, lua) { + WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe { + let args = match A::from_stack_args(nargs, 1, None, rawlua) { Ok(args) => args, Err(e) => return Box::pin(future::ready(Err(e))), }; - let fut = func(lua, args); + let lua = rawlua.lua().clone(); + let fut = func(lua.clone(), args); Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) })) } diff --git a/src/state.rs b/src/state.rs index ae479d2d..4db87f29 100644 --- a/src/state.rs +++ b/src/state.rs @@ -137,13 +137,6 @@ impl LuaOptions { } } -#[cfg(not(feature = "module"))] -impl Drop for Lua { - fn drop(&mut self) { - let _ = self.gc_collect(); - } -} - impl fmt::Debug for Lua { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Lua({:p})", self.lock().state()) @@ -1138,7 +1131,7 @@ impl Lua { /// use std::time::Duration; /// use mlua::{Lua, Result}; /// - /// async fn sleep(_lua: &Lua, n: u64) -> Result<&'static str> { + /// async fn sleep(_lua: Lua, n: u64) -> Result<&'static str> { /// tokio::time::sleep(Duration::from_millis(n)).await; /// Ok("done") /// } @@ -1157,20 +1150,20 @@ impl Lua { /// [`AsyncThread`]: crate::AsyncThread #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub fn create_async_function<'lua, 'a, F, A, FR, R>(&'lua self, func: F) -> Result + pub fn create_async_function(&self, func: F) -> Result where - 'lua: 'a, - F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, + F: Fn(Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, - FR: Future> + MaybeSend + 'a, + FR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { - (self.lock()).create_async_callback(Box::new(move |lua, args| unsafe { - let args = match A::from_lua_args(args, 1, None, lua) { + (self.lock()).create_async_callback(Box::new(move |rawlua, nargs| unsafe { + let args = match A::from_stack_args(nargs, 1, None, rawlua) { Ok(args) => args, Err(e) => return Box::pin(future::ready(Err(e))), }; - let fut = func(lua, args); + let lua = rawlua.lua().clone(); + let fut = func(lua.clone(), args); Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) })) } @@ -1274,11 +1267,11 @@ impl Lua { /// struct MyUserData(i32); /// /// impl UserData for MyUserData { - /// fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { + /// fn add_fields>(fields: &mut F) { /// fields.add_field_method_get("val", |_, this| Ok(this.0)); /// } /// - /// fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { + /// fn add_methods>(methods: &mut M) { /// methods.add_function("new", |_, value: i32| Ok(MyUserData(value))); /// } /// } diff --git a/src/state/raw.rs b/src/state/raw.rs index 45c35ad9..795dc2b0 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -1053,7 +1053,6 @@ impl RawLua { let _sg = StackGuard::new(state); check_stack(state, 4)?; - let func = mem::transmute::>(func); let extra = XRc::clone(&self.extra); let protect = !self.unlikely_memory_error(); push_internal_userdata(state, CallbackUpvalue { data: func, extra }, protect)?; @@ -1090,9 +1089,8 @@ impl RawLua { let rawlua = (*extra).raw_lua(); let _guard = StateGuard::new(rawlua, state); - let args = MultiValue::from_stack_multi(nargs, rawlua)?; let func = &*(*upvalue).data; - let fut = func(rawlua.lua(), args); + let fut = func(rawlua, nargs); let extra = XRc::clone(&(*upvalue).extra); let protect = !rawlua.unlikely_memory_error(); push_internal_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?; @@ -1152,7 +1150,6 @@ impl RawLua { let _sg = StackGuard::new(state); check_stack(state, 4)?; - let func = mem::transmute::>(func); let extra = XRc::clone(&self.extra); let protect = !self.unlikely_memory_error(); let upvalue = AsyncCallbackUpvalue { data: func, extra }; diff --git a/src/types.rs b/src/types.rs index 8462a092..89bd75b9 100644 --- a/src/types.rs +++ b/src/types.rs @@ -12,9 +12,6 @@ use crate::error::Result; use crate::hook::Debug; use crate::state::{ExtraData, Lua, RawLua, WeakLua}; -#[cfg(feature = "async")] -use crate::value::MultiValue; - #[cfg(all(feature = "async", feature = "send"))] pub(crate) type BoxFuture<'a, T> = futures_util::future::BoxFuture<'a, T>; @@ -52,21 +49,20 @@ unsafe impl Send for LightUserData {} #[cfg(feature = "send")] unsafe impl Sync for LightUserData {} -pub(crate) type Callback<'a> = Box Result + 'static>; +pub(crate) type Callback = Box Result + 'static>; pub(crate) struct Upvalue { pub(crate) data: T, pub(crate) extra: XRc>, } -pub(crate) type CallbackUpvalue = Upvalue>; +pub(crate) type CallbackUpvalue = Upvalue; #[cfg(feature = "async")] -pub(crate) type AsyncCallback<'a> = - Box BoxFuture<'a, Result> + 'static>; +pub(crate) type AsyncCallback = Box BoxFuture<'static, Result> + 'static>; #[cfg(feature = "async")] -pub(crate) type AsyncCallbackUpvalue = Upvalue>; +pub(crate) type AsyncCallbackUpvalue = Upvalue; #[cfg(feature = "async")] pub(crate) type AsyncPollUpvalue = Upvalue>>; diff --git a/src/userdata.rs b/src/userdata.rs index 2ebe46bc..5e8b5a52 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -248,7 +248,7 @@ impl AsRef for MetaMethod { /// Method registry for [`UserData`] implementors. /// /// [`UserData`]: crate::UserData -pub trait UserDataMethods<'a, T> { +pub trait UserDataMethods { /// Add a regular method which accepts a `&T` as the first parameter. /// /// Regular methods are implemented by overriding the `__index` metamethod and returning the @@ -258,7 +258,7 @@ pub trait UserDataMethods<'a, T> { /// be used as a fall-back if no regular method is found. fn add_method(&mut self, name: impl ToString, method: M) where - M: Fn(&'a Lua, &T, A) -> Result + MaybeSend + 'static, + M: Fn(&Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti; @@ -269,7 +269,7 @@ pub trait UserDataMethods<'a, T> { /// [`add_method`]: #method.add_method fn add_method_mut(&mut self, name: impl ToString, method: M) where - M: FnMut(&'a Lua, &mut T, A) -> Result + MaybeSend + 'static, + M: FnMut(&Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti; @@ -285,9 +285,9 @@ pub trait UserDataMethods<'a, T> { fn add_async_method(&mut self, name: impl ToString, method: M) where T: 'static, - M: Fn(&'a Lua, &'a T, A) -> MR + MaybeSend + 'static, + M: Fn(Lua, UserDataRef, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + MaybeSend + 'a, + MR: Future> + MaybeSend + 'static, R: IntoLuaMulti; /// Add an async method which accepts a `&mut T` as the first parameter and returns Future. @@ -302,9 +302,9 @@ pub trait UserDataMethods<'a, T> { fn add_async_method_mut(&mut self, name: impl ToString, method: M) where T: 'static, - M: Fn(&'a Lua, &'a mut T, A) -> MR + MaybeSend + 'static, + M: Fn(Lua, UserDataRefMut, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + MaybeSend + 'a, + MR: Future> + MaybeSend + 'static, R: IntoLuaMulti; /// Add a regular method as a function which accepts generic arguments, the first argument will @@ -319,7 +319,7 @@ pub trait UserDataMethods<'a, T> { /// [`add_method_mut`]: #method.add_method_mut fn add_function(&mut self, name: impl ToString, function: F) where - F: Fn(&'a Lua, A) -> Result + MaybeSend + 'static, + F: Fn(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti; @@ -330,7 +330,7 @@ pub trait UserDataMethods<'a, T> { /// [`add_function`]: #method.add_function fn add_function_mut(&mut self, name: impl ToString, function: F) where - F: FnMut(&'a Lua, A) -> Result + MaybeSend + 'static, + F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti; @@ -346,9 +346,9 @@ pub trait UserDataMethods<'a, T> { #[cfg_attr(docsrs, doc(cfg(feature = "async")))] fn add_async_function(&mut self, name: impl ToString, function: F) where - F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, + F: Fn(Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, - FR: Future> + MaybeSend + 'a, + FR: Future> + MaybeSend + 'static, R: IntoLuaMulti; /// Add a metamethod which accepts a `&T` as the first parameter. @@ -361,7 +361,7 @@ pub trait UserDataMethods<'a, T> { /// [`add_meta_function`]: #method.add_meta_function fn add_meta_method(&mut self, name: impl ToString, method: M) where - M: Fn(&'a Lua, &T, A) -> Result + MaybeSend + 'static, + M: Fn(&Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti; @@ -375,7 +375,7 @@ pub trait UserDataMethods<'a, T> { /// [`add_meta_function`]: #method.add_meta_function fn add_meta_method_mut(&mut self, name: impl ToString, method: M) where - M: FnMut(&'a Lua, &mut T, A) -> Result + MaybeSend + 'static, + M: FnMut(&Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti; @@ -391,9 +391,9 @@ pub trait UserDataMethods<'a, T> { fn add_async_meta_method(&mut self, name: impl ToString, method: M) where T: 'static, - M: Fn(&'a Lua, &'a T, A) -> MR + MaybeSend + 'static, + M: Fn(Lua, UserDataRef, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + MaybeSend + 'a, + MR: Future> + MaybeSend + 'static, R: IntoLuaMulti; /// Add an async metamethod which accepts a `&mut T` as the first parameter and returns Future. @@ -408,9 +408,9 @@ pub trait UserDataMethods<'a, T> { fn add_async_meta_method_mut(&mut self, name: impl ToString, method: M) where T: 'static, - M: Fn(&'a Lua, &'a mut T, A) -> MR + MaybeSend + 'static, + M: Fn(Lua, UserDataRefMut, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + MaybeSend + 'a, + MR: Future> + MaybeSend + 'static, R: IntoLuaMulti; /// Add a metamethod which accepts generic arguments. @@ -420,7 +420,7 @@ pub trait UserDataMethods<'a, T> { /// userdata of type `T`. fn add_meta_function(&mut self, name: impl ToString, function: F) where - F: Fn(&'a Lua, A) -> Result + MaybeSend + 'static, + F: Fn(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti; @@ -431,7 +431,7 @@ pub trait UserDataMethods<'a, T> { /// [`add_meta_function`]: #method.add_meta_function fn add_meta_function_mut(&mut self, name: impl ToString, function: F) where - F: FnMut(&'a Lua, A) -> Result + MaybeSend + 'static, + F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti; @@ -446,16 +446,16 @@ pub trait UserDataMethods<'a, T> { #[cfg_attr(docsrs, doc(cfg(feature = "async")))] fn add_async_meta_function(&mut self, name: impl ToString, function: F) where - F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, + F: Fn(Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, - FR: Future> + MaybeSend + 'a, + FR: Future> + MaybeSend + 'static, R: IntoLuaMulti; } /// Field registry for [`UserData`] implementors. /// /// [`UserData`]: crate::UserData -pub trait UserDataFields<'a, T> { +pub trait UserDataFields { /// Add a static field to the `UserData`. /// /// Static fields are implemented by updating the `__index` metamethod and returning the @@ -478,7 +478,7 @@ pub trait UserDataFields<'a, T> { /// be used as a fall-back if no regular field or method are found. fn add_field_method_get(&mut self, name: impl ToString, method: M) where - M: Fn(&'a Lua, &T) -> Result + MaybeSend + 'static, + M: Fn(&Lua, &T) -> Result + MaybeSend + 'static, R: IntoLua; /// Add a regular field setter as a method which accepts a `&mut T` as the first parameter. @@ -491,7 +491,7 @@ pub trait UserDataFields<'a, T> { /// will be used as a fall-back if no regular field is found. fn add_field_method_set(&mut self, name: impl ToString, method: M) where - M: FnMut(&'a Lua, &mut T, A) -> Result<()> + MaybeSend + 'static, + M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static, A: FromLua; /// Add a regular field getter as a function which accepts a generic [`AnyUserData`] of type `T` @@ -503,7 +503,7 @@ pub trait UserDataFields<'a, T> { /// [`add_field_method_get`]: #method.add_field_method_get fn add_field_function_get(&mut self, name: impl ToString, function: F) where - F: Fn(&'a Lua, AnyUserData) -> Result + MaybeSend + 'static, + F: Fn(&Lua, AnyUserData) -> Result + MaybeSend + 'static, R: IntoLua; /// Add a regular field setter as a function which accepts a generic [`AnyUserData`] of type `T` @@ -515,7 +515,7 @@ pub trait UserDataFields<'a, T> { /// [`add_field_method_set`]: #method.add_field_method_set fn add_field_function_set(&mut self, name: impl ToString, function: F) where - F: FnMut(&'a Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static, + F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static, A: FromLua; /// Add a metatable field. @@ -540,7 +540,7 @@ pub trait UserDataFields<'a, T> { /// like `__gc` or `__metatable`. fn add_meta_field_with(&mut self, name: impl ToString, f: F) where - F: Fn(&'a Lua) -> Result + MaybeSend + 'static, + F: Fn(&Lua) -> Result + MaybeSend + 'static, R: IntoLua; } @@ -579,12 +579,12 @@ pub trait UserDataFields<'a, T> { /// struct MyUserData(i32); /// /// impl UserData for MyUserData { -/// fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { +/// fn add_fields>(fields: &mut F) { /// fields.add_field_method_get("val", |_, this| Ok(this.0)); /// } /// -/// fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { -/// methods.add_method_mut("add", |_, this, value: i32| { +/// fn add_methods>(methods: &mut M) { +/// methods.add_method_mut("add", |_, mut this, value: i32| { /// this.0 += value; /// Ok(()) /// }); @@ -614,11 +614,11 @@ pub trait UserDataFields<'a, T> { pub trait UserData: Sized { /// Adds custom fields specific to this userdata. #[allow(unused_variables)] - fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) {} + fn add_fields>(fields: &mut F) {} /// Adds custom methods and operators specific to this userdata. #[allow(unused_variables)] - fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) {} + fn add_methods>(methods: &mut M) {} /// Registers this type for use in Lua. /// @@ -663,7 +663,7 @@ impl AnyUserData { /// `UserDataTypeMismatch` if the userdata is not of type `T`. #[inline] pub fn borrow(&self) -> Result> { - self.inspect(|variant, guard| variant.try_make_ref(guard)) + self.inspect(|variant, _| variant.try_borrow_owned()) } /// Borrow this userdata mutably if it is of type `T`. @@ -674,7 +674,7 @@ impl AnyUserData { /// Returns a `UserDataTypeMismatch` if the userdata is not of type `T`. #[inline] pub fn borrow_mut(&self) -> Result> { - self.inspect(|variant, guard| variant.try_make_mut_ref(guard)) + self.inspect(|variant, _| variant.try_borrow_owned_mut()) } /// Takes the value out of this userdata. @@ -940,13 +940,6 @@ impl AnyUserData { self.0.to_pointer() } - #[cfg(feature = "async")] - #[inline] - pub(crate) fn type_id(&self) -> Result> { - let lua = self.0.lua.lock(); - unsafe { lua.get_userdata_ref_type_id(&self.0) } - } - /// Returns a type name of this `UserData` (from a metatable field). pub(crate) fn type_name(&self) -> Result> { match self.1 { @@ -1182,6 +1175,7 @@ where mod cell; mod ext; +mod lock; mod registry; #[cfg(test)] diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index 61d87e2d..c327a86d 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -1,34 +1,42 @@ use std::any::{type_name, TypeId}; -use std::cell::{Cell, UnsafeCell}; +use std::cell::UnsafeCell; use std::fmt; use std::ops::{Deref, DerefMut}; use std::os::raw::c_int; -use std::rc::Rc; #[cfg(feature = "serialize")] use serde::ser::{Serialize, Serializer}; use crate::error::{Error, Result}; -use crate::state::{Lua, LuaGuard, RawLua}; +use crate::state::{Lua, RawLua}; +use crate::types::{MaybeSend, XRc}; use crate::userdata::AnyUserData; use crate::util::get_userdata; use crate::value::{FromLua, Value}; +use super::lock::{RawLock, UserDataLock}; + +#[cfg(all(feature = "serialize", not(feature = "send")))] +type DynSerialize = dyn erased_serde::Serialize; + +#[cfg(all(feature = "serialize", feature = "send"))] +type DynSerialize = dyn erased_serde::Serialize + Send; + // A enum for storing userdata values. // It's stored inside a Lua VM and protected by the outer `ReentrantMutex`. pub(crate) enum UserDataVariant { - Default(Rc>), + Default(XRc>), #[cfg(feature = "serialize")] - Serializable(Rc>>), + Serializable(XRc>>>), } impl Clone for UserDataVariant { #[inline] fn clone(&self) -> Self { match self { - Self::Default(inner) => Self::Default(Rc::clone(inner)), + Self::Default(inner) => Self::Default(XRc::clone(inner)), #[cfg(feature = "serialize")] - Self::Serializable(inner) => UserDataVariant::Serializable(Rc::clone(inner)), + Self::Serializable(inner) => Self::Serializable(XRc::clone(inner)), } } } @@ -36,54 +44,56 @@ impl Clone for UserDataVariant { impl UserDataVariant { #[inline(always)] pub(crate) fn new(data: T) -> Self { - Self::Default(Rc::new(InnerRefCell::new(data))) + Self::Default(XRc::new(UserDataCell::new(data))) } // Immutably borrows the wrapped value in-place. #[inline(always)] - pub(crate) unsafe fn try_borrow(&self) -> Result> { + pub(crate) fn try_borrow(&self) -> Result> { UserDataBorrowRef::try_from(self) } // Immutably borrows the wrapped value and returns an owned reference. #[inline(always)] - pub(crate) fn try_make_ref(&self, guard: LuaGuard) -> Result> { - UserDataRef::try_from(self.clone(), guard) + pub(crate) fn try_borrow_owned(&self) -> Result> { + UserDataRef::try_from(self.clone()) } // Mutably borrows the wrapped value in-place. #[inline(always)] - pub(crate) unsafe fn try_borrow_mut(&self) -> Result> { + pub(crate) fn try_borrow_mut(&self) -> Result> { UserDataBorrowMut::try_from(self) } // Mutably borrows the wrapped value and returns an owned reference. #[inline(always)] - pub(crate) fn try_make_mut_ref(&self, guard: LuaGuard) -> Result> { - UserDataRefMut::try_from(self.clone(), guard) + pub(crate) fn try_borrow_owned_mut(&self) -> Result> { + UserDataRefMut::try_from(self.clone()) } // Returns the wrapped value. // // This method checks that we have exclusive access to the value. pub(crate) fn into_inner(self) -> Result { - set_writing(self.flag())?; + if !self.raw_lock().try_lock_exclusive() { + return Err(Error::UserDataBorrowMutError); + } Ok(match self { - Self::Default(inner) => Rc::into_inner(inner).unwrap().value.into_inner(), + Self::Default(inner) => XRc::into_inner(inner).unwrap().value.into_inner(), #[cfg(feature = "serialize")] Self::Serializable(inner) => unsafe { - let raw = Box::into_raw(Rc::into_inner(inner).unwrap().value.into_inner()); + let raw = Box::into_raw(XRc::into_inner(inner).unwrap().value.into_inner().0); *Box::from_raw(raw as *mut T) }, }) } #[inline(always)] - fn flag(&self) -> &Cell { + fn raw_lock(&self) -> &RawLock { match self { - Self::Default(inner) => &inner.borrow, + Self::Default(inner) => &inner.raw_lock, #[cfg(feature = "serialize")] - Self::Serializable(inner) => &inner.borrow, + Self::Serializable(inner) => &inner.raw_lock, } } @@ -98,11 +108,12 @@ impl UserDataVariant { } #[cfg(feature = "serialize")] -impl UserDataVariant { +impl UserDataVariant { #[inline(always)] pub(crate) fn new_ser(data: T) -> Self { - let data = Box::new(data) as Box; - Self::Serializable(Rc::new(InnerRefCell::new(data))) + let data = Box::new(data) as Box; + let data = ForceSync(data); + Self::Serializable(XRc::new(UserDataCell::new(data))) } } @@ -110,29 +121,34 @@ impl UserDataVariant { impl Serialize for UserDataVariant<()> { fn serialize(&self, serializer: S) -> std::result::Result { match self { - UserDataVariant::Default(_) => Err(serde::ser::Error::custom("cannot serialize ")), - UserDataVariant::Serializable(inner) => unsafe { - let _ = self.try_borrow().map_err(serde::ser::Error::custom)?; - (*inner.value.get()).serialize(serializer) + Self::Default(_) => Err(serde::ser::Error::custom("cannot serialize ")), + Self::Serializable(inner) => unsafe { + // We need to borrow the inner value exclusively to serialize it. + #[cfg(feature = "send")] + let _guard = self.try_borrow_mut().map_err(serde::ser::Error::custom)?; + // No need to do this if the `send` feature is disabled. + #[cfg(not(feature = "send"))] + let _guard = self.try_borrow().map_err(serde::ser::Error::custom)?; + (*inner.value.get()).0.serialize(serializer) }, } } } -// -// Inspired by `std::cell::RefCell`` implementation -// - -pub(crate) struct InnerRefCell { - borrow: Cell, +/// A type that provides interior mutability for a userdata value (thread-safe). +pub(crate) struct UserDataCell { + raw_lock: RawLock, value: UnsafeCell, } -impl InnerRefCell { +unsafe impl Send for UserDataCell {} +unsafe impl Sync for UserDataCell {} + +impl UserDataCell { #[inline(always)] pub fn new(value: T) -> Self { - InnerRefCell { - borrow: Cell::new(UNUSED), + UserDataCell { + raw_lock: RawLock::INIT, value: UnsafeCell::new(value), } } @@ -141,25 +157,21 @@ impl InnerRefCell { /// A wrapper type for a [`UserData`] value that provides read access. /// /// It implements [`FromLua`] and can be used to receive a typed userdata from Lua. -pub struct UserDataRef { - variant: UserDataVariant, - #[allow(unused)] - guard: LuaGuard, -} +pub struct UserDataRef(UserDataVariant); impl Deref for UserDataRef { type Target = T; #[inline] fn deref(&self) -> &T { - unsafe { &*self.variant.as_ptr() } + unsafe { &*self.0.as_ptr() } } } impl Drop for UserDataRef { #[inline] fn drop(&mut self) { - unset_reading(self.variant.flag()); + unsafe { self.0.raw_lock().unlock_shared() }; } } @@ -175,11 +187,15 @@ impl fmt::Display for UserDataRef { } } -impl UserDataRef { +impl TryFrom> for UserDataRef { + type Error = Error; + #[inline] - fn try_from(variant: UserDataVariant, guard: LuaGuard) -> Result { - set_reading(variant.flag())?; - Ok(UserDataRef { variant, guard }) + fn try_from(variant: UserDataVariant) -> Result { + if !variant.raw_lock().try_lock_shared() { + return Err(Error::UserDataBorrowError); + } + Ok(UserDataRef(variant)) } } @@ -192,8 +208,7 @@ impl FromLua for UserDataRef { let type_id = lua.get_userdata_type_id(idx)?; match type_id { Some(type_id) if type_id == TypeId::of::() => { - let guard = lua.lua().lock_arc(); - (*get_userdata::>(lua.state(), idx)).try_make_ref(guard) + (*get_userdata::>(lua.state(), idx)).try_borrow_owned() } _ => Err(Error::UserDataTypeMismatch), } @@ -203,32 +218,28 @@ impl FromLua for UserDataRef { /// A wrapper type for a mutably borrowed value from a `AnyUserData`. /// /// It implements [`FromLua`] and can be used to receive a typed userdata from Lua. -pub struct UserDataRefMut { - variant: UserDataVariant, - #[allow(unused)] - guard: LuaGuard, -} +pub struct UserDataRefMut(UserDataVariant); impl Deref for UserDataRefMut { type Target = T; #[inline] fn deref(&self) -> &Self::Target { - unsafe { &*self.variant.as_ptr() } + unsafe { &*self.0.as_ptr() } } } impl DerefMut for UserDataRefMut { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { &mut *self.variant.as_ptr() } + unsafe { &mut *self.0.as_ptr() } } } impl Drop for UserDataRefMut { #[inline] fn drop(&mut self) { - unset_writing(self.variant.flag()); + unsafe { self.0.raw_lock().unlock_exclusive() }; } } @@ -244,11 +255,15 @@ impl fmt::Display for UserDataRefMut { } } -impl UserDataRefMut { - fn try_from(variant: UserDataVariant, guard: LuaGuard) -> Result { - // There must currently be no existing references - set_writing(variant.flag())?; - Ok(UserDataRefMut { variant, guard }) +impl TryFrom> for UserDataRefMut { + type Error = Error; + + #[inline] + fn try_from(variant: UserDataVariant) -> Result { + if !variant.raw_lock().try_lock_exclusive() { + return Err(Error::UserDataBorrowMutError); + } + Ok(UserDataRefMut(variant)) } } @@ -261,73 +276,20 @@ impl FromLua for UserDataRefMut { let type_id = lua.get_userdata_type_id(idx)?; match type_id { Some(type_id) if type_id == TypeId::of::() => { - let guard = lua.lua().lock_arc(); - (*get_userdata::>(lua.state(), idx)).try_make_mut_ref(guard) + (*get_userdata::>(lua.state(), idx)).try_borrow_owned_mut() } _ => Err(Error::UserDataTypeMismatch), } } } -// Positive values represent the number of `Ref` active. Negative values -// represent the number of `RefMut` active. Multiple `RefMut`s can only be -// active at a time if they refer to distinct, nonoverlapping components of a -// `RefCell` (e.g., different ranges of a slice). -type BorrowFlag = isize; -const UNUSED: BorrowFlag = 0; - -#[inline(always)] -fn is_writing(x: BorrowFlag) -> bool { - x < UNUSED -} - -#[inline(always)] -fn is_reading(x: BorrowFlag) -> bool { - x > UNUSED -} - -#[inline(always)] -fn set_writing(borrow: &Cell) -> Result<()> { - let flag = borrow.get(); - if flag != UNUSED { - return Err(Error::UserDataBorrowMutError); - } - borrow.set(UNUSED - 1); - Ok(()) -} - -#[inline(always)] -fn set_reading(borrow: &Cell) -> Result<()> { - let flag = borrow.get().wrapping_add(1); - if !is_reading(flag) { - return Err(Error::UserDataBorrowError); - } - borrow.set(flag); - Ok(()) -} - -#[inline(always)] -#[track_caller] -fn unset_writing(borrow: &Cell) { - let flag = borrow.get(); - debug_assert!(is_writing(flag)); - borrow.set(flag + 1); -} - -#[inline(always)] -#[track_caller] -fn unset_reading(borrow: &Cell) { - let flag = borrow.get(); - debug_assert!(is_reading(flag)); - borrow.set(flag - 1); -} - +/// A type that provides read access to a userdata value (borrowing the value). pub(crate) struct UserDataBorrowRef<'a, T>(&'a UserDataVariant); impl<'a, T> Drop for UserDataBorrowRef<'a, T> { #[inline] fn drop(&mut self) { - unset_reading(self.0.flag()); + unsafe { self.0.raw_lock().unlock_shared() }; } } @@ -336,6 +298,7 @@ impl<'a, T> Deref for UserDataBorrowRef<'a, T> { #[inline] fn deref(&self) -> &T { + // SAFETY: `UserDataBorrowRef` is only created with shared access to the value. unsafe { &*self.0.as_ptr() } } } @@ -345,25 +308,19 @@ impl<'a, T> TryFrom<&'a UserDataVariant> for UserDataBorrowRef<'a, T> { #[inline(always)] fn try_from(variant: &'a UserDataVariant) -> Result { - set_reading(variant.flag())?; + if !variant.raw_lock().try_lock_shared() { + return Err(Error::UserDataBorrowError); + } Ok(UserDataBorrowRef(variant)) } } -impl<'a, T> UserDataBorrowRef<'a, T> { - #[inline(always)] - pub(crate) fn get_ref(&self) -> &'a T { - // SAFETY: `UserDataBorrowRef` is only created when the borrow flag is set to reading. - unsafe { &*self.0.as_ptr() } - } -} - pub(crate) struct UserDataBorrowMut<'a, T>(&'a UserDataVariant); impl<'a, T> Drop for UserDataBorrowMut<'a, T> { #[inline] fn drop(&mut self) { - unset_writing(self.0.flag()); + unsafe { self.0.raw_lock().unlock_exclusive() }; } } @@ -388,18 +345,17 @@ impl<'a, T> TryFrom<&'a UserDataVariant> for UserDataBorrowMut<'a, T> { #[inline(always)] fn try_from(variant: &'a UserDataVariant) -> Result { - set_writing(variant.flag())?; + if !variant.raw_lock().try_lock_exclusive() { + return Err(Error::UserDataBorrowMutError); + } Ok(UserDataBorrowMut(variant)) } } -impl<'a, T> UserDataBorrowMut<'a, T> { - #[inline(always)] - pub(crate) fn get_mut(&mut self) -> &'a mut T { - // SAFETY: `UserDataBorrowMut` is only created when the borrow flag is set to writing. - unsafe { &mut *self.0.as_ptr() } - } -} +#[repr(transparent)] +pub(crate) struct ForceSync(T); + +unsafe impl Sync for ForceSync {} #[inline] fn try_value_to_userdata(value: Value) -> Result { @@ -417,6 +373,25 @@ fn try_value_to_userdata(value: Value) -> Result { mod assertions { use super::*; - static_assertions::assert_not_impl_all!(UserDataRef<()>: Sync, Send); - static_assertions::assert_not_impl_all!(UserDataRefMut<()>: Sync, Send); + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(UserDataRef<()>: Send, Sync); + #[cfg(feature = "send")] + static_assertions::assert_not_impl_all!(UserDataRef>: Send, Sync); + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(UserDataRefMut<()>: Sync, Send); + #[cfg(feature = "send")] + static_assertions::assert_not_impl_all!(UserDataRefMut>: Send, Sync); + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(UserDataBorrowRef<'_, ()>: Send, Sync); + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(UserDataBorrowMut<'_, ()>: Send, Sync); + + #[cfg(not(feature = "send"))] + static_assertions::assert_not_impl_all!(UserDataRef<()>: Send, Sync); + #[cfg(not(feature = "send"))] + static_assertions::assert_not_impl_all!(UserDataRefMut<()>: Send, Sync); + #[cfg(not(feature = "send"))] + static_assertions::assert_not_impl_all!(UserDataBorrowRef<'_, ()>: Send, Sync); + #[cfg(not(feature = "send"))] + static_assertions::assert_not_impl_all!(UserDataBorrowMut<'_, ()>: Send, Sync); } diff --git a/src/userdata/lock.rs b/src/userdata/lock.rs new file mode 100644 index 00000000..7ddf6be4 --- /dev/null +++ b/src/userdata/lock.rs @@ -0,0 +1,93 @@ +pub(crate) trait UserDataLock { + const INIT: Self; + + fn try_lock_shared(&self) -> bool; + fn try_lock_exclusive(&self) -> bool; + + unsafe fn unlock_shared(&self); + unsafe fn unlock_exclusive(&self); +} + +pub(crate) use lock_impl::RawLock; + +#[cfg(not(feature = "send"))] +mod lock_impl { + use std::cell::Cell; + + // Positive values represent the number of read references. + // Negative values represent the number of write references (only one allowed). + pub(crate) type RawLock = Cell; + + const UNUSED: isize = 0; + + impl super::UserDataLock for RawLock { + #[allow(clippy::declare_interior_mutable_const)] + const INIT: Self = Cell::new(UNUSED); + + #[inline(always)] + fn try_lock_shared(&self) -> bool { + let flag = self.get().wrapping_add(1); + if flag <= UNUSED { + return false; + } + self.set(flag); + true + } + + #[inline(always)] + fn try_lock_exclusive(&self) -> bool { + let flag = self.get(); + if flag != UNUSED { + return false; + } + self.set(UNUSED - 1); + true + } + + #[inline(always)] + unsafe fn unlock_shared(&self) { + let flag = self.get(); + debug_assert!(flag > UNUSED); + self.set(flag - 1); + } + + #[inline(always)] + unsafe fn unlock_exclusive(&self) { + let flag = self.get(); + debug_assert!(flag < UNUSED); + self.set(flag + 1); + } + } +} + +#[cfg(feature = "send")] +mod lock_impl { + use parking_lot::lock_api::RawRwLock; + + pub(crate) type RawLock = parking_lot::RawRwLock; + + impl super::UserDataLock for RawLock { + #[allow(clippy::declare_interior_mutable_const)] + const INIT: Self = ::INIT; + + #[inline(always)] + fn try_lock_shared(&self) -> bool { + RawRwLock::try_lock_shared(self) + } + + #[inline(always)] + fn try_lock_exclusive(&self) -> bool { + RawRwLock::try_lock_exclusive(self) + } + + #[inline(always)] + unsafe fn unlock_shared(&self) { + RawRwLock::unlock_shared(self) + } + + #[inline(always)] + unsafe fn unlock_exclusive(&self) { + RawRwLock::unlock_exclusive(self) + } + } +} diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index de31f307..c7b236f8 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -9,7 +9,9 @@ use std::string::String as StdString; use crate::error::{Error, Result}; use crate::state::Lua; use crate::types::{Callback, MaybeSend}; -use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods}; +use crate::userdata::{ + AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, UserDataRefMut, +}; use crate::util::{get_userdata, short_type_name}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; @@ -22,25 +24,25 @@ use { }; /// Handle to registry for userdata methods and metamethods. -pub struct UserDataRegistry<'a, T: 'static> { +pub struct UserDataRegistry { // Fields - pub(crate) fields: Vec<(String, Callback<'a>)>, - pub(crate) field_getters: Vec<(String, Callback<'a>)>, - pub(crate) field_setters: Vec<(String, Callback<'a>)>, - pub(crate) meta_fields: Vec<(String, Callback<'a>)>, + pub(crate) fields: Vec<(String, Callback)>, + pub(crate) field_getters: Vec<(String, Callback)>, + pub(crate) field_setters: Vec<(String, Callback)>, + pub(crate) meta_fields: Vec<(String, Callback)>, // Methods - pub(crate) methods: Vec<(String, Callback<'a>)>, + pub(crate) methods: Vec<(String, Callback)>, #[cfg(feature = "async")] - pub(crate) async_methods: Vec<(String, AsyncCallback<'a>)>, - pub(crate) meta_methods: Vec<(String, Callback<'a>)>, + pub(crate) async_methods: Vec<(String, AsyncCallback)>, + pub(crate) meta_methods: Vec<(String, Callback)>, #[cfg(feature = "async")] - pub(crate) async_meta_methods: Vec<(String, AsyncCallback<'a>)>, + pub(crate) async_meta_methods: Vec<(String, AsyncCallback)>, _type: PhantomData, } -impl<'a, T: 'static> UserDataRegistry<'a, T> { +impl UserDataRegistry { pub(crate) const fn new() -> Self { UserDataRegistry { fields: Vec::new(), @@ -57,9 +59,9 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { } } - fn box_method(name: &str, method: M) -> Callback<'a> + fn box_method(name: &str, method: M) -> Callback where - M: Fn(&'a Lua, &T, A) -> Result + MaybeSend + 'static, + M: Fn(&Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { @@ -77,13 +79,13 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { } let state = rawlua.state(); // Find absolute "self" index before processing args - let index = ffi::lua_absindex(state, -nargs); + let self_index = ffi::lua_absindex(state, -nargs); // Self was at position 1, so we pass 2 here let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua); - match try_self_arg!(rawlua.get_userdata_type_id(index)) { + match try_self_arg!(rawlua.get_userdata_type_id(self_index)) { Some(id) if id == TypeId::of::() => { - let ud = try_self_arg!(borrow_userdata_ref::(state, index)); + let ud = try_self_arg!(borrow_userdata_ref::(state, self_index)); method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua) } _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), @@ -91,9 +93,9 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { }) } - fn box_method_mut(name: &str, method: M) -> Callback<'a> + fn box_method_mut(name: &str, method: M) -> Callback where - M: FnMut(&'a Lua, &mut T, A) -> Result + MaybeSend + 'static, + M: FnMut(&Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { @@ -113,13 +115,13 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { } let state = rawlua.state(); // Find absolute "self" index before processing args - let index = ffi::lua_absindex(state, -nargs); + let self_index = ffi::lua_absindex(state, -nargs); // Self was at position 1, so we pass 2 here let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua); - match try_self_arg!(rawlua.get_userdata_type_id(index)) { + match try_self_arg!(rawlua.get_userdata_type_id(self_index)) { Some(id) if id == TypeId::of::() => { - let mut ud = try_self_arg!(borrow_userdata_mut::(state, index)); + let mut ud = try_self_arg!(borrow_userdata_mut::(state, self_index)); method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua) } _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), @@ -128,11 +130,11 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { } #[cfg(feature = "async")] - fn box_async_method(name: &str, method: M) -> AsyncCallback<'a> + fn box_async_method(name: &str, method: M) -> AsyncCallback where - M: Fn(&'a Lua, &'a T, A) -> MR + MaybeSend + 'static, + M: Fn(Lua, UserDataRef, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + MaybeSend + 'a, + MR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { let name = get_function_name::(name); @@ -145,38 +147,33 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { }; } - Box::new(move |lua, mut args| unsafe { - let this = args - .pop_front() - .ok_or_else(|| Error::from_lua_conversion("missing argument", "userdata", None)); - let this = try_self_arg!(AnyUserData::from_lua(try_self_arg!(this), lua)); - let args = A::from_lua_args(args, 2, Some(&name), lua); - - let (ref_thread, index) = (lua.raw_lua().ref_thread(), this.0.index); - match try_self_arg!(this.type_id()) { - Some(id) if id == TypeId::of::() => { - let ud = try_self_arg!(borrow_userdata_ref::(ref_thread, index)); - let args = match args { - Ok(args) => args, - Err(e) => return Box::pin(future::ready(Err(e))), - }; - let fut = method(lua, ud.get_ref(), args); - Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) - } - _ => { - let err = Error::bad_self_argument(&name, Error::UserDataTypeMismatch); - Box::pin(future::ready(Err(err))) - } + Box::new(move |rawlua, nargs| unsafe { + if nargs == 0 { + let err = Error::from_lua_conversion("missing argument", "userdata", None); + try_self_arg!(Err(err)); } + // Stack will be empty when polling the future, keep `self` on the ref thread + let self_ud = try_self_arg!(AnyUserData::from_stack(-nargs, rawlua)); + let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua); + + let self_ud = try_self_arg!(self_ud.borrow()); + let args = match args { + Ok(args) => args, + Err(e) => return Box::pin(future::ready(Err(e))), + }; + let lua = rawlua.lua().clone(); + let fut = method(lua.clone(), self_ud, args); + // Lua is locked when the future is polled + Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) }) } #[cfg(feature = "async")] - fn box_async_method_mut(name: &str, method: M) -> AsyncCallback<'a> + fn box_async_method_mut(name: &str, method: M) -> AsyncCallback where - M: Fn(&'a Lua, &'a mut T, A) -> MR + MaybeSend + 'static, + M: Fn(Lua, UserDataRefMut, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + MaybeSend + 'a, + MR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { let name = get_function_name::(name); @@ -189,35 +186,30 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { }; } - Box::new(move |lua, mut args| unsafe { - let this = args - .pop_front() - .ok_or_else(|| Error::from_lua_conversion("missing argument", "userdata", None)); - let this = try_self_arg!(AnyUserData::from_lua(try_self_arg!(this), lua)); - let args = A::from_lua_args(args, 2, Some(&name), lua); - - let (ref_thread, index) = (lua.raw_lua().ref_thread(), this.0.index); - match try_self_arg!(this.type_id()) { - Some(id) if id == TypeId::of::() => { - let mut ud = try_self_arg!(borrow_userdata_mut::(ref_thread, index)); - let args = match args { - Ok(args) => args, - Err(e) => return Box::pin(future::ready(Err(e))), - }; - let fut = method(lua, ud.get_mut(), args); - Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) - } - _ => { - let err = Error::bad_self_argument(&name, Error::UserDataTypeMismatch); - Box::pin(future::ready(Err(err))) - } + Box::new(move |rawlua, nargs| unsafe { + if nargs == 0 { + let err = Error::from_lua_conversion("missing argument", "userdata", None); + try_self_arg!(Err(err)); } + // Stack will be empty when polling the future, keep `self` on the ref thread + let self_ud = try_self_arg!(AnyUserData::from_stack(-nargs, rawlua)); + let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua); + + let self_ud = try_self_arg!(self_ud.borrow_mut()); + let args = match args { + Ok(args) => args, + Err(e) => return Box::pin(future::ready(Err(e))), + }; + let lua = rawlua.lua().clone(); + let fut = method(lua.clone(), self_ud, args); + // Lua is locked when the future is polled + Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) }) } - fn box_function(name: &str, function: F) -> Callback<'a> + fn box_function(name: &str, function: F) -> Callback where - F: Fn(&'a Lua, A) -> Result + MaybeSend + 'static, + F: Fn(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { @@ -228,9 +220,9 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { }) } - fn box_function_mut(name: &str, function: F) -> Callback<'a> + fn box_function_mut(name: &str, function: F) -> Callback where - F: FnMut(&'a Lua, A) -> Result + MaybeSend + 'static, + F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { @@ -246,20 +238,21 @@ impl<'a, T: 'static> UserDataRegistry<'a, T> { } #[cfg(feature = "async")] - fn box_async_function(name: &str, function: F) -> AsyncCallback<'a> + fn box_async_function(name: &str, function: F) -> AsyncCallback where - F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, + F: Fn(Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, - FR: Future> + MaybeSend + 'a, + FR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { let name = get_function_name::(name); - Box::new(move |lua, args| unsafe { - let args = match A::from_lua_args(args, 1, Some(&name), lua) { + Box::new(move |rawlua, nargs| unsafe { + let args = match A::from_stack_args(nargs, 1, Some(&name), rawlua) { Ok(args) => args, Err(e) => return Box::pin(future::ready(Err(e))), }; - let fut = function(lua, args); + let lua = rawlua.lua().clone(); + let fut = function(lua.clone(), args); Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) }) } @@ -287,19 +280,19 @@ fn get_function_name(name: &str) -> StdString { format!("{}.{name}", short_type_name::()) } -impl<'a, T: 'static> UserDataFields<'a, T> for UserDataRegistry<'a, T> { +impl UserDataFields for UserDataRegistry { fn add_field(&mut self, name: impl ToString, value: V) where V: IntoLua + Clone + 'static, { let name = name.to_string(); - let callback = Box::new(move |lua, _| unsafe { value.clone().push_into_stack_multi(lua) }); + let callback: Callback = Box::new(move |lua, _| unsafe { value.clone().push_into_stack_multi(lua) }); self.fields.push((name, callback)); } fn add_field_method_get(&mut self, name: impl ToString, method: M) where - M: Fn(&'a Lua, &T) -> Result + MaybeSend + 'static, + M: Fn(&Lua, &T) -> Result + MaybeSend + 'static, R: IntoLua, { let name = name.to_string(); @@ -309,7 +302,7 @@ impl<'a, T: 'static> UserDataFields<'a, T> for UserDataRegistry<'a, T> { fn add_field_method_set(&mut self, name: impl ToString, method: M) where - M: FnMut(&'a Lua, &mut T, A) -> Result<()> + MaybeSend + 'static, + M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static, A: FromLua, { let name = name.to_string(); @@ -319,7 +312,7 @@ impl<'a, T: 'static> UserDataFields<'a, T> for UserDataRegistry<'a, T> { fn add_field_function_get(&mut self, name: impl ToString, function: F) where - F: Fn(&'a Lua, AnyUserData) -> Result + MaybeSend + 'static, + F: Fn(&Lua, AnyUserData) -> Result + MaybeSend + 'static, R: IntoLua, { let name = name.to_string(); @@ -329,7 +322,7 @@ impl<'a, T: 'static> UserDataFields<'a, T> for UserDataRegistry<'a, T> { fn add_field_function_set(&mut self, name: impl ToString, mut function: F) where - F: FnMut(&'a Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static, + F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static, A: FromLua, { let name = name.to_string(); @@ -352,7 +345,7 @@ impl<'a, T: 'static> UserDataFields<'a, T> for UserDataRegistry<'a, T> { fn add_meta_field_with(&mut self, name: impl ToString, f: F) where - F: Fn(&'a Lua) -> Result + MaybeSend + 'static, + F: Fn(&Lua) -> Result + MaybeSend + 'static, R: IntoLua, { let name = name.to_string(); @@ -366,10 +359,10 @@ impl<'a, T: 'static> UserDataFields<'a, T> for UserDataRegistry<'a, T> { } } -impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { +impl UserDataMethods for UserDataRegistry { fn add_method(&mut self, name: impl ToString, method: M) where - M: Fn(&'a Lua, &T, A) -> Result + MaybeSend + 'static, + M: Fn(&Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { @@ -380,7 +373,7 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { fn add_method_mut(&mut self, name: impl ToString, method: M) where - M: FnMut(&'a Lua, &mut T, A) -> Result + MaybeSend + 'static, + M: FnMut(&Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { @@ -392,9 +385,9 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { #[cfg(feature = "async")] fn add_async_method(&mut self, name: impl ToString, method: M) where - M: Fn(&'a Lua, &'a T, A) -> MR + MaybeSend + 'static, + M: Fn(Lua, UserDataRef, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + MaybeSend + 'a, + MR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { let name = name.to_string(); @@ -405,9 +398,9 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { #[cfg(feature = "async")] fn add_async_method_mut(&mut self, name: impl ToString, method: M) where - M: Fn(&'a Lua, &'a mut T, A) -> MR + MaybeSend + 'static, + M: Fn(Lua, UserDataRefMut, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + MaybeSend + 'a, + MR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { let name = name.to_string(); @@ -417,7 +410,7 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { fn add_function(&mut self, name: impl ToString, function: F) where - F: Fn(&'a Lua, A) -> Result + MaybeSend + 'static, + F: Fn(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { @@ -428,7 +421,7 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { fn add_function_mut(&mut self, name: impl ToString, function: F) where - F: FnMut(&'a Lua, A) -> Result + MaybeSend + 'static, + F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { @@ -440,9 +433,9 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { #[cfg(feature = "async")] fn add_async_function(&mut self, name: impl ToString, function: F) where - F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, + F: Fn(Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, - FR: Future> + MaybeSend + 'a, + FR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { let name = name.to_string(); @@ -452,7 +445,7 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { fn add_meta_method(&mut self, name: impl ToString, method: M) where - M: Fn(&'a Lua, &T, A) -> Result + MaybeSend + 'static, + M: Fn(&Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { @@ -463,7 +456,7 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { fn add_meta_method_mut(&mut self, name: impl ToString, method: M) where - M: FnMut(&'a Lua, &mut T, A) -> Result + MaybeSend + 'static, + M: FnMut(&Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { @@ -475,9 +468,9 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] fn add_async_meta_method(&mut self, name: impl ToString, method: M) where - M: Fn(&'a Lua, &'a T, A) -> MR + MaybeSend + 'static, + M: Fn(Lua, UserDataRef, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + MaybeSend + 'a, + MR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { let name = name.to_string(); @@ -488,9 +481,9 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] fn add_async_meta_method_mut(&mut self, name: impl ToString, method: M) where - M: Fn(&'a Lua, &'a mut T, A) -> MR + MaybeSend + 'static, + M: Fn(Lua, UserDataRefMut, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, - MR: Future> + MaybeSend + 'a, + MR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { let name = name.to_string(); @@ -500,7 +493,7 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { fn add_meta_function(&mut self, name: impl ToString, function: F) where - F: Fn(&'a Lua, A) -> Result + MaybeSend + 'static, + F: Fn(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { @@ -511,7 +504,7 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { fn add_meta_function_mut(&mut self, name: impl ToString, function: F) where - F: FnMut(&'a Lua, A) -> Result + MaybeSend + 'static, + F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { @@ -523,9 +516,9 @@ impl<'a, T: 'static> UserDataMethods<'a, T> for UserDataRegistry<'a, T> { #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] fn add_async_meta_function(&mut self, name: impl ToString, function: F) where - F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, + F: Fn(Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, - FR: Future> + MaybeSend + 'a, + FR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { let name = name.to_string(); diff --git a/src/util/types.rs b/src/util/types.rs index d8705411..829b7e9d 100644 --- a/src/util/types.rs +++ b/src/util/types.rs @@ -21,7 +21,7 @@ impl TypeKey for String { static CALLBACK_TYPE_KEY: u8 = 0; -impl TypeKey for Callback<'static> { +impl TypeKey for Callback { #[inline(always)] fn type_key() -> *const c_void { &CALLBACK_TYPE_KEY as *const u8 as *const c_void @@ -41,7 +41,7 @@ impl TypeKey for CallbackUpvalue { static ASYNC_CALLBACK_TYPE_KEY: u8 = 0; #[cfg(feature = "async")] -impl TypeKey for AsyncCallback<'static> { +impl TypeKey for AsyncCallback { #[inline(always)] fn type_key() -> *const c_void { &ASYNC_CALLBACK_TYPE_KEY as *const u8 as *const c_void diff --git a/tests/async.rs b/tests/async.rs index 038d14fb..59e178da 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -378,13 +378,13 @@ async fn test_async_userdata() -> Result<()> { struct MyUserData(u64); impl UserData for MyUserData { - fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_async_method("get_value", |_, data, ()| async move { sleep_ms(10).await; Ok(data.0) }); - methods.add_async_method_mut("set_value", |_, data, n| async move { + methods.add_async_method_mut("set_value", |_, mut data, n| async move { sleep_ms(10).await; data.0 = n; Ok(()) @@ -415,7 +415,7 @@ async fn test_async_userdata() -> Result<()> { #[cfg(not(any(feature = "lua51", feature = "luau")))] methods.add_async_meta_method_mut( mlua::MetaMethod::NewIndex, - |_, data, (key, value): (String, f64)| async move { + |_, mut data, (key, value): (String, f64)| async move { sleep_ms(10).await; match key.as_str() { "ms" => data.0 = value as u64, @@ -477,7 +477,7 @@ async fn test_async_thread_error() -> Result<()> { struct MyUserData; impl UserData for MyUserData { - fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_meta_method("__tostring", |_, _this, ()| Ok("myuserdata error")) } } diff --git a/tests/userdata.rs b/tests/userdata.rs index f925e20f..925c5bd8 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -39,7 +39,7 @@ fn test_methods() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { - fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_method("get_value", |_, data, ()| Ok(data.0)); methods.add_method_mut("set_value", |_, data, args| { data.0 = args; @@ -89,7 +89,7 @@ fn test_method_variadic() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { - fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_method("get", |_, data, ()| Ok(data.0)); methods.add_method_mut("add", |_, data, vals: Variadic| { data.0 += vals.into_iter().sum::(); @@ -114,7 +114,7 @@ fn test_metamethods() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { - fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_method("get", |_, data, ()| Ok(data.0)); methods.add_meta_function( MetaMethod::Add, @@ -213,7 +213,7 @@ fn test_metamethod_close() -> Result<()> { struct MyUserData(Arc); impl UserData for MyUserData { - fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_method("get", |_, data, ()| Ok(data.0.load(Ordering::Relaxed))); methods.add_meta_method(MetaMethod::Close, |_, data, _err: Value| { data.0.store(0, Ordering::Relaxed); @@ -259,7 +259,7 @@ fn test_gc_userdata() -> Result<()> { } impl UserData for MyUserdata { - fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_method("access", |_, this, ()| { assert!(this.id == 123); Ok(()) @@ -298,7 +298,7 @@ fn test_userdata_take() -> Result<()> { struct MyUserdata(Arc); impl UserData for MyUserdata { - fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_method("num", |_, this, ()| Ok(*this.0)) } } @@ -433,7 +433,7 @@ fn test_functions() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { - fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_function("get_value", |_, ud: AnyUserData| Ok(ud.borrow::()?.0)); methods.add_function_mut("set_value", |_, (ud, value): (AnyUserData, i64)| { ud.borrow_mut::()?.0 = value; @@ -485,7 +485,7 @@ fn test_fields() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { - fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { + fn add_fields>(fields: &mut F) { fields.add_field("static", "constant"); fields.add_field_method_get("val", |_, data| Ok(data.0)); fields.add_field_method_set("val", |_, data, val| { @@ -531,12 +531,12 @@ fn test_fields() -> Result<()> { struct MyUserData2(i64); impl UserData for MyUserData2 { - fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { + fn add_fields>(fields: &mut F) { fields.add_field("z", 0); fields.add_field_method_get("x", |_, data| Ok(data.0)); } - fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_meta_method(MetaMethod::Index, |_, _, name: StdString| match &*name { "y" => Ok(Some(-1)), _ => Ok(None), @@ -563,7 +563,7 @@ fn test_metatable() -> Result<()> { struct MyUserData; impl UserData for MyUserData { - fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_function("my_type_name", |_, data: AnyUserData| { let metatable = data.get_metatable()?; metatable.get::(MetaMethod::Type) @@ -608,7 +608,7 @@ fn test_metatable() -> Result<()> { struct MyUserData2; impl UserData for MyUserData2 { - fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { + fn add_fields>(fields: &mut F) { fields.add_meta_field_with("__index", |_| Ok(1)); } } @@ -623,7 +623,7 @@ fn test_metatable() -> Result<()> { struct MyUserData3; impl UserData for MyUserData3 { - fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { + fn add_fields>(fields: &mut F) { fields.add_meta_field_with(MetaMethod::Type, |_| Ok("CustomName")); } } @@ -640,12 +640,12 @@ fn test_userdata_proxy() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { - fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { + fn add_fields>(fields: &mut F) { fields.add_field("static_field", 123); fields.add_field_method_get("n", |_, this| Ok(this.0)); } - fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_function("new", |_, n| Ok(Self(n))); methods.add_method("plus", |_, this, n: i64| Ok(this.0 + n)); @@ -733,7 +733,7 @@ fn test_userdata_ext() -> Result<()> { struct MyUserData(u32); impl UserData for MyUserData { - fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { + fn add_fields>(fields: &mut F) { fields.add_field_method_get("n", |_, this| Ok(this.0)); fields.add_field_method_set("n", |_, this, val| { this.0 = val; @@ -741,7 +741,7 @@ fn test_userdata_ext() -> Result<()> { }); } - fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_meta_method(MetaMethod::Call, |_, _this, ()| Ok("called")); methods.add_method_mut("add", |_, this, x: u32| { this.0 += x; @@ -774,7 +774,7 @@ fn test_userdata_method_errors() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { - fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_method("get_value", |_, data, ()| Ok(data.0)); } } From fdc50bffc9cbc5e50a520e772d11f9f8d222abac Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 23 Aug 2024 01:03:54 +0100 Subject: [PATCH 150/635] Fix memory leak when polling async futures --- src/state.rs | 2 +- src/types.rs | 3 ++- src/userdata/registry.rs | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/state.rs b/src/state.rs index 4db87f29..9c91f825 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1162,7 +1162,7 @@ impl Lua { Ok(args) => args, Err(e) => return Box::pin(future::ready(Err(e))), }; - let lua = rawlua.lua().clone(); + let lua = rawlua.lua(); let fut = func(lua.clone(), args); Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) })) diff --git a/src/types.rs b/src/types.rs index 89bd75b9..f4445796 100644 --- a/src/types.rs +++ b/src/types.rs @@ -59,7 +59,8 @@ pub(crate) struct Upvalue { pub(crate) type CallbackUpvalue = Upvalue; #[cfg(feature = "async")] -pub(crate) type AsyncCallback = Box BoxFuture<'static, Result> + 'static>; +pub(crate) type AsyncCallback = + Box Fn(&'a RawLua, c_int) -> BoxFuture<'a, Result> + 'static>; #[cfg(feature = "async")] pub(crate) type AsyncCallbackUpvalue = Upvalue; diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index c7b236f8..e80b1ae1 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -161,7 +161,7 @@ impl UserDataRegistry { Ok(args) => args, Err(e) => return Box::pin(future::ready(Err(e))), }; - let lua = rawlua.lua().clone(); + let lua = rawlua.lua(); let fut = method(lua.clone(), self_ud, args); // Lua is locked when the future is polled Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) @@ -200,7 +200,7 @@ impl UserDataRegistry { Ok(args) => args, Err(e) => return Box::pin(future::ready(Err(e))), }; - let lua = rawlua.lua().clone(); + let lua = rawlua.lua(); let fut = method(lua.clone(), self_ud, args); // Lua is locked when the future is polled Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) @@ -251,7 +251,7 @@ impl UserDataRegistry { Ok(args) => args, Err(e) => return Box::pin(future::ready(Err(e))), }; - let lua = rawlua.lua().clone(); + let lua = rawlua.lua(); let fut = function(lua.clone(), args); Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) }) From 2857cb76c6f4bc4c461107782988137595cab825 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 23 Aug 2024 00:19:55 +0100 Subject: [PATCH 151/635] Add `A` param to `AsyncThread`. This reduces internal dependency on `MultiValue` container and delay args conversion to the future `poll()` stage. --- src/thread.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index d5cdb353..c3b1f44f 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -16,7 +16,6 @@ use crate::{ #[cfg(feature = "async")] use { - crate::value::MultiValue, futures_util::stream::Stream, std::{ future::Future, @@ -60,9 +59,9 @@ unsafe impl Sync for Thread {} #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct AsyncThread { +pub struct AsyncThread { thread: Thread, - init_args: Option>, + init_args: Option, ret: PhantomData, recycle: bool, } @@ -299,12 +298,10 @@ impl Thread { /// ``` #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub fn into_async(self, args: impl IntoLuaMulti) -> AsyncThread + pub fn into_async(self, args: impl IntoLuaMulti) -> AsyncThread where R: FromLuaMulti, { - let lua = self.0.lua.lock(); - let args = args.into_lua_multi(lua.lua()); AsyncThread { thread: self, init_args: Some(args), @@ -376,7 +373,7 @@ impl PartialEq for Thread { } #[cfg(feature = "async")] -impl AsyncThread { +impl AsyncThread { #[inline] pub(crate) fn set_recyclable(&mut self, recyclable: bool) { self.recycle = recyclable; @@ -385,7 +382,7 @@ impl AsyncThread { #[cfg(feature = "async")] #[cfg(any(feature = "lua54", feature = "luau"))] -impl Drop for AsyncThread { +impl Drop for AsyncThread { fn drop(&mut self) { if self.recycle { if let Some(lua) = self.thread.0.lua.try_lock() { @@ -407,7 +404,7 @@ impl Drop for AsyncThread { } #[cfg(feature = "async")] -impl Stream for AsyncThread { +impl Stream for AsyncThread { type Item = Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { @@ -426,7 +423,7 @@ impl Stream for AsyncThread { // This is safe as we are not moving the whole struct let this = self.get_unchecked_mut(); let nresults = if let Some(args) = this.init_args.take() { - this.thread.resume_inner(&lua, args?)? + this.thread.resume_inner(&lua, args)? } else { this.thread.resume_inner(&lua, ())? }; @@ -445,7 +442,7 @@ impl Stream for AsyncThread { } #[cfg(feature = "async")] -impl Future for AsyncThread { +impl Future for AsyncThread { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -464,7 +461,7 @@ impl Future for AsyncThread { // This is safe as we are not moving the whole struct let this = self.get_unchecked_mut(); let nresults = if let Some(args) = this.init_args.take() { - this.thread.resume_inner(&lua, args?)? + this.thread.resume_inner(&lua, args)? } else { this.thread.resume_inner(&lua, ())? }; @@ -529,7 +526,7 @@ mod assertions { #[cfg(feature = "send")] static_assertions::assert_impl_all!(Thread: Send, Sync); #[cfg(all(feature = "async", not(feature = "send")))] - static_assertions::assert_not_impl_any!(AsyncThread<()>: Send); + static_assertions::assert_not_impl_any!(AsyncThread<(), ()>: Send); #[cfg(all(feature = "async", feature = "send"))] - static_assertions::assert_impl_all!(AsyncThread<()>: Send, Sync); + static_assertions::assert_impl_all!(AsyncThread<(), ()>: Send, Sync); } From 23d4e2519b8cb210aeb22c5bce70c3d50fdcca93 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 24 Aug 2024 09:22:21 +0100 Subject: [PATCH 152/635] Switch to `Mutex` from `RwLock` for userdata access in `send` mode. Unfortunately RwLock allow access to the userdata from multiple threads without enforcing `Sync` marker. --- src/userdata/cell.rs | 14 ++++---------- src/userdata/lock.rs | 14 +++++++------- tests/send.rs | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 17 deletions(-) create mode 100644 tests/send.rs diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index c327a86d..10313693 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -27,7 +27,7 @@ type DynSerialize = dyn erased_serde::Serialize + Send; pub(crate) enum UserDataVariant { Default(XRc>), #[cfg(feature = "serialize")] - Serializable(XRc>>>), + Serializable(XRc>>), } impl Clone for UserDataVariant { @@ -82,7 +82,7 @@ impl UserDataVariant { Self::Default(inner) => XRc::into_inner(inner).unwrap().value.into_inner(), #[cfg(feature = "serialize")] Self::Serializable(inner) => unsafe { - let raw = Box::into_raw(XRc::into_inner(inner).unwrap().value.into_inner().0); + let raw = Box::into_raw(XRc::into_inner(inner).unwrap().value.into_inner()); *Box::from_raw(raw as *mut T) }, }) @@ -112,7 +112,6 @@ impl UserDataVariant { #[inline(always)] pub(crate) fn new_ser(data: T) -> Self { let data = Box::new(data) as Box; - let data = ForceSync(data); Self::Serializable(XRc::new(UserDataCell::new(data))) } } @@ -129,7 +128,7 @@ impl Serialize for UserDataVariant<()> { // No need to do this if the `send` feature is disabled. #[cfg(not(feature = "send"))] let _guard = self.try_borrow().map_err(serde::ser::Error::custom)?; - (*inner.value.get()).0.serialize(serializer) + (*inner.value.get()).serialize(serializer) }, } } @@ -142,7 +141,7 @@ pub(crate) struct UserDataCell { } unsafe impl Send for UserDataCell {} -unsafe impl Sync for UserDataCell {} +unsafe impl Sync for UserDataCell {} impl UserDataCell { #[inline(always)] @@ -352,11 +351,6 @@ impl<'a, T> TryFrom<&'a UserDataVariant> for UserDataBorrowMut<'a, T> { } } -#[repr(transparent)] -pub(crate) struct ForceSync(T); - -unsafe impl Sync for ForceSync {} - #[inline] fn try_value_to_userdata(value: Value) -> Result { match value { diff --git a/src/userdata/lock.rs b/src/userdata/lock.rs index 7ddf6be4..8845f332 100644 --- a/src/userdata/lock.rs +++ b/src/userdata/lock.rs @@ -62,32 +62,32 @@ mod lock_impl { #[cfg(feature = "send")] mod lock_impl { - use parking_lot::lock_api::RawRwLock; + use parking_lot::lock_api::RawMutex; - pub(crate) type RawLock = parking_lot::RawRwLock; + pub(crate) type RawLock = parking_lot::RawMutex; impl super::UserDataLock for RawLock { #[allow(clippy::declare_interior_mutable_const)] - const INIT: Self = ::INIT; + const INIT: Self = ::INIT; #[inline(always)] fn try_lock_shared(&self) -> bool { - RawRwLock::try_lock_shared(self) + RawLock::try_lock(self) } #[inline(always)] fn try_lock_exclusive(&self) -> bool { - RawRwLock::try_lock_exclusive(self) + RawLock::try_lock(self) } #[inline(always)] unsafe fn unlock_shared(&self) { - RawRwLock::unlock_shared(self) + RawLock::unlock(self) } #[inline(always)] unsafe fn unlock_exclusive(&self) { - RawRwLock::unlock_exclusive(self) + RawLock::unlock(self) } } } diff --git a/tests/send.rs b/tests/send.rs new file mode 100644 index 00000000..25b0602e --- /dev/null +++ b/tests/send.rs @@ -0,0 +1,35 @@ +#![cfg(feature = "send")] + +use std::cell::UnsafeCell; +use std::marker::PhantomData; +use std::string::String as StdString; + +use mlua::{AnyUserData, Error, Lua, Result, UserDataRef}; +use static_assertions::{assert_impl_all, assert_not_impl_all}; + +#[test] +fn test_userdata_multithread_access() -> Result<()> { + let lua = Lua::new(); + + // This type is `Send` but not `Sync`. + struct MyUserData(#[allow(unused)] StdString, PhantomData>); + + assert_impl_all!(MyUserData: Send); + assert_not_impl_all!(MyUserData: Sync); + + lua.globals().set( + "ud", + AnyUserData::wrap(MyUserData("hello".to_string(), PhantomData)), + )?; + // We acquired the exclusive reference. + let _ud1 = lua.globals().get::>("ud")?; + + std::thread::scope(|s| { + s.spawn(|| { + let res = lua.globals().get::>("ud"); + assert!(matches!(res, Err(Error::UserDataBorrowError))); + }); + }); + + Ok(()) +} From 7bfd32750dca1d9f1fa26cbe70356ae578fd2f29 Mon Sep 17 00:00:00 2001 From: bjcscat Date: Sun, 25 Aug 2024 10:50:41 -0500 Subject: [PATCH 153/635] Change chunk env to use luau's load env parameter (#442) --- mlua-sys/src/luau/compat.rs | 9 +++++---- src/state/raw.rs | 11 ++++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index 47ec5b6d..7ca9fbdf 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -326,6 +326,7 @@ pub unsafe fn luaL_loadbufferx( mut size: usize, name: *const c_char, mode: *const c_char, + env: c_int ) -> c_int { extern "C" { fn free(p: *mut c_void); @@ -345,12 +346,12 @@ pub unsafe fn luaL_loadbufferx( if chunk_is_text { let data = luau_compile_(data, size, ptr::null_mut(), &mut size); - let ok = luau_load(L, name, data, size, 0) == 0; + let ok = luau_load(L, name, data, size, env) == 0; free(data as *mut c_void); if !ok { return LUA_ERRSYNTAX; } - } else if luau_load(L, name, data, size, 0) != 0 { + } else if luau_load(L, name, data, size, env) != 0 { return LUA_ERRSYNTAX; } LUA_OK @@ -361,9 +362,9 @@ pub unsafe fn luaL_loadbuffer( L: *mut lua_State, data: *const c_char, size: usize, - name: *const c_char, + name: *const c_char ) -> c_int { - luaL_loadbufferx(L, data, size, name, ptr::null()) + luaL_loadbufferx(L, data, size, name, ptr::null(), 0) } #[inline(always)] diff --git a/src/state/raw.rs b/src/state/raw.rs index 795dc2b0..845594f3 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -319,13 +319,22 @@ impl RawLua { source.len(), name.map(|n| n.as_ptr()).unwrap_or_else(ptr::null), mode_str, + #[cfg(feature="luau")] + match &env { + Some(env) => { + self.push_ref(&env.0); + -1 + } + _ => 0 + }, ) { ffi::LUA_OK => { if let Some(env) = env { + #[cfg(not(feature="luau"))] self.push_ref(&env.0); #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] ffi::lua_setupvalue(state, -2, 1); - #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + #[cfg(any(feature = "lua51", feature = "luajit"))] ffi::lua_setfenv(state, -2); } From ecc09c4387729e05f466187b91e793193635082a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 25 Aug 2024 17:29:58 +0100 Subject: [PATCH 154/635] Add `luaL_loadbufferenv` helper to all Lua versions --- mlua-sys/src/lua51/compat.rs | 19 +++++++++++++++++++ mlua-sys/src/lua52/compat.rs | 19 +++++++++++++++++++ mlua-sys/src/lua53/compat.rs | 22 +++++++++++++++++++++- mlua-sys/src/lua54/lauxlib.rs | 19 +++++++++++++++++++ mlua-sys/src/luau/compat.rs | 19 +++++++++++++++---- src/state/raw.rs | 18 ++++-------------- 6 files changed, 97 insertions(+), 19 deletions(-) diff --git a/mlua-sys/src/lua51/compat.rs b/mlua-sys/src/lua51/compat.rs index 942bb4da..66002b67 100644 --- a/mlua-sys/src/lua51/compat.rs +++ b/mlua-sys/src/lua51/compat.rs @@ -403,6 +403,25 @@ pub unsafe fn luaL_newmetatable(L: *mut lua_State, tname: *const c_char) -> c_in } } +pub unsafe fn luaL_loadbufferenv( + L: *mut lua_State, + data: *const c_char, + size: usize, + name: *const c_char, + mode: *const c_char, + mut env: c_int, +) -> c_int { + if env != 0 { + env = lua_absindex(L, env); + } + let status = luaL_loadbufferx(L, data, size, name, mode); + if status == LUA_OK && env != 0 { + lua_pushvalue(L, env); + lua_setfenv(L, -2); + } + status +} + #[inline(always)] pub unsafe fn luaL_loadbufferx( L: *mut lua_State, diff --git a/mlua-sys/src/lua52/compat.rs b/mlua-sys/src/lua52/compat.rs index 32f321dd..d6a79ac6 100644 --- a/mlua-sys/src/lua52/compat.rs +++ b/mlua-sys/src/lua52/compat.rs @@ -247,3 +247,22 @@ pub unsafe fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lu } lua_replace(L, -2); } + +pub unsafe fn luaL_loadbufferenv( + L: *mut lua_State, + data: *const c_char, + size: usize, + name: *const c_char, + mode: *const c_char, + mut env: c_int, +) -> c_int { + if env != 0 { + env = lua_absindex(L, env); + } + let status = luaL_loadbufferx(L, data, size, name, mode); + if status == LUA_OK && env != 0 { + lua_pushvalue(L, env); + lua_setupvalue(L, -2, 1); + } + status +} diff --git a/mlua-sys/src/lua53/compat.rs b/mlua-sys/src/lua53/compat.rs index 5ea404f1..30b936ea 100644 --- a/mlua-sys/src/lua53/compat.rs +++ b/mlua-sys/src/lua53/compat.rs @@ -1,7 +1,8 @@ //! MLua compatibility layer for Lua 5.3 -use std::os::raw::c_int; +use std::os::raw::{c_char, c_int}; +use super::lauxlib::*; use super::lua::*; #[inline(always)] @@ -12,3 +13,22 @@ pub unsafe fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int, n } ret } + +pub unsafe fn luaL_loadbufferenv( + L: *mut lua_State, + data: *const c_char, + size: usize, + name: *const c_char, + mode: *const c_char, + mut env: c_int, +) -> c_int { + if env != 0 { + env = lua_absindex(L, env); + } + let status = luaL_loadbufferx(L, data, size, name, mode); + if status == LUA_OK && env != 0 { + lua_pushvalue(L, env); + lua_setupvalue(L, -2, 1); + } + status +} diff --git a/mlua-sys/src/lua54/lauxlib.rs b/mlua-sys/src/lua54/lauxlib.rs index b4873baa..78b0881e 100644 --- a/mlua-sys/src/lua54/lauxlib.rs +++ b/mlua-sys/src/lua54/lauxlib.rs @@ -169,6 +169,25 @@ pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: luaL_loadbufferx(L, s, sz, n, ptr::null()) } +pub unsafe fn luaL_loadbufferenv( + L: *mut lua_State, + data: *const c_char, + size: usize, + name: *const c_char, + mode: *const c_char, + mut env: c_int, +) -> c_int { + if env != 0 { + env = lua::lua_absindex(L, env); + } + let status = luaL_loadbufferx(L, data, size, name, mode); + if status == lua::LUA_OK && env != 0 { + lua::lua_pushvalue(L, env); + lua::lua_setupvalue(L, -2, 1); + } + status +} + // // TODO: Generic Buffer Manipulation // diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index 7ca9fbdf..c9e02e2d 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -320,13 +320,13 @@ pub unsafe fn luaL_newmetatable(L: *mut lua_State, tname: *const c_char) -> c_in } } -pub unsafe fn luaL_loadbufferx( +pub unsafe fn luaL_loadbufferenv( L: *mut lua_State, data: *const c_char, mut size: usize, name: *const c_char, mode: *const c_char, - env: c_int + env: c_int, ) -> c_int { extern "C" { fn free(p: *mut c_void); @@ -357,14 +357,25 @@ pub unsafe fn luaL_loadbufferx( LUA_OK } +#[inline(always)] +pub unsafe fn luaL_loadbufferx( + L: *mut lua_State, + data: *const c_char, + size: usize, + name: *const c_char, + mode: *const c_char, +) -> c_int { + luaL_loadbufferenv(L, data, size, name, mode, 0) +} + #[inline(always)] pub unsafe fn luaL_loadbuffer( L: *mut lua_State, data: *const c_char, size: usize, - name: *const c_char + name: *const c_char, ) -> c_int { - luaL_loadbufferx(L, data, size, name, ptr::null(), 0) + luaL_loadbufferenv(L, data, size, name, ptr::null(), 0) } #[inline(always)] diff --git a/src/state/raw.rs b/src/state/raw.rs index 845594f3..7bbedfd6 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -305,7 +305,7 @@ impl RawLua { let state = self.state(); unsafe { let _sg = StackGuard::new(state); - check_stack(state, 1)?; + check_stack(state, 2)?; let mode_str = match mode { Some(ChunkMode::Binary) => cstr!("b"), @@ -313,31 +313,21 @@ impl RawLua { None => cstr!("bt"), }; - match ffi::luaL_loadbufferx( + match ffi::luaL_loadbufferenv( state, source.as_ptr() as *const c_char, source.len(), name.map(|n| n.as_ptr()).unwrap_or_else(ptr::null), mode_str, - #[cfg(feature="luau")] - match &env { + match env { Some(env) => { self.push_ref(&env.0); -1 } - _ => 0 + _ => 0, }, ) { ffi::LUA_OK => { - if let Some(env) = env { - #[cfg(not(feature="luau"))] - self.push_ref(&env.0); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - ffi::lua_setupvalue(state, -2, 1); - #[cfg(any(feature = "lua51", feature = "luajit"))] - ffi::lua_setfenv(state, -2); - } - #[cfg(feature = "luau-jit")] if (*self.extra.get()).enable_jit && ffi::luau_codegen_supported() != 0 { ffi::luau_codegen_compile(state, -1); From 6317b8e0c851e83b247ae4eeee54503b6d9aa51b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 25 Aug 2024 21:02:12 +0100 Subject: [PATCH 155/635] Test GC for nested userdata (userdata in userdata) --- tests/userdata.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/userdata.rs b/tests/userdata.rs index 925c5bd8..7c6ae05f 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -845,3 +845,21 @@ fn test_userdata_derive() -> Result<()> { Ok(()) } + +#[test] +fn test_nested_userdata_gc() -> Result<()> { + let lua = Lua::new(); + + let counter = Arc::new(()); + let arr = vec![lua.create_any_userdata(counter.clone())?]; + let arr_ud = lua.create_any_userdata(arr)?; + + assert_eq!(Arc::strong_count(&counter), 2); + drop(arr_ud); + // On first iteration Lua will destroy the array, on second - userdata + lua.gc_collect()?; + lua.gc_collect()?; + assert_eq!(Arc::strong_count(&counter), 1); + + Ok(()) +} From 4977b91a987e28b9fad590cd6989d238e63da4a2 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 25 Aug 2024 23:04:22 +0100 Subject: [PATCH 156/635] Detect compilation error and return `Result` when using `Compiler::compile()` interface. Closes #387 --- src/chunk.rs | 24 +++++++++++++++++++----- tests/tests.rs | 2 +- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index 5e521df3..d54b385a 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -240,7 +240,9 @@ impl Compiler { } /// Compiles the `source` into bytecode. - pub fn compile(&self, source: impl AsRef<[u8]>) -> Vec { + /// + /// Returns `Error::SyntaxError` if the source code is invalid. + pub fn compile(&self, source: impl AsRef<[u8]>) -> Result> { use std::os::raw::c_int; use std::ptr; @@ -274,7 +276,7 @@ impl Compiler { vec2cstring_ptr!(mutable_globals, mutable_globals_ptr); vec2cstring_ptr!(userdata_types, userdata_types_ptr); - unsafe { + let bytecode = unsafe { let mut options = ffi::lua_CompileOptions::default(); options.optimizationLevel = self.optimization_level as c_int; options.debugLevel = self.debug_level as c_int; @@ -286,7 +288,19 @@ impl Compiler { options.mutableGlobals = mutable_globals_ptr; options.userdataTypes = userdata_types_ptr; ffi::luau_compile(source.as_ref(), options) + }; + + if bytecode.first() == Some(&0) { + // The rest of the bytecode is the error message starting with `:` + // See https://github.com/luau-lang/luau/blob/0.640/Compiler/src/Compiler.cpp#L4336 + let message = String::from_utf8_lossy(&bytecode[2..]).to_string(); + return Err(Error::SyntaxError { + incomplete_input: message.ends_with(""), + message, + }); } + + Ok(bytecode) } } @@ -443,13 +457,12 @@ impl<'a> Chunk<'a> { /// Compiles the chunk and changes mode to binary. /// - /// It does nothing if the chunk is already binary. + /// It does nothing if the chunk is already binary or invalid. fn compile(&mut self) { if let Ok(ref source) = self.source { if self.detect_mode() == ChunkMode::Text { #[cfg(feature = "luau")] - { - let data = self.compiler.get_or_insert_with(Default::default).compile(source); + if let Ok(data) = self.compiler.get_or_insert_with(Default::default).compile(source) { self.source = Ok(Cow::Owned(data)); self.mode = Some(ChunkMode::Binary); } @@ -516,6 +529,7 @@ impl<'a> Chunk<'a> { .compiler .as_ref() .map(|c| c.compile(&source)) + .transpose()? .unwrap_or(source); let name = Self::convert_name(self.name.clone())?; diff --git a/tests/tests.rs b/tests/tests.rs index 36cc35fa..4e68513d 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -147,7 +147,7 @@ fn test_load_mode() -> Result<()> { #[cfg(not(feature = "luau"))] let bytecode = lua.load("return 1 + 1").into_function()?.dump(true); #[cfg(feature = "luau")] - let bytecode = mlua::Compiler::new().compile("return 1 + 1"); + let bytecode = mlua::Compiler::new().compile("return 1 + 1")?; assert_eq!(lua.load(&bytecode).eval::()?, 2); assert_eq!(lua.load(&bytecode).set_mode(ChunkMode::Binary).eval::()?, 2); match lua.load(&bytecode).set_mode(ChunkMode::Text).exec() { From e3c5cfdf19e0e5ba30c26872887c3913d600c38f Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 26 Aug 2024 00:09:06 +0100 Subject: [PATCH 157/635] Extract `registry_key` and `vector` modules from `types` --- src/types.rs | 197 +++----------------------------------- src/types/registry_key.rs | 101 +++++++++++++++++++ src/types/vector.rs | 86 +++++++++++++++++ 3 files changed, 198 insertions(+), 186 deletions(-) create mode 100644 src/types/registry_key.rs create mode 100644 src/types/vector.rs diff --git a/src/types.rs b/src/types.rs index f4445796..8357c12f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,29 +1,26 @@ use std::cell::UnsafeCell; -use std::hash::{Hash, Hasher}; +use std::fmt; use std::os::raw::{c_int, c_void}; use std::rc::Rc; -use std::sync::Arc; -use std::{fmt, mem, ptr}; - -use parking_lot::Mutex; use crate::error::Result; #[cfg(not(feature = "luau"))] use crate::hook::Debug; use crate::state::{ExtraData, Lua, RawLua, WeakLua}; +// Re-export mutex wrappers +pub(crate) use sync::{ArcReentrantMutexGuard, ReentrantMutex, ReentrantMutexGuard, XRc, XWeak}; + #[cfg(all(feature = "async", feature = "send"))] pub(crate) type BoxFuture<'a, T> = futures_util::future::BoxFuture<'a, T>; #[cfg(all(feature = "async", not(feature = "send")))] pub(crate) type BoxFuture<'a, T> = futures_util::future::LocalBoxFuture<'a, T>; -#[cfg(all(feature = "luau", feature = "serialize"))] -use serde::ser::{Serialize, SerializeTupleStruct, Serializer}; - -// Re-export mutex wrappers pub use app_data::{AppData, AppDataRef, AppDataRefMut}; -pub(crate) use sync::{ArcReentrantMutexGuard, ReentrantMutex, ReentrantMutexGuard, XRc, XWeak}; +pub use registry_key::RegistryKey; +#[cfg(any(feature = "luau", doc))] +pub use vector::Vector; /// Type of Lua integer numbers. pub type Integer = ffi::lua_Integer; @@ -105,182 +102,8 @@ pub trait MaybeSend {} #[cfg(not(feature = "send"))] impl MaybeSend for T {} -/// A Luau vector type. -/// -/// By default vectors are 3-dimensional, but can be 4-dimensional -/// if the `luau-vector4` feature is enabled. -#[cfg(any(feature = "luau", doc))] -#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] -#[derive(Debug, Default, Clone, Copy, PartialEq)] -pub struct Vector(pub(crate) [f32; Self::SIZE]); - -#[cfg(any(feature = "luau", doc))] -impl fmt::Display for Vector { - #[rustfmt::skip] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - #[cfg(not(feature = "luau-vector4"))] - return write!(f, "vector({}, {}, {})", self.x(), self.y(), self.z()); - #[cfg(feature = "luau-vector4")] - return write!(f, "vector({}, {}, {}, {})", self.x(), self.y(), self.z(), self.w()); - } -} - -#[cfg(any(feature = "luau", doc))] -impl Vector { - pub(crate) const SIZE: usize = if cfg!(feature = "luau-vector4") { 4 } else { 3 }; - - /// Creates a new vector. - #[cfg(not(feature = "luau-vector4"))] - pub const fn new(x: f32, y: f32, z: f32) -> Self { - Self([x, y, z]) - } - - /// Creates a new vector. - #[cfg(feature = "luau-vector4")] - pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self { - Self([x, y, z, w]) - } - - /// Creates a new vector with all components set to `0.0`. - #[doc(hidden)] - pub const fn zero() -> Self { - Self([0.0; Self::SIZE]) - } - - /// Returns 1st component of the vector. - pub const fn x(&self) -> f32 { - self.0[0] - } - - /// Returns 2nd component of the vector. - pub const fn y(&self) -> f32 { - self.0[1] - } - - /// Returns 3rd component of the vector. - pub const fn z(&self) -> f32 { - self.0[2] - } - - /// Returns 4th component of the vector. - #[cfg(any(feature = "luau-vector4", doc))] - #[cfg_attr(docsrs, doc(cfg(feature = "luau-vector4")))] - pub const fn w(&self) -> f32 { - self.0[3] - } -} - -#[cfg(all(feature = "luau", feature = "serialize"))] -impl Serialize for Vector { - fn serialize(&self, serializer: S) -> std::result::Result { - let mut ts = serializer.serialize_tuple_struct("Vector", Self::SIZE)?; - ts.serialize_field(&self.x())?; - ts.serialize_field(&self.y())?; - ts.serialize_field(&self.z())?; - #[cfg(feature = "luau-vector4")] - ts.serialize_field(&self.w())?; - ts.end() - } -} - -#[cfg(any(feature = "luau", doc))] -impl PartialEq<[f32; Self::SIZE]> for Vector { - #[inline] - fn eq(&self, other: &[f32; Self::SIZE]) -> bool { - self.0 == *other - } -} - pub(crate) struct DestructedUserdata; -/// An auto generated key into the Lua registry. -/// -/// This is a handle to a value stored inside the Lua registry. It is not automatically -/// garbage collected on Drop, but it can be removed with [`Lua::remove_registry_value`], -/// and instances not manually removed can be garbage collected with -/// [`Lua::expire_registry_values`]. -/// -/// Be warned, If you place this into Lua via a [`UserData`] type or a rust callback, it is *very -/// easy* to accidentally cause reference cycles that the Lua garbage collector cannot resolve. -/// Instead of placing a [`RegistryKey`] into a [`UserData`] type, prefer instead to use -/// [`AnyUserData::set_user_value`] / [`AnyUserData::user_value`]. -/// -/// [`UserData`]: crate::UserData -/// [`RegistryKey`]: crate::RegistryKey -/// [`Lua::remove_registry_value`]: crate::Lua::remove_registry_value -/// [`Lua::expire_registry_values`]: crate::Lua::expire_registry_values -/// [`AnyUserData::set_user_value`]: crate::AnyUserData::set_user_value -/// [`AnyUserData::user_value`]: crate::AnyUserData::user_value -pub struct RegistryKey { - pub(crate) registry_id: i32, - pub(crate) unref_list: Arc>>>, -} - -impl fmt::Debug for RegistryKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "RegistryKey({})", self.id()) - } -} - -impl Hash for RegistryKey { - fn hash(&self, state: &mut H) { - self.id().hash(state) - } -} - -impl PartialEq for RegistryKey { - fn eq(&self, other: &RegistryKey) -> bool { - self.id() == other.id() && Arc::ptr_eq(&self.unref_list, &other.unref_list) - } -} - -impl Eq for RegistryKey {} - -impl Drop for RegistryKey { - fn drop(&mut self) { - let registry_id = self.id(); - // We don't need to collect nil slot - if registry_id > ffi::LUA_REFNIL { - let mut unref_list = self.unref_list.lock(); - if let Some(list) = unref_list.as_mut() { - list.push(registry_id); - } - } - } -} - -impl RegistryKey { - /// Creates a new instance of `RegistryKey` - pub(crate) const fn new(id: c_int, unref_list: Arc>>>) -> Self { - RegistryKey { - registry_id: id, - unref_list, - } - } - - /// Returns the underlying Lua reference of this `RegistryKey` - #[inline(always)] - pub fn id(&self) -> c_int { - self.registry_id - } - - /// Sets the unique Lua reference key of this `RegistryKey` - #[inline(always)] - pub(crate) fn set_id(&mut self, id: c_int) { - self.registry_id = id; - } - - /// Destroys the `RegistryKey` without adding to the unref list - pub(crate) fn take(self) -> i32 { - let registry_id = self.id(); - unsafe { - ptr::read(&self.unref_list); - mem::forget(self); - } - registry_id - } -} - pub(crate) struct ValueRef { pub(crate) lua: WeakLua, pub(crate) index: c_int, @@ -338,14 +161,16 @@ impl PartialEq for ValueRef { } mod app_data; +mod registry_key; mod sync; +#[cfg(any(feature = "luau", doc))] +mod vector; + #[cfg(test)] mod assertions { use super::*; - static_assertions::assert_impl_all!(RegistryKey: Send, Sync); - #[cfg(not(feature = "send"))] static_assertions::assert_not_impl_any!(ValueRef: Send); #[cfg(feature = "send")] diff --git a/src/types/registry_key.rs b/src/types/registry_key.rs new file mode 100644 index 00000000..b92b103b --- /dev/null +++ b/src/types/registry_key.rs @@ -0,0 +1,101 @@ +use std::hash::{Hash, Hasher}; +use std::os::raw::c_int; +use std::sync::Arc; +use std::{fmt, mem, ptr}; + +use parking_lot::Mutex; + +/// An auto generated key into the Lua registry. +/// +/// This is a handle to a value stored inside the Lua registry. It is not automatically +/// garbage collected on Drop, but it can be removed with [`Lua::remove_registry_value`], +/// and instances not manually removed can be garbage collected with +/// [`Lua::expire_registry_values`]. +/// +/// Be warned, If you place this into Lua via a [`UserData`] type or a rust callback, it is *very +/// easy* to accidentally cause reference cycles that the Lua garbage collector cannot resolve. +/// Instead of placing a [`RegistryKey`] into a [`UserData`] type, prefer instead to use +/// [`AnyUserData::set_user_value`] / [`AnyUserData::user_value`]. +/// +/// [`UserData`]: crate::UserData +/// [`RegistryKey`]: crate::RegistryKey +/// [`Lua::remove_registry_value`]: crate::Lua::remove_registry_value +/// [`Lua::expire_registry_values`]: crate::Lua::expire_registry_values +/// [`AnyUserData::set_user_value`]: crate::AnyUserData::set_user_value +/// [`AnyUserData::user_value`]: crate::AnyUserData::user_value +pub struct RegistryKey { + pub(crate) registry_id: i32, + pub(crate) unref_list: Arc>>>, +} + +impl fmt::Debug for RegistryKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "RegistryKey({})", self.id()) + } +} + +impl Hash for RegistryKey { + fn hash(&self, state: &mut H) { + self.id().hash(state) + } +} + +impl PartialEq for RegistryKey { + fn eq(&self, other: &RegistryKey) -> bool { + self.id() == other.id() && Arc::ptr_eq(&self.unref_list, &other.unref_list) + } +} + +impl Eq for RegistryKey {} + +impl Drop for RegistryKey { + fn drop(&mut self) { + let registry_id = self.id(); + // We don't need to collect nil slot + if registry_id > ffi::LUA_REFNIL { + let mut unref_list = self.unref_list.lock(); + if let Some(list) = unref_list.as_mut() { + list.push(registry_id); + } + } + } +} + +impl RegistryKey { + /// Creates a new instance of `RegistryKey` + pub(crate) const fn new(id: c_int, unref_list: Arc>>>) -> Self { + RegistryKey { + registry_id: id, + unref_list, + } + } + + /// Returns the underlying Lua reference of this `RegistryKey` + #[inline(always)] + pub fn id(&self) -> c_int { + self.registry_id + } + + /// Sets the unique Lua reference key of this `RegistryKey` + #[inline(always)] + pub(crate) fn set_id(&mut self, id: c_int) { + self.registry_id = id; + } + + /// Destroys the `RegistryKey` without adding to the unref list + pub(crate) fn take(self) -> i32 { + let registry_id = self.id(); + unsafe { + ptr::read(&self.unref_list); + mem::forget(self); + } + registry_id + } +} + +#[cfg(test)] +mod assertions { + use super::*; + + static_assertions::assert_impl_all!(RegistryKey: Send, Sync); +} diff --git a/src/types/vector.rs b/src/types/vector.rs new file mode 100644 index 00000000..d4ac5c61 --- /dev/null +++ b/src/types/vector.rs @@ -0,0 +1,86 @@ +use std::fmt; + +#[cfg(all(any(feature = "luau", doc), feature = "serialize"))] +use serde::ser::{Serialize, SerializeTupleStruct, Serializer}; + +/// A Luau vector type. +/// +/// By default vectors are 3-dimensional, but can be 4-dimensional +/// if the `luau-vector4` feature is enabled. +#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub struct Vector(pub(crate) [f32; Self::SIZE]); + +impl fmt::Display for Vector { + #[rustfmt::skip] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[cfg(not(feature = "luau-vector4"))] + return write!(f, "vector({}, {}, {})", self.x(), self.y(), self.z()); + #[cfg(feature = "luau-vector4")] + return write!(f, "vector({}, {}, {}, {})", self.x(), self.y(), self.z(), self.w()); + } +} + +impl Vector { + pub(crate) const SIZE: usize = if cfg!(feature = "luau-vector4") { 4 } else { 3 }; + + /// Creates a new vector. + #[cfg(not(feature = "luau-vector4"))] + pub const fn new(x: f32, y: f32, z: f32) -> Self { + Self([x, y, z]) + } + + /// Creates a new vector. + #[cfg(feature = "luau-vector4")] + pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self { + Self([x, y, z, w]) + } + + /// Creates a new vector with all components set to `0.0`. + #[doc(hidden)] + pub const fn zero() -> Self { + Self([0.0; Self::SIZE]) + } + + /// Returns 1st component of the vector. + pub const fn x(&self) -> f32 { + self.0[0] + } + + /// Returns 2nd component of the vector. + pub const fn y(&self) -> f32 { + self.0[1] + } + + /// Returns 3rd component of the vector. + pub const fn z(&self) -> f32 { + self.0[2] + } + + /// Returns 4th component of the vector. + #[cfg(any(feature = "luau-vector4", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau-vector4")))] + pub const fn w(&self) -> f32 { + self.0[3] + } +} + +#[cfg(all(any(feature = "luau", doc), feature = "serialize"))] +impl Serialize for Vector { + fn serialize(&self, serializer: S) -> std::result::Result { + let mut ts = serializer.serialize_tuple_struct("Vector", Self::SIZE)?; + ts.serialize_field(&self.x())?; + ts.serialize_field(&self.y())?; + ts.serialize_field(&self.z())?; + #[cfg(feature = "luau-vector4")] + ts.serialize_field(&self.w())?; + ts.end() + } +} + +impl PartialEq<[f32; Self::SIZE]> for Vector { + #[inline] + fn eq(&self, other: &[f32; Self::SIZE]) -> bool { + self.0 == *other + } +} From 9891e86d16fb1ea7cf38afb15271e254b1a6d49b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 26 Aug 2024 11:10:34 +0100 Subject: [PATCH 158/635] Remove `Clone` requirement from `UserDataFields::add_field()` --- src/state/raw.rs | 28 +++++++++++++++++----------- src/userdata.rs | 6 +++--- src/userdata/registry.rs | 28 ++++++++++++++++------------ 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index 7bbedfd6..3ef37b5e 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -805,10 +805,9 @@ impl RawLua { rawset_field(state, -2, MetaMethod::validate(&k)?)?; } let mut has_name = false; - for (k, f) in registry.meta_fields { + for (k, push_field) in registry.meta_fields { has_name = has_name || k == MetaMethod::Type; - let rawlua = mem::transmute::<&RawLua, &RawLua>(self); - mlua_assert!(f(rawlua, 0)? == 1, "field function must return one value"); + push_field(self)?; rawset_field(state, -2, MetaMethod::validate(&k)?)?; } // Set `__name/__type` if not provided @@ -832,31 +831,38 @@ impl RawLua { ffi::lua_pop(state, 1); push_table(state, 0, fields_nrec, true)?; } - for (k, f) in registry.fields { - let rawlua = mem::transmute::<&RawLua, &RawLua>(self); - mlua_assert!(f(rawlua, 0)? == 1, "field function must return one value"); + for (k, push_field) in mem::take(&mut registry.fields) { + push_field(self)?; rawset_field(state, -2, &k)?; } rawset_field(state, metatable_index, "__index")?; } _ => { ffi::lua_pop(state, 1); - // Propagate fields to the field getters - for (k, f) in registry.fields { - registry.field_getters.push((k, f)) - } + // Fields will be converted to functions and added to field getters } } } let mut field_getters_index = None; - let field_getters_nrec = registry.field_getters.len(); + let field_getters_nrec = registry.field_getters.len() + registry.fields.len(); if field_getters_nrec > 0 { push_table(state, 0, field_getters_nrec, true)?; for (k, m) in registry.field_getters { self.push(self.create_callback(m)?)?; rawset_field(state, -2, &k)?; } + for (k, push_field) in registry.fields { + unsafe extern "C-unwind" fn return_field(state: *mut ffi::lua_State) -> c_int { + ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1)); + 1 + } + push_field(self)?; + protect_lua!(state, 1, 1, fn(state) { + ffi::lua_pushcclosure(state, return_field, 1); + })?; + rawset_field(state, -2, &k)?; + } field_getters_index = Some(ffi::lua_absindex(state, -1)); extra_tables_count += 1; } diff --git a/src/userdata.rs b/src/userdata.rs index 5e8b5a52..9424e273 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -467,7 +467,7 @@ pub trait UserDataFields { /// be used as a fall-back if no regular field or method are found. fn add_field(&mut self, name: impl ToString, value: V) where - V: IntoLua + Clone + 'static; + V: IntoLua + 'static; /// Add a regular field getter as a method which accepts a `&T` as the parameter. /// @@ -528,7 +528,7 @@ pub trait UserDataFields { /// like `__gc` or `__metatable`. fn add_meta_field(&mut self, name: impl ToString, value: V) where - V: IntoLua + Clone + 'static; + V: IntoLua + 'static; /// Add a metatable field computed from `f`. /// @@ -540,7 +540,7 @@ pub trait UserDataFields { /// like `__gc` or `__metatable`. fn add_meta_field_with(&mut self, name: impl ToString, f: F) where - F: Fn(&Lua) -> Result + MaybeSend + 'static, + F: FnOnce(&Lua) -> Result + 'static, R: IntoLua; } diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index e80b1ae1..79eb080b 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -7,7 +7,7 @@ use std::os::raw::c_int; use std::string::String as StdString; use crate::error::{Error, Result}; -use crate::state::Lua; +use crate::state::{Lua, RawLua}; use crate::types::{Callback, MaybeSend}; use crate::userdata::{ AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, UserDataRefMut, @@ -23,13 +23,15 @@ use { std::future::{self, Future}, }; +type StaticFieldCallback = Box Result<()> + 'static>; + /// Handle to registry for userdata methods and metamethods. pub struct UserDataRegistry { // Fields - pub(crate) fields: Vec<(String, Callback)>, + pub(crate) fields: Vec<(String, StaticFieldCallback)>, pub(crate) field_getters: Vec<(String, Callback)>, pub(crate) field_setters: Vec<(String, Callback)>, - pub(crate) meta_fields: Vec<(String, Callback)>, + pub(crate) meta_fields: Vec<(String, StaticFieldCallback)>, // Methods pub(crate) methods: Vec<(String, Callback)>, @@ -283,11 +285,13 @@ fn get_function_name(name: &str) -> StdString { impl UserDataFields for UserDataRegistry { fn add_field(&mut self, name: impl ToString, value: V) where - V: IntoLua + Clone + 'static, + V: IntoLua + 'static, { let name = name.to_string(); - let callback: Callback = Box::new(move |lua, _| unsafe { value.clone().push_into_stack_multi(lua) }); - self.fields.push((name, callback)); + self.fields.push(( + name, + Box::new(move |rawlua| unsafe { value.push_into_stack(rawlua) }), + )); } fn add_field_method_get(&mut self, name: impl ToString, method: M) @@ -332,28 +336,28 @@ impl UserDataFields for UserDataRegistry { fn add_meta_field(&mut self, name: impl ToString, value: V) where - V: IntoLua + Clone + 'static, + V: IntoLua + 'static, { let name = name.to_string(); self.meta_fields.push(( name.clone(), - Box::new(move |lua, _| unsafe { - Self::check_meta_field(lua.lua(), &name, value.clone())?.push_into_stack_multi(lua) + Box::new(move |rawlua| unsafe { + Self::check_meta_field(rawlua.lua(), &name, value)?.push_into_stack(rawlua) }), )); } fn add_meta_field_with(&mut self, name: impl ToString, f: F) where - F: Fn(&Lua) -> Result + MaybeSend + 'static, + F: FnOnce(&Lua) -> Result + 'static, R: IntoLua, { let name = name.to_string(); self.meta_fields.push(( name.clone(), - Box::new(move |rawlua, _| unsafe { + Box::new(move |rawlua| unsafe { let lua = rawlua.lua(); - Self::check_meta_field(lua, &name, f(lua)?)?.push_into_stack_multi(rawlua) + Self::check_meta_field(lua, &name, f(lua)?)?.push_into_stack(rawlua) }), )); } From 74bebe6da37c6315b509aa212fdf03fabcb70058 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 26 Aug 2024 11:13:06 +0100 Subject: [PATCH 159/635] Add optional `Send` requirement to internall callbacks --- src/types.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/types.rs b/src/types.rs index 8357c12f..39ff12a6 100644 --- a/src/types.rs +++ b/src/types.rs @@ -46,6 +46,10 @@ unsafe impl Send for LightUserData {} #[cfg(feature = "send")] unsafe impl Sync for LightUserData {} +#[cfg(feature = "send")] +pub(crate) type Callback = Box Result + Send + 'static>; + +#[cfg(not(feature = "send"))] pub(crate) type Callback = Box Result + 'static>; pub(crate) struct Upvalue { @@ -55,7 +59,11 @@ pub(crate) struct Upvalue { pub(crate) type CallbackUpvalue = Upvalue; -#[cfg(feature = "async")] +#[cfg(all(feature = "async", feature = "send"))] +pub(crate) type AsyncCallback = + Box Fn(&'a RawLua, c_int) -> BoxFuture<'a, Result> + Send + 'static>; + +#[cfg(all(feature = "async", not(feature = "send")))] pub(crate) type AsyncCallback = Box Fn(&'a RawLua, c_int) -> BoxFuture<'a, Result> + 'static>; From 21149106ee71c14b92264ed942de79058fdad9f1 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 26 Aug 2024 11:18:17 +0100 Subject: [PATCH 160/635] Remove `drop` field from `ValueRef` --- src/state/raw.rs | 2 +- src/types.rs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index 3ef37b5e..8b42c03b 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -489,7 +489,7 @@ impl RawLua { #[cfg(feature = "luau")] ffi::lua_resetthread(thread_state); extra.thread_pool.push(thread.0.index); - thread.0.drop = false; + thread.0.index = 0; // Prevent reference from being dropped return true; } false diff --git a/src/types.rs b/src/types.rs index 39ff12a6..14f30aa1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -115,7 +115,6 @@ pub(crate) struct DestructedUserdata; pub(crate) struct ValueRef { pub(crate) lua: WeakLua, pub(crate) index: c_int, - pub(crate) drop: bool, } impl ValueRef { @@ -124,7 +123,6 @@ impl ValueRef { ValueRef { lua: lua.weak().clone(), index, - drop: true, } } @@ -149,7 +147,7 @@ impl Clone for ValueRef { impl Drop for ValueRef { fn drop(&mut self) { - if self.drop { + if self.index > 0 { if let Some(lua) = self.lua.try_lock() { unsafe { lua.drop_ref(self) }; } From 66b4a865c2d088ee997135c8e0f151e31dffb3c7 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 26 Aug 2024 11:26:12 +0100 Subject: [PATCH 161/635] Remove `MultiValue` pool --- src/multi.rs | 4 ++-- src/value.rs | 44 ++++---------------------------------------- 2 files changed, 6 insertions(+), 42 deletions(-) diff --git a/src/multi.rs b/src/multi.rs index 8488f61d..c631ccdc 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -32,7 +32,7 @@ impl IntoLuaMulti for StdResult<(), E> { #[inline] fn into_lua_multi(self, lua: &Lua) -> Result { match self { - Ok(_) => Ok(MultiValue::new()), + Ok(_) => const { Ok(MultiValue::new()) }, Err(err) => (Nil, err).into_lua_multi(lua), } } @@ -197,7 +197,7 @@ macro_rules! impl_tuple { impl IntoLuaMulti for () { #[inline] fn into_lua_multi(self, _: &Lua) -> Result { - Ok(MultiValue::new()) + const { Ok(MultiValue::new()) } } #[inline] diff --git a/src/value.rs b/src/value.rs index 5505d728..c88a17b4 100644 --- a/src/value.rs +++ b/src/value.rs @@ -745,35 +745,10 @@ pub trait FromLua: Sized { } } -// Per-thread size of the VecDeque pool for MultiValue container. -const MULTIVALUE_POOL_SIZE: usize = 32; - -thread_local! { - static MULTIVALUE_POOL: RefCell>> = const { RefCell::new(Vec::new()) }; -} - /// Multiple Lua values used for both argument passing and also for multiple return values. -#[derive(Debug, Clone)] +#[derive(Default, Debug, Clone)] pub struct MultiValue(VecDeque); -impl Drop for MultiValue { - fn drop(&mut self) { - MULTIVALUE_POOL.with_borrow_mut(|pool| { - if pool.len() < MULTIVALUE_POOL_SIZE { - self.0.clear(); - pool.push(mem::take(&mut self.0)); - } - }); - } -} - -impl Default for MultiValue { - #[inline] - fn default() -> MultiValue { - MultiValue::new() - } -} - impl Deref for MultiValue { type Target = VecDeque; @@ -793,24 +768,13 @@ impl DerefMut for MultiValue { impl MultiValue { /// Creates an empty `MultiValue` containing no values. #[inline] - pub fn new() -> MultiValue { - Self::with_capacity(0) + pub const fn new() -> MultiValue { + MultiValue(VecDeque::new()) } /// Creates an empty `MultiValue` container with space for at least `capacity` elements. pub fn with_capacity(capacity: usize) -> MultiValue { - let deque = MULTIVALUE_POOL.with_borrow_mut(|pool| { - pool.pop().map_or_else( - || VecDeque::with_capacity(capacity), - |mut deque| { - if capacity > 0 { - deque.reserve(capacity); - } - deque - }, - ) - }); - MultiValue(deque) + MultiValue(VecDeque::with_capacity(capacity)) } #[inline] From ece66c46bfdc62685b758ffff16286f2806b2662 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 26 Aug 2024 23:51:58 +0100 Subject: [PATCH 162/635] Remove `unstable` feature flag --- .github/workflows/main.yml | 23 ++++++++++++----------- Cargo.toml | 3 +-- README.md | 1 - src/state.rs | 6 +++--- tarpaulin.toml | 6 +++--- tests/luau.rs | 2 +- 6 files changed, 20 insertions(+), 21 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 06f5354f..ae47add6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,8 @@ jobs: - name: Build ${{ matrix.lua }} vendored run: | cargo build --features "${{ matrix.lua }},vendored" - cargo build --features "${{ matrix.lua }},vendored,async,send,serialize,macros,unstable" + cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros" + cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,send" shell: bash - name: Build ${{ matrix.lua }} pkg-config if: ${{ matrix.os == 'ubuntu-22.04' }} @@ -50,7 +51,7 @@ jobs: toolchain: stable target: aarch64-apple-darwin - name: Cross-compile - run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros,unstable" + run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros" build_aarch64_cross_ubuntu: name: Cross-compile to aarch64-unknown-linux-gnu @@ -71,7 +72,7 @@ jobs: sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross shell: bash - name: Cross-compile - run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros,unstable" + run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros" shell: bash build_armv7_cross_ubuntu: @@ -93,7 +94,7 @@ jobs: sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross shell: bash - name: Cross-compile - run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros,unstable" + run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros" shell: bash test: @@ -122,14 +123,14 @@ jobs: - name: Run ${{ matrix.lua }} tests run: | cargo test --features "${{ matrix.lua }},vendored" - cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros" - cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,unstable" + cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros" + cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,send" shell: bash - name: Run compile tests (macos lua54) if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }} run: | TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" -- --ignored - TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,unstable" -- --ignored + TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros" -- --ignored shell: bash test_with_sanitizer: @@ -153,7 +154,8 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Run ${{ matrix.lua }} tests with address sanitizer run: | - cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,unstable" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions + cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions + cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,send" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions shell: bash env: RUSTFLAGS: -Z sanitizer=address @@ -226,8 +228,7 @@ jobs: - name: Run ${{ matrix.lua }} tests run: | cargo test --tests --features "${{ matrix.lua }},vendored" - cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros" - cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,unstable" + cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros" rustfmt: name: Rustfmt @@ -254,4 +255,4 @@ jobs: - uses: giraffate/clippy-action@v1 with: reporter: 'github-pr-review' - clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,unstable" + clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros" diff --git a/Cargo.toml b/Cargo.toml index 3936a4b7..79290402 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ with async/await features and support of writing native Lua modules in Rust. """ [package.metadata.docs.rs] -features = ["lua54", "vendored", "async", "send", "serialize", "macros", "unstable"] +features = ["lua54", "vendored", "async", "send", "serialize", "macros"] rustdoc-args = ["--cfg", "docsrs"] [workspace] @@ -41,7 +41,6 @@ async = ["dep:futures-util"] send = ["parking_lot/send_guard"] serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value"] macros = ["mlua_derive/macros"] -unstable = [] [dependencies] mlua_derive = { version = "=0.9.3", optional = true, path = "mlua_derive" } diff --git a/README.md b/README.md index da803a0c..3b05b1c7 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,6 @@ Below is a list of the available feature flags. By default `mlua` does not enabl * `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework * `macros`: enable procedural macros (such as `chunk!`) * `parking_lot`: support UserData types wrapped in [parking_lot]'s primitives (`Arc` and `Arc`) -* `unstable`: enable **unstable** features. The public API of these features may break between releases. [5.4]: https://www.lua.org/manual/5.4/manual.html [5.3]: https://www.lua.org/manual/5.3/manual.html diff --git a/src/state.rs b/src/state.rs index 9c91f825..10401342 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1292,12 +1292,12 @@ impl Lua { } /// Sets the metatable for a Luau builtin vector type. - #[cfg(any(all(feature = "luau", feature = "unstable"), doc))] - #[cfg_attr(docsrs, doc(cfg(all(feature = "luau", feature = "unstable"))))] + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn set_vector_metatable(&self, metatable: Option
) { let lua = self.lock(); + let state = lua.state(); unsafe { - let state = lua.state(); let _sg = StackGuard::new(state); assert_stack(state, 2); diff --git a/tarpaulin.toml b/tarpaulin.toml index 8df2c2c6..f5f71b4f 100644 --- a/tarpaulin.toml +++ b/tarpaulin.toml @@ -1,8 +1,8 @@ [lua54_coverage] -features = "lua54,vendored,async,serialize,macros,unstable" +features = "lua54,vendored,async,serialize,macros" [lua51_coverage] -features = "lua51,vendored,async,serialize,macros,unstable" +features = "lua51,vendored,async,serialize,macros" [luau_coverage] -features = "luau,async,serialize,macros,unstable" +features = "luau,async,serialize,macros" diff --git a/tests/luau.rs b/tests/luau.rs index 37218508..afcdcbdf 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -173,7 +173,7 @@ fn test_vectors() -> Result<()> { Ok(()) } -#[cfg(all(not(feature = "luau-vector4"), feature = "unstable"))] +#[cfg(not(feature = "luau-vector4"))] #[test] fn test_vector_metatable() -> Result<()> { let lua = Lua::new(); From 3774296835d887115e7084aae51666a22fd6e294 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 29 Aug 2024 11:59:52 +0100 Subject: [PATCH 163/635] Run gargabe collection on main Lua instance drop This should help preventing leaking memory when capturing Lua in async block and dropping future without finishing polling. --- src/state.rs | 36 +++++++++++++++++++++++++++--------- src/state/extra.rs | 27 ++++++++++++++------------- src/state/raw.rs | 4 ++-- tests/async.rs | 27 ++++++++++++++------------- 4 files changed, 57 insertions(+), 37 deletions(-) diff --git a/src/state.rs b/src/state.rs index 10401342..3d3c0961 100644 --- a/src/state.rs +++ b/src/state.rs @@ -43,11 +43,13 @@ use util::{callback_error_ext, StateGuard}; /// Top level Lua struct which represents an instance of Lua VM. #[derive(Clone)] -#[repr(transparent)] -pub struct Lua(XRc>); +pub struct Lua { + pub(self) raw: XRc>, + // Controls whether garbage collection should be run on drop + pub(self) collect_garbage: bool, +} #[derive(Clone)] -#[repr(transparent)] pub(crate) struct WeakLua(XWeak>); pub(crate) struct LuaGuard(ArcReentrantMutexGuard); @@ -137,6 +139,14 @@ impl LuaOptions { } } +impl Drop for Lua { + fn drop(&mut self) { + if self.collect_garbage { + let _ = self.gc_collect(); + } + } +} + impl fmt::Debug for Lua { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Lua({:p})", self.lock().state()) @@ -242,7 +252,10 @@ impl Lua { /// Creates a new Lua state with required `libs` and `options` unsafe fn inner_new(libs: StdLib, options: LuaOptions) -> Lua { - let lua = Lua(RawLua::new(libs, options)); + let lua = Lua { + raw: RawLua::new(libs, options), + collect_garbage: true, + }; #[cfg(feature = "luau")] mlua_expect!(lua.configure_luau(), "Error configuring Luau"); @@ -257,7 +270,10 @@ impl Lua { #[allow(clippy::missing_safety_doc)] #[inline] pub unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> Lua { - Lua(RawLua::init_from_ptr(state)) + Lua { + raw: RawLua::init_from_ptr(state), + collect_garbage: true, + } } /// FIXME: Deprecated load_from_std_lib @@ -1157,6 +1173,8 @@ impl Lua { FR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { + // In future we should switch to async closures when they are stable to capture `&Lua` + // See https://rust-lang.github.io/rfcs/3668-async-closures.html (self.lock()).create_async_callback(Box::new(move |rawlua, nargs| unsafe { let args = match A::from_stack_args(nargs, 1, None, rawlua) { Ok(args) => args, @@ -1819,17 +1837,17 @@ impl Lua { #[inline(always)] pub(crate) fn lock(&self) -> ReentrantMutexGuard { - self.0.lock() + self.raw.lock() } #[inline(always)] pub(crate) fn lock_arc(&self) -> LuaGuard { - LuaGuard(self.0.lock_arc()) + LuaGuard(self.raw.lock_arc()) } #[inline(always)] pub(crate) fn weak(&self) -> WeakLua { - WeakLua(XRc::downgrade(&self.0)) + WeakLua(XRc::downgrade(&self.raw)) } /// Returns a handle to the unprotected Lua state without any synchronization. @@ -1837,7 +1855,7 @@ impl Lua { /// This is useful where we know that the lock is already held by the caller. #[inline(always)] pub(crate) unsafe fn raw_lua(&self) -> &RawLua { - &*self.0.data_ptr() + &*self.raw.data_ptr() } } diff --git a/src/state/extra.rs b/src/state/extra.rs index e8bbc646..3c04f077 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -1,6 +1,6 @@ use std::any::TypeId; use std::cell::UnsafeCell; -use std::mem::{self, MaybeUninit}; +use std::mem::MaybeUninit; use std::os::raw::{c_int, c_void}; use std::ptr; use std::rc::Rc; @@ -12,7 +12,7 @@ use rustc_hash::FxHashMap; use crate::error::Result; use crate::state::RawLua; use crate::stdlib::StdLib; -use crate::types::{AppData, ReentrantMutex, XRc, XWeak}; +use crate::types::{AppData, ReentrantMutex, XRc}; use crate::util::{get_internal_metatable, push_internal_userdata, TypeKey, WrappedFailure}; #[cfg(any(feature = "luau", doc))] @@ -31,10 +31,8 @@ const REF_STACK_RESERVE: c_int = 1; /// Data associated with the Lua state. pub(crate) struct ExtraData { - // Same layout as `Lua` - pub(super) lua: MaybeUninit>>, - // Same layout as `WeakLua` - pub(super) weak: MaybeUninit>>, + pub(super) lua: MaybeUninit, + pub(super) weak: MaybeUninit, pub(super) registered_userdata: FxHashMap, pub(super) registered_userdata_mt: FxHashMap<*const c_void, Option>, @@ -185,12 +183,15 @@ impl ExtraData { extra } - pub(super) unsafe fn set_lua(&mut self, lua: &XRc>) { - self.lua.write(XRc::clone(lua)); + pub(super) unsafe fn set_lua(&mut self, raw: &XRc>) { + self.lua.write(Lua { + raw: XRc::clone(raw), + collect_garbage: false, + }); if cfg!(not(feature = "module")) { - XRc::decrement_strong_count(XRc::as_ptr(lua)); + XRc::decrement_strong_count(XRc::as_ptr(raw)); } - self.weak.write(XRc::downgrade(lua)); + self.weak.write(WeakLua(XRc::downgrade(raw))); } pub(super) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { @@ -228,16 +229,16 @@ impl ExtraData { #[inline(always)] pub(super) unsafe fn lua(&self) -> &Lua { - mem::transmute(self.lua.assume_init_ref()) + self.lua.assume_init_ref() } #[inline(always)] pub(super) unsafe fn raw_lua(&self) -> &RawLua { - &*self.lua.assume_init_ref().data_ptr() + &*self.lua.assume_init_ref().raw.data_ptr() } #[inline(always)] pub(super) unsafe fn weak(&self) -> &WeakLua { - mem::transmute(self.weak.assume_init_ref()) + self.weak.assume_init_ref() } } diff --git a/src/state/raw.rs b/src/state/raw.rs index 8b42c03b..d489576f 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -219,10 +219,10 @@ impl RawLua { rawlua } - pub(super) unsafe fn try_from_ptr(state: *mut ffi::lua_State) -> Option>> { + unsafe fn try_from_ptr(state: *mut ffi::lua_State) -> Option>> { match ExtraData::get(state) { extra if extra.is_null() => None, - extra => Some(XRc::clone(&(*extra).lua().0)), + extra => Some(XRc::clone(&(*extra).lua().raw)), } } diff --git a/tests/async.rs b/tests/async.rs index 59e178da..887965d3 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -498,21 +498,22 @@ async fn test_async_thread_error() -> Result<()> { #[tokio::test] async fn test_async_terminate() -> Result<()> { - let lua = Lua::new(); - let mutex = Arc::new(Mutex::new(0u32)); - let mutex2 = mutex.clone(); - let func = lua.create_async_function(move |_, ()| { - let mutex = mutex2.clone(); - async move { - let _guard = mutex.lock().await; - sleep_ms(100).await; - Ok(()) - } - })?; + { + let lua = Lua::new(); + let mutex2 = mutex.clone(); + let func = lua.create_async_function(move |lua, ()| { + let mutex = mutex2.clone(); + async move { + let _guard = mutex.lock().await; + sleep_ms(100).await; + drop(lua); // Move Lua to the future to test drop + Ok(()) + } + })?; - let _ = tokio::time::timeout(Duration::from_millis(30), func.call_async::<()>(())).await; - lua.gc_collect()?; + let _ = tokio::time::timeout(Duration::from_millis(30), func.call_async::<()>(())).await; + } assert!(mutex.try_lock().is_ok()); Ok(()) From 5ebbc0868c93cdbb745ebb57a685f3551225ca45 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 29 Aug 2024 22:05:28 +0100 Subject: [PATCH 164/635] More inline const expressions --- src/chunk.rs | 9 ++++----- src/multi.rs | 2 +- src/serde/de.rs | 2 +- src/serde/ser.rs | 2 +- src/state.rs | 4 ++-- src/state/raw.rs | 4 ++-- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index d54b385a..0dc781d7 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -134,7 +134,7 @@ pub struct Compiler { #[cfg(any(feature = "luau", doc))] impl Default for Compiler { fn default() -> Self { - Self::new() + const { Self::new() } } } @@ -323,8 +323,8 @@ impl<'a> Chunk<'a> { /// necessary to populate the environment in order for scripts using custom environments to be /// useful. pub fn set_environment(mut self, env: impl IntoLua) -> Self { - let lua = self.lua.lock(); - let lua = lua.lua(); + let guard = self.lua.lock(); + let lua = guard.lua(); self.env = env .into_lua(lua) .and_then(|val| lua.unpack(val)) @@ -357,8 +357,7 @@ impl<'a> Chunk<'a> { /// /// This is equivalent to calling the chunk function with no arguments and no return values. pub fn exec(self) -> Result<()> { - self.call::<()>(())?; - Ok(()) + self.call(()) } /// Asynchronously execute this chunk of code. diff --git a/src/multi.rs b/src/multi.rs index c631ccdc..29d2f383 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -141,7 +141,7 @@ impl Variadic { impl Default for Variadic { fn default() -> Variadic { - Variadic::new() + const { Variadic::new() } } } diff --git a/src/serde/de.rs b/src/serde/de.rs index 0d72d83b..c54241ae 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -51,7 +51,7 @@ pub struct Options { impl Default for Options { fn default() -> Self { - Self::new() + const { Self::new() } } } diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 0267db78..3f207431 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -52,7 +52,7 @@ pub struct Options { impl Default for Options { fn default() -> Self { - Self::new() + const { Self::new() } } } diff --git a/src/state.rs b/src/state.rs index 3d3c0961..c8ba1f5f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -104,7 +104,7 @@ pub struct LuaOptions { impl Default for LuaOptions { fn default() -> Self { - LuaOptions::new() + const { LuaOptions::new() } } } @@ -1251,7 +1251,7 @@ impl Lua { /// /// This methods provides a way to add fields or methods to userdata objects of a type `T`. pub fn register_userdata_type(&self, f: impl FnOnce(&mut UserDataRegistry)) -> Result<()> { - let mut registry = UserDataRegistry::new(); + let mut registry = const { UserDataRegistry::new() }; f(&mut registry); let lua = self.lock(); diff --git a/src/state/raw.rs b/src/state/raw.rs index d489576f..c10ccc8e 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -723,7 +723,7 @@ impl RawLua { } // Create a new metatable from `UserData` definition - let mut registry = UserDataRegistry::new(); + let mut registry = const { UserDataRegistry::new() }; T::register(&mut registry); self.register_userdata_metatable(registry) @@ -742,7 +742,7 @@ impl RawLua { } // Create an empty metatable - let registry = UserDataRegistry::new(); + let registry = const { UserDataRegistry::new() }; self.register_userdata_metatable::(registry) }) } From 4018a17e262d3ae7087c285eba6057dc9f74db63 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 30 Aug 2024 22:50:03 +0100 Subject: [PATCH 165/635] Combine `TableExt` and `AnyUserDataExt` traits into `ObjectLike` --- src/lib.rs | 8 +- src/prelude.rs | 20 ++--- src/state/raw.rs | 2 +- src/table.rs | 152 ++++++++++++++----------------------- src/traits.rs | 78 +++++++++++++++++++ src/types.rs | 14 +++- src/userdata.rs | 3 +- src/userdata/ext.rs | 168 ----------------------------------------- src/userdata/object.rs | 93 +++++++++++++++++++++++ tests/async.rs | 26 ++++--- tests/table.rs | 18 ++++- tests/userdata.rs | 15 +++- 12 files changed, 300 insertions(+), 297 deletions(-) create mode 100644 src/traits.rs delete mode 100644 src/userdata/ext.rs create mode 100644 src/userdata/object.rs diff --git a/src/lib.rs b/src/lib.rs index 5f5985b9..b6a256b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,7 @@ mod stdlib; mod string; mod table; mod thread; +mod traits; mod types; mod userdata; mod util; @@ -112,12 +113,13 @@ pub use crate::state::{GCMode, Lua, LuaOptions}; // pub use crate::scope::Scope; pub use crate::stdlib::StdLib; pub use crate::string::{BorrowedBytes, BorrowedStr, String}; -pub use crate::table::{Table, TableExt, TablePairs, TableSequence}; +pub use crate::table::{Table, TablePairs, TableSequence}; pub use crate::thread::{Thread, ThreadStatus}; +pub use crate::traits::ObjectLike; pub use crate::types::{AppDataRef, AppDataRefMut, Integer, LightUserData, MaybeSend, Number, RegistryKey}; pub use crate::userdata::{ - AnyUserData, AnyUserDataExt, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, - UserDataRef, UserDataRefMut, UserDataRegistry, + AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef, + UserDataRefMut, UserDataRegistry, }; pub use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value}; diff --git a/src/prelude.rs b/src/prelude.rs index b6168657..faf8f7c1 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,17 +2,17 @@ #[doc(no_inline)] pub use crate::{ - AnyUserData as LuaAnyUserData, AnyUserDataExt as LuaAnyUserDataExt, Chunk as LuaChunk, Error as LuaError, - ErrorContext as LuaErrorContext, ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, - FromLua, FromLuaMulti, Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, - Integer as LuaInteger, IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaOptions, - MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, + AnyUserData as LuaAnyUserData, Chunk as LuaChunk, Error as LuaError, ErrorContext as LuaErrorContext, + ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti, + Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, Integer as LuaInteger, + IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaOptions, MetaMethod as LuaMetaMethod, + MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, String as LuaString, - Table as LuaTable, TableExt as LuaTableExt, TablePairs as LuaTablePairs, - TableSequence as LuaTableSequence, Thread as LuaThread, ThreadStatus as LuaThreadStatus, - UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, - UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, - UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, + Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, Thread as LuaThread, + ThreadStatus as LuaThreadStatus, UserData as LuaUserData, UserDataFields as LuaUserDataFields, + UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, + UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut, + UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, }; #[cfg(not(feature = "luau"))] diff --git a/src/state/raw.rs b/src/state/raw.rs index c10ccc8e..ab056428 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -489,7 +489,7 @@ impl RawLua { #[cfg(feature = "luau")] ffi::lua_resetthread(thread_state); extra.thread_pool.push(thread.0.index); - thread.0.index = 0; // Prevent reference from being dropped + thread.0.drop = false; // Prevent thread from being garbage collected return true; } false diff --git a/src/table.rs b/src/table.rs index 80c62283..2e0d999e 100644 --- a/src/table.rs +++ b/src/table.rs @@ -2,6 +2,7 @@ use std::collections::HashSet; use std::fmt; use std::marker::PhantomData; use std::os::raw::c_void; +use std::string::String as StdString; #[cfg(feature = "serialize")] use { @@ -12,14 +13,14 @@ use { use crate::error::{Error, Result}; use crate::function::Function; -use crate::private::Sealed; use crate::state::{LuaGuard, RawLua}; +use crate::traits::ObjectLike; use crate::types::{Integer, ValueRef}; use crate::util::{assert_stack, check_stack, StackGuard}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Nil, Value}; #[cfg(feature = "async")] -use std::future::Future; +use futures_util::future::{self, Either, Future}; /// Handle to an internal Lua table. #[derive(Clone)] @@ -60,11 +61,15 @@ impl Table { /// /// [`raw_set`]: #method.raw_set pub fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { - // Fast track + // Fast track (skip protected call) if !self.has_metatable() { return self.raw_set(key, value); } + self.set_protected(key, value) + } + + pub(crate) fn set_protected(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { @@ -103,11 +108,15 @@ impl Table { /// /// [`raw_get`]: #method.raw_get pub fn get(&self, key: impl IntoLua) -> Result { - // Fast track + // Fast track (skip protected call) if !self.has_metatable() { return self.raw_get(key); } + self.get_protected(key) + } + + pub(crate) fn get_protected(&self, key: impl IntoLua) -> Result { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { @@ -133,7 +142,7 @@ impl Table { /// /// This might invoke the `__len` and `__newindex` metamethods. pub fn push(&self, value: impl IntoLua) -> Result<()> { - // Fast track + // Fast track (skip protected call) if !self.has_metatable() { return self.raw_push(value); } @@ -158,7 +167,7 @@ impl Table { /// /// This might invoke the `__len` and `__newindex` metamethods. pub fn pop(&self) -> Result { - // Fast track + // Fast track (skip protected call) if !self.has_metatable() { return self.raw_pop(); } @@ -433,7 +442,7 @@ impl Table { /// /// [`raw_len`]: #method.raw_len pub fn len(&self) -> Result { - // Fast track + // Fast track (skip protected call) if !self.has_metatable() { return Ok(self.raw_len() as Integer); } @@ -858,107 +867,41 @@ where } } -/// An extension trait for `Table`s that provides a variety of convenient functionality. -pub trait TableExt: Sealed { - /// Calls the table as function assuming it has `__call` metamethod. - /// - /// The metamethod is called with the table as its first argument, followed by the passed - /// arguments. - fn call(&self, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti; - - /// Asynchronously calls the table as function assuming it has `__call` metamethod. - /// - /// The metamethod is called with the table as its first argument, followed by the passed - /// arguments. - #[cfg(feature = "async")] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> - where - R: FromLuaMulti; - - /// Gets the function associated to `key` from the table and executes it, - /// passing the table itself along with `args` as function arguments. - /// - /// This is a shortcut for - /// `table.get::(key)?.call((table.clone(), arg1, ..., argN))` - /// - /// This might invoke the `__index` metamethod. - fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti; - - /// Gets the function associated to `key` from the table and executes it, - /// passing `args` as function arguments. - /// - /// This is a shortcut for - /// `table.get::(key)?.call(args)` - /// - /// This might invoke the `__index` metamethod. - fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti; - - /// Gets the function associated to `key` from the table and asynchronously executes it, - /// passing the table itself along with `args` as function arguments and returning Future. - /// - /// Requires `feature = "async"` - /// - /// This might invoke the `__index` metamethod. - #[cfg(feature = "async")] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> - where - R: FromLuaMulti; +impl ObjectLike for Table { + #[inline] + fn get(&self, key: impl IntoLua) -> Result { + self.get(key) + } - /// Gets the function associated to `key` from the table and asynchronously executes it, - /// passing `args` as function arguments and returning Future. - /// - /// Requires `feature = "async"` - /// - /// This might invoke the `__index` metamethod. - #[cfg(feature = "async")] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> - where - R: FromLuaMulti; -} + #[inline] + fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { + self.set(key, value) + } -impl TableExt for Table { + #[inline] fn call(&self, args: impl IntoLuaMulti) -> Result where R: FromLuaMulti, { // Convert table to a function and call via pcall that respects the `__call` metamethod. - Function(self.0.clone()).call(args) + Function(self.0.copy()).call(args) } #[cfg(feature = "async")] + #[inline] fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> where R: FromLuaMulti, { - let lua = self.0.lua.lock(); - let args = args.into_lua_multi(lua.lua()); - async move { - let func = Function(self.0.clone()); - func.call_async(args?).await - } + Function(self.0.copy()).call_async(args) } + #[inline] fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result where R: FromLuaMulti, { - self.get::(name)?.call((self, args)) - } - - fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti, - { - self.get::(name)?.call(args) + self.call_function(name, (self, args)) } #[cfg(feature = "async")] @@ -969,18 +912,37 @@ impl TableExt for Table { self.call_async_function(name, (self, args)) } + #[inline] + fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result { + match self.get(name)? { + Value::Function(func) => func.call(args), + val => { + let msg = format!("attempt to call a {} value (function '{name}')", val.type_name()); + Err(Error::runtime(msg)) + } + } + } + #[cfg(feature = "async")] + #[inline] fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> where R: FromLuaMulti, { - let lua = self.0.lua.lock(); - let args = args.into_lua_multi(lua.lua()); - async move { - let func = self.get::(name)?; - func.call_async(args?).await + match self.get(name) { + Ok(Value::Function(func)) => Either::Left(func.call_async(args)), + Ok(val) => { + let msg = format!("attempt to call a {} value (function '{name}')", val.type_name()); + Either::Right(future::ready(Err(Error::RuntimeError(msg)))) + } + Err(err) => Either::Right(future::ready(Err(err))), } } + + #[inline] + fn to_string(&self) -> Result { + Value::Table(self.clone()).to_string() + } } /// A wrapped [`Table`] with customized serialization behavior. @@ -1050,7 +1012,7 @@ impl<'a> Serialize for SerializableTable<'a> { seq.serialize_element(&SerializableValue::new(&value, options, Some(visited))) .map_err(|err| { serialize_err = Some(err); - Error::SerializeError(String::new()) + Error::SerializeError(StdString::new()) }) }); convert_result(res, serialize_err)?; @@ -1075,7 +1037,7 @@ impl<'a> Serialize for SerializableTable<'a> { ) .map_err(|err| { serialize_err = Some(err); - Error::SerializeError(String::new()) + Error::SerializeError(StdString::new()) }) }; diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 00000000..eddbb5f7 --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,78 @@ +use std::string::String as StdString; + +use crate::error::Result; +use crate::private::Sealed; +use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; + +#[cfg(feature = "async")] +use std::future::Future; + +/// A trait for types that can be used as Lua objects (usually table and userdata). +pub trait ObjectLike: Sealed { + /// Gets the value associated to `key` from the object, assuming it has `__index` metamethod. + fn get(&self, key: impl IntoLua) -> Result; + + /// Sets the value associated to `key` in the object, assuming it has `__newindex` metamethod. + fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()>; + + /// Calls the object as a function assuming it has `__call` metamethod. + /// + /// The metamethod is called with the object as its first argument, followed by the passed + /// arguments. + fn call(&self, args: impl IntoLuaMulti) -> Result + where + R: FromLuaMulti; + + /// Asynchronously calls the object as a function assuming it has `__call` metamethod. + /// + /// The metamethod is called with the object as its first argument, followed by the passed + /// arguments. + #[cfg(feature = "async")] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> + where + R: FromLuaMulti; + + /// Gets the function associated to `key` from the object and calls it, + /// passing the object itself along with `args` as function arguments. + fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result + where + R: FromLuaMulti; + + /// Gets the function associated to `key` from the object and asynchronously calls it, + /// passing the object itself along with `args` as function arguments. + /// + /// Requires `feature = "async"` + /// + /// This might invoke the `__index` metamethod. + #[cfg(feature = "async")] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> + where + R: FromLuaMulti; + + /// Gets the function associated to `key` from the object and calls it, + /// passing `args` as function arguments. + /// + /// This might invoke the `__index` metamethod. + fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result + where + R: FromLuaMulti; + + /// Gets the function associated to `key` from the object and asynchronously calls it, + /// passing `args` as function arguments. + /// + /// Requires `feature = "async"` + /// + /// This might invoke the `__index` metamethod. + #[cfg(feature = "async")] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> + where + R: FromLuaMulti; + + /// Converts the object to a string in a human-readable format. + /// + /// This might invoke the `__tostring` metamethod. + fn to_string(&self) -> Result; +} diff --git a/src/types.rs b/src/types.rs index 14f30aa1..3f0d31d2 100644 --- a/src/types.rs +++ b/src/types.rs @@ -115,6 +115,7 @@ pub(crate) struct DestructedUserdata; pub(crate) struct ValueRef { pub(crate) lua: WeakLua, pub(crate) index: c_int, + pub(crate) drop: bool, } impl ValueRef { @@ -123,6 +124,7 @@ impl ValueRef { ValueRef { lua: lua.weak().clone(), index, + drop: true, } } @@ -131,6 +133,16 @@ impl ValueRef { let lua = self.lua.lock(); unsafe { ffi::lua_topointer(lua.ref_thread(), self.index) } } + + /// Returns a copy of the value, which is valid as long as the original value is held. + #[inline] + pub(crate) fn copy(&self) -> Self { + ValueRef { + lua: self.lua.clone(), + index: self.index, + drop: false, + } + } } impl fmt::Debug for ValueRef { @@ -147,7 +159,7 @@ impl Clone for ValueRef { impl Drop for ValueRef { fn drop(&mut self) { - if self.index > 0 { + if self.drop { if let Some(lua) = self.lua.try_lock() { unsafe { lua.drop_ref(self) }; } diff --git a/src/userdata.rs b/src/userdata.rs index 9424e273..f28b96a6 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -26,7 +26,6 @@ use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; // Re-export for convenience pub(crate) use cell::UserDataVariant; pub use cell::{UserDataRef, UserDataRefMut}; -pub use ext::AnyUserDataExt; pub(crate) use registry::UserDataProxy; pub use registry::UserDataRegistry; @@ -1174,8 +1173,8 @@ where } mod cell; -mod ext; mod lock; +mod object; mod registry; #[cfg(test)] diff --git a/src/userdata/ext.rs b/src/userdata/ext.rs deleted file mode 100644 index b2588778..00000000 --- a/src/userdata/ext.rs +++ /dev/null @@ -1,168 +0,0 @@ -use crate::error::{Error, Result}; -use crate::private::Sealed; -use crate::userdata::{AnyUserData, MetaMethod}; -use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; - -#[cfg(feature = "async")] -use std::future::Future; - -/// An extension trait for [`AnyUserData`] that provides a variety of convenient functionality. -pub trait AnyUserDataExt: Sealed { - /// Gets the value associated to `key` from the userdata, assuming it has `__index` metamethod. - fn get(&self, key: impl IntoLua) -> Result; - - /// Sets the value associated to `key` in the userdata, assuming it has `__newindex` metamethod. - fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()>; - - /// Calls the userdata as a function assuming it has `__call` metamethod. - /// - /// The metamethod is called with the userdata as its first argument, followed by the passed - /// arguments. - fn call(&self, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti; - - /// Asynchronously calls the userdata as a function assuming it has `__call` metamethod. - /// - /// The metamethod is called with the userdata as its first argument, followed by the passed - /// arguments. - #[cfg(feature = "async")] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> - where - R: FromLuaMulti; - - /// Calls the userdata method, assuming it has `__index` metamethod - /// and a function associated to `name`. - fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti; - - /// Gets the function associated to `key` from the table and asynchronously executes it, - /// passing the table itself along with `args` as function arguments and returning Future. - /// - /// Requires `feature = "async"` - /// - /// This might invoke the `__index` metamethod. - #[cfg(feature = "async")] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> - where - R: FromLuaMulti; - - /// Gets the function associated to `key` from the table and executes it, - /// passing `args` as function arguments. - /// - /// This is a shortcut for - /// `table.get::(key)?.call(args)` - /// - /// This might invoke the `__index` metamethod. - fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti; - - /// Gets the function associated to `key` from the table and asynchronously executes it, - /// passing `args` as function arguments and returning Future. - /// - /// Requires `feature = "async"` - /// - /// This might invoke the `__index` metamethod. - #[cfg(feature = "async")] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> - where - R: FromLuaMulti; -} - -impl AnyUserDataExt for AnyUserData { - fn get(&self, key: impl IntoLua) -> Result { - let metatable = self.get_metatable()?; - match metatable.get::(MetaMethod::Index)? { - Value::Table(table) => table.raw_get(key), - Value::Function(func) => func.call((self, key)), - _ => Err(Error::runtime("attempt to index a userdata value")), - } - } - - fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { - let metatable = self.get_metatable()?; - match metatable.get::(MetaMethod::NewIndex)? { - Value::Table(table) => table.raw_set(key, value), - Value::Function(func) => func.call((self, key, value)), - _ => Err(Error::runtime("attempt to index a userdata value")), - } - } - - fn call(&self, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti, - { - let metatable = self.get_metatable()?; - match metatable.get::(MetaMethod::Call)? { - Value::Function(func) => func.call((self, args)), - _ => Err(Error::runtime("attempt to call a userdata value")), - } - } - - #[cfg(feature = "async")] - fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> - where - R: FromLuaMulti, - { - let lua = self.0.lua.lock(); - let args = (self, args).into_lua_multi(lua.lua()); - async move { - let metatable = self.get_metatable()?; - match metatable.get::(MetaMethod::Call)? { - Value::Function(func) => func.call_async(args?).await, - _ => Err(Error::runtime("attempt to call a userdata value")), - } - } - } - - fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti, - { - self.call_function(name, (self, args)) - } - - #[cfg(feature = "async")] - fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> - where - R: FromLuaMulti, - { - self.call_async_function(name, (self, args)) - } - - fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti, - { - match self.get(name)? { - Value::Function(func) => func.call(args), - val => { - let msg = format!("attempt to call a {} value", val.type_name()); - Err(Error::runtime(msg)) - } - } - } - - #[cfg(feature = "async")] - fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> - where - R: FromLuaMulti, - { - let lua = self.0.lua.lock(); - let args = args.into_lua_multi(lua.lua()); - async move { - match self.get::(name)? { - Value::Function(func) => func.call_async(args?).await, - val => { - let msg = format!("attempt to call a {} value", val.type_name()); - Err(Error::runtime(msg)) - } - } - } - } -} diff --git a/src/userdata/object.rs b/src/userdata/object.rs new file mode 100644 index 00000000..e9582e56 --- /dev/null +++ b/src/userdata/object.rs @@ -0,0 +1,93 @@ +use std::string::String as StdString; + +use crate::error::{Error, Result}; +use crate::table::Table; +use crate::traits::ObjectLike; +use crate::userdata::AnyUserData; +use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; +use crate::Function; + +#[cfg(feature = "async")] +use futures_util::future::{self, Either, Future}; + +impl ObjectLike for AnyUserData { + #[inline] + fn get(&self, key: impl IntoLua) -> Result { + // `lua_gettable` method used under the hood can work with any Lua value + // that has `__index` metamethod + Table(self.0.copy()).get_protected(key) + } + + #[inline] + fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { + // `lua_settable` method used under the hood can work with any Lua value + // that has `__newindex` metamethod + Table(self.0.copy()).set_protected(key, value) + } + + #[inline] + fn call(&self, args: impl IntoLuaMulti) -> Result + where + R: FromLuaMulti, + { + Function(self.0.copy()).call(args) + } + + #[cfg(feature = "async")] + #[inline] + fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> + where + R: FromLuaMulti, + { + Function(self.0.copy()).call_async(args) + } + + #[inline] + fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result + where + R: FromLuaMulti, + { + self.call_function(name, (self, args)) + } + + #[cfg(feature = "async")] + fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> + where + R: FromLuaMulti, + { + self.call_async_function(name, (self, args)) + } + + fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result + where + R: FromLuaMulti, + { + match self.get(name)? { + Value::Function(func) => func.call(args), + val => { + let msg = format!("attempt to call a {} value (function '{name}')", val.type_name()); + Err(Error::RuntimeError(msg)) + } + } + } + + #[cfg(feature = "async")] + fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> + where + R: FromLuaMulti, + { + match self.get(name) { + Ok(Value::Function(func)) => Either::Left(func.call_async(args)), + Ok(val) => { + let msg = format!("attempt to call a {} value (function '{name}')", val.type_name()); + Either::Right(future::ready(Err(Error::RuntimeError(msg)))) + } + Err(err) => Either::Right(future::ready(Err(err))), + } + } + + #[inline] + fn to_string(&self) -> Result { + Value::UserData(self.clone()).to_string() + } +} diff --git a/tests/async.rs b/tests/async.rs index 887965d3..29cee045 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -7,7 +7,7 @@ use futures_util::stream::TryStreamExt; use tokio::sync::Mutex; use mlua::{ - AnyUserDataExt, Error, Function, Lua, LuaOptions, MultiValue, Result, StdLib, Table, TableExt, UserData, + Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, UserData, UserDataMethods, Value, }; @@ -315,7 +315,7 @@ fn test_async_thread_capture() -> Result<()> { } #[tokio::test] -async fn test_async_table() -> Result<()> { +async fn test_async_table_object_like() -> Result<()> { let options = LuaOptions::new().thread_pool_size(4); let lua = Lua::new_with(StdLib::ALL_SAFE, options)?; @@ -334,19 +334,20 @@ async fn test_async_table() -> Result<()> { })?; table.set("set_value", set_value)?; - let sleep = lua.create_async_function(|_, n| async move { - sleep_ms(n).await; - Ok(format!("elapsed:{}ms", n)) - })?; - table.set("sleep", sleep)?; - assert_eq!(table.call_async_method::("get_value", ()).await?, 10); table.call_async_method::<()>("set_value", 15).await?; assert_eq!(table.call_async_method::("get_value", ()).await?, 15); - assert_eq!( - table.call_async_function::("sleep", 7).await?, - "elapsed:7ms" - ); + + let metatable = lua.create_table()?; + metatable.set( + "__call", + lua.create_async_function(|_, table: Table| async move { + sleep_ms(10).await; + table.get::("val") + })?, + )?; + table.set_metatable(Some(metatable)); + assert_eq!(table.call_async::(()).await.unwrap(), 15); Ok(()) } @@ -461,6 +462,7 @@ async fn test_async_userdata() -> Result<()> { .exec_async() .await?; + // ObjectLike methods userdata.call_async_method::<()>("set_value", 24).await?; let n: u64 = userdata.call_async_method("get_value", ()).await?; assert_eq!(n, 24); diff --git a/tests/table.rs b/tests/table.rs index 30b51d90..957d5a48 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -1,4 +1,4 @@ -use mlua::{Error, Lua, Nil, Result, Table, TableExt, Value}; +use mlua::{Error, Lua, Nil, ObjectLike, Result, Table, Value}; #[test] fn test_globals_set_get() -> Result<()> { @@ -399,7 +399,7 @@ fn test_table_error() -> Result<()> { } #[test] -fn test_table_call() -> Result<()> { +fn test_table_object_like() -> Result<()> { let lua = Lua::new(); lua.load( @@ -408,6 +408,10 @@ fn test_table_call() -> Result<()> { setmetatable(table, { __call = function(t, key) return "call_"..t[key] + end, + + __tostring = function() + return "table object" end }) @@ -424,9 +428,19 @@ fn test_table_call() -> Result<()> { let table: Table = lua.globals().get("table")?; +
::set(&table, "c", 3)?; + assert_eq!(
::get::(&table, "c")?, 3); assert_eq!(table.call::("b")?, "call_2"); assert_eq!(table.call_function::("func", "a")?, "func_a"); assert_eq!(table.call_method::("method", "a")?, "method_1"); + assert_eq!(table.to_string()?, "table object"); + + match table.call_method::<()>("non_existent", ()) { + Err(Error::RuntimeError(err)) => { + assert!(err.contains("attempt to call a nil value (function 'non_existent')")) + } + r => panic!("expected RuntimeError, got {r:?}"), + } // Test calling non-callable table let table2 = lua.create_table()?; diff --git a/tests/userdata.rs b/tests/userdata.rs index 7c6ae05f..e305ae38 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -6,8 +6,8 @@ use std::sync::Arc; use std::sync::atomic::{AtomicI64, Ordering}; use mlua::{ - AnyUserData, AnyUserDataExt, Error, ExternalError, Function, Lua, MetaMethod, Nil, Result, String, - UserData, UserDataFields, UserDataMethods, UserDataRef, Value, Variadic, + AnyUserData, Error, ExternalError, Function, Lua, MetaMethod, Nil, ObjectLike, Result, String, UserData, + UserDataFields, UserDataMethods, UserDataRef, Value, Variadic, }; #[test] @@ -726,7 +726,7 @@ fn test_any_userdata_wrap() -> Result<()> { } #[test] -fn test_userdata_ext() -> Result<()> { +fn test_userdata_object_like() -> Result<()> { let lua = Lua::new(); #[derive(Clone, Copy)] @@ -766,6 +766,15 @@ fn test_userdata_ext() -> Result<()> { ud.call_method::<()>("add", 2)?; assert_eq!(ud.get::("n")?, 323); + match ud.call_method::<()>("non_existent", ()) { + Err(Error::RuntimeError(err)) => { + assert!(err.contains("attempt to call a nil value (function 'non_existent')")) + } + r => panic!("expected RuntimeError, got {r:?}"), + } + + assert!(ud.to_string()?.starts_with("MyUserData")); + Ok(()) } From d6b27de34eb104c767a8f69bef750b2eca375db3 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 30 Aug 2024 22:55:53 +0100 Subject: [PATCH 166/635] Optimize `ObjectLike::to_string` for tables and userdata --- src/table.rs | 2 +- src/userdata/object.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/table.rs b/src/table.rs index 2e0d999e..86a14c98 100644 --- a/src/table.rs +++ b/src/table.rs @@ -941,7 +941,7 @@ impl ObjectLike for Table { #[inline] fn to_string(&self) -> Result { - Value::Table(self.clone()).to_string() + Value::Table(Table(self.0.copy())).to_string() } } diff --git a/src/userdata/object.rs b/src/userdata/object.rs index e9582e56..faa68f72 100644 --- a/src/userdata/object.rs +++ b/src/userdata/object.rs @@ -88,6 +88,6 @@ impl ObjectLike for AnyUserData { #[inline] fn to_string(&self) -> Result { - Value::UserData(self.clone()).to_string() + Value::UserData(AnyUserData(self.0.copy(), self.1)).to_string() } } From 1634c43f0afaf7a71dc555cb6b3624250e5ff209 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 30 Aug 2024 23:37:13 +0100 Subject: [PATCH 167/635] Disable `send` feature in module mode We don't have exclusive access to Lua VM and cannot provide `Sync` soundness --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index b6a256b6..a61385e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -259,6 +259,9 @@ pub use mlua_derive::FromLua; #[cfg_attr(docsrs, doc(cfg(feature = "module")))] pub use mlua_derive::lua_module; +#[cfg(all(feature = "module", feature = "send"))] +compile_error!("`send` feature is not supported in module mode"); + pub(crate) mod private { use super::*; From 825bdbfa046fd8d6a28b1081a482a5cc6a501291 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 31 Aug 2024 22:52:55 +0100 Subject: [PATCH 168/635] Use dynamic Lua state ownership instead of compile-time `module` cfg flag to allow creating new VMs in module mode (and destructing them properly). --- README.md | 2 +- src/state.rs | 2 +- src/state/extra.rs | 14 ++++++++------ src/state/raw.rs | 14 +++++++------- tests/function.rs | 4 ++-- tests/module/loader/.cargo/{config => config.toml} | 0 tests/module/loader/tests/load.rs | 12 ++++++++++++ tests/module/src/lib.rs | 12 ++++++++++++ 8 files changed, 43 insertions(+), 17 deletions(-) rename tests/module/loader/.cargo/{config => config.toml} (100%) diff --git a/README.md b/README.md index 3b05b1c7..9256a15d 100644 --- a/README.md +++ b/README.md @@ -195,7 +195,7 @@ $ lua5.4 -e 'require("my_module").hello("world")' hello, world! ``` -On macOS, you need to set additional linker arguments. One option is to compile with `cargo rustc --release -- -C link-arg=-undefined -C link-arg=dynamic_lookup`, the other is to create a `.cargo/config` with the following content: +On macOS, you need to set additional linker arguments. One option is to compile with `cargo rustc --release -- -C link-arg=-undefined -C link-arg=dynamic_lookup`, the other is to create a `.cargo/config.toml` with the following content: ``` toml [target.x86_64-apple-darwin] rustflags = [ diff --git a/src/state.rs b/src/state.rs index c8ba1f5f..35397ff5 100644 --- a/src/state.rs +++ b/src/state.rs @@ -271,7 +271,7 @@ impl Lua { #[inline] pub unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> Lua { Lua { - raw: RawLua::init_from_ptr(state), + raw: RawLua::init_from_ptr(state, false), collect_garbage: true, } } diff --git a/src/state/extra.rs b/src/state/extra.rs index 3c04f077..5b2f8c08 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -33,6 +33,7 @@ const REF_STACK_RESERVE: c_int = 1; pub(crate) struct ExtraData { pub(super) lua: MaybeUninit, pub(super) weak: MaybeUninit, + pub(super) owned: bool, pub(super) registered_userdata: FxHashMap, pub(super) registered_userdata_mt: FxHashMap<*const c_void, Option>, @@ -46,7 +47,7 @@ pub(crate) struct ExtraData { pub(super) safe: bool, pub(super) libs: StdLib, - #[cfg(feature = "module")] + // Used in module mode pub(super) skip_memory_check: bool, // Auxiliary thread to store references @@ -88,8 +89,9 @@ pub(crate) struct ExtraData { impl Drop for ExtraData { fn drop(&mut self) { unsafe { - #[cfg(feature = "module")] - self.lua.assume_init_drop(); + if !self.owned { + self.lua.assume_init_drop(); + } self.weak.assume_init_drop(); } @@ -111,7 +113,7 @@ impl ExtraData { #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] pub(super) const ERROR_TRACEBACK_IDX: c_int = 1; - pub(super) unsafe fn init(state: *mut ffi::lua_State) -> XRc> { + pub(super) unsafe fn init(state: *mut ffi::lua_State, owned: bool) -> XRc> { // Create ref stack thread and place it in the registry to prevent it // from being garbage collected. let ref_thread = mlua_expect!( @@ -141,6 +143,7 @@ impl ExtraData { let extra = XRc::new(UnsafeCell::new(ExtraData { lua: MaybeUninit::uninit(), weak: MaybeUninit::uninit(), + owned, registered_userdata: FxHashMap::default(), registered_userdata_mt: FxHashMap::default(), last_checked_userdata_mt: (ptr::null(), None), @@ -148,7 +151,6 @@ impl ExtraData { app_data: AppData::default(), safe: false, libs: StdLib::NONE, - #[cfg(feature = "module")] skip_memory_check: false, ref_thread, // We need some reserved stack space to move values in and out of the ref stack. @@ -188,7 +190,7 @@ impl ExtraData { raw: XRc::clone(raw), collect_garbage: false, }); - if cfg!(not(feature = "module")) { + if self.owned { XRc::decrement_strong_count(XRc::as_ptr(raw)); } self.weak.write(WeakLua(XRc::downgrade(raw))); diff --git a/src/state/raw.rs b/src/state/raw.rs index ab056428..98f47956 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -50,10 +50,13 @@ pub struct RawLua { pub(super) extra: XRc>, } -#[cfg(not(feature = "module"))] impl Drop for RawLua { fn drop(&mut self) { unsafe { + if !(*self.extra.get()).owned { + return; + } + let mem_state = MemoryState::get(self.main_state); ffi::lua_close(self.main_state); @@ -115,7 +118,7 @@ impl RawLua { ffi::luau_codegen_create(state); } - let rawlua = Self::init_from_ptr(state); + let rawlua = Self::init_from_ptr(state, true); let extra = rawlua.lock().extra.get(); mlua_expect!( @@ -154,7 +157,7 @@ impl RawLua { rawlua } - pub(super) unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> XRc> { + pub(super) unsafe fn init_from_ptr(state: *mut ffi::lua_State, owned: bool) -> XRc> { assert!(!state.is_null(), "Lua state is NULL"); if let Some(lua) = Self::try_from_ptr(state) { return lua; @@ -191,7 +194,7 @@ impl RawLua { ); // Init ExtraData - let extra = ExtraData::init(main_state); + let extra = ExtraData::init(main_state, owned); // Register `DestructedUserdata` type get_destructed_userdata_metatable(main_state); @@ -704,10 +707,7 @@ impl RawLua { // MemoryInfo is empty in module mode so we cannot predict memory limits match MemoryState::get(self.main_state) { mem_state if !mem_state.is_null() => (*mem_state).memory_limit() == 0, - #[cfg(feature = "module")] _ => (*self.extra.get()).skip_memory_check, // Check the special flag (only for module mode) - #[cfg(not(feature = "module"))] - _ => false, } } diff --git a/tests/function.rs b/tests/function.rs index 2735e655..82c05fe0 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -84,8 +84,8 @@ fn test_c_function() -> Result<()> { let lua = Lua::new(); unsafe extern "C-unwind" fn c_function(state: *mut mlua::lua_State) -> std::os::raw::c_int { - let lua = Lua::init_from_ptr(state); - lua.globals().set("c_function", true).unwrap(); + ffi::lua_pushboolean(state, 1); + ffi::lua_setglobal(state, b"c_function\0" as *const _ as *const _); 0 } diff --git a/tests/module/loader/.cargo/config b/tests/module/loader/.cargo/config.toml similarity index 100% rename from tests/module/loader/.cargo/config rename to tests/module/loader/.cargo/config.toml diff --git a/tests/module/loader/tests/load.rs b/tests/module/loader/tests/load.rs index 8c4a5957..08473b99 100644 --- a/tests/module/loader/tests/load.rs +++ b/tests/module/loader/tests/load.rs @@ -92,6 +92,18 @@ fn test_module_multi_from_thread() -> Result<()> { .exec() } +#[test] +fn test_module_new_vm() -> Result<()> { + let lua = make_lua()?; + lua.load( + r#" + local mod = require("test_module.new_vm") + assert(mod.eval("return \"hello, world\"") == "hello, world") + "#, + ) + .exec() +} + fn make_lua() -> Result { let (dylib_path, dylib_ext, separator); if cfg!(target_os = "macos") { diff --git a/tests/module/src/lib.rs b/tests/module/src/lib.rs index 25689fe8..ac505824 100644 --- a/tests/module/src/lib.rs +++ b/tests/module/src/lib.rs @@ -33,6 +33,18 @@ fn test_module2(lua: &Lua) -> LuaResult { Ok(exports) } +#[mlua::lua_module] +fn test_module_new_vm(lua: &Lua) -> LuaResult { + let eval = lua.create_function(|_, prog: String| { + let lua = Lua::new(); + lua.load(prog).eval::>() + })?; + + let exports = lua.create_table()?; + exports.set("eval", eval)?; + Ok(exports) +} + #[mlua::lua_module] fn test_module_error(_: &Lua) -> LuaResult { Err("custom module error".into_lua_err()) From 104e242ddd885be1205b0997b209a1a448f97d07 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 1 Sep 2024 22:47:00 +0100 Subject: [PATCH 169/635] Some cosmetic changes --- src/state.rs | 4 ++-- src/state/raw.rs | 9 ++++----- tests/async.rs | 2 +- tests/function.rs | 7 +++++++ 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/state.rs b/src/state.rs index 35397ff5..d2cc7200 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1127,8 +1127,8 @@ impl Lua { /// Wraps a Rust async function or closure, creating a callable Lua function handle to it. /// - /// While executing the function Rust will poll Future and if the result is not ready, call - /// `yield()` passing internal representation of a `Poll::Pending` value. + /// While executing the function Rust will poll the Future and if the result is not ready, + /// call `yield()` passing internal representation of a `Poll::Pending` value. /// /// The function must be called inside Lua coroutine ([`Thread`]) to be able to suspend its /// execution. An executor should be used to poll [`AsyncThread`] and mlua will take a diff --git a/src/state/raw.rs b/src/state/raw.rs index 98f47956..d82dcf83 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -1075,6 +1075,7 @@ impl RawLua { #[cfg(feature = "async")] pub(crate) fn create_async_callback(&self, func: AsyncCallback) -> Result { + // Ensure that the coroutine library is loaded #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] unsafe { if !(*self.extra.get()).libs.contains(StdLib::COROUTINE) { @@ -1130,7 +1131,7 @@ impl RawLua { } Poll::Ready(nresults) => { match nresults? { - nresults @ 0..=2 => { + nresults if nresults < 3 => { // Fast path for up to 2 results without creating a table ffi::lua_pushinteger(state, nresults as _); if nresults > 0 { @@ -1182,13 +1183,11 @@ impl RawLua { let lua = self.lua(); let coroutine = lua.globals().get::
("coroutine")?; + // Prepare environment for the async poller let env = lua.create_table_with_capacity(0, 3)?; env.set("get_poll", get_poll)?; - // Cache `yield` function env.set("yield", coroutine.get::("yield")?)?; - unsafe { - env.set("unpack", lua.create_c_function(unpack)?)?; - } + env.set("unpack", unsafe { lua.create_c_function(unpack)? })?; lua.load( r#" diff --git a/tests/async.rs b/tests/async.rs index 29cee045..0f4101b4 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -75,7 +75,7 @@ async fn test_async_call() -> Result<()> { match hello.call::<()>("alex") { Err(Error::RuntimeError(_)) => {} - _ => panic!("non-async executing async function must fail on the yield stage with RuntimeError"), + err => panic!("expected `RuntimeError`, got {err:?}"), }; assert_eq!(hello.call_async::("alex").await?, "hello, alex!"); diff --git a/tests/function.rs b/tests/function.rs index 82c05fe0..7415e5e9 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -168,6 +168,13 @@ fn test_function_environment() -> Result<()> { lua.gc_collect()?; assert_eq!(lua_func2.call::(())?, "local"); + // Test getting environment set by chunk loader + let chunk = lua + .load("return hello") + .set_environment(lua.create_table_from([("hello", "chunk")])?) + .into_function()?; + assert_eq!(chunk.environment().unwrap().get::("hello")?, "chunk"); + Ok(()) } From d25f2fc07ccddef435f69a2dee6c36a4487974b5 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 1 Sep 2024 22:49:12 +0100 Subject: [PATCH 170/635] Take `Table` instead of `impl IntoLua` in `Chunk::set_environment()` --- src/chunk.rs | 24 +++++++++++------------- src/state/raw.rs | 2 +- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index 0dc781d7..8722a063 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -5,11 +5,11 @@ use std::io::Result as IoResult; use std::path::{Path, PathBuf}; use std::string::String as StdString; -use crate::error::{Error, ErrorContext, Result}; +use crate::error::{Error, Result}; use crate::function::Function; use crate::state::{Lua, WeakLua}; use crate::table::Table; -use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti}; +use crate::value::{FromLuaMulti, IntoLuaMulti}; /// Trait for types [loadable by Lua] and convertible to a [`Chunk`] /// @@ -322,13 +322,8 @@ impl<'a> Chunk<'a> { /// All global variables (including the standard library!) are looked up in `_ENV`, so it may be /// necessary to populate the environment in order for scripts using custom environments to be /// useful. - pub fn set_environment(mut self, env: impl IntoLua) -> Self { - let guard = self.lua.lock(); - let lua = guard.lua(); - self.env = env - .into_lua(lua) - .and_then(|val| lua.unpack(val)) - .context("bad environment value"); + pub fn set_environment(mut self, env: Table) -> Self { + self.env = Ok(Some(env)); self } @@ -451,7 +446,7 @@ impl<'a> Chunk<'a> { let name = Self::convert_name(self.name)?; self.lua .lock() - .load_chunk(Some(&name), self.env?, self.mode, self.source?.as_ref()) + .load_chunk(Some(&name), self.env?.as_ref(), self.mode, self.source?.as_ref()) } /// Compiles the chunk and changes mode to binary. @@ -532,9 +527,12 @@ impl<'a> Chunk<'a> { .unwrap_or(source); let name = Self::convert_name(self.name.clone())?; - self.lua - .lock() - .load_chunk(Some(&name), self.env.clone()?, None, &source) + let env = match &self.env { + Ok(Some(env)) => Some(env), + Ok(None) => None, + Err(err) => return Err(err.clone()), + }; + self.lua.lock().load_chunk(Some(&name), env, None, &source) } fn detect_mode(&self) -> ChunkMode { diff --git a/src/state/raw.rs b/src/state/raw.rs index d82dcf83..bd0a4490 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -301,7 +301,7 @@ impl RawLua { pub(crate) fn load_chunk( &self, name: Option<&CStr>, - env: Option
, + env: Option<&Table>, mode: Option, source: &[u8], ) -> Result { From c6ef393ce91a04e4451d11fb8752d04a1c21006b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 2 Sep 2024 23:43:52 +0100 Subject: [PATCH 171/635] Turn `Lua::entrypoint()` to constructor (don't require initializing Lua first) --- mlua_derive/src/lib.rs | 7 ++++--- src/state.rs | 36 ++++++++++++++++++------------------ src/state/raw.rs | 9 ++++----- src/state/util.rs | 4 ++-- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/mlua_derive/src/lib.rs b/mlua_derive/src/lib.rs index 07209be4..4d3bfb29 100644 --- a/mlua_derive/src/lib.rs +++ b/mlua_derive/src/lib.rs @@ -64,9 +64,10 @@ pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream { #[no_mangle] unsafe extern "C-unwind" fn #ext_entrypoint_name(state: *mut mlua::lua_State) -> ::std::os::raw::c_int { - let lua = mlua::Lua::init_from_ptr(state); - #skip_memory_check - lua.entrypoint1(state, #func_name) + mlua::Lua::entrypoint1(state, move |lua| { + #skip_memory_check + #func_name(lua) + }) } }; diff --git a/src/state.rs b/src/state.rs index d2cc7200..e849e87b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -356,23 +356,24 @@ impl Lua { // The returned value then pushed onto the stack. #[doc(hidden)] #[cfg(not(tarpaulin_include))] - pub unsafe fn entrypoint(self, state: *mut ffi::lua_State, func: F) -> c_int + pub unsafe fn entrypoint(state: *mut ffi::lua_State, func: F) -> c_int where - F: Fn(&Lua, A) -> Result + MaybeSend + 'static, + F: FnOnce(&Lua, A) -> Result, A: FromLuaMulti, R: IntoLua, { - let extra = self.lock().extra.get(); - // `self` is no longer needed and must be dropped at this point to avoid possible memory leak + // Make sure that Lua is initialized + let mut lua = Self::init_from_ptr(state); + lua.collect_garbage = false; + // `Lua` is no longer needed and must be dropped at this point to avoid possible memory leak // in case of possible longjmp (lua_error) below - drop(self); - - callback_error_ext(state, extra, move |nargs| { - let lua = (*extra).lua(); - let rawlua = lua.lock(); - let _guard = StateGuard::new(&rawlua, state); - let args = A::from_stack_args(nargs, 1, None, &rawlua)?; - func(lua, args)?.push_into_stack(&rawlua)?; + drop(lua); + + callback_error_ext(state, ptr::null_mut(), move |extra, nargs| { + let rawlua = (*extra).raw_lua(); + let _guard = StateGuard::new(rawlua, state); + let args = A::from_stack_args(nargs, 1, None, rawlua)?; + func(rawlua.lua(), args)?.push_into_stack(rawlua)?; Ok(1) }) } @@ -380,12 +381,12 @@ impl Lua { // A simple module entrypoint without arguments #[doc(hidden)] #[cfg(not(tarpaulin_include))] - pub unsafe fn entrypoint1(self, state: *mut ffi::lua_State, func: F) -> c_int + pub unsafe fn entrypoint1(state: *mut ffi::lua_State, func: F) -> c_int where + F: FnOnce(&Lua) -> Result, R: IntoLua, - F: Fn(&Lua) -> Result + MaybeSend + 'static, { - self.entrypoint(state, move |lua, _: ()| func(lua)) + Self::entrypoint(state, move |lua, _: ()| func(lua)) } /// Skips memory checks for some operations. @@ -575,8 +576,7 @@ impl Lua { // We don't support GC interrupts since they cannot survive Lua exceptions return; } - let extra = ExtraData::get(state); - let result = callback_error_ext(state, extra, move |_| { + let result = callback_error_ext(state, ptr::null_mut(), move |extra, _| { let interrupt_cb = (*extra).interrupt_callback.clone(); let interrupt_cb = mlua_expect!(interrupt_cb, "no interrupt callback set in interrupt_proc"); if Rc::strong_count(&interrupt_cb) > 2 { @@ -629,7 +629,7 @@ impl Lua { unsafe extern "C-unwind" fn warn_proc(ud: *mut c_void, msg: *const c_char, tocont: c_int) { let extra = ud as *mut ExtraData; - callback_error_ext((*extra).raw_lua().state(), extra, |_| { + callback_error_ext((*extra).raw_lua().state(), extra, |extra, _| { let cb = mlua_expect!( (*extra).warn_callback.as_ref(), "no warning callback set in warn_proc" diff --git a/src/state/raw.rs b/src/state/raw.rs index bd0a4490..d8a9fa5a 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -360,7 +360,7 @@ impl RawLua { ffi::lua_sethook(state, None, 0, 0); return; } - callback_error_ext(state, extra, move |_| { + callback_error_ext(state, extra, move |extra, _| { let hook_cb = (*extra).hook_callback.clone(); let hook_cb = mlua_expect!(hook_cb, "no hook callback set in hook_proc"); if std::rc::Rc::strong_count(&hook_cb) > 2 { @@ -1038,7 +1038,7 @@ impl RawLua { } _ => (ptr::null_mut(), ptr::null_mut()), }; - callback_error_ext(state, extra, |nargs| { + callback_error_ext(state, extra, |extra, nargs| { // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) if upvalue.is_null() { return Err(Error::CallbackDestructed); @@ -1089,7 +1089,7 @@ impl RawLua { // so the first upvalue is always valid let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); let extra = (*upvalue).extra.get(); - callback_error_ext(state, extra, |nargs| { + callback_error_ext(state, extra, |extra, nargs| { // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) // The lock must be already held as the callback is executed let rawlua = (*extra).raw_lua(); @@ -1114,8 +1114,7 @@ impl RawLua { unsafe extern "C-unwind" fn poll_future(state: *mut ffi::lua_State) -> c_int { let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); - let extra = (*upvalue).extra.get(); - callback_error_ext(state, extra, |_| { + callback_error_ext(state, (*upvalue).extra.get(), |extra, _| { // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) // The lock must be already held as the future is polled let rawlua = (*extra).raw_lua(); diff --git a/src/state/util.rs b/src/state/util.rs index bb082c72..49c36a23 100644 --- a/src/state/util.rs +++ b/src/state/util.rs @@ -32,7 +32,7 @@ pub(super) unsafe fn callback_error_ext( f: F, ) -> R where - F: FnOnce(c_int) -> Result, + F: FnOnce(*mut ExtraData, c_int) -> Result, { if extra.is_null() { extra = ExtraData::get(state); @@ -113,7 +113,7 @@ where // to store a wrapped failure (error or panic) *before* we proceed. let prealloc_failure = PreallocatedFailure::reserve(state, extra); - match catch_unwind(AssertUnwindSafe(|| f(nargs))) { + match catch_unwind(AssertUnwindSafe(|| f(extra, nargs))) { Ok(Ok(r)) => { // Return unused `WrappedFailure` to the pool prealloc_failure.release(state, extra); From 7272e40c23c2c4863caa84b1070dfbf50bef9000 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 3 Sep 2024 01:34:55 +0100 Subject: [PATCH 172/635] Faster non-scoped callbacks --- src/state/raw.rs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index d8a9fa5a..06b6ad03 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -1028,27 +1028,16 @@ impl RawLua { // Creates a Function out of a Callback containing a 'static Fn. pub(crate) fn create_callback(&self, func: Callback) -> Result { + // This is non-scoped version of the callback (upvalue is always valid) + // TODO: add a scoped version unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int { - // Normal functions can be scoped and therefore destroyed, - // so we need to check that the first upvalue is valid - let (upvalue, extra) = match ffi::lua_type(state, ffi::lua_upvalueindex(1)) { - ffi::LUA_TUSERDATA => { - let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); - (upvalue, (*upvalue).extra.get()) - } - _ => (ptr::null_mut(), ptr::null_mut()), - }; - callback_error_ext(state, extra, |extra, nargs| { + let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); + callback_error_ext(state, (*upvalue).extra.get(), |extra, nargs| { // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) - if upvalue.is_null() { - return Err(Error::CallbackDestructed); - } - // The lock must be already held as the callback is executed let rawlua = (*extra).raw_lua(); let _guard = StateGuard::new(rawlua, state); let func = &*(*upvalue).data; - func(rawlua, nargs) }) } From 9c86eefb764aeb5bc0b748b4482fc6186b3c7af1 Mon Sep 17 00:00:00 2001 From: Caleb Maclennan Date: Fri, 6 Sep 2024 21:28:40 +0300 Subject: [PATCH 173/635] Update documentation of traits to match the expected argument name (#447) --- src/traits.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/traits.rs b/src/traits.rs index eddbb5f7..0fd48d2b 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -33,13 +33,13 @@ pub trait ObjectLike: Sealed { where R: FromLuaMulti; - /// Gets the function associated to `key` from the object and calls it, + /// Gets the function associated to key `name` from the object and calls it, /// passing the object itself along with `args` as function arguments. fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result where R: FromLuaMulti; - /// Gets the function associated to `key` from the object and asynchronously calls it, + /// Gets the function associated to key `name` from the object and asynchronously calls it, /// passing the object itself along with `args` as function arguments. /// /// Requires `feature = "async"` @@ -51,7 +51,7 @@ pub trait ObjectLike: Sealed { where R: FromLuaMulti; - /// Gets the function associated to `key` from the object and calls it, + /// Gets the function associated to key `name` from the object and calls it, /// passing `args` as function arguments. /// /// This might invoke the `__index` metamethod. @@ -59,7 +59,7 @@ pub trait ObjectLike: Sealed { where R: FromLuaMulti; - /// Gets the function associated to `key` from the object and asynchronously calls it, + /// Gets the function associated to key `name` from the object and asynchronously calls it, /// passing `args` as function arguments. /// /// Requires `feature = "async"` From 7957c6868d6d5d739f701ef3aeee52b91420d333 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 7 Sep 2024 12:24:50 +0100 Subject: [PATCH 174/635] Fastpath for LuaString/integer/float conversion from Lua --- src/conversion.rs | 63 +++++++++++++++++++++++++++++++++++++++------ src/state/raw.rs | 6 ++--- src/table.rs | 2 +- src/value.rs | 4 +-- tests/conversion.rs | 52 +++++++++++++++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 14 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index 0e77f50b..45e34cac 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -76,6 +76,17 @@ impl FromLua for String { message: Some("expected string or number".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_TSTRING { + ffi::lua_xpush(state, lua.ref_thread(), idx); + return Ok(String(lua.pop_ref_thread())); + } + // Fallback to default + Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua()) + } } impl IntoLua for Table { @@ -385,7 +396,8 @@ impl FromLua for StdString { #[inline] unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { let state = lua.state(); - if ffi::lua_type(state, idx) == ffi::LUA_TSTRING { + let type_id = ffi::lua_type(state, idx); + if type_id == ffi::LUA_TSTRING { let mut size = 0; let data = ffi::lua_tolstring(state, idx, &mut size); let bytes = slice::from_raw_parts(data as *const u8, size); @@ -398,7 +410,7 @@ impl FromLua for StdString { }); } // Fallback to default - Self::from_lua(lua.stack_value(idx), lua.lua()) + Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua()) } } @@ -536,9 +548,9 @@ impl FromLua for BString { mlua_assert!(!buf.is_null(), "invalid Luau buffer"); Ok(slice::from_raw_parts(buf as *const u8, size).into()) } - _ => { + type_id => { // Fallback to default - Self::from_lua(lua.stack_value(idx), lua.lua()) + Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua()) } } } @@ -622,6 +634,24 @@ macro_rules! lua_convert_int { message: Some("out of range".to_owned()), }) } + + 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_tointegerx(state, idx, &mut ok); + if ok != 0 { + return cast(i).ok_or_else(|| Error::FromLuaConversionError { + from: "integer", + to: stringify!($x), + message: Some("out of range".to_owned()), + }); + } + } + // Fallback to default + Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua()) + } } }; } @@ -672,6 +702,24 @@ macro_rules! lua_convert_float { }) }) } + + 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), + message: Some("out of range".to_owned()), + }); + } + } + // Fallback to default + Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua()) + } } }; } @@ -893,10 +941,9 @@ impl FromLua for Option { #[inline] unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - if ffi::lua_isnil(lua.state(), idx) != 0 { - Ok(None) - } else { - Ok(Some(T::from_stack(idx, lua)?)) + match ffi::lua_type(lua.state(), idx) { + ffi::LUA_TNIL => Ok(None), + _ => Ok(Some(T::from_stack(idx, lua)?)), } } } diff --git a/src/state/raw.rs b/src/state/raw.rs index 06b6ad03..0e62140b 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -542,7 +542,7 @@ impl RawLua { /// /// Uses 2 stack spaces, does not call `checkstack`. pub(crate) unsafe fn pop_value(&self) -> Value { - let value = self.stack_value(-1); + let value = self.stack_value(-1, None); ffi::lua_pop(self.state(), 1); value } @@ -550,9 +550,9 @@ impl RawLua { /// Returns value at given stack index without popping it. /// /// Uses 2 stack spaces, does not call checkstack. - pub(crate) unsafe fn stack_value(&self, idx: c_int) -> Value { + pub(crate) unsafe fn stack_value(&self, idx: c_int, type_hint: Option) -> Value { let state = self.state(); - match ffi::lua_type(state, idx) { + match type_hint.unwrap_or_else(|| ffi::lua_type(state, idx)) { ffi::LUA_TNIL => Nil, ffi::LUA_TBOOLEAN => Value::Boolean(ffi::lua_toboolean(state, idx) != 0), diff --git a/src/table.rs b/src/table.rs index 86a14c98..3bf638c4 100644 --- a/src/table.rs +++ b/src/table.rs @@ -1092,7 +1092,7 @@ where // a permitted operation. // It fails only if the key is not found (never existed) which seems impossible scenario. if ffi::lua_next(state, -2) != 0 { - let key = lua.stack_value(-2); + let key = lua.stack_value(-2, None); Ok(Some(( key.clone(), K::from_lua(key, lua.lua())?, diff --git a/src/value.rs b/src/value.rs index c88a17b4..d7de0029 100644 --- a/src/value.rs +++ b/src/value.rs @@ -729,7 +729,7 @@ pub trait FromLua: Sized { #[doc(hidden)] #[inline] unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - Self::from_lua(lua.stack_value(idx), lua.lua()) + Self::from_lua(lua.stack_value(idx, None), lua.lua()) } /// Same as `from_lua_arg` but for a value in the Lua stack at index `idx`. @@ -875,7 +875,7 @@ pub trait FromLuaMulti: Sized { unsafe fn from_stack_multi(nvals: c_int, lua: &RawLua) -> Result { let mut values = MultiValue::with_capacity(nvals as usize); for idx in 0..nvals { - values.push_back(lua.stack_value(-nvals + idx)); + values.push_back(lua.stack_value(-nvals + idx, None)); } if nvals > 0 { // It's safe to clear the stack as all references moved to ref thread diff --git a/tests/conversion.rs b/tests/conversion.rs index 5684c285..4cac1145 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -42,6 +42,22 @@ fn test_string_into_lua() -> Result<()> { Ok(()) } +#[test] +fn test_string_from_lua() -> Result<()> { + let lua = Lua::new(); + + // From stack + let f = lua.create_function(|_, s: mlua::String| Ok(s))?; + let s = f.call::("hello, world!")?; + assert_eq!(s, "hello, world!"); + + // Should fallback to default conversion + let s = f.call::(42)?; + assert_eq!(s, "42"); + + Ok(()) +} + #[test] fn test_table_into_lua() -> Result<()> { let lua = Lua::new(); @@ -157,6 +173,42 @@ fn test_registry_key_from_lua() -> Result<()> { Ok(()) } +#[test] +fn test_integer_from_lua() -> Result<()> { + let lua = Lua::new(); + + // From stack + let f = lua.create_function(|_, i: i32| Ok(i))?; + assert_eq!(f.call::(42)?, 42); + + // Out of range + let err = f.call::(i64::MAX).err().unwrap().to_string(); + assert!(err.starts_with("bad argument #1: error converting Lua number to i32 (out of range)")); + + // Should fallback to default conversion + assert_eq!(f.call::("42")?, 42); + + Ok(()) +} + +#[test] +fn test_float_from_lua() -> Result<()> { + let lua = Lua::new(); + + // From stack + let f = lua.create_function(|_, f: f32| Ok(f))?; + assert_eq!(f.call::(42.0)?, 42.0); + + // Out of range (but never fails) + let val = f.call::(f64::MAX)?; + assert!(val.is_infinite()); + + // Should fallback to default conversion + assert_eq!(f.call::("42.0")?, 42.0); + + Ok(()) +} + #[test] fn test_conv_vec() -> Result<()> { let lua = Lua::new(); From e1c0aa84919cbb4650e7051f078b8e298a3a02e1 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 7 Sep 2024 16:50:28 +0100 Subject: [PATCH 175/635] Fix test `test_integer_from_lua` --- tests/conversion.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/conversion.rs b/tests/conversion.rs index 4cac1145..f975a01f 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -182,8 +182,18 @@ fn test_integer_from_lua() -> Result<()> { assert_eq!(f.call::(42)?, 42); // Out of range - let err = f.call::(i64::MAX).err().unwrap().to_string(); - assert!(err.starts_with("bad argument #1: error converting Lua number to i32 (out of range)")); + match f.call::(i64::MAX).err() { + Some(Error::CallbackError { cause, .. }) => match cause.as_ref() { + Error::BadArgument { cause, .. } => match cause.as_ref() { + Error::FromLuaConversionError { message, .. } => { + assert_eq!(message.as_ref().unwrap(), "out of range"); + } + err => panic!("expected Error::FromLuaConversionError, got {err:?}"), + }, + err => panic!("expected Error::BadArgument, got {err:?}"), + }, + err => panic!("expected Error::CallbackError, got {err:?}"), + } // Should fallback to default conversion assert_eq!(f.call::("42")?, 42); From 8e111058c3e792b823b8900ec8fdd56dc0bcca03 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 7 Sep 2024 23:17:25 +0100 Subject: [PATCH 176/635] Make `BorrowedBytes`/`BorrowedStr`: Send + Sync They are immutable and don't require holding a lock --- src/state.rs | 9 +++++++++ src/string.rs | 18 ++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/state.rs b/src/state.rs index e849e87b..abde1365 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1870,6 +1870,15 @@ impl WeakLua { pub(crate) fn try_lock(&self) -> Option { Some(LuaGuard::new(self.0.upgrade()?)) } + + #[track_caller] + #[inline(always)] + pub(crate) fn upgrade(&self) -> Lua { + Lua { + raw: self.0.upgrade().expect("Lua instance is destroyed"), + collect_garbage: false, + } + } } impl PartialEq for WeakLua { diff --git a/src/string.rs b/src/string.rs index 1bdd4071..a4707d37 100644 --- a/src/string.rs +++ b/src/string.rs @@ -12,7 +12,7 @@ use { }; use crate::error::{Error, Result}; -use crate::state::LuaGuard; +use crate::state::Lua; use crate::types::ValueRef; /// Handle to an internal Lua string. @@ -103,9 +103,10 @@ impl String { BorrowedBytes(bytes, guard) } - unsafe fn to_slice(&self) -> (&[u8], LuaGuard) { - let lua = self.0.lua.lock(); - let ref_thread = lua.ref_thread(); + unsafe fn to_slice(&self) -> (&[u8], Lua) { + let lua = self.0.lua.upgrade(); + let rawlua = lua.lock(); + let ref_thread = rawlua.ref_thread(); unsafe { mlua_debug_assert!( ffi::lua_type(ref_thread, self.0.index) == ffi::LUA_TSTRING, @@ -117,6 +118,7 @@ impl String { // string type let data = ffi::lua_tolstring(ref_thread, self.0.index, &mut size); + drop(rawlua); (slice::from_raw_parts(data as *const u8, size + 1), lua) } } @@ -211,7 +213,7 @@ impl Serialize for String { } /// A borrowed string (`&str`) that holds a strong reference to the Lua state. -pub struct BorrowedStr<'a>(&'a str, #[allow(unused)] LuaGuard); +pub struct BorrowedStr<'a>(&'a str, #[allow(unused)] Lua); impl Deref for BorrowedStr<'_> { type Target = str; @@ -267,7 +269,7 @@ where } /// A borrowed byte slice (`&[u8]`) that holds a strong reference to the Lua state. -pub struct BorrowedBytes<'a>(&'a [u8], #[allow(unused)] LuaGuard); +pub struct BorrowedBytes<'a>(&'a [u8], #[allow(unused)] Lua); impl Deref for BorrowedBytes<'_> { type Target = [u8]; @@ -333,4 +335,8 @@ mod assertions { static_assertions::assert_not_impl_any!(String: Send); #[cfg(feature = "send")] static_assertions::assert_impl_all!(String: Send, Sync); + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(BorrowedBytes: Send, Sync); + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(BorrowedStr: Send, Sync); } From 7543b0674e281b1603a453ae83e37cc68609fe9c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 7 Sep 2024 23:23:32 +0100 Subject: [PATCH 177/635] mlua-sys: v0.6.3 --- Cargo.toml | 2 +- mlua-sys/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 79290402..920cd090 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ erased-serde = { version = "0.4", optional = true } serde-value = { version = "0.7", optional = true } parking_lot = { version = "0.12", features = ["arc_lock"] } -ffi = { package = "mlua-sys", version = "0.6.1", path = "mlua-sys" } +ffi = { package = "mlua-sys", version = "0.6.3", path = "mlua-sys" } [target.'cfg(unix)'.dependencies] libloading = { version = "0.8", optional = true } diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index e213b42b..2da25a09 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.6.2" +version = "0.6.3" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" From 8677b57847d39235948ac3805af7113b2775909e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 7 Sep 2024 23:27:35 +0100 Subject: [PATCH 178/635] Update README --- README.md | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 9256a15d..e6debaa9 100644 --- a/README.md +++ b/README.md @@ -38,22 +38,21 @@ WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for a `mlua` uses feature flags to reduce the amount of dependencies, compiled code and allow to choose only required set of features. Below is a list of the available feature flags. By default `mlua` does not enable any features. -* `lua54`: activate Lua [5.4] support -* `lua53`: activate Lua [5.3] support -* `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 -* `luau`: activate [Luau] support (auto vendored mode) -* `luau-jit`: activate [Luau] support with JIT backend. -* `luau-vector4`: activate [Luau] support with 4-dimensional vector. +* `lua54`: enable Lua [5.4] support +* `lua53`: enable Lua [5.3] support +* `lua52`: enable Lua [5.2] support +* `lua51`: enable Lua [5.1] support +* `luajit`: enable [LuaJIT] support +* `luajit52`: enable [LuaJIT] support with partial compatibility with Lua 5.2 +* `luau`: enable [Luau] support (auto vendored mode) +* `luau-jit`: enable [Luau] support with JIT backend. +* `luau-vector4`: enable [Luau] support with 4-dimensional vector. * `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]) -* `send`: make `mlua::Lua` transferable across thread boundaries (adds [`Send`] requirement to `mlua::Function` and `mlua::UserData`) +* `send`: make `mlua::Lua: Send + Sync` (adds [`Send`] requirement to `mlua::Function` and `mlua::UserData`) * `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework * `macros`: enable procedural macros (such as `chunk!`) -* `parking_lot`: support UserData types wrapped in [parking_lot]'s primitives (`Arc` and `Arc`) [5.4]: https://www.lua.org/manual/5.4/manual.html [5.3]: https://www.lua.org/manual/5.3/manual.html @@ -67,7 +66,6 @@ Below is a list of the available feature flags. By default `mlua` does not enabl [async-std]: https://github.com/async-rs/async-std [`Send`]: https://doc.rust-lang.org/std/marker/trait.Send.html [serde]: https://github.com/serde-rs/serde -[parking_lot]: https://github.com/Amanieu/parking_lot ### Async/await support @@ -91,7 +89,7 @@ cargo run --example async_http_client --features=lua54,async,macros cargo run --example async_http_reqwest --features=lua54,async,macros,serialize # async http server -cargo run --example async_http_server --features=lua54,async,macros +cargo run --example async_http_server --features=lua54,async,macros,send curl -v http://localhost:3000 ``` From b88228b3d47e9da689c26ccbac8a7b42a9fc4653 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 7 Sep 2024 23:43:20 +0100 Subject: [PATCH 179/635] Update CHANGELOG --- CHANGELOG.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c27db22..710c0efb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## v0.10.0-beta.2 + +- Updated `ThreadStatus` enum to include `Running` and `Finished` variants. +- `Error::CoroutineInactive` renamed to `Error::CoroutineUnresumable`. +- `IntoLua`/`IntoLuaMulti` now uses `impl trait` syntax for args (shorten from `a.get::<_, T>` to `a.get::`). +- Removed undocumented `Lua::into_static`/`from_static` methods. +- Futures now require `Send` bound if `send` feature is enabled. +- Dropped lifetime from `UserDataMethods` and `UserDataFields` traits. +- `Compiler::compile()` now returns `Result` (Luau). +- Removed `Clone` requirement from `UserDataFields::add_field()`. +- `TableExt` and `AnyUserDataExt` traits were combined into `ObjectLike` trait. +- Disabled `send` feature in module mode (since we don't have exclusive access to Lua). +- `Chunk::set_environment()` takes `Table` instead of `IntoLua` type. +- Reduced the compile time contribution of `next_key_seed` and `next_value_seed`. +- Reduced the compile time contribution of `serde_userdata`. +- Performance improvements. + ## v0.10.0-beta.1 - Dropped `'lua` lifetime (subtypes now store a weak reference to Lua) @@ -6,8 +23,6 @@ - Removed `UserData` impl for Rc/Arc types ("any" userdata functions can be used instead) - `Lua::replace_registry_value` takes `&mut RegistryKey` - `Lua::scope` temporary disabled (will be re-added in the next release) -- Reduced the compile time contribution of `next_key_seed` and `next_value_seed`. -- Reduced the compile time contribution of `serde_userdata`. ## v0.9.9 From 5db545e7b436e6606a74d7a8aef3077bbfb0fb1b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 7 Sep 2024 23:46:13 +0100 Subject: [PATCH 180/635] mlua_derive: v0.10.0-beta.1 --- Cargo.toml | 2 +- mlua_derive/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 920cd090..8c9d5993 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value"] macros = ["mlua_derive/macros"] [dependencies] -mlua_derive = { version = "=0.9.3", optional = true, path = "mlua_derive" } +mlua_derive = { version = "=0.10.0-beta.1", optional = true, path = "mlua_derive" } bstr = { version = "1.0", features = ["std"], default-features = false } num-traits = { version = "0.2.14" } rustc-hash = "2.0" diff --git a/mlua_derive/Cargo.toml b/mlua_derive/Cargo.toml index 9cfeeddf..71cdb08b 100644 --- a/mlua_derive/Cargo.toml +++ b/mlua_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua_derive" -version = "0.9.3" +version = "0.10.0-beta.1" authors = ["Aleksandr Orlenko "] edition = "2021" description = "Procedural macros for the mlua crate." @@ -19,6 +19,6 @@ quote = "1.0" proc-macro2 = { version = "1.0", features = ["span-locations"] } proc-macro-error = { version = "1.0", optional = true } syn = { version = "2.0", features = ["full"] } -itertools = { version = "0.12", optional = true } +itertools = { version = "0.13", optional = true } regex = { version = "1.4", optional = true } once_cell = { version = "1.0", optional = true } From 7c2e9b5a7c43fb7844486f3acfbd70261c900c66 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 7 Sep 2024 23:47:21 +0100 Subject: [PATCH 181/635] v0.10.0-beta.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8c9d5993..027afa52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.10.0-beta.1" # remember to update mlua_derive +version = "0.10.0-beta.2" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.79.0" edition = "2021" From da4404baa54081e1306e05acde792b2056a0ff69 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 20 Sep 2024 13:01:55 +0100 Subject: [PATCH 182/635] Add `Lua::scope` back --- src/lib.rs | 2 +- src/scope.rs | 900 +++--------------- src/state.rs | 37 +- src/state/extra.rs | 4 +- src/state/raw.rs | 106 +-- src/types.rs | 4 +- src/userdata.rs | 100 +- src/userdata/cell.rs | 161 +++- src/userdata/registry.rs | 185 ++-- src/util/error.rs | 2 +- src/util/mod.rs | 5 +- src/util/userdata.rs | 44 +- tests/compile.rs | 3 - tests/compile/async_any_userdata_method.rs | 9 +- .../compile/async_any_userdata_method.stderr | 82 +- tests/compile/async_nonstatic_userdata.rs | 4 +- tests/compile/async_nonstatic_userdata.stderr | 8 +- tests/compile/async_userdata_method.rs | 14 - tests/compile/async_userdata_method.stderr | 17 - tests/compile/function_borrow.rs | 4 +- tests/compile/function_borrow.stderr | 31 +- tests/compile/lua_norefunwindsafe.stderr | 84 +- tests/compile/non_send.rs | 6 +- tests/compile/non_send.stderr | 31 +- tests/compile/ref_nounwindsafe.stderr | 129 +-- tests/compile/scope_callback_capture.rs | 12 +- tests/compile/scope_callback_capture.stderr | 29 +- tests/compile/scope_callback_inner.rs | 15 - tests/compile/scope_callback_inner.stderr | 5 - tests/compile/scope_callback_outer.rs | 15 - tests/compile/scope_callback_outer.stderr | 5 - tests/compile/scope_invariance.rs | 11 +- tests/compile/scope_invariance.stderr | 29 +- tests/compile/scope_mutable_aliasing.rs | 6 +- tests/compile/scope_mutable_aliasing.stderr | 13 +- tests/compile/scope_userdata_borrow.rs | 6 +- tests/compile/scope_userdata_borrow.stderr | 16 +- tests/compile/userdata_borrow.rs | 19 - tests/compile/userdata_borrow.stderr | 13 - tests/{scope.rs.1 => scope.rs} | 240 ++--- tests/serde.rs | 44 - tests/userdata.rs | 4 +- 42 files changed, 847 insertions(+), 1607 deletions(-) delete mode 100644 tests/compile/async_userdata_method.rs delete mode 100644 tests/compile/async_userdata_method.stderr delete mode 100644 tests/compile/scope_callback_inner.rs delete mode 100644 tests/compile/scope_callback_inner.stderr delete mode 100644 tests/compile/scope_callback_outer.rs delete mode 100644 tests/compile/scope_callback_outer.stderr delete mode 100644 tests/compile/userdata_borrow.rs delete mode 100644 tests/compile/userdata_borrow.stderr rename tests/{scope.rs.1 => scope.rs} (55%) diff --git a/src/lib.rs b/src/lib.rs index a61385e7..b6e153f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,7 +87,7 @@ mod hook; mod luau; mod memory; mod multi; -// mod scope; +mod scope; mod state; mod stdlib; mod string; diff --git a/src/scope.rs b/src/scope.rs index 25678c90..57f6598f 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,55 +1,39 @@ -use std::any::Any; -use std::cell::{Cell, RefCell}; +use std::cell::RefCell; use std::marker::PhantomData; use std::mem; -use std::os::raw::c_int; - -#[cfg(feature = "serialize")] -use serde::Serialize; +use std::os::raw::c_void; use crate::error::{Error, Result}; use crate::function::Function; -use crate::lua::Lua; -use crate::types::{Callback, CallbackUpvalue, MaybeSend, SubtypeId, ValueRef}; -use crate::userdata::{ - AnyUserData, MetaMethod, UserData, UserDataCell, UserDataFields, UserDataMethods, -}; -use crate::userdata_impl::UserDataRegistry; -use crate::util::{ - self, assert_stack, check_stack, init_userdata_metatable, push_string, push_table, - rawset_field, short_type_name, take_userdata, StackGuard, -}; -use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; - -#[cfg(feature = "lua54")] -use crate::userdata::USER_VALUE_MAXSLOT; - -#[cfg(feature = "async")] -use std::future::Future; +use crate::state::{Lua, LuaGuard, RawLua}; +use crate::types::{Callback, CallbackUpvalue, ScopedCallback, SubtypeId, ValueRef}; +use crate::userdata::{AnyUserData, UserData, UserDataRegistry, UserDataStorage}; +use crate::util::{self, assert_stack, check_stack, get_userdata, take_userdata, StackGuard}; +use crate::value::{FromLuaMulti, IntoLuaMulti}; /// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and -/// callbacks that are not required to be Send or 'static. +/// callbacks that are not required to be `Send` or `'static`. /// /// See [`Lua::scope`] for more details. -/// -/// [`Lua::scope`]: crate::Lua.html::scope -pub struct Scope<'lua, 'scope> -where - 'lua: 'scope, -{ - lua: &'lua Lua, - destructors: RefCell, DestructorCallback<'lua>)>>, - _scope_invariant: PhantomData>, +pub struct Scope<'scope, 'env: 'scope> { + lua: LuaGuard, + destructors: Destructors<'env>, + _scope_invariant: PhantomData<&'scope mut &'scope ()>, + _env_invariant: PhantomData<&'env mut &'env ()>, } -type DestructorCallback<'lua> = Box) -> Vec> + 'lua>; +type DestructorCallback<'a> = Box Vec>>; -impl<'lua, 'scope> Scope<'lua, 'scope> { - pub(crate) fn new(lua: &'lua Lua) -> Scope<'lua, 'scope> { +// Implement Drop on Destructors instead of Scope to avoid compilation error +struct Destructors<'a>(RefCell)>>); + +impl<'scope, 'env: 'scope> Scope<'scope, 'env> { + pub(crate) fn new(lua: LuaGuard) -> Self { Scope { lua, - destructors: RefCell::new(Vec::new()), + destructors: Destructors(RefCell::new(Vec::new())), _scope_invariant: PhantomData, + _env_invariant: PhantomData, } } @@ -57,28 +41,16 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { /// /// This is a version of [`Lua::create_function`] that creates a callback which expires on /// scope drop. See [`Lua::scope`] for more details. - /// - /// [`Lua::create_function`]: crate::Lua::create_function - /// [`Lua::scope`]: crate::Lua::scope - pub fn create_function<'callback, A, R, F>(&'callback self, func: F) -> Result> + pub fn create_function(&'scope self, func: F) -> Result where - A: FromLuaMulti<'callback>, + F: Fn(&Lua, A) -> Result + 'scope, + A: FromLuaMulti, R: IntoLuaMulti, - F: Fn(&'callback Lua, A) -> Result + 'scope, { - // Safe, because 'scope must outlive 'callback (due to Self containing 'scope), however the - // callback itself must be 'scope lifetime, so the function should not be able to capture - // anything of 'callback lifetime. 'scope can't be shortened due to being invariant, and - // the 'callback lifetime here can't be enlarged due to coming from a universal - // quantification in Lua::scope. - // - // I hope I got this explanation right, but in any case this is tested with compiletest_rs - // to make sure callbacks can't capture handles with lifetime outside the scope, inside the - // scope, and owned inside the callback itself. unsafe { - self.create_callback(Box::new(move |lua, nargs| { - let args = A::from_stack_args(nargs, 1, None, lua)?; - func(lua, args)?.push_into_stack_multi(lua) + self.create_callback(Box::new(move |rawlua, nargs| { + let args = A::from_stack_args(nargs, 1, None, rawlua)?; + func(rawlua.lua(), args)?.push_into_stack_multi(rawlua) })) } } @@ -87,86 +59,31 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { /// /// This is a version of [`Lua::create_function_mut`] that creates a callback which expires /// on scope drop. See [`Lua::scope`] and [`Scope::create_function`] for more details. - /// - /// [`Lua::create_function_mut`]: crate::Lua::create_function_mut - /// [`Lua::scope`]: crate::Lua::scope - /// [`Scope::create_function`]: #method.create_function - pub fn create_function_mut<'callback, A, R, F>( - &'callback self, - func: F, - ) -> Result> + pub fn create_function_mut(&'scope self, func: F) -> Result where - A: FromLuaMulti<'callback>, + F: FnMut(&Lua, A) -> Result + 'scope, + A: FromLuaMulti, R: IntoLuaMulti, - F: FnMut(&'callback Lua, A) -> Result + 'scope, { let func = RefCell::new(func); self.create_function(move |lua, args| { - (*func - .try_borrow_mut() - .map_err(|_| Error::RecursiveMutCallback)?)(lua, args) + (*func.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?)(lua, args) }) } - /// Creates a Lua userdata object from a custom userdata type. - /// - /// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on - /// scope drop, and does not require that the userdata type be Send (but still requires that the - /// UserData be 'static). - /// See [`Lua::scope`] for more details. - /// - /// [`Lua::create_userdata`]: crate::Lua::create_userdata - /// [`Lua::scope`]: crate::Lua::scope - pub fn create_userdata(&self, data: T) -> Result> - where - T: UserData + 'static, - { - // Safe even though T may not be Send, because the parent Lua cannot be sent to another - // thread while the Scope is alive (or the returned AnyUserData handle even). - unsafe { - let ud = self.lua.make_userdata(UserDataCell::new(data))?; - self.seal_userdata::(&ud)?; - Ok(ud) - } - } - - /// Creates a Lua userdata object from a custom serializable userdata type. - /// - /// This is a version of [`Lua::create_ser_userdata`] that creates a userdata which expires on - /// scope drop, and does not require that the userdata type be Send (but still requires that the - /// UserData be 'static). - /// See [`Lua::scope`] for more details. - /// - /// Requires `feature = "serialize"` - /// - /// [`Lua::create_ser_userdata`]: crate::Lua::create_ser_userdata - /// [`Lua::scope`]: crate::Lua::scope - #[cfg(feature = "serialize")] - #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] - pub fn create_ser_userdata(&self, data: T) -> Result> - where - T: UserData + Serialize + 'static, - { - unsafe { - let ud = self.lua.make_userdata(UserDataCell::new_ser(data))?; - self.seal_userdata::(&ud)?; - Ok(ud) - } - } - /// Creates a Lua userdata object from a reference to custom userdata type. /// /// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on - /// scope drop, and does not require that the userdata type be Send. This method takes non-'static - /// reference to the data. See [`Lua::scope`] for more details. + /// scope drop, and does not require that the userdata type be Send. This method takes + /// non-'static reference to the data. See [`Lua::scope`] for more details. /// /// Userdata created with this method will not be able to be mutated from Lua. - pub fn create_userdata_ref(&self, data: &'scope T) -> Result> + pub fn create_userdata_ref(&'scope self, data: &'env T) -> Result where T: UserData + 'static, { unsafe { - let ud = self.lua.make_userdata(UserDataCell::new_ref(data))?; + let ud = self.lua.make_userdata(UserDataStorage::new_ref(data))?; self.seal_userdata::(&ud)?; Ok(ud) } @@ -175,31 +92,14 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { /// Creates a Lua userdata object from a mutable reference to custom userdata type. /// /// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on - /// scope drop, and does not require that the userdata type be Send. This method takes non-'static - /// mutable reference to the data. See [`Lua::scope`] for more details. - pub fn create_userdata_ref_mut(&self, data: &'scope mut T) -> Result> + /// scope drop, and does not require that the userdata type be Send. This method takes + /// non-'static mutable reference to the data. See [`Lua::scope`] for more details. + pub fn create_userdata_ref_mut(&'scope self, data: &'env mut T) -> Result where T: UserData + 'static, { unsafe { - let ud = self.lua.make_userdata(UserDataCell::new_ref_mut(data))?; - self.seal_userdata::(&ud)?; - Ok(ud) - } - } - - /// Creates a Lua userdata object from a custom Rust type. - /// - /// This is a version of [`Lua::create_any_userdata`] that creates a userdata which expires on - /// scope drop and does not require that the userdata type be Send (but still requires that the - /// UserData be 'static). See [`Lua::scope`] for more details. - #[inline] - pub fn create_any_userdata(&self, data: T) -> Result> - where - T: 'static, - { - unsafe { - let ud = self.lua.make_any_userdata(UserDataCell::new(data))?; + let ud = self.lua.make_userdata(UserDataStorage::new_ref_mut(data))?; self.seal_userdata::(&ud)?; Ok(ud) } @@ -212,12 +112,12 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { /// reference to the data. See [`Lua::scope`] for more details. /// /// Userdata created with this method will not be able to be mutated from Lua. - pub fn create_any_userdata_ref(&self, data: &'scope T) -> Result> + pub fn create_any_userdata_ref(&'scope self, data: &'env T) -> Result where T: 'static, { unsafe { - let ud = self.lua.make_any_userdata(UserDataCell::new_ref(data))?; + let ud = self.lua.make_any_userdata(UserDataStorage::new_ref(data))?; self.seal_userdata::(&ud)?; Ok(ud) } @@ -228,301 +128,73 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { /// This is a version of [`Lua::create_any_userdata`] that creates a userdata which expires on /// scope drop, and does not require that the Rust type be Send. This method takes non-'static /// mutable reference to the data. See [`Lua::scope`] for more details. - pub fn create_any_userdata_ref_mut(&self, data: &'scope mut T) -> Result> + pub fn create_any_userdata_ref_mut(&'scope self, data: &'env mut T) -> Result where T: 'static, { - let lua = self.lua; unsafe { - let ud = lua.make_any_userdata(UserDataCell::new_ref_mut(data))?; + let ud = self.lua.make_any_userdata(UserDataStorage::new_ref_mut(data))?; self.seal_userdata::(&ud)?; Ok(ud) } } - /// Shortens the lifetime of a userdata to the lifetime of the scope. - unsafe fn seal_userdata(&self, ud: &AnyUserData<'lua>) -> Result<()> { - #[cfg(any(feature = "lua51", feature = "luajit"))] - let newtable = self.lua.create_table()?; - let destructor: DestructorCallback = Box::new(move |ud| { - let state = ud.lua.state(); - let _sg = StackGuard::new(state); - assert_stack(state, 2); - - // Check that userdata is not destructed (via `take()` call) - if ud.lua.push_userdata_ref(&ud).is_err() { - return vec![]; - } - - // Clear associated user values - #[cfg(feature = "lua54")] - for i in 1..=USER_VALUE_MAXSLOT { - ffi::lua_pushnil(state); - ffi::lua_setiuservalue(state, -2, i as _); - } - #[cfg(any(feature = "lua53", feature = "lua52", feature = "luau"))] - { - ffi::lua_pushnil(state); - ffi::lua_setuservalue(state, -2); - } - #[cfg(any(feature = "lua51", feature = "luajit"))] - { - ud.lua.push_ref(&newtable.0); - ffi::lua_setuservalue(state, -2); - } - - vec![Box::new(take_userdata::>(state))] - }); - self.destructors - .borrow_mut() - .push((ud.0.clone(), destructor)); - - Ok(()) - } - /// Creates a Lua userdata object from a custom userdata type. /// /// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on - /// scope drop, and does not require that the userdata type be Send or 'static. See + /// scope drop, and does not require that the userdata type be `Send` or `'static`. See /// [`Lua::scope`] for more details. /// - /// Lifting the requirement that the UserData type be 'static comes with some important - /// limitations, so if you only need to eliminate the Send requirement, it is probably better to - /// use [`Scope::create_userdata`] instead. - /// /// The main limitation that comes from using non-'static userdata is that the produced userdata /// will no longer have a `TypeId` associated with it, because `TypeId` can only work for - /// 'static types. This means that it is impossible, once the userdata is created, to get a + /// `'static` types. This means that it is impossible, once the userdata is created, to get a /// reference to it back *out* of an `AnyUserData` handle. This also implies that the /// "function" type methods that can be added via [`UserDataMethods`] (the ones that accept /// `AnyUserData` as a first parameter) are vastly less useful. Also, there is no way to re-use /// a single metatable for multiple non-'static types, so there is a higher cost associated with /// creating the userdata metatable each time a new userdata is created. /// - /// [`Scope::create_userdata`]: #method.create_userdata - /// [`Lua::create_userdata`]: crate::Lua::create_userdata - /// [`Lua::scope`]:crate::Lua::scope /// [`UserDataMethods`]: crate::UserDataMethods - pub fn create_nonstatic_userdata(&self, data: T) -> Result> + pub fn create_userdata(&'scope self, data: T) -> Result where - T: UserData + 'scope, + T: UserData + 'env, { - // 'callback outliving 'scope is a lie to make the types work out, required due to the - // inability to work with the more correct callback type that is universally quantified over - // 'lua. This is safe though, because `UserData::add_methods` does not get to pick the 'lua - // lifetime, so none of the static methods UserData types can add can possibly capture - // parameters. - unsafe fn wrap_method<'scope, 'lua, 'callback: 'scope, T: 'scope>( - scope: &Scope<'lua, 'scope>, - ud_ptr: *const UserDataCell, - name: &str, - method: NonStaticMethod<'callback, T>, - ) -> Result> { - // On methods that actually receive the userdata, we fake a type check on the passed in - // userdata, where we pretend there is a unique type per call to - // `Scope::create_nonstatic_userdata`. You can grab a method from a userdata and call - // it on a mismatched userdata type, which when using normal 'static userdata will fail - // with a type mismatch, but here without this check would proceed as though you had - // called the method on the original value (since we otherwise completely ignore the - // first argument). - let func_name = format!("{}.{name}", short_type_name::()); - let check_self_type = move |lua: &Lua, nargs: c_int| -> Result<&UserDataCell> { - let state = lua.state(); - if nargs > 0 && ffi::lua_touserdata(state, -nargs) as *const _ == ud_ptr { - return Ok(&*ud_ptr); - } - Err(Error::bad_self_argument( - &func_name, - Error::UserDataTypeMismatch, - )) - }; - - match method { - NonStaticMethod::Method(method) => { - let f = Box::new(move |lua, nargs| { - let data = check_self_type(lua, nargs)?; - let data = data.try_borrow()?; - method(lua, &*data, nargs - 1) - }); - scope.create_callback(f) - } - NonStaticMethod::MethodMut(method) => { - let method = RefCell::new(method); - let f = Box::new(move |lua, nargs| { - let data = check_self_type(lua, nargs)?; - let mut method = method - .try_borrow_mut() - .map_err(|_| Error::RecursiveMutCallback)?; - let mut data = data.try_borrow_mut()?; - (*method)(lua, &mut *data, nargs - 1) - }); - scope.create_callback(f) - } - NonStaticMethod::Function(function) => scope.create_callback(function), - NonStaticMethod::FunctionMut(function) => { - let function = RefCell::new(function); - let f = Box::new(move |lua, nargs| { - let mut func = function - .try_borrow_mut() - .map_err(|_| Error::RecursiveMutCallback)?; - func(lua, nargs) - }); - scope.create_callback(f) - } - } - } - - let mut registry = NonStaticUserDataRegistry::new(); - T::add_fields(&mut registry); - T::add_methods(&mut registry); - - let lua = self.lua; - let state = lua.state(); + let state = self.lua.state(); unsafe { let _sg = StackGuard::new(state); - check_stack(state, 13)?; - - #[cfg(not(feature = "luau"))] - let ud_ptr = protect_lua!(state, 0, 1, |state| { - let ud = ffi::lua_newuserdata(state, mem::size_of::>()); - - // Set empty environment for Lua 5.1 - #[cfg(any(feature = "lua51", feature = "luajit"))] - { - ffi::lua_newtable(state); - ffi::lua_setuservalue(state, -2); - } + check_stack(state, 3)?; - ud as *const UserDataCell - })?; + // // We don't write the data to the userdata until pushing the metatable + let protect = !self.lua.unlikely_memory_error(); #[cfg(feature = "luau")] let ud_ptr = { - util::push_userdata(state, UserDataCell::new(data), true)?; - ffi::lua_touserdata(state, -1) as *const UserDataCell + let data = UserDataStorage::new_scoped(data); + util::push_userdata::>(state, data, protect)? }; - - // Prepare metatable, add meta methods first and then meta fields - let meta_methods_nrec = registry.meta_methods.len() + registry.meta_fields.len() + 1; - push_table(state, 0, meta_methods_nrec, true)?; - for (k, m) in registry.meta_methods { - lua.push(wrap_method(self, ud_ptr, &k, m)?)?; - rawset_field(state, -2, MetaMethod::validate(&k)?)?; - } - let mut has_name = false; - for (k, f) in registry.meta_fields { - has_name = has_name || k == MetaMethod::Type; - mlua_assert!(f(lua, 0)? == 1, "field function must return one value"); - rawset_field(state, -2, MetaMethod::validate(&k)?)?; - } - // Set `__name/__type` if not provided - if !has_name { - let type_name = short_type_name::(); - push_string(state, type_name.as_bytes(), !lua.unlikely_memory_error())?; - rawset_field(state, -2, MetaMethod::Type.name())?; - } - let metatable_index = ffi::lua_absindex(state, -1); - - let fields_nrec = registry.fields.len(); - if fields_nrec > 0 { - // If __index is a table then update it inplace - let index_type = ffi::lua_getfield(state, metatable_index, cstr!("__index")); - match index_type { - ffi::LUA_TNIL | ffi::LUA_TTABLE => { - if index_type == ffi::LUA_TNIL { - // Create a new table - ffi::lua_pop(state, 1); - push_table(state, 0, fields_nrec, true)?; - } - for (k, f) in registry.fields { - #[rustfmt::skip] - let NonStaticMethod::Function(f) = f else { unreachable!() }; - mlua_assert!(f(lua, 0)? == 1, "field function must return one value"); - rawset_field(state, -2, &k)?; - } - rawset_field(state, metatable_index, "__index")?; - } - _ => { - // Propagate fields to the field getters - for (k, f) in registry.fields { - registry.field_getters.push((k, f)) - } - } - } - } - - let mut field_getters_index = None; - let field_getters_nrec = registry.field_getters.len(); - if field_getters_nrec > 0 { - push_table(state, 0, field_getters_nrec, true)?; - for (k, m) in registry.field_getters { - lua.push(wrap_method(self, ud_ptr, &k, m)?)?; - rawset_field(state, -2, &k)?; - } - field_getters_index = Some(ffi::lua_absindex(state, -1)); - } - - let mut field_setters_index = None; - let field_setters_nrec = registry.field_setters.len(); - if field_setters_nrec > 0 { - push_table(state, 0, field_setters_nrec, true)?; - for (k, m) in registry.field_setters { - lua.push(wrap_method(self, ud_ptr, &k, m)?)?; - rawset_field(state, -2, &k)?; - } - field_setters_index = Some(ffi::lua_absindex(state, -1)); - } - - let mut methods_index = None; - let methods_nrec = registry.methods.len(); - if methods_nrec > 0 { - // Create table used for methods lookup - push_table(state, 0, methods_nrec, true)?; - for (k, m) in registry.methods { - lua.push(wrap_method(self, ud_ptr, &k, m)?)?; - rawset_field(state, -2, &k)?; - } - methods_index = Some(ffi::lua_absindex(state, -1)); - } - - #[cfg(feature = "luau")] - let extra_init = None; #[cfg(not(feature = "luau"))] - let extra_init: Option Result<()>> = Some(|state| { - ffi::lua_pushcfunction(state, util::userdata_destructor::>); - rawset_field(state, -2, "__gc") - }); - - init_userdata_metatable( - state, - metatable_index, - field_getters_index, - field_setters_index, - methods_index, - extra_init, - )?; - - let count = field_getters_index.map(|_| 1).unwrap_or(0) - + field_setters_index.map(|_| 1).unwrap_or(0) - + methods_index.map(|_| 1).unwrap_or(0); - ffi::lua_pop(state, count); + let ud_ptr = util::push_uninit_userdata::>(state, protect)?; + // Push the metatable and register it with no TypeId + let mut registry = UserDataRegistry::new_unique(ud_ptr as *const c_void); + T::register(&mut registry); + self.lua.push_userdata_metatable(registry)?; let mt_ptr = ffi::lua_topointer(state, -1); - // Write userdata just before attaching metatable with `__gc` metamethod + self.lua.register_userdata_metatable(mt_ptr, None); + + // Write data to the pointer and attach metatable #[cfg(not(feature = "luau"))] - std::ptr::write(ud_ptr as _, UserDataCell::new(data)); + std::ptr::write(ud_ptr, UserDataStorage::new_scoped(data)); ffi::lua_setmetatable(state, -2); - let ud = AnyUserData(lua.pop_ref(), SubtypeId::None); - lua.register_raw_userdata_metatable(mt_ptr, None); - #[cfg(any(feature = "lua51", feature = "luajit"))] - let newtable = lua.create_table()?; - let destructor: DestructorCallback = Box::new(move |ud| { - let state = ud.lua.state(); + let ud = AnyUserData(self.lua.pop_ref(), SubtypeId::None); + + let destructor: DestructorCallback = Box::new(|rawlua, vref| { + let state = rawlua.state(); let _sg = StackGuard::new(state); assert_stack(state, 2); // Check that userdata is valid (very likely) - if ud.lua.push_userdata_ref(&ud).is_err() { + if rawlua.push_userdata_ref(&vref).is_err() { return vec![]; } @@ -530,421 +202,71 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { ffi::lua_getmetatable(state, -1); let mt_ptr = ffi::lua_topointer(state, -1); ffi::lua_pop(state, 1); - ud.lua.deregister_raw_userdata_metatable(mt_ptr); + rawlua.deregister_userdata_metatable(mt_ptr); - // Clear associated user values - #[cfg(feature = "lua54")] - for i in 1..=USER_VALUE_MAXSLOT { - ffi::lua_pushnil(state); - ffi::lua_setiuservalue(state, -2, i as _); - } - #[cfg(any(feature = "lua53", feature = "lua52", feature = "luau"))] - { - ffi::lua_pushnil(state); - ffi::lua_setuservalue(state, -2); - } - #[cfg(any(feature = "lua51", feature = "luajit"))] - { - ud.lua.push_ref(&newtable.0); - ffi::lua_setuservalue(state, -2); - } + let ud = take_userdata::>(state); - // A hack to drop non-static `T` - unsafe fn seal(t: T) -> Box { - let f: Box = Box::new(move || drop(t)); - mem::transmute(f) - } - - let ud = take_userdata::>(state); - vec![Box::new(seal(ud))] + vec![Box::new(move || drop(ud))] }); - self.destructors - .borrow_mut() - .push((ud.0.clone(), destructor)); + self.destructors.0.borrow_mut().push((ud.0.clone(), destructor)); Ok(ud) } } - // Unsafe, because the callback can improperly capture any value with 'callback scope, such as - // improperly capturing an argument. Since the 'callback lifetime is chosen by the user and the - // lifetime of the callback itself is 'scope (non-'static), the borrow checker will happily pick - // a 'callback that outlives 'scope to allow this. In order for this to be safe, the callback - // must NOT capture any parameters. - unsafe fn create_callback<'callback>( - &self, - f: Callback<'callback, 'scope>, - ) -> Result> { - let f = mem::transmute::, Callback<'lua, 'static>>(f); + unsafe fn create_callback(&'scope self, f: ScopedCallback<'scope>) -> Result { + let f = mem::transmute::(f); let f = self.lua.create_callback(f)?; - let destructor: DestructorCallback = Box::new(|f| { - let state = f.lua.state(); - let _sg = StackGuard::new(state); - assert_stack(state, 3); + let destructor: DestructorCallback = Box::new(|rawlua, vref| { + let ref_thread = rawlua.ref_thread(); + ffi::lua_getupvalue(ref_thread, vref.index, 1); + let upvalue = get_userdata::(ref_thread, -1); + let data = (*upvalue).data.take(); + ffi::lua_pop(ref_thread, 1); + vec![Box::new(move || drop(data))] + }); + self.destructors.0.borrow_mut().push((f.0.clone(), destructor)); - f.lua.push_ref(&f); + Ok(f) + } - // We know the destructor has not run yet because we hold a reference to the callback. + /// Shortens the lifetime of the userdata to the lifetime of the scope. + unsafe fn seal_userdata(&self, ud: &AnyUserData) -> Result<()> { + let destructor: DestructorCallback = Box::new(|rawlua, vref| { + let state = rawlua.state(); + let _sg = StackGuard::new(state); + assert_stack(state, 2); - ffi::lua_getupvalue(state, -1, 1); - let ud = take_userdata::(state); - ffi::lua_pushnil(state); - ffi::lua_setupvalue(state, -2, 1); + // Ensure that userdata is not destructed + if rawlua.push_userdata_ref(&vref).is_err() { + return vec![]; + } - vec![Box::new(ud)] + let data = take_userdata::>(state); + vec![Box::new(move || drop(data))] }); - self.destructors - .borrow_mut() - .push((f.0.clone(), destructor)); + self.destructors.0.borrow_mut().push((ud.0.clone(), destructor)); - Ok(f) + Ok(()) } } -impl<'lua, 'scope> Drop for Scope<'lua, 'scope> { +impl Drop for Destructors<'_> { fn drop(&mut self) { // We separate the action of invalidating the userdata in Lua and actually dropping the - // userdata type into two phases. This is so that, in the event a userdata drop panics, we - // can be sure that all of the userdata in Lua is actually invalidated. - - // All destructors are non-panicking, so this is fine - let to_drop = self - .destructors - .get_mut() - .drain(..) - .flat_map(|(r, dest)| dest(r)) - .collect::>(); - - drop(to_drop); - } -} - -#[allow(clippy::type_complexity)] -enum NonStaticMethod<'lua, T> { - Method(Box Result>), - MethodMut(Box Result>), - Function(Box Result>), - FunctionMut(Box Result>), -} - -struct NonStaticUserDataRegistry<'lua, T> { - // Fields - fields: Vec<(String, NonStaticMethod<'lua, T>)>, - field_getters: Vec<(String, NonStaticMethod<'lua, T>)>, - field_setters: Vec<(String, NonStaticMethod<'lua, T>)>, - meta_fields: Vec<(String, Callback<'lua, 'static>)>, - - // Methods - methods: Vec<(String, NonStaticMethod<'lua, T>)>, - meta_methods: Vec<(String, NonStaticMethod<'lua, T>)>, -} - -impl<'lua, T> NonStaticUserDataRegistry<'lua, T> { - const fn new() -> NonStaticUserDataRegistry<'lua, T> { - NonStaticUserDataRegistry { - fields: Vec::new(), - field_getters: Vec::new(), - field_setters: Vec::new(), - meta_fields: Vec::new(), - methods: Vec::new(), - meta_methods: Vec::new(), + // userdata type into two phases. This is so that, in the event a userdata drop panics, + // we can be sure that all of the userdata in Lua is actually invalidated. + + let destructors = mem::take(&mut *self.0.borrow_mut()); + if let Some(lua) = destructors.first().map(|(vref, _)| vref.lua.lock()) { + // All destructors are non-panicking, so this is fine + let to_drop = destructors + .into_iter() + .flat_map(|(vref, destructor)| destructor(&lua, vref)) + .collect::>(); + + drop(to_drop); } } } - -impl<'lua, T> UserDataFields<'lua, T> for NonStaticUserDataRegistry<'lua, T> { - fn add_field(&mut self, name: impl AsRef, value: V) - where - V: IntoLua + Clone + 'static, - { - let name = name.as_ref().to_string(); - self.fields.push(( - name, - NonStaticMethod::Function(Box::new(move |lua, _| unsafe { - value.clone().push_into_stack_multi(lua) - })), - )); - } - - fn add_field_method_get(&mut self, name: impl AsRef, method: M) - where - M: Fn(&'lua Lua, &T) -> Result + MaybeSend + 'static, - R: IntoLua, - { - let method = NonStaticMethod::Method(Box::new(move |lua, ud, _| unsafe { - method(lua, ud)?.push_into_stack_multi(lua) - })); - self.field_getters.push((name.as_ref().into(), method)); - } - - fn add_field_method_set(&mut self, name: impl AsRef, mut method: M) - where - M: FnMut(&'lua Lua, &mut T, A) -> Result<()> + MaybeSend + 'static, - A: FromLua<'lua>, - { - let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); - let method = NonStaticMethod::MethodMut(Box::new(move |lua, ud, nargs| unsafe { - let val = A::from_stack_args(nargs, 2, Some(&func_name), lua)?; - method(lua, ud, val)?.push_into_stack_multi(lua) - })); - self.field_setters.push((name.as_ref().into(), method)); - } - - fn add_field_function_get(&mut self, name: impl AsRef, function: F) - where - F: Fn(&'lua Lua, AnyUserData<'lua>) -> Result + MaybeSend + 'static, - R: IntoLua, - { - let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); - let func = NonStaticMethod::Function(Box::new(move |lua, nargs| unsafe { - let ud = AnyUserData::from_stack_args(nargs, 1, Some(&func_name), lua)?; - function(lua, ud)?.push_into_stack_multi(lua) - })); - self.field_getters.push((name.as_ref().into(), func)); - } - - fn add_field_function_set(&mut self, name: impl AsRef, mut function: F) - where - F: FnMut(&'lua Lua, AnyUserData<'lua>, A) -> Result<()> + MaybeSend + 'static, - A: FromLua<'lua>, - { - let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); - let func = NonStaticMethod::FunctionMut(Box::new(move |lua, nargs| unsafe { - let (ud, val) = <_>::from_stack_args(nargs, 1, Some(&func_name), lua)?; - function(lua, ud, val)?.push_into_stack_multi(lua) - })); - self.field_setters.push((name.as_ref().into(), func)); - } - - fn add_meta_field(&mut self, name: impl AsRef, value: V) - where - V: IntoLua + Clone + 'static, - { - let name = name.as_ref().to_string(); - let name2 = name.clone(); - self.meta_fields.push(( - name, - Box::new(move |lua, _| unsafe { - UserDataRegistry::<()>::check_meta_field(lua, &name2, value.clone())? - .push_into_stack_multi(lua) - }), - )); - } - - fn add_meta_field_with(&mut self, name: impl AsRef, f: F) - where - F: Fn(&'lua Lua) -> Result + MaybeSend + 'static, - R: IntoLua, - { - let name = name.as_ref().to_string(); - let name2 = name.clone(); - self.meta_fields.push(( - name, - Box::new(move |lua, _| unsafe { - UserDataRegistry::<()>::check_meta_field(lua, &name2, f(lua)?)? - .push_into_stack_multi(lua) - }), - )); - } -} - -impl<'lua, T> UserDataMethods<'lua, T> for NonStaticUserDataRegistry<'lua, T> { - fn add_method(&mut self, name: impl AsRef, method: M) - where - M: Fn(&'lua Lua, &T, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - R: IntoLuaMulti, - { - let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); - let method = NonStaticMethod::Method(Box::new(move |lua, ud, nargs| unsafe { - let args = A::from_stack_args(nargs, 2, Some(&func_name), lua)?; - method(lua, ud, args)?.push_into_stack_multi(lua) - })); - self.methods.push((name.as_ref().into(), method)); - } - - fn add_method_mut(&mut self, name: impl AsRef, mut method: M) - where - M: FnMut(&'lua Lua, &mut T, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - R: IntoLuaMulti, - { - let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); - let method = NonStaticMethod::MethodMut(Box::new(move |lua, ud, nargs| unsafe { - let args = A::from_stack_args(nargs, 2, Some(&func_name), lua)?; - method(lua, ud, args)?.push_into_stack_multi(lua) - })); - self.methods.push((name.as_ref().into(), method)); - } - - #[cfg(feature = "async")] - fn add_async_method<'s, M, A, MR, R>(&mut self, _name: impl AsRef, _method: M) - where - 'lua: 's, - T: 'static, - M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - MR: Future> + 's, - R: IntoLuaMulti, - { - // The panic should never happen as async non-static code wouldn't compile - // Non-static lifetime must be bounded to 'lua lifetime - panic!("asynchronous methods are not supported for non-static userdata") - } - - #[cfg(feature = "async")] - fn add_async_method_mut<'s, M, A, MR, R>(&mut self, _name: impl AsRef, _method: M) - where - 'lua: 's, - T: 'static, - M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - MR: Future> + 's, - R: IntoLuaMulti, - { - // The panic should never happen as async non-static code wouldn't compile - // Non-static lifetime must be bounded to 'lua lifetime - panic!("asynchronous methods are not supported for non-static userdata") - } - - fn add_function(&mut self, name: impl AsRef, function: F) - where - F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - R: IntoLuaMulti, - { - let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); - let func = NonStaticMethod::Function(Box::new(move |lua, nargs| unsafe { - let args = A::from_stack_args(nargs, 1, Some(&func_name), lua)?; - function(lua, args)?.push_into_stack_multi(lua) - })); - self.methods.push((name.as_ref().into(), func)); - } - - fn add_function_mut(&mut self, name: impl AsRef, mut function: F) - where - F: FnMut(&'lua Lua, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - R: IntoLuaMulti, - { - let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); - let func = NonStaticMethod::FunctionMut(Box::new(move |lua, nargs| unsafe { - let args = A::from_stack_args(nargs, 1, Some(&func_name), lua)?; - function(lua, args)?.push_into_stack_multi(lua) - })); - self.methods.push((name.as_ref().into(), func)); - } - - #[cfg(feature = "async")] - fn add_async_function(&mut self, _name: impl AsRef, _function: F) - where - F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - FR: Future> + 'lua, - R: IntoLuaMulti, - { - // The panic should never happen as async non-static code wouldn't compile - // Non-static lifetime must be bounded to 'lua lifetime - panic!("asynchronous functions are not supported for non-static userdata") - } - - fn add_meta_method(&mut self, name: impl AsRef, method: M) - where - M: Fn(&'lua Lua, &T, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - R: IntoLuaMulti, - { - let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); - let method = NonStaticMethod::Method(Box::new(move |lua, ud, nargs| unsafe { - let args = A::from_stack_args(nargs, 2, Some(&func_name), lua)?; - method(lua, ud, args)?.push_into_stack_multi(lua) - })); - self.meta_methods.push((name.as_ref().into(), method)); - } - - fn add_meta_method_mut(&mut self, name: impl AsRef, mut method: M) - where - M: FnMut(&'lua Lua, &mut T, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - R: IntoLuaMulti, - { - let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); - let method = NonStaticMethod::MethodMut(Box::new(move |lua, ud, nargs| unsafe { - let args = A::from_stack_args(nargs, 2, Some(&func_name), lua)?; - method(lua, ud, args)?.push_into_stack_multi(lua) - })); - self.meta_methods.push((name.as_ref().into(), method)); - } - - #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] - fn add_async_meta_method<'s, M, A, MR, R>(&mut self, _name: impl AsRef, _method: M) - where - 'lua: 's, - T: 'static, - M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - MR: Future> + 's, - R: IntoLuaMulti, - { - // The panic should never happen as async non-static code wouldn't compile - // Non-static lifetime must be bounded to 'lua lifetime - panic!("asynchronous meta methods are not supported for non-static userdata") - } - - #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] - fn add_async_meta_method_mut<'s, M, A, MR, R>(&mut self, _name: impl AsRef, _method: M) - where - 'lua: 's, - T: 'static, - M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - MR: Future> + 's, - R: IntoLuaMulti, - { - // The panic should never happen as async non-static code wouldn't compile - // Non-static lifetime must be bounded to 'lua lifetime - panic!("asynchronous meta methods are not supported for non-static userdata") - } - - fn add_meta_function(&mut self, name: impl AsRef, function: F) - where - F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - R: IntoLuaMulti, - { - let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); - let func = NonStaticMethod::Function(Box::new(move |lua, nargs| unsafe { - let args = A::from_stack_args(nargs, 1, Some(&func_name), lua)?; - function(lua, args)?.push_into_stack_multi(lua) - })); - self.meta_methods.push((name.as_ref().into(), func)); - } - - fn add_meta_function_mut(&mut self, name: impl AsRef, mut function: F) - where - F: FnMut(&'lua Lua, A) -> Result + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - R: IntoLuaMulti, - { - let func_name = format!("{}.{}", short_type_name::(), name.as_ref()); - let func = NonStaticMethod::FunctionMut(Box::new(move |lua, nargs| unsafe { - let args = A::from_stack_args(nargs, 1, Some(&func_name), lua)?; - function(lua, args)?.push_into_stack_multi(lua) - })); - self.meta_methods.push((name.as_ref().into(), func)); - } - - #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] - fn add_async_meta_function(&mut self, _name: impl AsRef, _function: F) - where - F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static, - A: FromLuaMulti<'lua>, - FR: Future> + 'lua, - R: IntoLuaMulti, - { - // The panic should never happen as async non-static code wouldn't compile - // Non-static lifetime must be bounded to 'lua lifetime - panic!("asynchronous meta functions are not supported for non-static userdata") - } -} diff --git a/src/state.rs b/src/state.rs index abde1365..2c93cc31 100644 --- a/src/state.rs +++ b/src/state.rs @@ -12,7 +12,7 @@ use crate::error::{Error, Result}; use crate::function::Function; use crate::hook::Debug; use crate::memory::MemoryState; -// use crate::scope::Scope; +use crate::scope::Scope; use crate::stdlib::StdLib; use crate::string::String; use crate::table::Table; @@ -21,7 +21,7 @@ use crate::types::{ AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LightUserData, MaybeSend, Number, ReentrantMutex, ReentrantMutexGuard, RegistryKey, XRc, XWeak, }; -use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataVariant}; +use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataStorage}; use crate::util::{assert_stack, check_stack, push_string, push_table, rawset_field, StackGuard}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value}; @@ -1201,7 +1201,7 @@ impl Lua { where T: UserData + MaybeSend + 'static, { - unsafe { self.lock().make_userdata(UserDataVariant::new(data)) } + unsafe { self.lock().make_userdata(UserDataStorage::new(data)) } } /// Creates a Lua userdata object from a custom serializable userdata type. @@ -1214,7 +1214,7 @@ impl Lua { where T: UserData + Serialize + MaybeSend + 'static, { - unsafe { self.lock().make_userdata(UserDataVariant::new_ser(data)) } + unsafe { self.lock().make_userdata(UserDataStorage::new_ser(data)) } } /// Creates a Lua userdata object from a custom Rust type. @@ -1229,7 +1229,7 @@ impl Lua { where T: MaybeSend + 'static, { - unsafe { self.lock().make_any_userdata(UserDataVariant::new(data)) } + unsafe { self.lock().make_any_userdata(UserDataStorage::new(data)) } } /// Creates a Lua userdata object from a custom serializable Rust type. @@ -1244,26 +1244,26 @@ impl Lua { where T: Serialize + MaybeSend + 'static, { - unsafe { (self.lock()).make_any_userdata(UserDataVariant::new_ser(data)) } + unsafe { (self.lock()).make_any_userdata(UserDataStorage::new_ser(data)) } } /// Registers a custom Rust type in Lua to use in userdata objects. /// /// This methods provides a way to add fields or methods to userdata objects of a type `T`. pub fn register_userdata_type(&self, f: impl FnOnce(&mut UserDataRegistry)) -> Result<()> { - let mut registry = const { UserDataRegistry::new() }; + let type_id = TypeId::of::(); + let mut registry = UserDataRegistry::new(type_id); f(&mut registry); let lua = self.lock(); unsafe { // Deregister the type if it already registered - let type_id = TypeId::of::(); - if let Some(&table_id) = (*lua.extra.get()).registered_userdata.get(&type_id) { + if let Some(&table_id) = (*lua.extra.get()).registered_userdata_t.get(&type_id) { ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, table_id); } // Register the type - lua.register_userdata_metatable(registry)?; + lua.create_userdata_metatable(registry)?; } Ok(()) } @@ -1306,7 +1306,7 @@ impl Lua { T: UserData + 'static, { let ud = UserDataProxy::(PhantomData); - unsafe { self.lock().make_userdata(UserDataVariant::new(ud)) } + unsafe { self.lock().make_userdata(UserDataStorage::new(ud)) } } /// Sets the metatable for a Luau builtin vector type. @@ -1380,15 +1380,12 @@ impl Lua { /// dropped. `Function` types will error when called, and `AnyUserData` will be typeless. It /// would be impossible to prevent handles to scoped values from escaping anyway, since you /// would always be able to smuggle them through Lua state. - // pub fn scope<'lua, 'scope, R>( - // &'lua self, - // f: impl FnOnce(&Scope<'lua, 'scope>) -> Result, - // ) -> Result - // where - // 'lua: 'scope, - // { - // f(&Scope::new(self)) - // } + pub fn scope<'env, R>( + &self, + f: impl for<'scope> FnOnce(&'scope mut Scope<'scope, 'env>) -> Result, + ) -> Result { + f(&mut Scope::new(self.lock_arc())) + } /// Attempts to coerce a Lua value into a String in a manner consistent with Lua's internal /// behavior. diff --git a/src/state/extra.rs b/src/state/extra.rs index 5b2f8c08..468f35f1 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -35,7 +35,7 @@ pub(crate) struct ExtraData { pub(super) weak: MaybeUninit, pub(super) owned: bool, - pub(super) registered_userdata: FxHashMap, + pub(super) registered_userdata_t: FxHashMap, pub(super) registered_userdata_mt: FxHashMap<*const c_void, Option>, pub(super) last_checked_userdata_mt: (*const c_void, Option), @@ -144,7 +144,7 @@ impl ExtraData { lua: MaybeUninit::uninit(), weak: MaybeUninit::uninit(), owned, - registered_userdata: FxHashMap::default(), + registered_userdata_t: FxHashMap::default(), registered_userdata_mt: FxHashMap::default(), last_checked_userdata_mt: (ptr::null(), None), registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))), diff --git a/src/state/raw.rs b/src/state/raw.rs index 0e62140b..ad042e5e 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -20,7 +20,7 @@ use crate::types::{ AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData, MaybeSend, ReentrantMutex, RegistryKey, SubtypeId, ValueRef, XRc, }; -use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataRegistry, UserDataVariant}; +use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataRegistry, UserDataStorage}; use crate::util::{ assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state, get_userdata, init_error_registry, init_internal_metatable, init_userdata_metatable, pop_error, @@ -711,45 +711,45 @@ impl RawLua { } } - pub(crate) unsafe fn make_userdata(&self, data: UserDataVariant) -> Result + pub(crate) unsafe fn make_userdata(&self, data: UserDataStorage) -> Result where T: UserData + 'static, { self.make_userdata_with_metatable(data, || { // Check if userdata/metatable is already registered let type_id = TypeId::of::(); - if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) { + if let Some(&table_id) = (*self.extra.get()).registered_userdata_t.get(&type_id) { return Ok(table_id as Integer); } // Create a new metatable from `UserData` definition - let mut registry = const { UserDataRegistry::new() }; + let mut registry = UserDataRegistry::new(type_id); T::register(&mut registry); - self.register_userdata_metatable(registry) + self.create_userdata_metatable(registry) }) } - pub(crate) unsafe fn make_any_userdata(&self, data: UserDataVariant) -> Result + pub(crate) unsafe fn make_any_userdata(&self, data: UserDataStorage) -> Result where T: 'static, { self.make_userdata_with_metatable(data, || { // Check if userdata/metatable is already registered let type_id = TypeId::of::(); - if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) { + if let Some(&table_id) = (*self.extra.get()).registered_userdata_t.get(&type_id) { return Ok(table_id as Integer); } // Create an empty metatable - let registry = const { UserDataRegistry::new() }; - self.register_userdata_metatable::(registry) + let registry = UserDataRegistry::::new(type_id); + self.create_userdata_metatable(registry) }) } unsafe fn make_userdata_with_metatable( &self, - data: UserDataVariant, + data: UserDataStorage, get_metatable_id: impl FnOnce() -> Result, ) -> Result { let state = self.state(); @@ -760,10 +760,7 @@ impl RawLua { ffi::lua_pushnil(state); ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, get_metatable_id()?); let protect = !self.unlikely_memory_error(); - #[cfg(not(feature = "lua54"))] crate::util::push_userdata(state, data, protect)?; - #[cfg(feature = "lua54")] - crate::util::push_userdata_uv(state, data, crate::userdata::USER_VALUE_MAXSLOT as c_int, protect)?; ffi::lua_replace(state, -3); ffi::lua_setmetatable(state, -2); @@ -782,12 +779,31 @@ impl RawLua { Ok(AnyUserData(self.pop_ref(), SubtypeId::None)) } - pub(crate) unsafe fn register_userdata_metatable( + pub(crate) unsafe fn create_userdata_metatable( &self, - mut registry: UserDataRegistry, + registry: UserDataRegistry, ) -> Result { let state = self.state(); - let _sg = StackGuard::new(state); + let type_id = registry.type_id(); + + self.push_userdata_metatable(registry)?; + + let mt_ptr = ffi::lua_topointer(state, -1); + let id = protect_lua!(state, 1, 0, |state| { + ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) + })?; + + if let Some(type_id) = type_id { + (*self.extra.get()).registered_userdata_t.insert(type_id, id); + } + self.register_userdata_metatable(mt_ptr, type_id); + + Ok(id as Integer) + } + + pub(crate) unsafe fn push_userdata_metatable(&self, mut registry: UserDataRegistry) -> Result<()> { + let state = self.state(); + let _sg = StackGuard::with_top(state, ffi::lua_gettop(state) + 1); check_stack(state, 13)?; // Prepare metatable, add meta methods first and then meta fields @@ -922,7 +938,7 @@ impl RawLua { let extra_init = None; #[cfg(not(feature = "luau"))] let extra_init: Option Result<()>> = Some(|state| { - ffi::lua_pushcfunction(state, crate::util::userdata_destructor::>); + ffi::lua_pushcfunction(state, crate::util::userdata_destructor::>); rawset_field(state, -2, "__gc") }); @@ -938,44 +954,21 @@ impl RawLua { // Pop extra tables to get metatable on top of the stack ffi::lua_pop(state, extra_tables_count); - let mt_ptr = ffi::lua_topointer(state, -1); - let id = protect_lua!(state, 1, 0, |state| { - ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) - })?; - - let type_id = TypeId::of::(); - (*self.extra.get()).registered_userdata.insert(type_id, id); - (*self.extra.get()) - .registered_userdata_mt - .insert(mt_ptr, Some(type_id)); + Ok(()) + } - Ok(id as Integer) + #[inline(always)] + pub(crate) unsafe fn register_userdata_metatable(&self, mt_ptr: *const c_void, type_id: Option) { + (*self.extra.get()).registered_userdata_mt.insert(mt_ptr, type_id); } - // #[inline] - // pub(crate) unsafe fn register_raw_userdata_metatable( - // &self, - // ptr: *const c_void, - // type_id: Option, - // ) { - // (*self.extra.get()) - // .registered_userdata_mt - // .insert(ptr, type_id); - // } - - // #[inline] - // pub(crate) unsafe fn deregister_raw_userdata_metatable(&self, ptr: *const c_void) { - // (*self.extra.get()).registered_userdata_mt.remove(&ptr); - // if (*self.extra.get()).last_checked_userdata_mt.0 == ptr { - // (*self.extra.get()).last_checked_userdata_mt = (ptr::null(), None); - // } - // } - - // #[inline(always)] - // pub(crate) unsafe fn get_userdata_ref(&self, idx: c_int) -> Result> { - // let guard = self.lua().lock_arc(); - // (*get_userdata::>(self.state(), idx)).try_make_ref(guard) - // } + #[inline(always)] + pub(crate) unsafe fn deregister_userdata_metatable(&self, mt_ptr: *const c_void) { + (*self.extra.get()).registered_userdata_mt.remove(&mt_ptr); + if (*self.extra.get()).last_checked_userdata_mt.0 == mt_ptr { + (*self.extra.get()).last_checked_userdata_mt = (ptr::null(), None); + } + } // Returns `TypeId` for the userdata ref, checking that it's registered and not destructed. // @@ -1028,8 +1021,6 @@ impl RawLua { // Creates a Function out of a Callback containing a 'static Fn. pub(crate) fn create_callback(&self, func: Callback) -> Result { - // This is non-scoped version of the callback (upvalue is always valid) - // TODO: add a scoped version unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int { let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); callback_error_ext(state, (*upvalue).extra.get(), |extra, nargs| { @@ -1037,8 +1028,10 @@ impl RawLua { // The lock must be already held as the callback is executed let rawlua = (*extra).raw_lua(); let _guard = StateGuard::new(rawlua, state); - let func = &*(*upvalue).data; - func(rawlua, nargs) + match (*upvalue).data { + Some(ref func) => func(rawlua, nargs), + None => Err(Error::CallbackDestructed), + } }) } @@ -1047,6 +1040,7 @@ impl RawLua { let _sg = StackGuard::new(state); check_stack(state, 4)?; + let func = Some(func); let extra = XRc::clone(&self.extra); let protect = !self.unlikely_memory_error(); push_internal_userdata(state, CallbackUpvalue { data: func, extra }, protect)?; diff --git a/src/types.rs b/src/types.rs index 3f0d31d2..a62e56de 100644 --- a/src/types.rs +++ b/src/types.rs @@ -52,12 +52,14 @@ pub(crate) type Callback = Box Result + Send + #[cfg(not(feature = "send"))] pub(crate) type Callback = Box Result + 'static>; +pub(crate) type ScopedCallback<'s> = Box Result + 's>; + pub(crate) struct Upvalue { pub(crate) data: T, pub(crate) extra: XRc>, } -pub(crate) type CallbackUpvalue = Upvalue; +pub(crate) type CallbackUpvalue = Upvalue>; #[cfg(all(feature = "async", feature = "send"))] pub(crate) type AsyncCallback = diff --git a/src/userdata.rs b/src/userdata.rs index f28b96a6..5b7ca723 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -2,7 +2,7 @@ use std::any::TypeId; use std::ffi::CStr; use std::fmt; use std::hash::Hash; -use std::os::raw::{c_char, c_int, c_void}; +use std::os::raw::{c_char, c_void}; use std::string::String as StdString; #[cfg(feature = "async")] @@ -16,7 +16,7 @@ use { use crate::error::{Error, Result}; use crate::function::Function; -use crate::state::{Lua, LuaGuard}; +use crate::state::Lua; use crate::string::String; use crate::table::{Table, TablePairs}; use crate::types::{MaybeSend, SubtypeId, ValueRef}; @@ -24,14 +24,11 @@ use crate::util::{check_stack, get_userdata, take_userdata, StackGuard}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; // Re-export for convenience -pub(crate) use cell::UserDataVariant; +pub(crate) use cell::UserDataStorage; pub use cell::{UserDataRef, UserDataRefMut}; pub(crate) use registry::UserDataProxy; pub use registry::UserDataRegistry; -#[cfg(feature = "lua54")] -pub(crate) const USER_VALUE_MAXSLOT: usize = 8; - /// Kinds of metamethods that can be overridden. /// /// Currently, this mechanism does not allow overriding the `__gc` metamethod, since there is @@ -650,8 +647,9 @@ pub struct AnyUserData(pub(crate) ValueRef, pub(crate) SubtypeId); impl AnyUserData { /// Checks whether the type of this userdata is `T`. + #[inline] pub fn is(&self) -> bool { - self.inspect::(|_, _| Ok(())).is_ok() + self.inspect::(|_| Ok(())).is_ok() } /// Borrow this userdata immutably if it is of type `T`. @@ -659,10 +657,18 @@ impl AnyUserData { /// # Errors /// /// Returns a `UserDataBorrowError` if the userdata is already mutably borrowed. Returns a - /// `UserDataTypeMismatch` if the userdata is not of type `T`. + /// `UserDataTypeMismatch` if the userdata is not of type `T` or if it's scoped. #[inline] pub fn borrow(&self) -> Result> { - self.inspect(|variant, _| variant.try_borrow_owned()) + self.inspect(|ud| ud.try_borrow_owned()) + } + + /// Borrow this userdata immutably if it is of type `T`, passing the borrowed value + /// to the closure. + /// + /// This method is the only way to borrow scoped userdata (created inside [`Lua::scope`]). + pub fn borrow_scoped(&self, f: impl FnOnce(&T) -> R) -> Result { + self.inspect(|ud| ud.try_borrow_scoped(|ud| f(ud))) } /// Borrow this userdata mutably if it is of type `T`. @@ -670,10 +676,18 @@ impl AnyUserData { /// # Errors /// /// Returns a `UserDataBorrowMutError` if the userdata cannot be mutably borrowed. - /// Returns a `UserDataTypeMismatch` if the userdata is not of type `T`. + /// Returns a `UserDataTypeMismatch` if the userdata is not of type `T` or if it's scoped. #[inline] pub fn borrow_mut(&self) -> Result> { - self.inspect(|variant, _| variant.try_borrow_owned_mut()) + self.inspect(|ud| ud.try_borrow_owned_mut()) + } + + /// Borrow this userdata mutably if it is of type `T`, passing the borrowed value + /// to the closure. + /// + /// This method is the only way to borrow scoped userdata (created inside [`Lua::scope`]). + pub fn borrow_mut_scoped(&self, f: impl FnOnce(&mut T) -> R) -> Result { + self.inspect(|ud| ud.try_borrow_scoped_mut(|ud| f(ud))) } /// Takes the value out of this userdata. @@ -692,8 +706,8 @@ impl AnyUserData { match type_id { Some(type_id) if type_id == TypeId::of::() => { // Try to borrow userdata exclusively - let _ = (*get_userdata::>(state, -1)).try_borrow_mut()?; - take_userdata::>(state).into_inner() + let _ = (*get_userdata::>(state, -1)).try_borrow_mut()?; + take_userdata::>(state).into_inner() } _ => Err(Error::UserDataTypeMismatch), } @@ -754,29 +768,16 @@ impl AnyUserData { lua.push_userdata_ref(&self.0)?; lua.push(v)?; - #[cfg(feature = "lua54")] - if n < USER_VALUE_MAXSLOT { - ffi::lua_setiuservalue(state, -2, n as c_int); - return Ok(()); - } - // Multiple (extra) user values are emulated by storing them in a table protect_lua!(state, 2, 0, |state| { - if getuservalue_table(state, -2) != ffi::LUA_TTABLE { + if ffi::lua_getuservalue(state, -2) != ffi::LUA_TTABLE { // Create a new table to use as uservalue ffi::lua_pop(state, 1); ffi::lua_newtable(state); ffi::lua_pushvalue(state, -1); - - #[cfg(feature = "lua54")] - ffi::lua_setiuservalue(state, -4, USER_VALUE_MAXSLOT as c_int); - #[cfg(not(feature = "lua54"))] ffi::lua_setuservalue(state, -4); } ffi::lua_pushvalue(state, -2); - #[cfg(feature = "lua54")] - ffi::lua_rawseti(state, -2, (n - USER_VALUE_MAXSLOT + 1) as ffi::lua_Integer); - #[cfg(not(feature = "lua54"))] ffi::lua_rawseti(state, -2, n as ffi::lua_Integer); })?; @@ -806,21 +807,12 @@ impl AnyUserData { lua.push_userdata_ref(&self.0)?; - #[cfg(feature = "lua54")] - if n < USER_VALUE_MAXSLOT { - ffi::lua_getiuservalue(state, -1, n as c_int); - return V::from_lua(lua.pop_value(), lua.lua()); - } - // Multiple (extra) user values are emulated by storing them in a table protect_lua!(state, 1, 1, |state| { - if getuservalue_table(state, -1) != ffi::LUA_TTABLE { + if ffi::lua_getuservalue(state, -1) != ffi::LUA_TTABLE { ffi::lua_pushnil(state); return; } - #[cfg(feature = "lua54")] - ffi::lua_rawgeti(state, -1, (n - USER_VALUE_MAXSLOT + 1) as ffi::lua_Integer); - #[cfg(not(feature = "lua54"))] ffi::lua_rawgeti(state, -1, n as ffi::lua_Integer); })?; @@ -851,15 +843,11 @@ impl AnyUserData { // Multiple (extra) user values are emulated by storing them in a table protect_lua!(state, 2, 0, |state| { - if getuservalue_table(state, -2) != ffi::LUA_TTABLE { + if ffi::lua_getuservalue(state, -2) != ffi::LUA_TTABLE { // Create a new table to use as uservalue ffi::lua_pop(state, 1); ffi::lua_newtable(state); ffi::lua_pushvalue(state, -1); - - #[cfg(feature = "lua54")] - ffi::lua_setiuservalue(state, -4, USER_VALUE_MAXSLOT as c_int); - #[cfg(not(feature = "lua54"))] ffi::lua_setuservalue(state, -4); } ffi::lua_pushlstring(state, name.as_ptr() as *const c_char, name.len()); @@ -885,7 +873,7 @@ impl AnyUserData { // Multiple (extra) user values are emulated by storing them in a table protect_lua!(state, 1, 1, |state| { - if getuservalue_table(state, -1) != ffi::LUA_TTABLE { + if ffi::lua_getuservalue(state, -1) != ffi::LUA_TTABLE { ffi::lua_pushnil(state); return; } @@ -998,29 +986,24 @@ impl AnyUserData { let is_serializable = || unsafe { // Userdata must be registered and not destructed let _ = lua.get_userdata_ref_type_id(&self.0)?; - - let ud = &*get_userdata::>(lua.ref_thread(), self.0.index); - match ud { - UserDataVariant::Serializable(..) => Result::Ok(true), - _ => Result::Ok(false), - } + let ud = &*get_userdata::>(lua.ref_thread(), self.0.index); + Ok::<_, Error>((*ud).is_serializable()) }; is_serializable().unwrap_or(false) } - pub(crate) fn inspect<'a, T, F, R>(&'a self, func: F) -> Result + pub(crate) fn inspect(&self, func: F) -> Result where T: 'static, - F: FnOnce(&'a UserDataVariant, LuaGuard) -> Result, + F: FnOnce(&UserDataStorage) -> Result, { let lua = self.0.lua.lock(); unsafe { let type_id = lua.get_userdata_ref_type_id(&self.0)?; match type_id { Some(type_id) if type_id == TypeId::of::() => { - let ref_thread = lua.ref_thread(); - let ud = get_userdata::>(ref_thread, self.0.index); - func(&*ud, lua) + let ud = get_userdata::>(lua.ref_thread(), self.0.index); + func(&*ud) } _ => Err(Error::UserDataTypeMismatch), } @@ -1041,13 +1024,6 @@ impl AsRef for AnyUserData { } } -unsafe fn getuservalue_table(state: *mut ffi::lua_State, idx: c_int) -> c_int { - #[cfg(feature = "lua54")] - return ffi::lua_getiuservalue(state, idx, USER_VALUE_MAXSLOT as c_int); - #[cfg(not(feature = "lua54"))] - return ffi::lua_getuservalue(state, idx); -} - /// Handle to a `UserData` metatable. #[derive(Clone, Debug)] pub struct UserDataMetatable(pub(crate) Table); @@ -1146,7 +1122,7 @@ impl Serialize for AnyUserData { let _ = lua .get_userdata_ref_type_id(&self.0) .map_err(ser::Error::custom)?; - let ud = &*get_userdata::>(lua.ref_thread(), self.0.index); + let ud = &*get_userdata::>(lua.ref_thread(), self.0.index); ud.serialize(serializer) } } diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index 10313693..6df36126 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -1,5 +1,5 @@ use std::any::{type_name, TypeId}; -use std::cell::UnsafeCell; +use std::cell::{RefCell, UnsafeCell}; use std::fmt; use std::ops::{Deref, DerefMut}; use std::os::raw::c_int; @@ -22,6 +22,11 @@ type DynSerialize = dyn erased_serde::Serialize; #[cfg(all(feature = "serialize", feature = "send"))] type DynSerialize = dyn erased_serde::Serialize + Send; +pub(crate) enum UserDataStorage { + Owned(UserDataVariant), + Scoped(ScopedUserDataVariant), +} + // A enum for storing userdata values. // It's stored inside a Lua VM and protected by the outer `ReentrantMutex`. pub(crate) enum UserDataVariant { @@ -42,39 +47,34 @@ impl Clone for UserDataVariant { } impl UserDataVariant { - #[inline(always)] - pub(crate) fn new(data: T) -> Self { - Self::Default(XRc::new(UserDataCell::new(data))) - } - // Immutably borrows the wrapped value in-place. #[inline(always)] - pub(crate) fn try_borrow(&self) -> Result> { + fn try_borrow(&self) -> Result> { UserDataBorrowRef::try_from(self) } // Immutably borrows the wrapped value and returns an owned reference. #[inline(always)] - pub(crate) fn try_borrow_owned(&self) -> Result> { + fn try_borrow_owned(&self) -> Result> { UserDataRef::try_from(self.clone()) } // Mutably borrows the wrapped value in-place. #[inline(always)] - pub(crate) fn try_borrow_mut(&self) -> Result> { + fn try_borrow_mut(&self) -> Result> { UserDataBorrowMut::try_from(self) } // Mutably borrows the wrapped value and returns an owned reference. #[inline(always)] - pub(crate) fn try_borrow_owned_mut(&self) -> Result> { + fn try_borrow_owned_mut(&self) -> Result> { UserDataRefMut::try_from(self.clone()) } // Returns the wrapped value. // // This method checks that we have exclusive access to the value. - pub(crate) fn into_inner(self) -> Result { + fn into_inner(self) -> Result { if !self.raw_lock().try_lock_exclusive() { return Err(Error::UserDataBorrowMutError); } @@ -108,20 +108,10 @@ impl UserDataVariant { } #[cfg(feature = "serialize")] -impl UserDataVariant { - #[inline(always)] - pub(crate) fn new_ser(data: T) -> Self { - let data = Box::new(data) as Box; - Self::Serializable(XRc::new(UserDataCell::new(data))) - } -} - -#[cfg(feature = "serialize")] -impl Serialize for UserDataVariant<()> { +impl Serialize for UserDataStorage<()> { fn serialize(&self, serializer: S) -> std::result::Result { match self { - Self::Default(_) => Err(serde::ser::Error::custom("cannot serialize ")), - Self::Serializable(inner) => unsafe { + Self::Owned(UserDataVariant::Serializable(inner)) => unsafe { // We need to borrow the inner value exclusively to serialize it. #[cfg(feature = "send")] let _guard = self.try_borrow_mut().map_err(serde::ser::Error::custom)?; @@ -130,6 +120,7 @@ impl Serialize for UserDataVariant<()> { let _guard = self.try_borrow().map_err(serde::ser::Error::custom)?; (*inner.value.get()).serialize(serializer) }, + _ => Err(serde::ser::Error::custom("cannot serialize ")), } } } @@ -145,7 +136,7 @@ unsafe impl Sync for UserDataCell {} impl UserDataCell { #[inline(always)] - pub fn new(value: T) -> Self { + fn new(value: T) -> Self { UserDataCell { raw_lock: RawLock::INIT, value: UnsafeCell::new(value), @@ -207,7 +198,7 @@ impl FromLua for UserDataRef { let type_id = lua.get_userdata_type_id(idx)?; match type_id { Some(type_id) if type_id == TypeId::of::() => { - (*get_userdata::>(lua.state(), idx)).try_borrow_owned() + (*get_userdata::>(lua.state(), idx)).try_borrow_owned() } _ => Err(Error::UserDataTypeMismatch), } @@ -275,7 +266,7 @@ impl FromLua for UserDataRefMut { let type_id = lua.get_userdata_type_id(idx)?; match type_id { Some(type_id) if type_id == TypeId::of::() => { - (*get_userdata::>(lua.state(), idx)).try_borrow_owned_mut() + (*get_userdata::>(lua.state(), idx)).try_borrow_owned_mut() } _ => Err(Error::UserDataTypeMismatch), } @@ -363,6 +354,124 @@ fn try_value_to_userdata(value: Value) -> Result { } } +pub(crate) enum ScopedUserDataVariant { + Ref(*const T), + RefMut(RefCell<*mut T>), + Boxed(RefCell<*mut T>), +} + +impl Drop for ScopedUserDataVariant { + #[inline] + fn drop(&mut self) { + if let Self::Boxed(value) = self { + if let Ok(value) = value.try_borrow_mut() { + unsafe { drop(Box::from_raw(*value)) }; + } + } + } +} + +impl UserDataStorage { + #[inline(always)] + pub(crate) fn new(data: T) -> Self { + Self::Owned(UserDataVariant::Default(XRc::new(UserDataCell::new(data)))) + } + + #[inline(always)] + pub(crate) fn new_ref(data: &T) -> Self { + Self::Scoped(ScopedUserDataVariant::Ref(data)) + } + + #[inline(always)] + pub(crate) fn new_ref_mut(data: &mut T) -> Self { + Self::Scoped(ScopedUserDataVariant::RefMut(RefCell::new(data))) + } + + #[cfg(feature = "serialize")] + #[inline(always)] + pub(crate) fn new_ser(data: T) -> Self + where + T: Serialize + MaybeSend, + { + let data = Box::new(data) as Box; + Self::Owned(UserDataVariant::Serializable(XRc::new(UserDataCell::new(data)))) + } + + #[cfg(feature = "serialize")] + #[inline(always)] + pub(crate) fn is_serializable(&self) -> bool { + matches!(self, Self::Owned(UserDataVariant::Serializable(_))) + } + + // Immutably borrows the wrapped value and returns an owned reference. + #[inline(always)] + pub(crate) fn try_borrow_owned(&self) -> Result> { + match self { + Self::Owned(data) => data.try_borrow_owned(), + Self::Scoped(_) => Err(Error::UserDataTypeMismatch), + } + } + + #[inline(always)] + pub(crate) fn try_borrow_mut(&self) -> Result> { + match self { + Self::Owned(data) => data.try_borrow_mut(), + Self::Scoped(_) => Err(Error::UserDataTypeMismatch), + } + } + + // Mutably borrows the wrapped value and returns an owned reference. + #[inline(always)] + pub(crate) fn try_borrow_owned_mut(&self) -> Result> { + match self { + Self::Owned(data) => data.try_borrow_owned_mut(), + Self::Scoped(_) => Err(Error::UserDataTypeMismatch), + } + } + + #[inline(always)] + pub(crate) fn into_inner(self) -> Result { + match self { + Self::Owned(data) => data.into_inner(), + Self::Scoped(_) => Err(Error::UserDataTypeMismatch), + } + } +} + +impl UserDataStorage { + #[inline(always)] + pub(crate) fn new_scoped(data: T) -> Self { + let data = Box::into_raw(Box::new(data)); + Self::Scoped(ScopedUserDataVariant::Boxed(RefCell::new(data))) + } + + #[inline] + pub(crate) fn try_borrow_scoped(&self, f: impl FnOnce(&T) -> R) -> Result { + match self { + Self::Owned(data) => Ok(f(&*data.try_borrow()?)), + Self::Scoped(ScopedUserDataVariant::Ref(value)) => Ok(f(unsafe { &**value })), + Self::Scoped(ScopedUserDataVariant::RefMut(value) | ScopedUserDataVariant::Boxed(value)) => { + let t = value.try_borrow().map_err(|_| Error::UserDataBorrowError)?; + Ok(f(unsafe { &**t })) + } + } + } + + #[inline] + pub(crate) fn try_borrow_scoped_mut(&self, f: impl FnOnce(&mut T) -> R) -> Result { + match self { + Self::Owned(data) => Ok(f(&mut *data.try_borrow_mut()?)), + Self::Scoped(ScopedUserDataVariant::Ref(_)) => Err(Error::UserDataBorrowMutError), + Self::Scoped(ScopedUserDataVariant::RefMut(value) | ScopedUserDataVariant::Boxed(value)) => { + let mut t = value + .try_borrow_mut() + .map_err(|_| Error::UserDataBorrowMutError)?; + Ok(f(unsafe { &mut **t })) + } + } + } +} + #[cfg(test)] mod assertions { use super::*; diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index 79eb080b..ad4e3178 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -3,7 +3,7 @@ use std::any::TypeId; use std::cell::RefCell; use std::marker::PhantomData; -use std::os::raw::c_int; +use std::os::raw::c_void; use std::string::String as StdString; use crate::error::{Error, Result}; @@ -11,12 +11,11 @@ use crate::state::{Lua, RawLua}; use crate::types::{Callback, MaybeSend}; use crate::userdata::{ AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, UserDataRefMut, + UserDataStorage, }; use crate::util::{get_userdata, short_type_name}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; -use super::cell::{UserDataBorrowMut, UserDataBorrowRef, UserDataVariant}; - #[cfg(feature = "async")] use { crate::types::AsyncCallback, @@ -25,8 +24,14 @@ use { type StaticFieldCallback = Box Result<()> + 'static>; +#[derive(Clone, Copy)] +pub(crate) enum UserDataTypeId { + Shared(TypeId), + Unique(usize), +} + /// Handle to registry for userdata methods and metamethods. -pub struct UserDataRegistry { +pub struct UserDataRegistry { // Fields pub(crate) fields: Vec<(String, StaticFieldCallback)>, pub(crate) field_getters: Vec<(String, Callback)>, @@ -41,11 +46,13 @@ pub struct UserDataRegistry { #[cfg(feature = "async")] pub(crate) async_meta_methods: Vec<(String, AsyncCallback)>, + pub(crate) type_id: UserDataTypeId, _type: PhantomData, } -impl UserDataRegistry { - pub(crate) const fn new() -> Self { +impl UserDataRegistry { + #[inline] + pub(crate) fn new(type_id: TypeId) -> Self { UserDataRegistry { fields: Vec::new(), field_getters: Vec::new(), @@ -57,11 +64,38 @@ impl UserDataRegistry { meta_methods: Vec::new(), #[cfg(feature = "async")] async_meta_methods: Vec::new(), + type_id: UserDataTypeId::Shared(type_id), _type: PhantomData, } } - fn box_method(name: &str, method: M) -> Callback + #[inline] + pub(crate) fn new_unique(ud_ptr: *const c_void) -> Self { + UserDataRegistry { + fields: Vec::new(), + field_getters: Vec::new(), + field_setters: Vec::new(), + meta_fields: Vec::new(), + methods: Vec::new(), + #[cfg(feature = "async")] + async_methods: Vec::new(), + meta_methods: Vec::new(), + #[cfg(feature = "async")] + async_meta_methods: Vec::new(), + type_id: UserDataTypeId::Unique(ud_ptr as usize), + _type: PhantomData, + } + } + + #[inline] + pub(crate) fn type_id(&self) -> Option { + match self.type_id { + UserDataTypeId::Shared(type_id) => Some(type_id), + UserDataTypeId::Unique(_) => None, + } + } + + fn box_method(&self, name: &str, method: M) -> Callback where M: Fn(&Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -74,6 +108,7 @@ impl UserDataRegistry { }; } + let target_type_id = self.type_id; Box::new(move |rawlua, nargs| unsafe { if nargs == 0 { let err = Error::from_lua_conversion("missing argument", "userdata", None); @@ -85,17 +120,34 @@ impl UserDataRegistry { // Self was at position 1, so we pass 2 here let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua); - match try_self_arg!(rawlua.get_userdata_type_id(self_index)) { - Some(id) if id == TypeId::of::() => { - let ud = try_self_arg!(borrow_userdata_ref::(state, self_index)); - method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua) + match target_type_id { + // This branch is for `'static` userdata that share type metatable + UserDataTypeId::Shared(target_type_id) => { + match try_self_arg!(rawlua.get_userdata_type_id(self_index)) { + Some(self_type_id) if self_type_id == target_type_id => { + let ud = get_userdata::>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + })) + } + _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), + } + } + UserDataTypeId::Unique(target_ptr) => { + match get_userdata::>(state, self_index) { + ud if ud as usize == target_ptr => { + try_self_arg!((*ud).try_borrow_scoped(|ud| { + method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + })) + } + _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), + } } - _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), } }) } - fn box_method_mut(name: &str, method: M) -> Callback + fn box_method_mut(&self, name: &str, method: M) -> Callback where M: FnMut(&Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -109,6 +161,7 @@ impl UserDataRegistry { } let method = RefCell::new(method); + let target_type_id = self.type_id; Box::new(move |rawlua, nargs| unsafe { let mut method = method.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?; if nargs == 0 { @@ -121,19 +174,37 @@ impl UserDataRegistry { // Self was at position 1, so we pass 2 here let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua); - match try_self_arg!(rawlua.get_userdata_type_id(self_index)) { - Some(id) if id == TypeId::of::() => { - let mut ud = try_self_arg!(borrow_userdata_mut::(state, self_index)); - method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua) + match target_type_id { + // This branch is for `'static` userdata that share type metatable + UserDataTypeId::Shared(target_type_id) => { + match try_self_arg!(rawlua.get_userdata_type_id(self_index)) { + Some(self_type_id) if self_type_id == target_type_id => { + let ud = get_userdata::>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped_mut(|ud| { + method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + })) + } + _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), + } + } + UserDataTypeId::Unique(target_ptr) => { + match get_userdata::>(state, self_index) { + ud if ud as usize == target_ptr => { + try_self_arg!((*ud).try_borrow_scoped_mut(|ud| { + method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + })) + } + _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), + } } - _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), } }) } #[cfg(feature = "async")] - fn box_async_method(name: &str, method: M) -> AsyncCallback + fn box_async_method(&self, name: &str, method: M) -> AsyncCallback where + T: 'static, M: Fn(Lua, UserDataRef, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, MR: Future> + MaybeSend + 'static, @@ -171,8 +242,9 @@ impl UserDataRegistry { } #[cfg(feature = "async")] - fn box_async_method_mut(name: &str, method: M) -> AsyncCallback + fn box_async_method_mut(&self, name: &str, method: M) -> AsyncCallback where + T: 'static, M: Fn(Lua, UserDataRefMut, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, MR: Future> + MaybeSend + 'static, @@ -209,7 +281,7 @@ impl UserDataRegistry { }) } - fn box_function(name: &str, function: F) -> Callback + fn box_function(&self, name: &str, function: F) -> Callback where F: Fn(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -222,7 +294,7 @@ impl UserDataRegistry { }) } - fn box_function_mut(name: &str, function: F) -> Callback + fn box_function_mut(&self, name: &str, function: F) -> Callback where F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -240,7 +312,7 @@ impl UserDataRegistry { } #[cfg(feature = "async")] - fn box_async_function(name: &str, function: F) -> AsyncCallback + fn box_async_function(&self, name: &str, function: F) -> AsyncCallback where F: Fn(Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, @@ -282,7 +354,7 @@ fn get_function_name(name: &str) -> StdString { format!("{}.{name}", short_type_name::()) } -impl UserDataFields for UserDataRegistry { +impl UserDataFields for UserDataRegistry { fn add_field(&mut self, name: impl ToString, value: V) where V: IntoLua + 'static, @@ -300,7 +372,7 @@ impl UserDataFields for UserDataRegistry { R: IntoLua, { let name = name.to_string(); - let callback = Self::box_method(&name, move |lua, data, ()| method(lua, data)); + let callback = self.box_method(&name, move |lua, data, ()| method(lua, data)); self.field_getters.push((name, callback)); } @@ -310,7 +382,7 @@ impl UserDataFields for UserDataRegistry { A: FromLua, { let name = name.to_string(); - let callback = Self::box_method_mut(&name, method); + let callback = self.box_method_mut(&name, method); self.field_setters.push((name, callback)); } @@ -320,7 +392,7 @@ impl UserDataFields for UserDataRegistry { R: IntoLua, { let name = name.to_string(); - let callback = Self::box_function(&name, function); + let callback = self.box_function(&name, function); self.field_getters.push((name, callback)); } @@ -330,7 +402,7 @@ impl UserDataFields for UserDataRegistry { A: FromLua, { let name = name.to_string(); - let callback = Self::box_function_mut(&name, move |lua, (data, val)| function(lua, data, val)); + let callback = self.box_function_mut(&name, move |lua, (data, val)| function(lua, data, val)); self.field_setters.push((name, callback)); } @@ -363,7 +435,7 @@ impl UserDataFields for UserDataRegistry { } } -impl UserDataMethods for UserDataRegistry { +impl UserDataMethods for UserDataRegistry { fn add_method(&mut self, name: impl ToString, method: M) where M: Fn(&Lua, &T, A) -> Result + MaybeSend + 'static, @@ -371,7 +443,7 @@ impl UserDataMethods for UserDataRegistry { R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_method(&name, method); + let callback = self.box_method(&name, method); self.methods.push((name, callback)); } @@ -382,33 +454,35 @@ impl UserDataMethods for UserDataRegistry { R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_method_mut(&name, method); + let callback = self.box_method_mut(&name, method); self.methods.push((name, callback)); } #[cfg(feature = "async")] fn add_async_method(&mut self, name: impl ToString, method: M) where + T: 'static, M: Fn(Lua, UserDataRef, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, MR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_async_method(&name, method); + let callback = self.box_async_method(&name, method); self.async_methods.push((name, callback)); } #[cfg(feature = "async")] fn add_async_method_mut(&mut self, name: impl ToString, method: M) where + T: 'static, M: Fn(Lua, UserDataRefMut, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, MR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_async_method_mut(&name, method); + let callback = self.box_async_method_mut(&name, method); self.async_methods.push((name, callback)); } @@ -419,7 +493,7 @@ impl UserDataMethods for UserDataRegistry { R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_function(&name, function); + let callback = self.box_function(&name, function); self.methods.push((name, callback)); } @@ -430,7 +504,7 @@ impl UserDataMethods for UserDataRegistry { R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_function_mut(&name, function); + let callback = self.box_function_mut(&name, function); self.methods.push((name, callback)); } @@ -443,7 +517,7 @@ impl UserDataMethods for UserDataRegistry { R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_async_function(&name, function); + let callback = self.box_async_function(&name, function); self.async_methods.push((name, callback)); } @@ -454,7 +528,7 @@ impl UserDataMethods for UserDataRegistry { R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_method(&name, method); + let callback = self.box_method(&name, method); self.meta_methods.push((name, callback)); } @@ -465,33 +539,35 @@ impl UserDataMethods for UserDataRegistry { R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_method_mut(&name, method); + let callback = self.box_method_mut(&name, method); self.meta_methods.push((name, callback)); } #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] fn add_async_meta_method(&mut self, name: impl ToString, method: M) where + T: 'static, M: Fn(Lua, UserDataRef, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, MR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_async_method(&name, method); + let callback = self.box_async_method(&name, method); self.async_meta_methods.push((name, callback)); } #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] fn add_async_meta_method_mut(&mut self, name: impl ToString, method: M) where + T: 'static, M: Fn(Lua, UserDataRefMut, A) -> MR + MaybeSend + 'static, A: FromLuaMulti, MR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_async_method_mut(&name, method); + let callback = self.box_async_method_mut(&name, method); self.async_meta_methods.push((name, callback)); } @@ -502,7 +578,7 @@ impl UserDataMethods for UserDataRegistry { R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_function(&name, function); + let callback = self.box_function(&name, function); self.meta_methods.push((name, callback)); } @@ -513,7 +589,7 @@ impl UserDataMethods for UserDataRegistry { R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_function_mut(&name, function); + let callback = self.box_function_mut(&name, function); self.meta_methods.push((name, callback)); } @@ -526,36 +602,17 @@ impl UserDataMethods for UserDataRegistry { R: IntoLuaMulti, { let name = name.to_string(); - let callback = Self::box_async_function(&name, function); + let callback = self.box_async_function(&name, function); self.async_meta_methods.push((name, callback)); } } -// Borrow the userdata in-place from the Lua stack -#[inline(always)] -unsafe fn borrow_userdata_ref<'a, T>( - state: *mut ffi::lua_State, - index: c_int, -) -> Result> { - let ud = get_userdata::>(state, index); - (*ud).try_borrow() -} - -// Borrow the userdata mutably in-place from the Lua stack -#[inline(always)] -unsafe fn borrow_userdata_mut<'a, T>( - state: *mut ffi::lua_State, - index: c_int, -) -> Result> { - let ud = get_userdata::>(state, index); - (*ud).try_borrow_mut() -} - macro_rules! lua_userdata_impl { ($type:ty) => { impl UserData for $type { fn register(registry: &mut UserDataRegistry) { - let mut orig_registry = UserDataRegistry::new(); + let type_id = TypeId::of::(); + let mut orig_registry = UserDataRegistry::new(type_id); T::register(&mut orig_registry); // Copy all fields, methods, etc. from the original registry diff --git a/src/util/error.rs b/src/util/error.rs index 0cd4abef..05814171 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -365,7 +365,7 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<( // Create destructed userdata metatable unsafe extern "C-unwind" fn destructed_error(state: *mut ffi::lua_State) -> c_int { - callback_error(state, |_| Err(Error::CallbackDestructed)) + callback_error(state, |_| Err(Error::UserDataDestructed)) } push_table(state, 0, 26, true)?; diff --git a/src/util/mod.rs b/src/util/mod.rs index 93519c27..b0f886f0 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -17,10 +17,9 @@ pub(crate) use userdata::{ DESTRUCTED_USERDATA_METATABLE, }; -#[cfg(not(feature = "lua54"))] +#[cfg(not(feature = "luau"))] +pub(crate) use userdata::push_uninit_userdata; pub(crate) use userdata::push_userdata; -#[cfg(feature = "lua54")] -pub(crate) use userdata::push_userdata_uv; #[cfg(not(feature = "luau"))] pub(crate) use userdata::userdata_destructor; diff --git a/src/util/userdata.rs b/src/util/userdata.rs index 59c00223..321bb903 100644 --- a/src/util/userdata.rs +++ b/src/util/userdata.rs @@ -83,46 +83,34 @@ pub(crate) unsafe fn get_internal_userdata( // Internally uses 3 stack spaces, does not call checkstack. #[inline] -pub(crate) unsafe fn push_userdata(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<()> { - #[cfg(not(feature = "luau"))] - let ud = if protect { +#[cfg(not(feature = "luau"))] +pub(crate) unsafe fn push_uninit_userdata(state: *mut ffi::lua_State, protect: bool) -> Result<*mut T> { + if protect { protect_lua!(state, 0, 1, |state| { ffi::lua_newuserdata(state, std::mem::size_of::()) as *mut T - })? - } else { - ffi::lua_newuserdata(state, std::mem::size_of::()) as *mut T - }; - #[cfg(feature = "luau")] - let ud = if protect { - protect_lua!(state, 0, 1, |state| { ffi::lua_newuserdata_t::(state) })? + }) } else { - ffi::lua_newuserdata_t::(state) - }; - ptr::write(ud, t); - Ok(()) + Ok(ffi::lua_newuserdata(state, std::mem::size_of::()) as *mut T) + } } // Internally uses 3 stack spaces, does not call checkstack. -#[cfg(feature = "lua54")] #[inline] -pub(crate) unsafe fn push_userdata_uv( - state: *mut ffi::lua_State, - t: T, - nuvalue: c_int, - protect: bool, -) -> Result<()> { - let ud = if protect { - protect_lua!(state, 0, 1, |state| { - ffi::lua_newuserdatauv(state, std::mem::size_of::(), nuvalue) as *mut T - })? +pub(crate) unsafe fn push_userdata(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<*mut T> { + #[cfg(not(feature = "luau"))] + let ud_ptr = push_uninit_userdata(state, protect)?; + #[cfg(feature = "luau")] + let ud_ptr = if protect { + protect_lua!(state, 0, 1, |state| { ffi::lua_newuserdata_t::(state) })? } else { - ffi::lua_newuserdatauv(state, std::mem::size_of::(), nuvalue) as *mut T + ffi::lua_newuserdata_t::(state) }; - ptr::write(ud, t); - Ok(()) + ptr::write(ud_ptr, t); + Ok(ud_ptr) } #[inline] +#[track_caller] pub(crate) unsafe fn get_userdata(state: *mut ffi::lua_State, index: c_int) -> *mut T { let ud = ffi::lua_touserdata(state, index) as *mut T; mlua_debug_assert!(!ud.is_null(), "userdata pointer is null"); diff --git a/tests/compile.rs b/tests/compile.rs index f237cad2..c8ee4511 100644 --- a/tests/compile.rs +++ b/tests/compile.rs @@ -7,8 +7,6 @@ fn test_compilation() { t.compile_fail("tests/compile/lua_norefunwindsafe.rs"); t.compile_fail("tests/compile/ref_nounwindsafe.rs"); t.compile_fail("tests/compile/scope_callback_capture.rs"); - t.compile_fail("tests/compile/scope_callback_inner.rs"); - t.compile_fail("tests/compile/scope_callback_outer.rs"); t.compile_fail("tests/compile/scope_invariance.rs"); t.compile_fail("tests/compile/scope_mutable_aliasing.rs"); t.compile_fail("tests/compile/scope_userdata_borrow.rs"); @@ -17,7 +15,6 @@ fn test_compilation() { { t.compile_fail("tests/compile/async_any_userdata_method.rs"); t.compile_fail("tests/compile/async_nonstatic_userdata.rs"); - t.compile_fail("tests/compile/async_userdata_method.rs"); } #[cfg(feature = "send")] diff --git a/tests/compile/async_any_userdata_method.rs b/tests/compile/async_any_userdata_method.rs index a9f2f8d7..680eaebd 100644 --- a/tests/compile/async_any_userdata_method.rs +++ b/tests/compile/async_any_userdata_method.rs @@ -1,4 +1,4 @@ -use mlua::{UserDataMethods, Lua}; +use mlua::{Lua, UserDataMethods}; fn main() { let lua = Lua::new(); @@ -6,9 +6,10 @@ fn main() { lua.register_userdata_type::(|reg| { let s = String::new(); let mut s = &s; - reg.add_async_method("t", |_, this: &String, ()| async { - s = this; + reg.add_async_method("t", |_, this, ()| async { + s = &*this; Ok(()) }); - }).unwrap(); + }) + .unwrap(); } diff --git a/tests/compile/async_any_userdata_method.stderr b/tests/compile/async_any_userdata_method.stderr index a33b5b6e..970feeff 100644 --- a/tests/compile/async_any_userdata_method.stderr +++ b/tests/compile/async_any_userdata_method.stderr @@ -1,20 +1,42 @@ error[E0596]: cannot borrow `s` as mutable, as it is a captured variable in a `Fn` closure - --> tests/compile/async_any_userdata_method.rs:9:58 + --> tests/compile/async_any_userdata_method.rs:9:49 | -9 | reg.add_async_method("t", |_, this: &String, ()| async { - | ^^^^^ cannot borrow as mutable -10 | s = this; +9 | reg.add_async_method("t", |_, this, ()| async { + | ^^^^^ cannot borrow as mutable +10 | s = &*this; | - mutable borrow occurs due to use of `s` in closure +error[E0373]: async block may outlive the current function, but it borrows `this`, which is owned by the current function + --> tests/compile/async_any_userdata_method.rs:9:49 + | +9 | reg.add_async_method("t", |_, this, ()| async { + | ^^^^^ may outlive borrowed value `this` +10 | s = &*this; + | ---- `this` is borrowed here + | +note: async block is returned here + --> tests/compile/async_any_userdata_method.rs:9:49 + | +9 | reg.add_async_method("t", |_, this, ()| async { + | _________________________________________________^ +10 | | s = &*this; +11 | | Ok(()) +12 | | }); + | |_________^ +help: to force the async block to take ownership of `this` (and any other referenced variables), use the `move` keyword + | +9 | reg.add_async_method("t", |_, this, ()| async move { + | ++++ + error: lifetime may not live long enough - --> tests/compile/async_any_userdata_method.rs:9:58 + --> tests/compile/async_any_userdata_method.rs:9:49 | -9 | reg.add_async_method("t", |_, this: &String, ()| async { - | ___________________________________----------------------_^ - | | | | - | | | return type of closure `{async block@$DIR/tests/compile/async_any_userdata_method.rs:9:58: 9:63}` contains a lifetime `'2` +9 | reg.add_async_method("t", |_, this, ()| async { + | ___________________________________-------------_^ + | | | | + | | | return type of closure `{async block@$DIR/tests/compile/async_any_userdata_method.rs:9:49: 9:54}` contains a lifetime `'2` | | lifetime `'1` represents this closure's body -10 | | s = this; +10 | | s = &*this; 11 | | Ok(()) 12 | | }); | |_________^ returning this value requires that `'1` must outlive `'2` @@ -28,53 +50,31 @@ error[E0597]: `s` does not live long enough | - binding `s` declared here 8 | let mut s = &s; | ^^ borrowed value does not live long enough -9 | / reg.add_async_method("t", |_, this: &String, ()| async { -10 | | s = this; +9 | / reg.add_async_method("t", |_, this, ()| async { +10 | | s = &*this; 11 | | Ok(()) 12 | | }); | |__________- argument requires that `s` is borrowed for `'static` -13 | }).unwrap(); +13 | }) | - `s` dropped here while still borrowed -error[E0521]: borrowed data escapes outside of closure - --> tests/compile/async_any_userdata_method.rs:9:9 - | -6 | lua.register_userdata_type::(|reg| { - | --- - | | - | `reg` is a reference that is only valid in the closure body - | has type `&mut LuaUserDataRegistry<'1, std::string::String>` -... -9 | / reg.add_async_method("t", |_, this: &String, ()| async { -10 | | s = this; -11 | | Ok(()) -12 | | }); - | | ^ - | | | - | |__________`reg` escapes the closure body here - | argument requires that `'1` must outlive `'static` - | - = note: requirement occurs because of a mutable reference to `LuaUserDataRegistry<'_, std::string::String>` - = note: mutable references are invariant over their type parameter - = help: see for more information about variance - error[E0373]: closure may outlive the current function, but it borrows `s`, which is owned by the current function --> tests/compile/async_any_userdata_method.rs:9:35 | -9 | reg.add_async_method("t", |_, this: &String, ()| async { - | ^^^^^^^^^^^^^^^^^^^^^^ may outlive borrowed value `s` -10 | s = this; +9 | reg.add_async_method("t", |_, this, ()| async { + | ^^^^^^^^^^^^^ may outlive borrowed value `s` +10 | s = &*this; | - `s` is borrowed here | note: function requires argument type to outlive `'static` --> tests/compile/async_any_userdata_method.rs:9:9 | -9 | / reg.add_async_method("t", |_, this: &String, ()| async { -10 | | s = this; +9 | / reg.add_async_method("t", |_, this, ()| async { +10 | | s = &*this; 11 | | Ok(()) 12 | | }); | |__________^ help: to force the closure to take ownership of `s` (and any other referenced variables), use the `move` keyword | -9 | reg.add_async_method("t", move |_, this: &String, ()| async { +9 | reg.add_async_method("t", move |_, this, ()| async { | ++++ diff --git a/tests/compile/async_nonstatic_userdata.rs b/tests/compile/async_nonstatic_userdata.rs index 8b5a7cff..d4a73eb9 100644 --- a/tests/compile/async_nonstatic_userdata.rs +++ b/tests/compile/async_nonstatic_userdata.rs @@ -4,8 +4,8 @@ fn main() { #[derive(Clone)] struct MyUserData<'a>(&'a i64); - impl<'a> UserData for MyUserData<'a> { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + impl UserData for MyUserData<'_> { + fn add_methods>(methods: &mut M) { methods.add_async_method("print", |_, data, ()| async move { println!("{}", data.0); Ok(()) diff --git a/tests/compile/async_nonstatic_userdata.stderr b/tests/compile/async_nonstatic_userdata.stderr index 316feb10..368e9f34 100644 --- a/tests/compile/async_nonstatic_userdata.stderr +++ b/tests/compile/async_nonstatic_userdata.stderr @@ -1,11 +1,11 @@ error: lifetime may not live long enough --> tests/compile/async_nonstatic_userdata.rs:9:13 | -7 | impl<'a> UserData for MyUserData<'a> { - | -- lifetime `'a` defined here -8 | fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { +7 | impl UserData for MyUserData<'_> { + | -- lifetime `'1` appears in the `impl`'s self type +8 | fn add_methods>(methods: &mut M) { 9 | / methods.add_async_method("print", |_, data, ()| async move { 10 | | println!("{}", data.0); 11 | | Ok(()) 12 | | }); - | |______________^ requires that `'a` must outlive `'static` + | |______________^ requires that `'1` must outlive `'static` diff --git a/tests/compile/async_userdata_method.rs b/tests/compile/async_userdata_method.rs deleted file mode 100644 index 7b07dc13..00000000 --- a/tests/compile/async_userdata_method.rs +++ /dev/null @@ -1,14 +0,0 @@ -use mlua::{UserData, UserDataMethods}; - -struct MyUserData; - -impl UserData for MyUserData { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_async_method("method", |_, this: &'static Self, ()| async { - Ok(()) - }); - // ^ lifetime may not live long enough - } -} - -fn main() {} diff --git a/tests/compile/async_userdata_method.stderr b/tests/compile/async_userdata_method.stderr deleted file mode 100644 index a7f25a22..00000000 --- a/tests/compile/async_userdata_method.stderr +++ /dev/null @@ -1,17 +0,0 @@ -warning: unused variable: `this` - --> tests/compile/async_userdata_method.rs:7:48 - | -7 | methods.add_async_method("method", |_, this: &'static Self, ()| async { - | ^^^^ help: if this is intentional, prefix it with an underscore: `_this` - | - = note: `#[warn(unused_variables)]` on by default - -error: lifetime may not live long enough - --> tests/compile/async_userdata_method.rs:7:9 - | -6 | fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { - | ---- lifetime `'lua` defined here -7 | / methods.add_async_method("method", |_, this: &'static Self, ()| async { -8 | | Ok(()) -9 | | }); - | |__________^ argument requires that `'lua` must outlive `'static` diff --git a/tests/compile/function_borrow.rs b/tests/compile/function_borrow.rs index f64f3b8f..2c532361 100644 --- a/tests/compile/function_borrow.rs +++ b/tests/compile/function_borrow.rs @@ -6,7 +6,5 @@ fn main() { let test = Test(0); let lua = Lua::new(); - let _ = lua.create_function(|_, ()| -> Result { - Ok(test.0) - }); + let _ = lua.create_function(|_, ()| -> Result { Ok(test.0) }); } diff --git a/tests/compile/function_borrow.stderr b/tests/compile/function_borrow.stderr index 16a78a32..5bc66d65 100644 --- a/tests/compile/function_borrow.stderr +++ b/tests/compile/function_borrow.stderr @@ -1,20 +1,17 @@ error[E0373]: closure may outlive the current function, but it borrows `test.0`, which is owned by the current function - --> tests/compile/function_borrow.rs:9:33 - | -9 | let _ = lua.create_function(|_, ()| -> Result { - | ^^^^^^^^^^^^^^^^^^^^^^ may outlive borrowed value `test.0` -10 | Ok(test.0) - | ------ `test.0` is borrowed here - | + --> tests/compile/function_borrow.rs:9:33 + | +9 | let _ = lua.create_function(|_, ()| -> Result { Ok(test.0) }); + | ^^^^^^^^^^^^^^^^^^^^^^ ------ `test.0` is borrowed here + | | + | may outlive borrowed value `test.0` + | note: function requires argument type to outlive `'static` - --> tests/compile/function_borrow.rs:9:13 - | -9 | let _ = lua.create_function(|_, ()| -> Result { - | _____________^ -10 | | Ok(test.0) -11 | | }); - | |______^ + --> tests/compile/function_borrow.rs:9:13 + | +9 | let _ = lua.create_function(|_, ()| -> Result { Ok(test.0) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: to force the closure to take ownership of `test.0` (and any other referenced variables), use the `move` keyword - | -9 | let _ = lua.create_function(move |_, ()| -> Result { - | ++++ + | +9 | let _ = lua.create_function(move |_, ()| -> Result { Ok(test.0) }); + | ++++ diff --git a/tests/compile/lua_norefunwindsafe.stderr b/tests/compile/lua_norefunwindsafe.stderr index a4410fec..ea2442bd 100644 --- a/tests/compile/lua_norefunwindsafe.stderr +++ b/tests/compile/lua_norefunwindsafe.stderr @@ -1,32 +1,36 @@ -error[E0277]: the type `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary +error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary --> tests/compile/lua_norefunwindsafe.rs:7:18 | 7 | catch_unwind(|| lua.create_table().unwrap()); - | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary | | | required by a bound introduced by this call | - = help: within `mlua::types::sync::inner::ReentrantMutex`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<*mut lua_State>`, which is required by `{closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:20}: UnwindSafe` -note: required because it appears within the type `Cell<*mut lua_State>` - --> $RUST/core/src/cell.rs + = help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell`, which is required by `{closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:20}: UnwindSafe` +note: required because it appears within the type `lock_api::remutex::ReentrantMutex` + --> $CARGO/lock_api-0.4.12/src/remutex.rs | - | pub struct Cell { - | ^^^^ -note: required because it appears within the type `mlua::state::raw::RawLua` - --> src/state/raw.rs + | pub struct ReentrantMutex { + | ^^^^^^^^^^^^^^ +note: required because it appears within the type `alloc::sync::ArcInner>` + --> $RUST/alloc/src/sync.rs + | + | struct ArcInner { + | ^^^^^^^^ +note: required because it appears within the type `PhantomData>>` + --> $RUST/core/src/marker.rs | - | pub struct RawLua { - | ^^^^^^ -note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex` - --> src/types/sync.rs + | pub struct PhantomData; + | ^^^^^^^^^^^ +note: required because it appears within the type `Arc>` + --> $RUST/alloc/src/sync.rs | - | pub(crate) struct ReentrantMutex(T); - | ^^^^^^^^^^^^^^ - = note: required for `Rc>` to implement `RefUnwindSafe` + | pub struct Arc< + | ^^^ note: required because it appears within the type `Lua` --> src/state.rs | - | pub struct Lua(XRc>); + | pub struct Lua { | ^^^ = note: required for `&Lua` to implement `UnwindSafe` note: required because it's used within this closure @@ -40,31 +44,49 @@ note: required by a bound in `std::panic::catch_unwind` | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { | ^^^^^^^^^^ required by this bound in `catch_unwind` -error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary +error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary --> tests/compile/lua_norefunwindsafe.rs:7:18 | 7 | catch_unwind(|| lua.create_table().unwrap()); - | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary | | | required by a bound introduced by this call | - = help: the trait `RefUnwindSafe` is not implemented for `UnsafeCell`, which is required by `{closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:20}: UnwindSafe` - = note: required for `Rc>` to implement `RefUnwindSafe` -note: required because it appears within the type `mlua::state::raw::RawLua` - --> src/state/raw.rs + = help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell`, which is required by `{closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:20}: UnwindSafe` +note: required because it appears within the type `Cell` + --> $RUST/core/src/cell.rs | - | pub struct RawLua { - | ^^^^^^ -note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex` - --> src/types/sync.rs + | pub struct Cell { + | ^^^^ +note: required because it appears within the type `lock_api::remutex::RawReentrantMutex` + --> $CARGO/lock_api-0.4.12/src/remutex.rs + | + | pub struct RawReentrantMutex { + | ^^^^^^^^^^^^^^^^^ +note: required because it appears within the type `lock_api::remutex::ReentrantMutex` + --> $CARGO/lock_api-0.4.12/src/remutex.rs + | + | pub struct ReentrantMutex { + | ^^^^^^^^^^^^^^ +note: required because it appears within the type `alloc::sync::ArcInner>` + --> $RUST/alloc/src/sync.rs | - | pub(crate) struct ReentrantMutex(T); - | ^^^^^^^^^^^^^^ - = note: required for `Rc>` to implement `RefUnwindSafe` + | struct ArcInner { + | ^^^^^^^^ +note: required because it appears within the type `PhantomData>>` + --> $RUST/core/src/marker.rs + | + | pub struct PhantomData; + | ^^^^^^^^^^^ +note: required because it appears within the type `Arc>` + --> $RUST/alloc/src/sync.rs + | + | pub struct Arc< + | ^^^ note: required because it appears within the type `Lua` --> src/state.rs | - | pub struct Lua(XRc>); + | pub struct Lua { | ^^^ = note: required for `&Lua` to implement `UnwindSafe` note: required because it's used within this closure diff --git a/tests/compile/non_send.rs b/tests/compile/non_send.rs index cd21f445..6f6b1e99 100644 --- a/tests/compile/non_send.rs +++ b/tests/compile/non_send.rs @@ -8,10 +8,8 @@ fn main() -> Result<()> { let data = Rc::new(Cell::new(0)); - lua.create_function(move |_, ()| { - Ok(data.get()) - })? - .call::(())?; + lua.create_function(move |_, ()| Ok(data.get()))? + .call::(())?; Ok(()) } diff --git a/tests/compile/non_send.stderr b/tests/compile/non_send.stderr index 408b682f..c7e28da1 100644 --- a/tests/compile/non_send.stderr +++ b/tests/compile/non_send.stderr @@ -1,28 +1,25 @@ error[E0277]: `Rc>` cannot be sent between threads safely --> tests/compile/non_send.rs:11:25 | -11 | lua.create_function(move |_, ()| { - | --------------- ^----------- - | | | - | _________|_______________within this `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}` - | | | - | | required by a bound introduced by this call -12 | | Ok(data.get()) -13 | | })? - | |_____^ `Rc>` cannot be sent between threads safely +11 | lua.create_function(move |_, ()| Ok(data.get()))? + | --------------- ------------^^^^^^^^^^^^^^^ + | | | + | | `Rc>` cannot be sent between threads safely + | | within this `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}` + | required by a bound introduced by this call | - = help: within `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}`, the trait `Send` is not implemented for `Rc>` + = help: within `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}`, the trait `Send` is not implemented for `Rc>`, which is required by `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}: MaybeSend` note: required because it's used within this closure --> tests/compile/non_send.rs:11:25 | -11 | lua.create_function(move |_, ()| { +11 | lua.create_function(move |_, ()| Ok(data.get()))? | ^^^^^^^^^^^^ - = note: required for `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}` to implement `mlua::types::MaybeSend` + = note: required for `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}` to implement `MaybeSend` note: required by a bound in `Lua::create_function` - --> src/lua.rs + --> src/state.rs | - | pub fn create_function<'lua, A, R, F>(&'lua self, func: F) -> Result> + | pub fn create_function(&self, func: F) -> Result | --------------- required by a bound in this associated function -... - | F: Fn(&'lua Lua, A) -> Result + MaybeSend + 'static, - | ^^^^^^^^^ required by this bound in `Lua::create_function` + | where + | F: Fn(&Lua, A) -> Result + MaybeSend + 'static, + | ^^^^^^^^^ required by this bound in `Lua::create_function` diff --git a/tests/compile/ref_nounwindsafe.stderr b/tests/compile/ref_nounwindsafe.stderr index 4c82de1b..39e70812 100644 --- a/tests/compile/ref_nounwindsafe.stderr +++ b/tests/compile/ref_nounwindsafe.stderr @@ -1,25 +1,25 @@ -error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary +error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary --> tests/compile/ref_nounwindsafe.rs:8:18 | 8 | catch_unwind(move || table.set("a", "b").unwrap()); - | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary | | | required by a bound introduced by this call | - = help: within `rc::RcBox>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell`, which is required by `{closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:25}: UnwindSafe` -note: required because it appears within the type `Cell` - --> $RUST/core/src/cell.rs + = help: within `alloc::sync::ArcInner>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell`, which is required by `{closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:25}: UnwindSafe` +note: required because it appears within the type `lock_api::remutex::ReentrantMutex` + --> $CARGO/lock_api-0.4.12/src/remutex.rs | - | pub struct Cell { - | ^^^^ -note: required because it appears within the type `rc::RcBox>` - --> $RUST/alloc/src/rc.rs + | pub struct ReentrantMutex { + | ^^^^^^^^^^^^^^ +note: required because it appears within the type `alloc::sync::ArcInner>` + --> $RUST/alloc/src/sync.rs | - | struct RcBox { - | ^^^^^ - = note: required for `NonNull>>` to implement `UnwindSafe` -note: required because it appears within the type `std::rc::Weak>` - --> $RUST/alloc/src/rc.rs + | struct ArcInner { + | ^^^^^^^^ + = note: required for `NonNull>>` to implement `UnwindSafe` +note: required because it appears within the type `std::sync::Weak>` + --> $RUST/alloc/src/sync.rs | | pub struct Weak< | ^^^^ @@ -49,95 +49,38 @@ note: required by a bound in `std::panic::catch_unwind` | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { | ^^^^^^^^^^ required by this bound in `catch_unwind` -error[E0277]: the type `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary +error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary --> tests/compile/ref_nounwindsafe.rs:8:18 | 8 | catch_unwind(move || table.set("a", "b").unwrap()); - | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary | | | required by a bound introduced by this call | - = help: within `rc::RcBox>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<*mut lua_State>`, which is required by `{closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:25}: UnwindSafe` -note: required because it appears within the type `Cell<*mut lua_State>` + = help: within `alloc::sync::ArcInner>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell`, which is required by `{closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:25}: UnwindSafe` +note: required because it appears within the type `Cell` --> $RUST/core/src/cell.rs | | pub struct Cell { | ^^^^ -note: required because it appears within the type `mlua::state::raw::RawLua` - --> src/state/raw.rs - | - | pub struct RawLua { - | ^^^^^^ -note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex` - --> src/types/sync.rs - | - | pub(crate) struct ReentrantMutex(T); - | ^^^^^^^^^^^^^^ -note: required because it appears within the type `rc::RcBox>` - --> $RUST/alloc/src/rc.rs - | - | struct RcBox { - | ^^^^^ - = note: required for `NonNull>>` to implement `UnwindSafe` -note: required because it appears within the type `std::rc::Weak>` - --> $RUST/alloc/src/rc.rs - | - | pub struct Weak< - | ^^^^ -note: required because it appears within the type `mlua::state::WeakLua` - --> src/state.rs - | - | pub(crate) struct WeakLua(XWeak>); - | ^^^^^^^ -note: required because it appears within the type `mlua::types::ValueRef` - --> src/types.rs - | - | pub(crate) struct ValueRef { - | ^^^^^^^^ -note: required because it appears within the type `LuaTable` - --> src/table.rs - | - | pub struct Table(pub(crate) ValueRef); - | ^^^^^ -note: required because it's used within this closure - --> tests/compile/ref_nounwindsafe.rs:8:18 - | -8 | catch_unwind(move || table.set("a", "b").unwrap()); - | ^^^^^^^ -note: required by a bound in `std::panic::catch_unwind` - --> $RUST/std/src/panic.rs - | - | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { - | ^^^^^^^^^^ required by this bound in `catch_unwind` - -error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary - --> tests/compile/ref_nounwindsafe.rs:8:18 - | -8 | catch_unwind(move || table.set("a", "b").unwrap()); - | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary - | | - | required by a bound introduced by this call - | - = help: the trait `RefUnwindSafe` is not implemented for `UnsafeCell`, which is required by `{closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:25}: UnwindSafe` - = note: required for `Rc>` to implement `RefUnwindSafe` -note: required because it appears within the type `mlua::state::raw::RawLua` - --> src/state/raw.rs - | - | pub struct RawLua { - | ^^^^^^ -note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex` - --> src/types/sync.rs - | - | pub(crate) struct ReentrantMutex(T); - | ^^^^^^^^^^^^^^ -note: required because it appears within the type `rc::RcBox>` - --> $RUST/alloc/src/rc.rs - | - | struct RcBox { - | ^^^^^ - = note: required for `NonNull>>` to implement `UnwindSafe` -note: required because it appears within the type `std::rc::Weak>` - --> $RUST/alloc/src/rc.rs +note: required because it appears within the type `lock_api::remutex::RawReentrantMutex` + --> $CARGO/lock_api-0.4.12/src/remutex.rs + | + | pub struct RawReentrantMutex { + | ^^^^^^^^^^^^^^^^^ +note: required because it appears within the type `lock_api::remutex::ReentrantMutex` + --> $CARGO/lock_api-0.4.12/src/remutex.rs + | + | pub struct ReentrantMutex { + | ^^^^^^^^^^^^^^ +note: required because it appears within the type `alloc::sync::ArcInner>` + --> $RUST/alloc/src/sync.rs + | + | struct ArcInner { + | ^^^^^^^^ + = note: required for `NonNull>>` to implement `UnwindSafe` +note: required because it appears within the type `std::sync::Weak>` + --> $RUST/alloc/src/sync.rs | | pub struct Weak< | ^^^^ diff --git a/tests/compile/scope_callback_capture.rs b/tests/compile/scope_callback_capture.rs index 8f0d0b07..9f8ec53c 100644 --- a/tests/compile/scope_callback_capture.rs +++ b/tests/compile/scope_callback_capture.rs @@ -4,14 +4,10 @@ fn main() { let lua = Lua::new(); lua.scope(|scope| { let mut inner: Option
= None; - let f = scope - .create_function_mut(move |_, t: Table| { - if let Some(old) = inner.take() { - // Access old callback `Lua`. - } - inner = Some(t); - Ok(()) - })?; + let f = scope.create_function_mut(|_, t: Table| { + inner = Some(t); + Ok(()) + })?; f.call::<()>(lua.create_table()?)?; Ok(()) }); diff --git a/tests/compile/scope_callback_capture.stderr b/tests/compile/scope_callback_capture.stderr index 2b641480..a6993916 100644 --- a/tests/compile/scope_callback_capture.stderr +++ b/tests/compile/scope_callback_capture.stderr @@ -1,5 +1,24 @@ -error[E0599]: no method named `scope` found for struct `Lua` in the current scope - --> tests/compile/scope_callback_capture.rs:5:9 - | -5 | lua.scope(|scope| { - | ----^^^^^ method not found in `Lua` +error[E0373]: closure may outlive the current function, but it borrows `inner`, which is owned by the current function + --> tests/compile/scope_callback_capture.rs:7:43 + | +5 | lua.scope(|scope| { + | ----- has type `&'1 mut mlua::scope::Scope<'1, '_>` +6 | let mut inner: Option
= None; +7 | let f = scope.create_function_mut(|_, t: Table| { + | ^^^^^^^^^^^^^ may outlive borrowed value `inner` +8 | inner = Some(t); + | ----- `inner` is borrowed here + | +note: function requires argument type to outlive `'1` + --> tests/compile/scope_callback_capture.rs:7:17 + | +7 | let f = scope.create_function_mut(|_, t: Table| { + | _________________^ +8 | | inner = Some(t); +9 | | Ok(()) +10 | | })?; + | |__________^ +help: to force the closure to take ownership of `inner` (and any other referenced variables), use the `move` keyword + | +7 | let f = scope.create_function_mut(move |_, t: Table| { + | ++++ diff --git a/tests/compile/scope_callback_inner.rs b/tests/compile/scope_callback_inner.rs deleted file mode 100644 index b7958b47..00000000 --- a/tests/compile/scope_callback_inner.rs +++ /dev/null @@ -1,15 +0,0 @@ -use mlua::{Lua, Table}; - -fn main() { - let lua = Lua::new(); - lua.scope(|scope| { - let mut inner: Option
= None; - let f = scope - .create_function_mut(|_, t: Table| { - inner = Some(t); - Ok(()) - })?; - f.call::<()>(lua.create_table()?)?; - Ok(()) - }); -} diff --git a/tests/compile/scope_callback_inner.stderr b/tests/compile/scope_callback_inner.stderr deleted file mode 100644 index 5fb9a570..00000000 --- a/tests/compile/scope_callback_inner.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error[E0599]: no method named `scope` found for struct `Lua` in the current scope - --> tests/compile/scope_callback_inner.rs:5:9 - | -5 | lua.scope(|scope| { - | ----^^^^^ method not found in `Lua` diff --git a/tests/compile/scope_callback_outer.rs b/tests/compile/scope_callback_outer.rs deleted file mode 100644 index 8aa09868..00000000 --- a/tests/compile/scope_callback_outer.rs +++ /dev/null @@ -1,15 +0,0 @@ -use mlua::{Lua, Table}; - -fn main() { - let lua = Lua::new(); - let mut outer: Option
= None; - lua.scope(|scope| { - let f = scope - .create_function_mut(|_, t: Table| { - outer = Some(t); - Ok(()) - })?; - f.call::<()>(lua.create_table()?)?; - Ok(()) - }); -} diff --git a/tests/compile/scope_callback_outer.stderr b/tests/compile/scope_callback_outer.stderr deleted file mode 100644 index dfafdaee..00000000 --- a/tests/compile/scope_callback_outer.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error[E0599]: no method named `scope` found for struct `Lua` in the current scope - --> tests/compile/scope_callback_outer.rs:6:9 - | -6 | lua.scope(|scope| { - | ----^^^^^ method not found in `Lua` diff --git a/tests/compile/scope_invariance.rs b/tests/compile/scope_invariance.rs index abf44bb9..0414efc8 100644 --- a/tests/compile/scope_invariance.rs +++ b/tests/compile/scope_invariance.rs @@ -10,12 +10,11 @@ fn main() { let f = { let mut test = Test { field: 0 }; - scope - .create_function_mut(|_, ()| { - test.field = 42; - //~^ error: `test` does not live long enough - Ok(()) - })? + scope.create_function_mut(|_, ()| { + test.field = 42; + //~^ error: `test` does not live long enough + Ok(()) + })? }; f.call::<()>(()) diff --git a/tests/compile/scope_invariance.stderr b/tests/compile/scope_invariance.stderr index 40ed6850..91158aa6 100644 --- a/tests/compile/scope_invariance.stderr +++ b/tests/compile/scope_invariance.stderr @@ -1,5 +1,24 @@ -error[E0599]: no method named `scope` found for struct `Lua` in the current scope - --> tests/compile/scope_invariance.rs:9:9 - | -9 | lua.scope(|scope| { - | ----^^^^^ method not found in `Lua` +error[E0373]: closure may outlive the current function, but it borrows `test.field`, which is owned by the current function + --> tests/compile/scope_invariance.rs:13:39 + | +9 | lua.scope(|scope| { + | ----- has type `&'1 mut mlua::scope::Scope<'1, '_>` +... +13 | scope.create_function_mut(|_, ()| { + | ^^^^^^^ may outlive borrowed value `test.field` +14 | test.field = 42; + | ---------- `test.field` is borrowed here + | +note: function requires argument type to outlive `'1` + --> tests/compile/scope_invariance.rs:13:13 + | +13 | / scope.create_function_mut(|_, ()| { +14 | | test.field = 42; +15 | | //~^ error: `test` does not live long enough +16 | | Ok(()) +17 | | })? + | |______________^ +help: to force the closure to take ownership of `test.field` (and any other referenced variables), use the `move` keyword + | +13 | scope.create_function_mut(move |_, ()| { + | ++++ diff --git a/tests/compile/scope_mutable_aliasing.rs b/tests/compile/scope_mutable_aliasing.rs index 4e1dcf9e..4745296b 100644 --- a/tests/compile/scope_mutable_aliasing.rs +++ b/tests/compile/scope_mutable_aliasing.rs @@ -2,14 +2,14 @@ use mlua::{Lua, UserData}; fn main() { struct MyUserData<'a>(&'a mut i32); - impl<'a> UserData for MyUserData<'a> {} + impl UserData for MyUserData<'_> {} let mut i = 1; let lua = Lua::new(); lua.scope(|scope| { - let _a = scope.create_nonstatic_userdata(MyUserData(&mut i)).unwrap(); - let _b = scope.create_nonstatic_userdata(MyUserData(&mut i)).unwrap(); + let _a = scope.create_userdata(MyUserData(&mut i)).unwrap(); + let _b = scope.create_userdata(MyUserData(&mut i)).unwrap(); Ok(()) }); } diff --git a/tests/compile/scope_mutable_aliasing.stderr b/tests/compile/scope_mutable_aliasing.stderr index adbb63b5..362cf91d 100644 --- a/tests/compile/scope_mutable_aliasing.stderr +++ b/tests/compile/scope_mutable_aliasing.stderr @@ -1,5 +1,12 @@ -error[E0599]: no method named `scope` found for struct `Lua` in the current scope - --> tests/compile/scope_mutable_aliasing.rs:10:9 +error[E0499]: cannot borrow `i` as mutable more than once at a time + --> tests/compile/scope_mutable_aliasing.rs:12:51 | 10 | lua.scope(|scope| { - | ----^^^^^ method not found in `Lua` + | ----- has type `&mut mlua::scope::Scope<'_, '1>` +11 | let _a = scope.create_userdata(MyUserData(&mut i)).unwrap(); + | ----------------------------------------- + | | | + | | first mutable borrow occurs here + | argument requires that `i` is borrowed for `'1` +12 | let _b = scope.create_userdata(MyUserData(&mut i)).unwrap(); + | ^^^^^^ second mutable borrow occurs here diff --git a/tests/compile/scope_userdata_borrow.rs b/tests/compile/scope_userdata_borrow.rs index 24652344..53b1b813 100644 --- a/tests/compile/scope_userdata_borrow.rs +++ b/tests/compile/scope_userdata_borrow.rs @@ -3,16 +3,16 @@ use mlua::{Lua, UserData}; fn main() { // Should not allow userdata borrow to outlive lifetime of AnyUserData handle struct MyUserData<'a>(&'a i32); - impl<'a> UserData for MyUserData<'a> {} + impl UserData for MyUserData<'_> {} let igood = 1; let lua = Lua::new(); lua.scope(|scope| { - let _ugood = scope.create_nonstatic_userdata(MyUserData(&igood)).unwrap(); + let _ugood = scope.create_userdata(MyUserData(&igood)).unwrap(); let _ubad = { let ibad = 42; - scope.create_nonstatic_userdata(MyUserData(&ibad)).unwrap(); + scope.create_userdata(MyUserData(&ibad)).unwrap(); }; Ok(()) }); diff --git a/tests/compile/scope_userdata_borrow.stderr b/tests/compile/scope_userdata_borrow.stderr index 87abf7b8..043a99b1 100644 --- a/tests/compile/scope_userdata_borrow.stderr +++ b/tests/compile/scope_userdata_borrow.stderr @@ -1,5 +1,15 @@ -error[E0599]: no method named `scope` found for struct `Lua` in the current scope - --> tests/compile/scope_userdata_borrow.rs:11:9 +error[E0597]: `ibad` does not live long enough + --> tests/compile/scope_userdata_borrow.rs:15:46 | 11 | lua.scope(|scope| { - | ----^^^^^ method not found in `Lua` + | ----- has type `&mut mlua::scope::Scope<'_, '1>` +... +14 | let ibad = 42; + | ---- binding `ibad` declared here +15 | scope.create_userdata(MyUserData(&ibad)).unwrap(); + | ---------------------------------^^^^^-- + | | | + | | borrowed value does not live long enough + | argument requires that `ibad` is borrowed for `'1` +16 | }; + | - `ibad` dropped here while still borrowed diff --git a/tests/compile/userdata_borrow.rs b/tests/compile/userdata_borrow.rs deleted file mode 100644 index d29e5275..00000000 --- a/tests/compile/userdata_borrow.rs +++ /dev/null @@ -1,19 +0,0 @@ -use mlua::{AnyUserData, Lua, Table, UserData, Result}; - -fn main() -> Result<()> { - let lua = Lua::new(); - let globals = lua.globals(); - - // Should not allow userdata borrow to outlive lifetime of AnyUserData handle - struct MyUserData; - impl UserData for MyUserData {}; - let _userdata_ref; - { - let touter = globals.get::
("touter")?; - touter.set("userdata", lua.create_userdata(MyUserData)?)?; - let userdata = touter.get::("userdata")?; - _userdata_ref = userdata.borrow::(); - //~^ error: `userdata` does not live long enough - } - Ok(()) -} diff --git a/tests/compile/userdata_borrow.stderr b/tests/compile/userdata_borrow.stderr deleted file mode 100644 index 7ac96703..00000000 --- a/tests/compile/userdata_borrow.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error[E0597]: `userdata` does not live long enough - --> $DIR/userdata_borrow.rs:15:25 - | -15 | _userdata_ref = userdata.borrow::(); - | ^^^^^^^^ borrowed value does not live long enough -16 | //~^ error: `userdata` does not live long enough -17 | } - | - `userdata` dropped here while still borrowed -18 | Ok(()) -19 | } - | - borrow might be used here, when `_userdata_ref` is dropped and runs the destructor for type `std::result::Result, mlua::error::Error>` - | - = note: values in a scope are dropped in the opposite order they are defined diff --git a/tests/scope.rs.1 b/tests/scope.rs similarity index 55% rename from tests/scope.rs.1 rename to tests/scope.rs index b04b7b50..edc06db6 100644 --- a/tests/scope.rs.1 +++ b/tests/scope.rs @@ -1,11 +1,10 @@ use std::cell::Cell; use std::rc::Rc; use std::string::String as StdString; -use std::sync::Arc; use mlua::{ - AnyUserData, Error, Function, Lua, MetaMethod, Result, String, UserData, UserDataFields, - UserDataMethods, + AnyUserData, Error, Function, Lua, MetaMethod, ObjectLike, Result, String, UserData, UserDataFields, + UserDataMethods, UserDataRegistry, }; #[test] @@ -14,20 +13,20 @@ fn test_scope_func() -> Result<()> { let rc = Rc::new(Cell::new(0)); lua.scope(|scope| { - let r = rc.clone(); + let rc2 = rc.clone(); let f = scope.create_function(move |_, ()| { - r.set(42); + rc2.set(42); Ok(()) })?; - lua.globals().set("bad", f.clone())?; - f.call::<_, ()>(())?; + lua.globals().set("f", &f)?; + f.call::<()>(())?; assert_eq!(Rc::strong_count(&rc), 2); Ok(()) })?; assert_eq!(rc.get(), 42); assert_eq!(Rc::strong_count(&rc), 1); - match lua.globals().get::<_, Function>("bad")?.call::<_, ()>(()) { + match lua.globals().get::("f")?.call::<()>(()) { Err(Error::CallbackError { ref cause, .. }) => match *cause.as_ref() { Error::CallbackDestructed => {} ref err => panic!("wrong error type {:?}", err), @@ -49,7 +48,7 @@ fn test_scope_capture() -> Result<()> { i = 42; Ok(()) })? - .call::<_, ()>(()) + .call::<()>(()) })?; assert_eq!(i, 42); @@ -61,12 +60,8 @@ fn test_scope_outer_lua_access() -> Result<()> { let lua = Lua::new(); let table = lua.create_table()?; - lua.scope(|scope| { - scope - .create_function_mut(|_, ()| table.set("a", "b"))? - .call::<_, ()>(()) - })?; - assert_eq!(table.get::<_, String>("a")?, "b"); + lua.scope(|scope| scope.create_function(|_, ()| table.set("a", "b"))?.call::<()>(()))?; + assert_eq!(table.get::("a")?, "b"); Ok(()) } @@ -75,11 +70,11 @@ fn test_scope_outer_lua_access() -> Result<()> { fn test_scope_userdata_fields() -> Result<()> { struct MyUserData<'a>(&'a Cell); - impl<'a> UserData for MyUserData<'a> { - fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { - fields.add_field("field", "hello"); - fields.add_field_method_get("val", |_, data| Ok(data.0.get())); - fields.add_field_method_set("val", |_, data, val| { + impl UserData for MyUserData<'_> { + fn register(reg: &mut UserDataRegistry) { + reg.add_field("field", "hello"); + reg.add_field_method_get("val", |_, data| Ok(data.0.get())); + reg.add_field_method_set("val", |_, data, val| { data.0.set(val); Ok(()) }); @@ -101,7 +96,7 @@ fn test_scope_userdata_fields() -> Result<()> { ) .eval()?; - lua.scope(|scope| f.call::<_, ()>(scope.create_nonstatic_userdata(MyUserData(&i))?))?; + lua.scope(|scope| f.call::<()>(scope.create_userdata(MyUserData(&i))?))?; assert_eq!(i.get(), 44); @@ -112,14 +107,14 @@ fn test_scope_userdata_fields() -> Result<()> { fn test_scope_userdata_methods() -> Result<()> { struct MyUserData<'a>(&'a Cell); - impl<'a> UserData for MyUserData<'a> { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_method("inc", |_, data, ()| { + impl UserData for MyUserData<'_> { + fn register(reg: &mut UserDataRegistry) { + reg.add_method("inc", |_, data, ()| { data.0.set(data.0.get() + 1); Ok(()) }); - methods.add_method("dec", |_, data, ()| { + reg.add_method("dec", |_, data, ()| { data.0.set(data.0.get() - 1); Ok(()) }); @@ -142,7 +137,7 @@ fn test_scope_userdata_methods() -> Result<()> { ) .eval()?; - lua.scope(|scope| f.call::<_, ()>(scope.create_nonstatic_userdata(MyUserData(&i))?))?; + lua.scope(|scope| f.call::<()>(scope.create_userdata(MyUserData(&i))?))?; assert_eq!(i.get(), 44); @@ -150,19 +145,19 @@ fn test_scope_userdata_methods() -> Result<()> { } #[test] -fn test_scope_userdata_functions() -> Result<()> { +fn test_scope_userdata_ops() -> Result<()> { struct MyUserData<'a>(&'a i64); - impl<'a> UserData for MyUserData<'a> { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_meta_method(MetaMethod::Add, |lua, this, ()| { + impl UserData for MyUserData<'_> { + fn register(reg: &mut UserDataRegistry) { + reg.add_meta_method(MetaMethod::Add, |lua, this, ()| { let globals = lua.globals(); - globals.set("i", globals.get::<_, i64>("i")? + this.0)?; + globals.set("i", globals.get::("i")? + this.0)?; Ok(()) }); - methods.add_meta_method(MetaMethod::Sub, |lua, this, ()| { + reg.add_meta_method(MetaMethod::Sub, |lua, this, ()| { let globals = lua.globals(); - globals.set("i", globals.get::<_, i64>("i")? + this.0)?; + globals.set("i", globals.get::("i")? + this.0)?; Ok(()) }); } @@ -184,9 +179,34 @@ fn test_scope_userdata_functions() -> Result<()> { ) .eval::()?; - lua.scope(|scope| f.call::<_, ()>(scope.create_nonstatic_userdata(MyUserData(&dummy))?))?; + lua.scope(|scope| f.call::<()>(scope.create_userdata(MyUserData(&dummy))?))?; - assert_eq!(lua.globals().get::<_, i64>("i")?, 3); + assert_eq!(lua.globals().get::("i")?, 3); + + Ok(()) +} + +#[test] +fn test_scope_userdata_values() -> Result<()> { + struct MyUserData<'a>(&'a i64); + + impl UserData for MyUserData<'_> { + fn register(registry: &mut UserDataRegistry) { + registry.add_method("get", |_, data, ()| Ok(*data.0)); + } + } + + let lua = Lua::new(); + + let i = 42; + let data = MyUserData(&i); + lua.scope(|scope| { + let ud = scope.create_userdata(data)?; + assert_eq!(ud.call_method::("get", &ud)?, 42); + ud.set_user_value("user_value")?; + assert_eq!(ud.user_value::()?, "user_value"); + Ok(()) + })?; Ok(()) } @@ -196,8 +216,8 @@ fn test_scope_userdata_mismatch() -> Result<()> { struct MyUserData<'a>(&'a Cell); impl<'a> UserData for MyUserData<'a> { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_method("inc", |_, data, ()| { + fn register(reg: &mut UserDataRegistry) { + reg.add_method("inc", |_, data, ()| { data.0.set(data.0.get() + 1); Ok(()) }); @@ -208,13 +228,7 @@ fn test_scope_userdata_mismatch() -> Result<()> { lua.load( r#" - function okay(a, b) - a.inc(a) - b.inc(b) - end - function bad(a, b) - a.inc(b) - end + function inc(a, b) a.inc(b) end "#, ) .exec()?; @@ -222,29 +236,22 @@ fn test_scope_userdata_mismatch() -> Result<()> { let a = Cell::new(1); let b = Cell::new(1); - let okay: Function = lua.globals().get("okay")?; - let bad: Function = lua.globals().get("bad")?; - + let inc: Function = lua.globals().get("inc")?; lua.scope(|scope| { - let au = scope.create_nonstatic_userdata(MyUserData(&a))?; - let bu = scope.create_nonstatic_userdata(MyUserData(&b))?; - assert!(okay.call::<_, ()>((au.clone(), bu.clone())).is_ok()); - match bad.call::<_, ()>((au, bu)) { + let au = scope.create_userdata(MyUserData(&a))?; + let bu = scope.create_userdata(MyUserData(&b))?; + assert!(inc.call::<()>((&au, &au)).is_ok()); + match inc.call::<()>((&au, &bu)) { Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() { - Error::BadArgument { - to, - pos, - name, - cause, - } => { + Error::BadArgument { to, pos, name, cause } => { assert_eq!(to.as_deref(), Some("MyUserData.inc")); assert_eq!(*pos, 1); assert_eq!(name.as_deref(), Some("self")); assert!(matches!(*cause.as_ref(), Error::UserDataTypeMismatch)); } - other => panic!("wrong error type {:?}", other), + other => panic!("wrong error type {other:?}"), }, - Err(other) => panic!("wrong error type {:?}", other), + Err(other) => panic!("wrong error type {other:?}"), Ok(_) => panic!("incorrectly returned Ok"), } Ok(()) @@ -257,114 +264,46 @@ fn test_scope_userdata_mismatch() -> Result<()> { fn test_scope_userdata_drop() -> Result<()> { let lua = Lua::new(); - struct MyUserData(#[allow(unused)] Rc<()>); - - impl UserData for MyUserData { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_method("method", |_, _, ()| Ok(())); - } - } - - struct MyUserDataArc(#[allow(unused)] Arc<()>); - - impl UserData for MyUserDataArc {} - - let rc = Rc::new(()); - let arc = Arc::new(()); - lua.scope(|scope| { - let ud = scope.create_userdata(MyUserData(rc.clone()))?; - ud.set_user_value(MyUserDataArc(arc.clone()))?; - lua.globals().set("ud", ud)?; - assert_eq!(Rc::strong_count(&rc), 2); - assert_eq!(Arc::strong_count(&arc), 2); - Ok(()) - })?; - - lua.gc_collect()?; - assert_eq!(Rc::strong_count(&rc), 1); - assert_eq!(Arc::strong_count(&arc), 1); - - match lua.load("ud:method()").exec() { - Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() { - Error::CallbackDestructed => {} - err => panic!("expected CallbackDestructed, got {:?}", err), - }, - r => panic!("improper return for destructed userdata: {:?}", r), - }; - - let ud = lua.globals().get::<_, AnyUserData>("ud")?; - match ud.borrow::() { - Ok(_) => panic!("succesfull borrow for destructed userdata"), - Err(Error::UserDataDestructed) => {} - Err(err) => panic!("improper borrow error for destructed userdata: {:?}", err), - } - - match ud.get_metatable() { - Ok(_) => panic!("successful metatable retrieval of destructed userdata"), - Err(Error::UserDataDestructed) => {} - Err(err) => panic!( - "improper metatable error for destructed userdata: {:?}", - err - ), - } - - Ok(()) -} - -#[test] -fn test_scope_nonstatic_userdata_drop() -> Result<()> { - let lua = Lua::new(); - - struct MyUserData<'a>(&'a Cell, #[allow(unused)] Arc<()>); + struct MyUserData<'a>(&'a Cell, #[allow(unused)] Rc<()>); - impl<'a> UserData for MyUserData<'a> { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_method("inc", |_, data, ()| { + impl UserData for MyUserData<'_> { + fn register(reg: &mut UserDataRegistry) { + reg.add_method("inc", |_, data, ()| { data.0.set(data.0.get() + 1); Ok(()) }); } } - struct MyUserDataArc(#[allow(unused)] Arc<()>); - - impl UserData for MyUserDataArc {} - - let i = Cell::new(1); - let arc = Arc::new(()); + let (i, rc) = (Cell::new(1), Rc::new(())); lua.scope(|scope| { - let ud = scope.create_nonstatic_userdata(MyUserData(&i, arc.clone()))?; - ud.set_user_value(MyUserDataArc(arc.clone()))?; + let ud = scope.create_userdata(MyUserData(&i, rc.clone()))?; lua.globals().set("ud", ud)?; lua.load("ud:inc()").exec()?; - assert_eq!(Arc::strong_count(&arc), 3); + assert_eq!(Rc::strong_count(&rc), 2); Ok(()) })?; - - lua.gc_collect()?; - assert_eq!(Arc::strong_count(&arc), 1); + assert_eq!(Rc::strong_count(&rc), 1); + assert_eq!(i.get(), 2); match lua.load("ud:inc()").exec() { Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() { - Error::CallbackDestructed => {} - err => panic!("expected CallbackDestructed, got {:?}", err), + Error::UserDataDestructed => {} + err => panic!("expected UserDataDestructed, got {err:?}"), }, - r => panic!("improper return for destructed userdata: {:?}", r), + r => panic!("improper return for destructed userdata: {r:?}"), }; - let ud = lua.globals().get::<_, AnyUserData>("ud")?; - match ud.borrow::() { + let ud = lua.globals().get::("ud")?; + match ud.borrow_scoped::(|_| Ok::<_, Error>(())) { Ok(_) => panic!("succesfull borrow for destructed userdata"), Err(Error::UserDataDestructed) => {} - Err(err) => panic!("improper borrow error for destructed userdata: {:?}", err), + Err(err) => panic!("improper borrow error for destructed userdata: {err:?}"), } match ud.get_metatable() { Ok(_) => panic!("successful metatable retrieval of destructed userdata"), Err(Error::UserDataDestructed) => {} - Err(err) => panic!( - "improper metatable error for destructed userdata: {:?}", - err - ), + Err(err) => panic!("improper metatable error for destructed userdata: {err:?}"), } Ok(()) @@ -377,7 +316,7 @@ fn test_scope_userdata_ref() -> Result<()> { struct MyUserData(Cell); impl UserData for MyUserData { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_method("inc", |_, data, ()| { data.0.set(data.0.get() + 1); Ok(()) @@ -407,7 +346,7 @@ fn test_scope_userdata_ref_mut() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_method_mut("inc", |_, data, ()| { data.0 += 1; Ok(()) @@ -438,8 +377,9 @@ fn test_scope_any_userdata() -> Result<()> { reg.add_meta_method("__tostring", |_, data, ()| Ok(data.clone())); })?; + let data = StdString::from("foo"); lua.scope(|scope| { - let ud = scope.create_any_userdata(StdString::from("foo"))?; + let ud = scope.create_any_userdata_ref(&data)?; lua.globals().set("ud", ud)?; lua.load("assert(tostring(ud) == 'foo')").exec() })?; @@ -447,10 +387,10 @@ fn test_scope_any_userdata() -> Result<()> { // Check that userdata is destructed match lua.load("tostring(ud)").exec() { Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() { - Error::CallbackDestructed => {} - err => panic!("expected CallbackDestructed, got {:?}", err), + Error::UserDataDestructed => {} + err => panic!("expected CallbackDestructed, got {err:?}"), }, - r => panic!("improper return for destructed userdata: {:?}", r), + r => panic!("improper return for destructed userdata: {r:?}"), }; Ok(()) @@ -495,7 +435,7 @@ fn modify_userdata(lua: &Lua, ud: AnyUserData) -> Result<()> { ) .eval()?; - f.call(ud)?; + f.call::<()>(ud)?; Ok(()) } diff --git a/tests/serde.rs b/tests/serde.rs index f4bd67a9..fb50c04c 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -71,50 +71,6 @@ fn test_serialize() -> Result<(), Box> { Ok(()) } -// #[test] -// fn test_serialize_in_scope() -> LuaResult<()> { -// #[derive(Serialize, Clone)] -// struct MyUserData(i64, String); - -// impl UserData for MyUserData {} - -// let lua = Lua::new(); -// lua.scope(|scope| { -// let ud = scope.create_ser_userdata(MyUserData(-5, "test userdata".into()))?; -// assert_eq!( -// serde_json::to_value(&ud).unwrap(), -// serde_json::json!((-5, "test userdata")) -// ); -// Ok(()) -// })?; - -// lua.scope(|scope| { -// let ud = scope.create_ser_userdata(MyUserData(-5, "test userdata".into()))?; -// lua.globals().set("ud", ud) -// })?; -// let val = lua.load("ud").eval::()?; -// match serde_json::to_value(&val) { -// Ok(v) => panic!("expected destructed error, got {}", v), -// Err(e) if e.to_string().contains("destructed") => {} -// Err(e) => panic!("expected destructed error, got {}", e), -// } - -// struct MyUserDataRef<'a>(#[allow(unused)] &'a ()); - -// impl<'a> UserData for MyUserDataRef<'a> {} - -// lua.scope(|scope| { -// let ud = scope.create_nonstatic_userdata(MyUserDataRef(&()))?; -// match serde_json::to_value(&ud) { -// Ok(v) => panic!("expected serialization error, got {}", v), -// Err(serde_json::Error { .. }) => {} -// }; -// Ok(()) -// })?; - -// Ok(()) -// } - #[test] fn test_serialize_any_userdata() -> Result<(), Box> { let lua = Lua::new(); diff --git a/tests/userdata.rs b/tests/userdata.rs index e305ae38..b78e0171 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -336,8 +336,8 @@ fn test_userdata_take() -> Result<()> { } match lua.load("userdata:num()").exec() { Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() { - Error::CallbackDestructed => {} - err => panic!("expected `CallbackDestructed`, got {:?}", err), + Error::UserDataDestructed => {} + err => panic!("expected `UserDataDestructed`, got {:?}", err), }, r => panic!("improper return for destructed userdata: {:?}", r), } From 640d27697ded93c93d1ea1c674fc94ecd986261e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 20 Sep 2024 21:20:52 +0100 Subject: [PATCH 183/635] Fix compile error in non-send mode --- src/userdata/cell.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index 6df36126..b62c3ecb 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -412,6 +412,15 @@ impl UserDataStorage { } } + #[allow(unused)] + #[inline(always)] + pub(crate) fn try_borrow(&self) -> Result> { + match self { + Self::Owned(data) => data.try_borrow(), + Self::Scoped(_) => Err(Error::UserDataTypeMismatch), + } + } + #[inline(always)] pub(crate) fn try_borrow_mut(&self) -> Result> { match self { From 5162a0f46e8b82760d4c490d86592f2e83d92795 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 22 Sep 2024 11:25:38 +0100 Subject: [PATCH 184/635] Add `Lua::with_raw_state` to provide easy low-level access to the Lua state. --- src/lib.rs | 2 +- src/state.rs | 26 +++++++++++++++++++++++++- src/state/raw.rs | 11 +++++++---- src/util/error.rs | 11 ++++++----- src/util/mod.rs | 1 + tests/tests.rs | 34 ++++++++++++++++++++++++++++++++++ 6 files changed, 74 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b6e153f7..a02a4a9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,8 +109,8 @@ pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Resul pub use crate::function::{Function, FunctionInfo}; pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack}; pub use crate::multi::Variadic; +pub use crate::scope::Scope; pub use crate::state::{GCMode, Lua, LuaOptions}; -// pub use crate::scope::Scope; pub use crate::stdlib::StdLib; pub use crate::string::{BorrowedBytes, BorrowedStr, String}; pub use crate::table::{Table, TablePairs, TableSequence}; diff --git a/src/state.rs b/src/state.rs index 2c93cc31..57bff326 100644 --- a/src/state.rs +++ b/src/state.rs @@ -22,7 +22,9 @@ use crate::types::{ ReentrantMutex, ReentrantMutexGuard, RegistryKey, XRc, XWeak, }; use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataStorage}; -use crate::util::{assert_stack, check_stack, push_string, push_table, rawset_field, StackGuard}; +use crate::util::{ + assert_stack, check_stack, protect_lua_closure, push_string, push_table, rawset_field, StackGuard, +}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value}; #[cfg(not(feature = "luau"))] @@ -276,6 +278,28 @@ impl Lua { } } + /// Calls provided function passing a raw lua state. + /// + /// The arguments will be pushed onto the stack before calling the function. + /// + /// This method ensures that the Lua instance is locked while the function is called + /// and restores Lua stack after the function returns. + pub unsafe fn with_raw_state( + &self, + args: impl IntoLuaMulti, + f: impl FnOnce(*mut ffi::lua_State), + ) -> Result { + let lua = self.lock(); + let state = lua.state(); + let _sg = StackGuard::new(state); + let stack_start = ffi::lua_gettop(state); + let nargs = args.push_into_stack_multi(&lua)?; + check_stack(state, 3)?; + protect_lua_closure::<_, ()>(state, nargs, ffi::LUA_MULTRET, f)?; + let nresults = ffi::lua_gettop(state) - stack_start; + R::from_stack_multi(nresults, &lua) + } + /// FIXME: Deprecated load_from_std_lib /// Loads the specified subset of the standard libraries into an existing Lua state. diff --git a/src/state/raw.rs b/src/state/raw.rs index ad042e5e..3dcca2bf 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -43,6 +43,7 @@ use { }; /// An inner Lua struct which holds a raw Lua state. +#[doc(hidden)] pub struct RawLua { // The state is dynamic and depends on context pub(super) state: Cell<*mut ffi::lua_State>, @@ -83,8 +84,11 @@ impl RawLua { unsafe { (*self.extra.get()).weak() } } + /// Returns a pointer to the current Lua state. + /// + /// The pointer refers to the active Lua coroutine and depends on the context. #[inline(always)] - pub(crate) fn state(&self) -> *mut ffi::lua_State { + pub fn state(&self) -> *mut ffi::lua_State { self.state.get() } @@ -500,10 +504,9 @@ impl RawLua { /// Pushes a value that implements `IntoLua` onto the Lua stack. /// - /// Uses 2 stack spaces, does not call checkstack. - #[doc(hidden)] + /// Uses up to 2 stack spaces to push a single value, does not call `checkstack`. #[inline(always)] - pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> { + pub(crate) unsafe fn push(&self, value: impl IntoLua) -> Result<()> { value.push_into_stack(self) } diff --git a/src/util/error.rs b/src/util/error.rs index 05814171..eb9ee1f5 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -204,24 +204,25 @@ pub(crate) unsafe fn protect_lua_closure( f: F, ) -> Result where - F: Fn(*mut ffi::lua_State) -> R, + F: FnOnce(*mut ffi::lua_State) -> R, R: Copy, { struct Params { - function: F, + function: Option, result: MaybeUninit, nresults: c_int, } unsafe extern "C-unwind" fn do_call(state: *mut ffi::lua_State) -> c_int where - F: Fn(*mut ffi::lua_State) -> R, + F: FnOnce(*mut ffi::lua_State) -> R, R: Copy, { let params = ffi::lua_touserdata(state, -1) as *mut Params; ffi::lua_pop(state, 1); - (*params).result.write(((*params).function)(state)); + let f = (*params).function.take().unwrap(); + (*params).result.write(f(state)); if (*params).nresults == ffi::LUA_MULTRET { ffi::lua_gettop(state) @@ -241,7 +242,7 @@ where } let mut params = Params { - function: f, + function: Some(f), result: MaybeUninit::uninit(), nresults, }; diff --git a/src/util/mod.rs b/src/util/mod.rs index b0f886f0..9722fbc4 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -69,6 +69,7 @@ impl StackGuard { } impl Drop for StackGuard { + #[track_caller] fn drop(&mut self) { unsafe { let top = ffi::lua_gettop(self.state); diff --git a/tests/tests.rs b/tests/tests.rs index 4e68513d..268cb80e 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1289,3 +1289,37 @@ fn test_multi_thread() -> Result<()> { Ok(()) } + +#[test] +fn test_with_raw_state() -> Result<()> { + let lua = Lua::new(); + + let sum = lua.create_function(|_, args: Variadic| { + let mut sum = 0; + for i in args { + sum += i; + } + Ok(sum) + })?; + lua.globals().set("sum", sum)?; + + let n: i32 = unsafe { + lua.with_raw_state((), |state| { + ffi::lua_getglobal(state, b"sum\0".as_ptr() as _); + ffi::lua_pushinteger(state, 1); + ffi::lua_pushinteger(state, 7); + ffi::lua_call(state, 2, 1); + }) + }?; + assert_eq!(n, 8); + + // Test error handling + let res: Result<()> = unsafe { + lua.with_raw_state("test error", |state| { + ffi::lua_error(state); + }) + }; + assert!(matches!(res, Err(Error::RuntimeError(err)) if err.contains("test error"))); + + Ok(()) +} From 3088516851670edcde79cd2064f9f662b649bbe6 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 22 Sep 2024 17:33:28 +0100 Subject: [PATCH 185/635] Update luaL_checkstack messages --- mlua-sys/src/lua51/compat.rs | 10 +++++----- mlua-sys/src/luau/compat.rs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mlua-sys/src/lua51/compat.rs b/mlua-sys/src/lua51/compat.rs index 66002b67..2294cd4f 100644 --- a/mlua-sys/src/lua51/compat.rs +++ b/mlua-sys/src/lua51/compat.rs @@ -176,7 +176,7 @@ pub unsafe fn lua_rotate(L: *mut lua_State, mut idx: c_int, mut n: c_int) { #[inline(always)] pub unsafe fn lua_copy(L: *mut lua_State, fromidx: c_int, toidx: c_int) { let abs_to = lua_absindex(L, toidx); - luaL_checkstack(L, 1, cstr!("not enough stack slots")); + luaL_checkstack(L, 1, cstr!("not enough stack slots available")); lua_pushvalue(L, fromidx); lua_replace(L, abs_to); } @@ -314,7 +314,7 @@ pub unsafe fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer) { #[inline(always)] pub unsafe fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void) { let abs_i = lua_absindex(L, idx); - luaL_checkstack(L, 1, cstr!("not enough stack slots")); + luaL_checkstack(L, 1, cstr!("not enough stack slots available")); lua_pushlightuserdata(L, p as *mut c_void); lua_insert(L, -2); lua_rawset(L, abs_i); @@ -444,7 +444,7 @@ pub unsafe fn luaL_loadbufferx( #[inline(always)] pub unsafe fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer { let mut isnum = 0; - luaL_checkstack(L, 1, cstr!("not enough stack slots")); + luaL_checkstack(L, 1, cstr!("not enough stack slots available")); lua_len(L, idx); let res = lua_tointegerx(L, -1, &mut isnum); lua_pop(L, 1); @@ -526,14 +526,14 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) #[inline(always)] pub unsafe fn luaL_setmetatable(L: *mut lua_State, tname: *const c_char) { - luaL_checkstack(L, 1, cstr!("not enough stack slots")); + luaL_checkstack(L, 1, cstr!("not enough stack slots available")); luaL_getmetatable(L, tname); lua_setmetatable(L, -2); } pub unsafe fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_char) -> c_int { let abs_i = lua_absindex(L, idx); - luaL_checkstack(L, 3, cstr!("not enough stack slots")); + luaL_checkstack(L, 3, cstr!("not enough stack slots available")); lua_pushstring_(L, fname); if lua_gettable(L, abs_i) == LUA_TTABLE { return 1; diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index c9e02e2d..a738dae3 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -108,7 +108,7 @@ pub unsafe fn lua_rotate(L: *mut lua_State, mut idx: c_int, mut n: c_int) { #[inline(always)] pub unsafe fn lua_copy(L: *mut lua_State, fromidx: c_int, toidx: c_int) { let abs_to = lua_absindex(L, toidx); - luaL_checkstack(L, 1, cstr!("not enough stack slots")); + luaL_checkstack(L, 1, cstr!("not enough stack slots available")); lua_pushvalue(L, fromidx); lua_replace(L, abs_to); } @@ -217,7 +217,7 @@ pub unsafe fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer) { #[inline(always)] pub unsafe fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void) { let abs_i = lua_absindex(L, idx); - luaL_checkstack(L, 1, cstr!("not enough stack slots")); + luaL_checkstack(L, 1, cstr!("not enough stack slots available")); lua_pushlightuserdata(L, p as *mut c_void); lua_insert(L, -2); lua_rawset(L, abs_i); @@ -381,7 +381,7 @@ pub unsafe fn luaL_loadbuffer( #[inline(always)] pub unsafe fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer { let mut isnum = 0; - luaL_checkstack(L, 1, cstr!("not enough stack slots")); + luaL_checkstack(L, 1, cstr!("not enough stack slots available")); lua_len(L, idx); let res = lua_tointegerx(L, -1, &mut isnum); lua_pop(L, 1); @@ -463,14 +463,14 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) #[inline(always)] pub unsafe fn luaL_setmetatable(L: *mut lua_State, tname: *const c_char) { - luaL_checkstack(L, 1, cstr!("not enough stack slots")); + luaL_checkstack(L, 1, cstr!("not enough stack slots available")); luaL_getmetatable(L, tname); lua_setmetatable(L, -2); } pub unsafe fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_char) -> c_int { let abs_i = lua_absindex(L, idx); - luaL_checkstack(L, 3, cstr!("not enough stack slots")); + luaL_checkstack(L, 3, cstr!("not enough stack slots available")); lua_pushstring_(L, fname); if lua_gettable(L, abs_i) == LUA_TTABLE { return 1; From fce85381c63af4c0ca5c0dfaae54d21fdc673a93 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 22 Sep 2024 18:59:59 +0100 Subject: [PATCH 186/635] Fix clippy warnings --- src/state.rs | 17 +++++++++++------ src/state/raw.rs | 3 ++- src/string.rs | 16 ++++++++-------- src/thread.rs | 2 +- src/userdata/cell.rs | 4 ++-- src/userdata/registry.rs | 6 ++---- src/value.rs | 11 ++++++----- 7 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/state.rs b/src/state.rs index 57bff326..ae05c72e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2,7 +2,7 @@ use std::any::TypeId; use std::cell::RefCell; use std::marker::PhantomData; use std::ops::Deref; -use std::os::raw::{c_int, c_void}; +use std::os::raw::c_int; use std::panic::Location; use std::result::Result as StdResult; use std::{fmt, mem, ptr}; @@ -18,8 +18,8 @@ use crate::string::String; use crate::table::Table; use crate::thread::Thread; use crate::types::{ - AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LightUserData, MaybeSend, Number, - ReentrantMutex, ReentrantMutexGuard, RegistryKey, XRc, XWeak, + AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, MaybeSend, Number, ReentrantMutex, + ReentrantMutexGuard, RegistryKey, XRc, XWeak, }; use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataStorage}; use crate::util::{ @@ -34,7 +34,10 @@ use crate::hook::HookTriggers; use crate::{chunk::Compiler, types::VmState}; #[cfg(feature = "async")] -use std::future::{self, Future}; +use { + crate::types::LightUserData, + std::future::{self, Future}, +}; #[cfg(feature = "serialize")] use serde::Serialize; @@ -284,6 +287,7 @@ impl Lua { /// /// This method ensures that the Lua instance is locked while the function is called /// and restores Lua stack after the function returns. + #[allow(clippy::missing_safety_doc)] pub unsafe fn with_raw_state( &self, args: impl IntoLuaMulti, @@ -648,7 +652,7 @@ impl Lua { F: Fn(&Lua, &str, bool) -> Result<()> + MaybeSend + 'static, { use std::ffi::CStr; - use std::os::raw::c_char; + use std::os::raw::{c_char, c_void}; use std::string::String as StdString; unsafe extern "C-unwind" fn warn_proc(ud: *mut c_void, msg: *const c_char, tocont: c_int) { @@ -1825,7 +1829,7 @@ impl Lua { #[inline(always)] pub fn poll_pending() -> LightUserData { static ASYNC_POLL_PENDING: u8 = 0; - LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut c_void) + LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut std::os::raw::c_void) } // Luau version located in `luau/mod.rs` @@ -1874,6 +1878,7 @@ impl Lua { /// Returns a handle to the unprotected Lua state without any synchronization. /// /// This is useful where we know that the lock is already held by the caller. + #[cfg(feature = "async")] #[inline(always)] pub(crate) unsafe fn raw_lua(&self) -> &RawLua { &*self.raw.data_ptr() diff --git a/src/state/raw.rs b/src/state/raw.rs index 3dcca2bf..4fbae99d 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -27,7 +27,7 @@ use crate::util::{ push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name, StackGuard, WrappedFailure, }; -use crate::value::{FromLuaMulti, IntoLua, MultiValue, Nil, Value}; +use crate::value::{IntoLua, Nil, Value}; use super::extra::ExtraData; use super::{Lua, LuaOptions, WeakLua}; @@ -38,6 +38,7 @@ use crate::hook::{Debug, HookTriggers}; #[cfg(feature = "async")] use { crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue}, + crate::value::{FromLuaMulti, MultiValue}, std::ptr::NonNull, std::task::{Context, Poll, Waker}, }; diff --git a/src/string.rs b/src/string.rs index a4707d37..39c1ddc7 100644 --- a/src/string.rs +++ b/src/string.rs @@ -105,22 +105,22 @@ impl String { unsafe fn to_slice(&self) -> (&[u8], Lua) { let lua = self.0.lua.upgrade(); - let rawlua = lua.lock(); - let ref_thread = rawlua.ref_thread(); - unsafe { + let slice = unsafe { + let rawlua = lua.lock(); + let ref_thread = rawlua.ref_thread(); + mlua_debug_assert!( ffi::lua_type(ref_thread, self.0.index) == ffi::LUA_TSTRING, "string ref is not string type" ); - let mut size = 0; // This will not trigger a 'm' error, because the reference is guaranteed to be of // string type + let mut size = 0; let data = ffi::lua_tolstring(ref_thread, self.0.index, &mut size); - - drop(rawlua); - (slice::from_raw_parts(data as *const u8, size + 1), lua) - } + slice::from_raw_parts(data as *const u8, size + 1) + }; + (slice, lua) } /// Converts this string to a generic C pointer. diff --git a/src/thread.rs b/src/thread.rs index c3b1f44f..c1c0a042 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -143,7 +143,7 @@ impl Thread { let state = lua.state(); let thread_state = self.state(); - let nargs = args.push_into_stack_multi(&lua)?; + let nargs = args.push_into_stack_multi(lua)?; if nargs > 0 { check_stack(thread_state, nargs)?; ffi::lua_xmove(state, thread_state, nargs); diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index b62c3ecb..e37590b0 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -9,7 +9,7 @@ use serde::ser::{Serialize, Serializer}; use crate::error::{Error, Result}; use crate::state::{Lua, RawLua}; -use crate::types::{MaybeSend, XRc}; +use crate::types::XRc; use crate::userdata::AnyUserData; use crate::util::get_userdata; use crate::value::{FromLua, Value}; @@ -391,7 +391,7 @@ impl UserDataStorage { #[inline(always)] pub(crate) fn new_ser(data: T) -> Self where - T: Serialize + MaybeSend, + T: Serialize + crate::types::MaybeSend, { let data = Box::new(data) as Box; Self::Owned(UserDataVariant::Serializable(XRc::new(UserDataCell::new(data)))) diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index ad4e3178..260a497e 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -9,16 +9,14 @@ use std::string::String as StdString; use crate::error::{Error, Result}; use crate::state::{Lua, RawLua}; use crate::types::{Callback, MaybeSend}; -use crate::userdata::{ - AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, UserDataRefMut, - UserDataStorage, -}; +use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataStorage}; use crate::util::{get_userdata, short_type_name}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; #[cfg(feature = "async")] use { crate::types::AsyncCallback, + crate::userdata::{UserDataRef, UserDataRefMut}, std::future::{self, Future}, }; diff --git a/src/value.rs b/src/value.rs index d7de0029..276904a0 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::cmp::Ordering; use std::collections::{vec_deque, HashSet, VecDeque}; use std::ops::{Deref, DerefMut}; @@ -24,12 +23,14 @@ use { crate::table::SerializableTable, rustc_hash::FxHashSet, serde::ser::{self, Serialize, Serializer}, - std::{rc::Rc, result::Result as StdResult}, + std::{cell::RefCell, rc::Rc, result::Result as StdResult}, }; -/// A dynamically typed Lua value. The `String`, `Table`, `Function`, `Thread`, and `UserData` -/// variants contain handle types into the internal Lua state. It is a logic error to mix handle -/// types between separate `Lua` instances, and doing so will result in a panic. +/// A dynamically typed Lua value. +/// +/// The `String`, `Table`, `Function`, `Thread`, and `UserData` variants contain handle types +/// into the internal Lua state. It is a logic error to mix handle types between separate +/// `Lua` instances, and doing so will result in a panic. #[derive(Clone)] pub enum Value { /// The Lua value `nil`. From fc1570d2d7e104d9992498dd925a707265be2bea Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 22 Sep 2024 19:04:32 +0100 Subject: [PATCH 187/635] Support yielding from hooks for Lua 5.3+ --- src/lib.rs | 10 ++++------ src/prelude.rs | 4 ++-- src/state.rs | 10 +++++----- src/state/raw.rs | 27 +++++++++++++++++++++----- src/thread.rs | 4 ++-- src/types.rs | 9 +++++---- tests/hooks.rs | 50 +++++++++++++++++++++++++++++++++++++++++------- 7 files changed, 83 insertions(+), 31 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a02a4a9a..c402e11c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,7 +116,9 @@ pub use crate::string::{BorrowedBytes, BorrowedStr, String}; pub use crate::table::{Table, TablePairs, TableSequence}; pub use crate::thread::{Thread, ThreadStatus}; pub use crate::traits::ObjectLike; -pub use crate::types::{AppDataRef, AppDataRefMut, Integer, LightUserData, MaybeSend, Number, RegistryKey}; +pub use crate::types::{ + AppDataRef, AppDataRefMut, Integer, LightUserData, MaybeSend, Number, RegistryKey, VmState, +}; pub use crate::userdata::{ AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef, UserDataRefMut, UserDataRegistry, @@ -128,11 +130,7 @@ pub use crate::hook::HookTriggers; #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] -pub use crate::{ - chunk::Compiler, - function::CoverageInfo, - types::{Vector, VmState}, -}; +pub use crate::{chunk::Compiler, function::CoverageInfo, types::Vector}; #[cfg(feature = "async")] pub use crate::thread::AsyncThread; diff --git a/src/prelude.rs b/src/prelude.rs index faf8f7c1..329f2ee7 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -12,7 +12,7 @@ pub use crate::{ ThreadStatus as LuaThreadStatus, UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut, - UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, + UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, VmState as LuaVmState, }; #[cfg(not(feature = "luau"))] @@ -21,7 +21,7 @@ pub use crate::HookTriggers as LuaHookTriggers; #[cfg(feature = "luau")] #[doc(no_inline)] -pub use crate::{CoverageInfo as LuaCoverageInfo, Vector as LuaVector, VmState as LuaVmState}; +pub use crate::{CoverageInfo as LuaCoverageInfo, Vector as LuaVector}; #[cfg(feature = "async")] #[doc(no_inline)] diff --git a/src/state.rs b/src/state.rs index ae05c72e..e10174b0 100644 --- a/src/state.rs +++ b/src/state.rs @@ -19,7 +19,7 @@ use crate::table::Table; use crate::thread::Thread; use crate::types::{ AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, MaybeSend, Number, ReentrantMutex, - ReentrantMutexGuard, RegistryKey, XRc, XWeak, + ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak, }; use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataStorage}; use crate::util::{ @@ -31,7 +31,7 @@ use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil use crate::hook::HookTriggers; #[cfg(any(feature = "luau", doc))] -use crate::{chunk::Compiler, types::VmState}; +use crate::chunk::Compiler; #[cfg(feature = "async")] use { @@ -499,12 +499,12 @@ impl Lua { /// Shows each line number of code being executed by the Lua interpreter. /// /// ``` - /// # use mlua::{Lua, HookTriggers, Result}; + /// # use mlua::{Lua, HookTriggers, Result, VmState}; /// # fn main() -> Result<()> { /// let lua = Lua::new(); /// lua.set_hook(HookTriggers::EVERY_LINE, |_lua, debug| { /// println!("line {}", debug.curr_line()); - /// Ok(()) + /// Ok(VmState::Continue) /// }); /// /// lua.load(r#" @@ -521,7 +521,7 @@ impl Lua { #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub fn set_hook(&self, triggers: HookTriggers, callback: F) where - F: Fn(&Lua, Debug) -> Result<()> + MaybeSend + 'static, + F: Fn(&Lua, Debug) -> Result + MaybeSend + 'static, { let lua = self.lock(); unsafe { lua.set_thread_hook(lua.state(), triggers, callback) }; diff --git a/src/state/raw.rs b/src/state/raw.rs index 4fbae99d..81d283e6 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -18,7 +18,7 @@ use crate::table::Table; use crate::thread::Thread; use crate::types::{ AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData, - MaybeSend, ReentrantMutex, RegistryKey, SubtypeId, ValueRef, XRc, + MaybeSend, ReentrantMutex, RegistryKey, SubtypeId, ValueRef, VmState, XRc, }; use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataRegistry, UserDataStorage}; use crate::util::{ @@ -356,7 +356,7 @@ impl RawLua { triggers: HookTriggers, callback: F, ) where - F: Fn(&Lua, Debug) -> Result<()> + MaybeSend + 'static, + F: Fn(&Lua, Debug) -> Result + MaybeSend + 'static, { unsafe extern "C-unwind" fn hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) { let extra = ExtraData::get(state); @@ -365,17 +365,34 @@ impl RawLua { ffi::lua_sethook(state, None, 0, 0); return; } - callback_error_ext(state, extra, move |extra, _| { + let result = callback_error_ext(state, extra, move |extra, _| { let hook_cb = (*extra).hook_callback.clone(); let hook_cb = mlua_expect!(hook_cb, "no hook callback set in hook_proc"); if std::rc::Rc::strong_count(&hook_cb) > 2 { - return Ok(()); // Don't allow recursion + return Ok(VmState::Continue); // Don't allow recursion } let rawlua = (*extra).raw_lua(); let _guard = StateGuard::new(rawlua, state); let debug = Debug::new(rawlua, ar); hook_cb((*extra).lua(), debug) - }) + }); + match result { + VmState::Continue => {} + VmState::Yield => { + // Only count and line events can yield + if (*ar).event == ffi::LUA_HOOKCOUNT || (*ar).event == ffi::LUA_HOOKLINE { + #[cfg(any(feature = "lua54", feature = "lua53"))] + if ffi::lua_isyieldable(state) != 0 { + ffi::lua_yield(state, 0); + } + #[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))] + { + ffi::lua_pushliteral(state, "attempt to yield from a hook"); + ffi::lua_error(state); + } + } + } + } } (*self.extra.get()).hook_callback = Some(std::rc::Rc::new(callback)); diff --git a/src/thread.rs b/src/thread.rs index c1c0a042..b4b56ee2 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -4,7 +4,7 @@ use crate::error::{Error, Result}; #[allow(unused)] use crate::state::Lua; use crate::state::RawLua; -use crate::types::ValueRef; +use crate::types::{ValueRef, VmState}; use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard}; use crate::value::{FromLuaMulti, IntoLuaMulti}; @@ -194,7 +194,7 @@ impl Thread { #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub fn set_hook(&self, triggers: HookTriggers, callback: F) where - F: Fn(&Lua, Debug) -> Result<()> + MaybeSend + 'static, + F: Fn(&Lua, Debug) -> Result + MaybeSend + 'static, { let lua = self.0.lua.lock(); unsafe { diff --git a/src/types.rs b/src/types.rs index a62e56de..6095c828 100644 --- a/src/types.rs +++ b/src/types.rs @@ -76,18 +76,19 @@ pub(crate) type AsyncCallbackUpvalue = Upvalue; pub(crate) type AsyncPollUpvalue = Upvalue>>; /// Type to set next Luau VM action after executing interrupt function. -#[cfg(any(feature = "luau", doc))] -#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub enum VmState { Continue, + /// Yield the current thread. + /// + /// Supported by Lua 5.3+ and Luau. Yield, } #[cfg(all(feature = "send", not(feature = "luau")))] -pub(crate) type HookCallback = Rc Result<()> + Send>; +pub(crate) type HookCallback = Rc Result + Send>; #[cfg(all(not(feature = "send"), not(feature = "luau")))] -pub(crate) type HookCallback = Rc Result<()>>; +pub(crate) type HookCallback = Rc Result>; #[cfg(all(feature = "send", feature = "luau"))] pub(crate) type InterruptCallback = Rc Result + Send>; diff --git a/tests/hooks.rs b/tests/hooks.rs index a7b3ff47..7231bdfd 100644 --- a/tests/hooks.rs +++ b/tests/hooks.rs @@ -4,7 +4,7 @@ use std::ops::Deref; use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::{Arc, Mutex}; -use mlua::{DebugEvent, Error, HookTriggers, Lua, Result, Value}; +use mlua::{DebugEvent, Error, HookTriggers, Lua, Result, ThreadStatus, Value, VmState}; #[test] fn test_hook_triggers() { @@ -26,7 +26,7 @@ fn test_line_counts() -> Result<()> { lua.set_hook(HookTriggers::EVERY_LINE, move |_lua, debug| { assert_eq!(debug.event(), DebugEvent::Line); hook_output.lock().unwrap().push(debug.curr_line()); - Ok(()) + Ok(VmState::Continue) }); lua.load( r#" @@ -61,7 +61,7 @@ fn test_function_calls() -> Result<()> { let source = debug.source(); let name = names.name.map(|s| s.into_owned()); hook_output.lock().unwrap().push((name, source.what)); - Ok(()) + Ok(VmState::Continue) }); lua.load( @@ -120,7 +120,7 @@ fn test_limit_execution_instructions() -> Result<()> { if max_instructions.fetch_sub(30, Ordering::Relaxed) <= 30 { Err(Error::runtime("time's up")) } else { - Ok(()) + Ok(VmState::Continue) } }, ); @@ -191,10 +191,10 @@ fn test_hook_swap_within_hook() -> Result<()> { TL_LUA.with(|tl| { tl.borrow().as_ref().unwrap().remove_hook(); }); - Ok(()) + Ok(VmState::Continue) }) }); - Ok(()) + Ok(VmState::Continue) }) }); @@ -234,7 +234,7 @@ fn test_hook_threads() -> Result<()> { co.set_hook(HookTriggers::EVERY_LINE, move |_lua, debug| { assert_eq!(debug.event(), DebugEvent::Line); hook_output.lock().unwrap().push(debug.curr_line()); - Ok(()) + Ok(VmState::Continue) }); co.resume::<()>(())?; @@ -249,3 +249,39 @@ fn test_hook_threads() -> Result<()> { Ok(()) } + +#[test] +fn test_hook_yield() -> Result<()> { + let lua = Lua::new(); + + let func = lua + .load( + r#" + local x = 2 + 3 + local y = x * 63 + local z = string.len(x..", "..y) + "#, + ) + .into_function()?; + let co = lua.create_thread(func)?; + + co.set_hook(HookTriggers::EVERY_LINE, move |_lua, _debug| Ok(VmState::Yield)); + + #[cfg(any(feature = "lua54", feature = "lua53"))] + { + assert!(co.resume::<()>(()).is_ok()); + assert!(co.resume::<()>(()).is_ok()); + assert!(co.resume::<()>(()).is_ok()); + assert!(co.resume::<()>(()).is_ok()); + assert!(co.status() == ThreadStatus::Finished); + } + #[cfg(any(feature = "lua51", feature = "lua52", feature = "luajit"))] + { + assert!( + matches!(co.resume::<()>(()), Err(Error::RuntimeError(err)) if err.contains("attempt to yield from a hook")) + ); + assert!(co.status() == ThreadStatus::Error); + } + + Ok(()) +} From 8bb2b444ab63042d412fe508eb0b784e922ba028 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 22 Sep 2024 23:20:18 +0100 Subject: [PATCH 188/635] Run tests with forced memory limit checks --- .github/workflows/coverage.yml | 4 +- .github/workflows/main.yml | 76 +++++++++++++++++++++++----------- src/state/raw.rs | 5 +++ tarpaulin.toml | 18 ++++++-- 4 files changed, 73 insertions(+), 30 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 730eb257..e944f62f 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -10,14 +10,14 @@ jobs: options: --security-opt seccomp=unconfined steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@main - name: Generate coverage report run: | cargo tarpaulin --out xml --tests --exclude-files benches/* --exclude-files mlua-sys/src/*/* - name: Upload report to codecov.io - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: token: ${{secrets.CODECOV_TOKEN}} fail_ci_if_error: false diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ae47add6..84331793 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,18 +7,18 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-22.04, macos-latest, windows-latest] + os: [ubuntu-latest, macos-latest, windows-latest] rust: [stable] lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] include: - - os: ubuntu-22.04 + - os: ubuntu-latest target: x86_64-unknown-linux-gnu - os: macos-latest target: x86_64-apple-darwin - os: windows-latest target: x86_64-pc-windows-msvc steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.rust }} @@ -31,7 +31,7 @@ jobs: cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,send" shell: bash - name: Build ${{ matrix.lua }} pkg-config - if: ${{ matrix.os == 'ubuntu-22.04' }} + if: ${{ matrix.os == 'ubuntu-latest' }} run: | sudo apt-get update sudo apt-get install -y --no-install-recommends liblua5.4-dev liblua5.3-dev liblua5.2-dev liblua5.1-0-dev libluajit-5.1-dev @@ -45,7 +45,7 @@ jobs: matrix: lua: [lua54, lua53, lua52, lua51, luajit] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@stable with: toolchain: stable @@ -55,13 +55,13 @@ jobs: build_aarch64_cross_ubuntu: name: Cross-compile to aarch64-unknown-linux-gnu - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest needs: build strategy: matrix: lua: [lua54, lua53, lua52, lua51, luajit] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@stable with: toolchain: stable @@ -77,13 +77,13 @@ jobs: build_armv7_cross_ubuntu: name: Cross-compile to armv7-unknown-linux-gnueabihf - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest needs: build strategy: matrix: lua: [lua54, lua53, lua52, lua51] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@stable with: toolchain: stable @@ -103,18 +103,18 @@ jobs: needs: build strategy: matrix: - os: [ubuntu-22.04, macos-latest, windows-latest] + os: [ubuntu-latest, macos-latest, windows-latest] rust: [stable, nightly] lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit, luau-vector4] include: - - os: ubuntu-22.04 + - os: ubuntu-latest target: x86_64-unknown-linux-gnu - os: macos-latest target: x86_64-apple-darwin - os: windows-latest target: x86_64-pc-windows-msvc steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.rust }} @@ -139,14 +139,14 @@ jobs: needs: build strategy: matrix: - os: [ubuntu-22.04] + os: [ubuntu-latest] rust: [nightly] lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] include: - - os: ubuntu-22.04 + - os: ubuntu-latest target: x86_64-unknown-linux-gnu steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.rust }} @@ -160,22 +160,48 @@ jobs: env: RUSTFLAGS: -Z sanitizer=address + test_with_memory_limit: + name: Test with memory limit + runs-on: ${{ matrix.os }} + needs: build + strategy: + matrix: + os: [ubuntu-latest] + rust: [nightly] + lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + steps: + - uses: actions/checkout@main + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + - uses: Swatinem/rust-cache@v2 + - name: Run ${{ matrix.lua }} tests with forced memory limit + run: | + cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros" + shell: bash + env: + RUSTFLAGS: --cfg=force_memory_limit + test_modules: name: Test modules runs-on: ${{ matrix.os }} needs: build strategy: matrix: - os: [ubuntu-22.04, macos-latest] + os: [ubuntu-latest, macos-latest] rust: [stable] lua: [lua54, lua53, lua52, lua51, luajit, luau] include: - - os: ubuntu-22.04 + - os: ubuntu-latest target: x86_64-unknown-linux-gnu - os: macos-latest target: x86_64-apple-darwin steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.rust }} @@ -199,7 +225,7 @@ jobs: shell: msys2 {0} steps: - uses: msys2/setup-msys2@v2 - - uses: actions/checkout@v4 + - uses: actions/checkout@main - name: Install Rust & Lua run: | pacman -S --noconfirm mingw-w64-x86_64-rust mingw-w64-x86_64-lua mingw-w64-x86_64-luajit mingw-w64-x86_64-pkg-config @@ -210,13 +236,13 @@ jobs: test_wasm32_emscripten: name: Test on wasm32-unknown-emscripten - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest needs: build strategy: matrix: lua: [lua54, lua53, lua52, lua51, luau] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@stable with: toolchain: stable @@ -232,9 +258,9 @@ jobs: rustfmt: name: Rustfmt - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@nightly with: components: rustfmt @@ -242,12 +268,12 @@ jobs: clippy: name: Clippy - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest strategy: matrix: lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@stable with: toolchain: nightly diff --git a/src/state/raw.rs b/src/state/raw.rs index 81d283e6..1b71a3f5 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -725,6 +725,11 @@ impl RawLua { #[inline] pub(crate) unsafe fn unlikely_memory_error(&self) -> bool { + #[cfg(debug_assertions)] + if cfg!(force_memory_limit) { + return false; + } + // MemoryInfo is empty in module mode so we cannot predict memory limits match MemoryState::get(self.main_state) { mem_state if !mem_state.is_null() => (*mem_state).memory_limit() == 0, diff --git a/tarpaulin.toml b/tarpaulin.toml index f5f71b4f..e6b58889 100644 --- a/tarpaulin.toml +++ b/tarpaulin.toml @@ -1,8 +1,20 @@ [lua54_coverage] -features = "lua54,vendored,async,serialize,macros" +features = "lua54,vendored,async,send,serialize,macros" + +[lua54_with_memory_limit_coverage] +features = "lua54,vendored,async,send,serialize,macros" +rustflags = "--cfg force_memory_limit" [lua51_coverage] -features = "lua51,vendored,async,serialize,macros" +features = "lua51,vendored,async,send,serialize,macros" + +[lua51_with_memory_limit_coverage] +features = "lua51,vendored,async,send,serialize,macros" +rustflags = "--cfg force_memory_limit" [luau_coverage] -features = "luau,async,serialize,macros" +features = "luau,async,send,serialize,macros" + +[luau_with_memory_limit_coverage] +features = "luau,async,send,serialize,macros" +rustflags = "--cfg force_memory_limit" From 16951e36284292e47359b405e8efb4d6b8af9781 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 22 Sep 2024 23:58:53 +0100 Subject: [PATCH 189/635] Move `ValueRef` to a new module --- src/types.rs | 71 ++---------------------------------------- src/types/value_ref.rs | 71 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 68 deletions(-) create mode 100644 src/types/value_ref.rs diff --git a/src/types.rs b/src/types.rs index 6095c828..ef90e805 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,12 +1,11 @@ use std::cell::UnsafeCell; -use std::fmt; use std::os::raw::{c_int, c_void}; use std::rc::Rc; use crate::error::Result; #[cfg(not(feature = "luau"))] use crate::hook::Debug; -use crate::state::{ExtraData, Lua, RawLua, WeakLua}; +use crate::state::{ExtraData, Lua, RawLua}; // Re-export mutex wrappers pub(crate) use sync::{ArcReentrantMutexGuard, ReentrantMutex, ReentrantMutexGuard, XRc, XWeak}; @@ -19,6 +18,7 @@ pub(crate) type BoxFuture<'a, T> = futures_util::future::LocalBoxFuture<'a, T>; pub use app_data::{AppData, AppDataRef, AppDataRefMut}; pub use registry_key::RegistryKey; +pub(crate) use value_ref::ValueRef; #[cfg(any(feature = "luau", doc))] pub use vector::Vector; @@ -115,75 +115,10 @@ impl MaybeSend for T {} pub(crate) struct DestructedUserdata; -pub(crate) struct ValueRef { - pub(crate) lua: WeakLua, - pub(crate) index: c_int, - pub(crate) drop: bool, -} - -impl ValueRef { - #[inline] - pub(crate) fn new(lua: &RawLua, index: c_int) -> Self { - ValueRef { - lua: lua.weak().clone(), - index, - drop: true, - } - } - - #[inline] - pub(crate) fn to_pointer(&self) -> *const c_void { - let lua = self.lua.lock(); - unsafe { ffi::lua_topointer(lua.ref_thread(), self.index) } - } - - /// Returns a copy of the value, which is valid as long as the original value is held. - #[inline] - pub(crate) fn copy(&self) -> Self { - ValueRef { - lua: self.lua.clone(), - index: self.index, - drop: false, - } - } -} - -impl fmt::Debug for ValueRef { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Ref({:p})", self.to_pointer()) - } -} - -impl Clone for ValueRef { - fn clone(&self) -> Self { - unsafe { self.lua.lock().clone_ref(self) } - } -} - -impl Drop for ValueRef { - fn drop(&mut self) { - if self.drop { - if let Some(lua) = self.lua.try_lock() { - unsafe { lua.drop_ref(self) }; - } - } - } -} - -impl PartialEq for ValueRef { - fn eq(&self, other: &Self) -> bool { - assert!( - self.lua == other.lua, - "Lua instance passed Value created from a different main Lua state" - ); - let lua = self.lua.lock(); - unsafe { ffi::lua_rawequal(lua.ref_thread(), self.index, other.index) == 1 } - } -} - mod app_data; mod registry_key; mod sync; +mod value_ref; #[cfg(any(feature = "luau", doc))] mod vector; diff --git a/src/types/value_ref.rs b/src/types/value_ref.rs new file mode 100644 index 00000000..89a60ec4 --- /dev/null +++ b/src/types/value_ref.rs @@ -0,0 +1,71 @@ +use std::fmt; +use std::os::raw::{c_int, c_void}; + +use crate::state::{RawLua, WeakLua}; + +/// A reference to a Lua (complex) value stored in the Lua auxiliary thread. +pub(crate) struct ValueRef { + pub(crate) lua: WeakLua, + pub(crate) index: c_int, + pub(crate) drop: bool, +} + +impl ValueRef { + #[inline] + pub(crate) fn new(lua: &RawLua, index: c_int) -> Self { + ValueRef { + lua: lua.weak().clone(), + index, + drop: true, + } + } + + #[inline] + pub(crate) fn to_pointer(&self) -> *const c_void { + let lua = self.lua.lock(); + unsafe { ffi::lua_topointer(lua.ref_thread(), self.index) } + } + + /// Returns a copy of the value, which is valid as long as the original value is held. + #[inline] + pub(crate) fn copy(&self) -> Self { + ValueRef { + lua: self.lua.clone(), + index: self.index, + drop: false, + } + } +} + +impl fmt::Debug for ValueRef { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Ref({:p})", self.to_pointer()) + } +} + +impl Clone for ValueRef { + fn clone(&self) -> Self { + unsafe { self.lua.lock().clone_ref(self) } + } +} + +impl Drop for ValueRef { + fn drop(&mut self) { + if self.drop { + if let Some(lua) = self.lua.try_lock() { + unsafe { lua.drop_ref(self) }; + } + } + } +} + +impl PartialEq for ValueRef { + fn eq(&self, other: &Self) -> bool { + assert!( + self.lua == other.lua, + "Lua instance passed Value created from a different main Lua state" + ); + let lua = self.lua.lock(); + unsafe { ffi::lua_rawequal(lua.ref_thread(), self.index, other.index) == 1 } + } +} From ca69be07ff638a092b5c1b5fcb9bbec09d3c2c85 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 23 Sep 2024 10:45:57 +0100 Subject: [PATCH 190/635] Support setting metatable for Lua builtin types. Closes #445 --- src/function.rs | 6 ++- src/state.rs | 62 ++++++++++++++++++++---- src/string.rs | 8 +++- src/table.rs | 8 +++- src/thread.rs | 6 ++- src/types.rs | 18 ++++++- src/types/vector.rs | 11 +++++ tests/luau.rs | 2 +- tests/types.rs | 113 +++++++++++++++++++++++++++++++++++++++++++- 9 files changed, 215 insertions(+), 19 deletions(-) diff --git a/src/function.rs b/src/function.rs index 95cb43a7..fdf4c1a7 100644 --- a/src/function.rs +++ b/src/function.rs @@ -5,7 +5,7 @@ use std::{mem, ptr, slice}; use crate::error::{Error, Result}; use crate::state::Lua; use crate::table::Table; -use crate::types::{Callback, MaybeSend, ValueRef}; +use crate::types::{Callback, LuaType, MaybeSend, ValueRef}; use crate::util::{ assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str, StackGuard, }; @@ -588,6 +588,10 @@ impl IntoLua for WrappedAsyncFunction { } } +impl LuaType for Function { + const TYPE_ID: c_int = ffi::LUA_TFUNCTION; +} + #[cfg(test)] mod assertions { use super::*; diff --git a/src/state.rs b/src/state.rs index e10174b0..f695296b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -18,7 +18,7 @@ use crate::string::String; use crate::table::Table; use crate::thread::Thread; use crate::types::{ - AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, MaybeSend, Number, ReentrantMutex, + AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LuaType, MaybeSend, Number, ReentrantMutex, ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak, }; use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataStorage}; @@ -1337,24 +1337,66 @@ impl Lua { unsafe { self.lock().make_userdata(UserDataStorage::new(ud)) } } - /// Sets the metatable for a Luau builtin vector type. - #[cfg(any(feature = "luau", doc))] - #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - pub fn set_vector_metatable(&self, metatable: Option
) { + /// Sets the metatable for a Lua builtin type. + /// + /// The metatable will be shared by all values of the given type. + /// + /// # Examples + /// + /// Change metatable for Lua boolean type: + /// + /// ``` + /// # use mlua::{Lua, Result, Function}; + /// # fn main() -> Result<()> { + /// # let lua = Lua::new(); + /// let mt = lua.create_table()?; + /// mt.set("__tostring", lua.create_function(|_, b: bool| Ok(if b { 2 } else { 0 }))?)?; + /// lua.set_type_metatable::(Some(mt)); + /// lua.load("assert(tostring(true) == '2')").exec()?; + /// # Ok(()) + /// # } + /// ``` + #[allow(private_bounds)] + pub fn set_type_metatable(&self, metatable: Option
) { let lua = self.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); assert_stack(state, 2); - #[cfg(not(feature = "luau-vector4"))] - ffi::lua_pushvector(state, 0., 0., 0.); - #[cfg(feature = "luau-vector4")] - ffi::lua_pushvector(state, 0., 0., 0., 0.); + match T::TYPE_ID { + ffi::LUA_TBOOLEAN => { + ffi::lua_pushboolean(state, 0); + } + ffi::LUA_TLIGHTUSERDATA => { + ffi::lua_pushlightuserdata(state, ptr::null_mut()); + } + ffi::LUA_TNUMBER => { + ffi::lua_pushnumber(state, 0.); + } + #[cfg(feature = "luau")] + ffi::LUA_TVECTOR => { + #[cfg(not(feature = "luau-vector4"))] + ffi::lua_pushvector(state, 0., 0., 0.); + #[cfg(feature = "luau-vector4")] + ffi::lua_pushvector(state, 0., 0., 0., 0.); + } + ffi::LUA_TSTRING => { + ffi::lua_pushstring(state, b"\0" as *const u8 as *const _); + } + ffi::LUA_TFUNCTION => match self.load("function() end").eval::() { + Ok(func) => lua.push_ref(&func.0), + Err(_) => return, + }, + ffi::LUA_TTHREAD => { + ffi::lua_newthread(state); + } + _ => {} + } match metatable { Some(metatable) => lua.push_ref(&metatable.0), None => ffi::lua_pushnil(state), - }; + } ffi::lua_setmetatable(state, -2); } } diff --git a/src/string.rs b/src/string.rs index 39c1ddc7..5057b0be 100644 --- a/src/string.rs +++ b/src/string.rs @@ -1,7 +1,7 @@ use std::borrow::Borrow; use std::hash::{Hash, Hasher}; use std::ops::Deref; -use std::os::raw::c_void; +use std::os::raw::{c_int, c_void}; use std::string::String as StdString; use std::{cmp, fmt, slice, str}; @@ -13,7 +13,7 @@ use { use crate::error::{Error, Result}; use crate::state::Lua; -use crate::types::ValueRef; +use crate::types::{LuaType, ValueRef}; /// Handle to an internal Lua string. /// @@ -327,6 +327,10 @@ impl<'a> IntoIterator for BorrowedBytes<'a> { } } +impl LuaType for String { + const TYPE_ID: c_int = ffi::LUA_TSTRING; +} + #[cfg(test)] mod assertions { use super::*; diff --git a/src/table.rs b/src/table.rs index 3bf638c4..b231f0eb 100644 --- a/src/table.rs +++ b/src/table.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use std::fmt; use std::marker::PhantomData; -use std::os::raw::c_void; +use std::os::raw::{c_int, c_void}; use std::string::String as StdString; #[cfg(feature = "serialize")] @@ -15,7 +15,7 @@ use crate::error::{Error, Result}; use crate::function::Function; use crate::state::{LuaGuard, RawLua}; use crate::traits::ObjectLike; -use crate::types::{Integer, ValueRef}; +use crate::types::{Integer, LuaType, ValueRef}; use crate::util::{assert_stack, check_stack, StackGuard}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Nil, Value}; @@ -961,6 +961,10 @@ impl Serialize for Table { } } +impl LuaType for Table { + const TYPE_ID: c_int = ffi::LUA_TTABLE; +} + #[cfg(feature = "serialize")] impl<'a> SerializableTable<'a> { #[inline] diff --git a/src/thread.rs b/src/thread.rs index b4b56ee2..cbbd61fe 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -4,7 +4,7 @@ use crate::error::{Error, Result}; #[allow(unused)] use crate::state::Lua; use crate::state::RawLua; -use crate::types::{ValueRef, VmState}; +use crate::types::{LuaType, ValueRef, VmState}; use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard}; use crate::value::{FromLuaMulti, IntoLuaMulti}; @@ -372,6 +372,10 @@ impl PartialEq for Thread { } } +impl LuaType for Thread { + const TYPE_ID: c_int = ffi::LUA_TTHREAD; +} + #[cfg(feature = "async")] impl AsyncThread { #[inline] diff --git a/src/types.rs b/src/types.rs index ef90e805..be5b53b3 100644 --- a/src/types.rs +++ b/src/types.rs @@ -27,7 +27,7 @@ pub type Integer = ffi::lua_Integer; /// Type of Lua floating point numbers. pub type Number = ffi::lua_Number; -// Represents different subtypes wrapped to AnyUserData +// Represents different subtypes wrapped in AnyUserData #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub(crate) enum SubtypeId { None, @@ -115,6 +115,22 @@ impl MaybeSend for T {} pub(crate) struct DestructedUserdata; +pub(crate) trait LuaType { + const TYPE_ID: c_int; +} + +impl LuaType for bool { + const TYPE_ID: c_int = ffi::LUA_TBOOLEAN; +} + +impl LuaType for Number { + const TYPE_ID: c_int = ffi::LUA_TNUMBER; +} + +impl LuaType for LightUserData { + const TYPE_ID: c_int = ffi::LUA_TLIGHTUSERDATA; +} + mod app_data; mod registry_key; mod sync; diff --git a/src/types/vector.rs b/src/types/vector.rs index d4ac5c61..f65ba863 100644 --- a/src/types/vector.rs +++ b/src/types/vector.rs @@ -3,6 +3,8 @@ use std::fmt; #[cfg(all(any(feature = "luau", doc), feature = "serialize"))] use serde::ser::{Serialize, SerializeTupleStruct, Serializer}; +use super::LuaType; + /// A Luau vector type. /// /// By default vectors are 3-dimensional, but can be 4-dimensional @@ -84,3 +86,12 @@ impl PartialEq<[f32; Self::SIZE]> for Vector { self.0 == *other } } + +impl LuaType for Vector { + #[cfg(feature = "luau")] + const TYPE_ID: i32 = ffi::LUA_TVECTOR; + + // This is a dummy value, as `Vector` is supported only by Luau + #[cfg(not(feature = "luau"))] + const TYPE_ID: i32 = ffi::LUA_TNONE; +} diff --git a/tests/luau.rs b/tests/luau.rs index afcdcbdf..3d7268b8 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -194,7 +194,7 @@ fn test_vector_metatable() -> Result<()> { ) .eval::
()?; vector_mt.set_metatable(Some(vector_mt.clone())); - lua.set_vector_metatable(Some(vector_mt.clone())); + lua.set_type_metatable::(Some(vector_mt.clone())); lua.globals().set("Vector3", vector_mt)?; let compiler = Compiler::new().set_vector_lib("Vector3").set_vector_ctor("new"); diff --git a/tests/types.rs b/tests/types.rs index ffe39607..830851ae 100644 --- a/tests/types.rs +++ b/tests/types.rs @@ -1,6 +1,6 @@ use std::os::raw::c_void; -use mlua::{Function, LightUserData, Lua, Result}; +use mlua::{Function, LightUserData, Lua, Number, Result, String as LuaString, Thread}; #[test] fn test_lightuserdata() -> Result<()> { @@ -24,3 +24,114 @@ fn test_lightuserdata() -> Result<()> { Ok(()) } + +#[test] +fn test_boolean_type_metatable() -> Result<()> { + let lua = Lua::new(); + + let mt = lua.create_table()?; + mt.set("__add", Function::wrap(|_, (a, b): (bool, bool)| Ok(a || b)))?; + lua.set_type_metatable::(Some(mt)); + + lua.load(r#"assert(true + true == true)"#).exec().unwrap(); + lua.load(r#"assert(true + false == true)"#).exec().unwrap(); + lua.load(r#"assert(false + true == true)"#).exec().unwrap(); + lua.load(r#"assert(false + false == false)"#).exec().unwrap(); + + Ok(()) +} + +#[test] +fn test_lightuserdata_type_metatable() -> Result<()> { + let lua = Lua::new(); + + let mt = lua.create_table()?; + mt.set( + "__add", + Function::wrap(|_, (a, b): (LightUserData, LightUserData)| { + Ok(LightUserData((a.0 as usize + b.0 as usize) as *mut c_void)) + }), + )?; + lua.set_type_metatable::(Some(mt)); + + let res = lua + .load( + r#" + local a, b = ... + return a + b + "#, + ) + .call::(( + LightUserData(42 as *mut c_void), + LightUserData(100 as *mut c_void), + )) + .unwrap(); + assert_eq!(res, LightUserData(142 as *mut c_void)); + + Ok(()) +} + +#[test] +fn test_number_type_metatable() -> Result<()> { + let lua = Lua::new(); + + let mt = lua.create_table()?; + mt.set("__call", Function::wrap(|_, (n1, n2): (f64, f64)| Ok(n1 * n2)))?; + lua.set_type_metatable::(Some(mt)); + lua.load(r#"assert((1.5)(3.0) == 4.5)"#).exec().unwrap(); + lua.load(r#"assert((5)(5) == 25)"#).exec().unwrap(); + + Ok(()) +} + +#[test] +fn test_string_type_metatable() -> Result<()> { + let lua = Lua::new(); + + let mt = lua.create_table()?; + mt.set( + "__add", + Function::wrap(|_, (a, b): (LuaString, LuaString)| Ok(format!("{}{}", a.to_str()?, b.to_str()?))), + )?; + lua.set_type_metatable::(Some(mt)); + + lua.load(r#"assert(("foo" + "bar") == "foobar")"#).exec().unwrap(); + + Ok(()) +} + +#[test] +fn test_function_type_metatable() -> Result<()> { + let lua = Lua::new(); + + let mt = lua.create_table()?; + mt.set( + "__index", + Function::wrap(|_, (_, key): (Function, String)| Ok(format!("function.{key}"))), + )?; + lua.set_type_metatable::(Some(mt)); + + lua.load(r#"assert((function() end).foo == "function.foo")"#) + .exec() + .unwrap(); + + Ok(()) +} + +#[test] +fn test_thread_type_metatable() -> Result<()> { + let lua = Lua::new(); + + let mt = lua.create_table()?; + mt.set( + "__index", + Function::wrap(|_, (_, key): (Thread, String)| Ok(format!("thread.{key}"))), + )?; + lua.set_type_metatable::(Some(mt)); + + lua.load(r#"assert((coroutine.create(function() end)).foo == "thread.foo")"#) + .exec() + .unwrap(); + + Ok(()) +} From 3714da5ec8a1029aac077756c6531918e0b64915 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 23 Sep 2024 11:01:32 +0100 Subject: [PATCH 191/635] Fix doc test for `Lua::set_type_metatable` --- src/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state.rs b/src/state.rs index f695296b..f5f76d53 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1350,7 +1350,7 @@ impl Lua { /// # fn main() -> Result<()> { /// # let lua = Lua::new(); /// let mt = lua.create_table()?; - /// mt.set("__tostring", lua.create_function(|_, b: bool| Ok(if b { 2 } else { 0 }))?)?; + /// mt.set("__tostring", lua.create_function(|_, b: bool| Ok(if b { "2" } else { "0" }))?)?; /// lua.set_type_metatable::(Some(mt)); /// lua.load("assert(tostring(true) == '2')").exec()?; /// # Ok(()) From e582e7c57f1958b3404cf5e1ae018d55ff87ab98 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 23 Sep 2024 11:13:13 +0100 Subject: [PATCH 192/635] Rename `get_metatable` to `metatable` for Table/AnyUserData types --- src/table.rs | 12 +++++++++--- src/userdata.rs | 8 +++++++- tests/scope.rs | 2 +- tests/table.rs | 2 +- tests/userdata.rs | 14 +++++++------- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/table.rs b/src/table.rs index b231f0eb..002bfb4b 100644 --- a/src/table.rs +++ b/src/table.rs @@ -227,12 +227,12 @@ impl Table { // Compare using __eq metamethod if exists // First, check the self for the metamethod. // If self does not define it, then check the other table. - if let Some(mt) = self.get_metatable() { + if let Some(mt) = self.metatable() { if mt.contains_key("__eq")? { return mt.get::("__eq")?.call((self, other)); } } - if let Some(mt) = other.get_metatable() { + if let Some(mt) = other.metatable() { if mt.contains_key("__eq")? { return mt.get::("__eq")?.call((self, other)); } @@ -493,7 +493,7 @@ impl Table { /// Returns a reference to the metatable of this table, or `None` if no metatable is set. /// /// Unlike the `getmetatable` Lua function, this method ignores the `__metatable` field. - pub fn get_metatable(&self) -> Option
{ + pub fn metatable(&self) -> Option
{ let lua = self.0.lua.lock(); let state = lua.state(); unsafe { @@ -509,6 +509,12 @@ impl Table { } } + #[doc(hidden)] + #[deprecated(since = "0.10.0", note = "please use `metatable` instead")] + pub fn get_metatable(&self) -> Option
{ + self.metatable() + } + /// Sets or removes the metatable of this table. /// /// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does diff --git a/src/userdata.rs b/src/userdata.rs index 5b7ca723..78e68bef 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -900,10 +900,16 @@ impl AnyUserData { /// /// [`UserDataMetatable`]: crate::UserDataMetatable #[inline] - pub fn get_metatable(&self) -> Result { + pub fn metatable(&self) -> Result { self.get_raw_metatable().map(UserDataMetatable) } + #[doc(hidden)] + #[deprecated(since = "0.10.0", note = "please use `metatable` instead")] + pub fn get_metatable(&self) -> Result { + self.metatable() + } + fn get_raw_metatable(&self) -> Result
{ let lua = self.0.lua.lock(); let state = lua.state(); diff --git a/tests/scope.rs b/tests/scope.rs index edc06db6..aa96c48d 100644 --- a/tests/scope.rs +++ b/tests/scope.rs @@ -300,7 +300,7 @@ fn test_scope_userdata_drop() -> Result<()> { Err(Error::UserDataDestructed) => {} Err(err) => panic!("improper borrow error for destructed userdata: {err:?}"), } - match ud.get_metatable() { + match ud.metatable() { Ok(_) => panic!("successful metatable retrieval of destructed userdata"), Err(Error::UserDataDestructed) => {} Err(err) => panic!("improper metatable error for destructed userdata: {err:?}"), diff --git a/tests/table.rs b/tests/table.rs index 957d5a48..e6410e53 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -181,7 +181,7 @@ fn test_table_clear() -> Result<()> { assert_eq!(t2.raw_len(), 0); assert!(t2.is_empty()); assert_eq!(t2.raw_get::("a")?, Value::Nil); - assert_ne!(t2.get_metatable(), None); + assert_ne!(t2.metatable(), None); Ok(()) } diff --git a/tests/userdata.rs b/tests/userdata.rs index b78e0171..dd894b30 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -198,10 +198,10 @@ fn test_metamethods() -> Result<()> { assert!(userdata2.equals(userdata3)?); let userdata1: AnyUserData = globals.get("userdata1")?; - assert!(userdata1.get_metatable()?.contains(MetaMethod::Add)?); - assert!(userdata1.get_metatable()?.contains(MetaMethod::Sub)?); - assert!(userdata1.get_metatable()?.contains(MetaMethod::Index)?); - assert!(!userdata1.get_metatable()?.contains(MetaMethod::Pow)?); + assert!(userdata1.metatable()?.contains(MetaMethod::Add)?); + assert!(userdata1.metatable()?.contains(MetaMethod::Sub)?); + assert!(userdata1.metatable()?.contains(MetaMethod::Index)?); + assert!(!userdata1.metatable()?.contains(MetaMethod::Pow)?); Ok(()) } @@ -565,7 +565,7 @@ fn test_metatable() -> Result<()> { impl UserData for MyUserData { fn add_methods>(methods: &mut M) { methods.add_function("my_type_name", |_, data: AnyUserData| { - let metatable = data.get_metatable()?; + let metatable = data.metatable()?; metatable.get::(MetaMethod::Type) }); } @@ -583,7 +583,7 @@ fn test_metatable() -> Result<()> { lua.load(r#"assert(typeof(ud) == "MyUserData")"#).exec()?; let ud: AnyUserData = globals.get("ud")?; - let metatable = ud.get_metatable()?; + let metatable = ud.metatable()?; match metatable.get::("__gc") { Ok(_) => panic!("expected MetaMethodRestricted, got no error"), @@ -629,7 +629,7 @@ fn test_metatable() -> Result<()> { } let ud = lua.create_userdata(MyUserData3)?; - let metatable = ud.get_metatable()?; + let metatable = ud.metatable()?; assert_eq!(metatable.get::(MetaMethod::Type)?.to_str()?, "CustomName"); Ok(()) From 5b5f1e466943af4274773c0e11718a14da15ee67 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 23 Sep 2024 11:14:24 +0100 Subject: [PATCH 193/635] Remove undocumented `Lua::push` in favour of `Lua::with_raw_state` --- src/state.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/state.rs b/src/state.rs index f5f76d53..d8f0b57a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1856,15 +1856,6 @@ impl Lua { extra.app_data.remove() } - /// Pushes a value that implements `IntoLua` onto the Lua stack. - /// - /// Uses 2 stack spaces, does not call checkstack. - #[doc(hidden)] - #[inline(always)] - pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> { - self.lock().push(value) - } - /// Returns an internal `Poll::Pending` constant used for executing async callbacks. #[cfg(feature = "async")] #[doc(hidden)] From 762e677a708cbac86d91ebdf27e4e2a31cc2afcc Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 23 Sep 2024 15:53:08 +0100 Subject: [PATCH 194/635] Update Error matching code This is mostly cosmetic change. --- src/error.rs | 58 ++++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/error.rs b/src/error.rs index 7c25f036..59a80b27 100644 --- a/src/error.rs +++ b/src/error.rs @@ -205,17 +205,17 @@ pub type Result = StdResult; #[cfg(not(tarpaulin_include))] impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - match *self { - Error::SyntaxError { ref message, .. } => write!(fmt, "syntax error: {message}"), - Error::RuntimeError(ref msg) => write!(fmt, "runtime error: {msg}"), - Error::MemoryError(ref msg) => { + match self { + Error::SyntaxError { message, .. } => write!(fmt, "syntax error: {message}"), + Error::RuntimeError(msg) => write!(fmt, "runtime error: {msg}"), + Error::MemoryError(msg) => { write!(fmt, "memory error: {msg}") } #[cfg(any(feature = "lua53", feature = "lua52"))] - Error::GarbageCollectorError(ref msg) => { + Error::GarbageCollectorError(msg) => { write!(fmt, "garbage collector error: {msg}") } - Error::SafetyError(ref msg) => { + Error::SafetyError(msg) => { write!(fmt, "safety error: {msg}") }, Error::MemoryLimitNotAvailable => { @@ -234,7 +234,7 @@ impl fmt::Display for Error { fmt, "too many arguments to Function::bind" ), - Error::BadArgument { ref to, pos, ref name, ref cause } => { + Error::BadArgument { to, pos, name, cause } => { if let Some(name) = name { write!(fmt, "bad argument `{name}`")?; } else { @@ -245,18 +245,18 @@ impl fmt::Display for Error { } write!(fmt, ": {cause}") }, - Error::ToLuaConversionError { from, to, ref message } => { + Error::ToLuaConversionError { from, to, message } => { write!(fmt, "error converting {from} to Lua {to}")?; - match *message { + match message { None => Ok(()), - Some(ref message) => write!(fmt, " ({message})"), + Some(message) => write!(fmt, " ({message})"), } } - Error::FromLuaConversionError { from, to, ref message } => { + Error::FromLuaConversionError { from, to, message } => { write!(fmt, "error converting Lua {from} to {to}")?; - match *message { + match message { None => Ok(()), - Some(ref message) => write!(fmt, " ({message})"), + Some(message) => write!(fmt, " ({message})"), } } Error::CoroutineUnresumable => write!(fmt, "coroutine is non-resumable"), @@ -264,21 +264,21 @@ impl fmt::Display for Error { Error::UserDataDestructed => write!(fmt, "userdata has been destructed"), Error::UserDataBorrowError => write!(fmt, "error borrowing userdata"), Error::UserDataBorrowMutError => write!(fmt, "error mutably borrowing userdata"), - Error::MetaMethodRestricted(ref method) => write!(fmt, "metamethod {method} is restricted"), - Error::MetaMethodTypeError { ref method, type_name, ref message } => { + Error::MetaMethodRestricted(method) => write!(fmt, "metamethod {method} is restricted"), + Error::MetaMethodTypeError { method, type_name, message } => { write!(fmt, "metamethod {method} has unsupported type {type_name}")?; - match *message { + match message { None => Ok(()), - Some(ref message) => write!(fmt, " ({message})"), + Some(message) => write!(fmt, " ({message})"), } } Error::MismatchedRegistryKey => { write!(fmt, "RegistryKey used from different Lua state") } - Error::CallbackError { ref cause, ref traceback } => { + Error::CallbackError { cause, traceback } => { // Trace errors down to the root let (mut cause, mut full_traceback) = (cause, None); - while let Error::CallbackError { cause: ref cause2, traceback: ref traceback2 } = **cause { + while let Error::CallbackError { cause: cause2, traceback: traceback2 } = &**cause { cause = cause2; full_traceback = Some(traceback2); } @@ -302,15 +302,15 @@ impl fmt::Display for Error { write!(fmt, "previously resumed panic returned again") } #[cfg(feature = "serialize")] - Error::SerializeError(ref err) => { + Error::SerializeError(err) => { write!(fmt, "serialize error: {err}") }, #[cfg(feature = "serialize")] - Error::DeserializeError(ref err) => { + Error::DeserializeError(err) => { write!(fmt, "deserialize error: {err}") }, - Error::ExternalError(ref err) => write!(fmt, "{err}"), - Error::WithContext { ref context, ref cause } => { + Error::ExternalError(err) => write!(fmt, "{err}"), + Error::WithContext { context, cause } => { writeln!(fmt, "{context}")?; write!(fmt, "{cause}") } @@ -320,15 +320,15 @@ impl fmt::Display for Error { impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { - match *self { + match self { // An error type with a source error should either return that error via source or // include that source's error message in its own Display output, but never both. // https://blog.rust-lang.org/inside-rust/2021/07/01/What-the-error-handling-project-group-is-working-towards.html // Given that we include source to fmt::Display implementation for `CallbackError`, this call // returns nothing. Error::CallbackError { .. } => None, - Error::ExternalError(ref err) => err.source(), - Error::WithContext { ref cause, .. } => match cause.as_ref() { + Error::ExternalError(err) => err.source(), + Error::WithContext { cause, .. } => match cause.as_ref() { Error::ExternalError(err) => err.source(), _ => None, }, @@ -374,15 +374,15 @@ impl Error { } } - pub(crate) fn from_lua_conversion<'a>( + pub(crate) fn from_lua_conversion( from: &'static str, to: &'static str, - message: impl Into>, + message: impl Into>, ) -> Self { Error::FromLuaConversionError { from, to, - message: message.into().map(|s| s.into()), + message: message.into(), } } } From 8274b5fa88079df49984044d94bd5efb2ecb3f17 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 23 Sep 2024 15:56:29 +0100 Subject: [PATCH 195/635] More user-friendly error message on userdata mismatch --- src/state/raw.rs | 14 ++++++++++++-- src/userdata/cell.rs | 4 ++-- src/userdata/registry.rs | 4 ++-- tests/userdata.rs | 35 ++++++++++++++++++++++------------- 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index 1b71a3f5..61e70d3f 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -1004,8 +1004,18 @@ impl RawLua { } // Same as `get_userdata_ref_type_id` but assumes the userdata is already on the stack. - pub(crate) unsafe fn get_userdata_type_id(&self, idx: c_int) -> Result> { - self.get_userdata_type_id_inner(self.state(), idx) + pub(crate) unsafe fn get_userdata_type_id(&self, idx: c_int) -> Result> { + match self.get_userdata_type_id_inner(self.state(), idx) { + Ok(type_id) => Ok(type_id), + Err(Error::UserDataTypeMismatch) if ffi::lua_type(self.state(), idx) != ffi::LUA_TUSERDATA => { + // Report `FromLuaConversionError` instead + let idx_type_name = CStr::from_ptr(ffi::luaL_typename(self.state(), idx)); + let idx_type_name = idx_type_name.to_str().unwrap(); + let message = format!("expected userdata of type '{}'", short_type_name::()); + Err(Error::from_lua_conversion(idx_type_name, "userdata", message)) + } + Err(err) => Err(err), + } } unsafe fn get_userdata_type_id_inner( diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index e37590b0..b795b751 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -195,7 +195,7 @@ impl FromLua for UserDataRef { } unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - let type_id = lua.get_userdata_type_id(idx)?; + let type_id = lua.get_userdata_type_id::(idx)?; match type_id { Some(type_id) if type_id == TypeId::of::() => { (*get_userdata::>(lua.state(), idx)).try_borrow_owned() @@ -263,7 +263,7 @@ impl FromLua for UserDataRefMut { } unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - let type_id = lua.get_userdata_type_id(idx)?; + let type_id = lua.get_userdata_type_id::(idx)?; match type_id { Some(type_id) if type_id == TypeId::of::() => { (*get_userdata::>(lua.state(), idx)).try_borrow_owned_mut() diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index 260a497e..072562dd 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -121,7 +121,7 @@ impl UserDataRegistry { match target_type_id { // This branch is for `'static` userdata that share type metatable UserDataTypeId::Shared(target_type_id) => { - match try_self_arg!(rawlua.get_userdata_type_id(self_index)) { + match try_self_arg!(rawlua.get_userdata_type_id::(self_index)) { Some(self_type_id) if self_type_id == target_type_id => { let ud = get_userdata::>(state, self_index); try_self_arg!((*ud).try_borrow_scoped(|ud| { @@ -175,7 +175,7 @@ impl UserDataRegistry { match target_type_id { // This branch is for `'static` userdata that share type metatable UserDataTypeId::Shared(target_type_id) => { - match try_self_arg!(rawlua.get_userdata_type_id(self_index)) { + match try_self_arg!(rawlua.get_userdata_type_id::(self_index)) { Some(self_type_id) if self_type_id == target_type_id => { let ud = get_userdata::>(state, self_index); try_self_arg!((*ud).try_borrow_scoped_mut(|ud| { diff --git a/tests/userdata.rs b/tests/userdata.rs index dd894b30..b71f2c08 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -206,8 +206,8 @@ fn test_metamethods() -> Result<()> { Ok(()) } -#[test] #[cfg(feature = "lua54")] +#[test] fn test_metamethod_close() -> Result<()> { #[derive(Clone)] struct MyUserData(Arc); @@ -791,18 +791,27 @@ fn test_userdata_method_errors() -> Result<()> { let lua = Lua::new(); let ud = lua.create_userdata(MyUserData(123))?; - let res = ud.call_function::<()>("get_value", ()); - let Err(Error::CallbackError { cause, .. }) = res else { - panic!("expected CallbackError, got {res:?}"); - }; - assert!(matches!( - &*cause, - Error::BadArgument { - to, - name, - .. - } if to.as_deref() == Some("MyUserData.get_value") && name.as_deref() == Some("self") - )); + let res = ud.call_function::<()>("get_value", "not a userdata"); + match res { + Err(Error::CallbackError { cause, .. }) => match cause.as_ref() { + Error::BadArgument { + to, + name, + cause: cause2, + .. + } => { + assert_eq!(to.as_deref(), Some("MyUserData.get_value")); + assert_eq!(name.as_deref(), Some("self")); + println!("{}", cause2.to_string()); + assert_eq!( + cause2.to_string(), + "error converting Lua string to userdata (expected userdata of type 'MyUserData')" + ); + } + err => panic!("expected BadArgument, got {err:?}"), + }, + r => panic!("expected CallbackError, got {r:?}"), + } Ok(()) } From 91fe02da45b40623295b8db9e054e7c8b35fe9f6 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 24 Sep 2024 14:31:28 +0100 Subject: [PATCH 196/635] Add `LuaNativeFn`/`LuaNativeFnMut`/`LuaNativeAsyncFn` traits for using in `Function::wrap` --- src/function.rs | 68 ++++++++++++++++++++++----- src/lib.rs | 4 +- src/prelude.rs | 19 ++++---- src/state.rs | 6 +++ src/traits.rs | 92 +++++++++++++++++++++++++++++++++++++ tests/async.rs | 29 +++++++++++- tests/chunk.rs | 2 +- tests/function.rs | 114 +++++++++++++++++++++++++++++++++++++--------- tests/types.rs | 12 ++--- 9 files changed, 293 insertions(+), 53 deletions(-) diff --git a/src/function.rs b/src/function.rs index fdf4c1a7..37211122 100644 --- a/src/function.rs +++ b/src/function.rs @@ -5,6 +5,7 @@ use std::{mem, ptr, slice}; use crate::error::{Error, Result}; use crate::state::Lua; use crate::table::Table; +use crate::traits::{LuaNativeFn, LuaNativeFnMut}; use crate::types::{Callback, LuaType, MaybeSend, ValueRef}; use crate::util::{ assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str, StackGuard, @@ -13,6 +14,7 @@ use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti, Value}; #[cfg(feature = "async")] use { + crate::traits::LuaNativeAsyncFn, crate::types::AsyncCallback, std::future::{self, Future}, }; @@ -522,31 +524,56 @@ impl Function { /// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`] /// trait. #[inline] - pub fn wrap(func: F) -> impl IntoLua + pub fn wrap(func: F) -> impl IntoLua where + F: LuaNativeFn> + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, - F: Fn(&Lua, A) -> Result + MaybeSend + 'static, { WrappedFunction(Box::new(move |lua, nargs| unsafe { let args = A::from_stack_args(nargs, 1, None, lua)?; - func(lua.lua(), args)?.push_into_stack_multi(lua) + func.call(args)?.push_into_stack_multi(lua) })) } /// Wraps a Rust mutable closure, returning an opaque type that implements [`IntoLua`] trait. - #[inline] - pub fn wrap_mut(func: F) -> impl IntoLua + pub fn wrap_mut(func: F) -> impl IntoLua where + F: LuaNativeFnMut> + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, - F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, { let func = RefCell::new(func); WrappedFunction(Box::new(move |lua, nargs| unsafe { let mut func = func.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?; let args = A::from_stack_args(nargs, 1, None, lua)?; - func(lua.lua(), args)?.push_into_stack_multi(lua) + func.call(args)?.push_into_stack_multi(lua) + })) + } + + #[inline] + pub fn wrap_raw(func: F) -> impl IntoLua + where + F: LuaNativeFn + MaybeSend + 'static, + A: FromLuaMulti, + { + WrappedFunction(Box::new(move |lua, nargs| unsafe { + let args = A::from_stack_args(nargs, 1, None, lua)?; + func.call(args).push_into_stack_multi(lua) + })) + } + + #[inline] + pub fn wrap_raw_mut(func: F) -> impl IntoLua + where + F: LuaNativeFnMut + MaybeSend + 'static, + A: FromLuaMulti, + { + let func = RefCell::new(func); + WrappedFunction(Box::new(move |lua, nargs| unsafe { + let mut func = func.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?; + let args = A::from_stack_args(nargs, 1, None, lua)?; + func.call(args).push_into_stack_multi(lua) })) } @@ -554,23 +581,40 @@ impl Function { /// trait. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub fn wrap_async(func: F) -> impl IntoLua + pub fn wrap_async(func: F) -> impl IntoLua where + F: LuaNativeAsyncFn> + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, - F: Fn(Lua, A) -> FR + MaybeSend + 'static, - FR: Future> + MaybeSend + 'static, { WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe { let args = match A::from_stack_args(nargs, 1, None, rawlua) { Ok(args) => args, Err(e) => return Box::pin(future::ready(Err(e))), }; - let lua = rawlua.lua().clone(); - let fut = func(lua.clone(), args); + let lua = rawlua.lua(); + let fut = func.call(args); Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) })) } + + #[cfg(feature = "async")] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + pub fn wrap_raw_async(func: F) -> impl IntoLua + where + F: LuaNativeAsyncFn + MaybeSend + 'static, + A: FromLuaMulti, + { + WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe { + let args = match A::from_stack_args(nargs, 1, None, rawlua) { + Ok(args) => args, + Err(e) => return Box::pin(future::ready(Err(e))), + }; + let lua = rawlua.lua(); + let fut = func.call(args); + Box::pin(async move { fut.await.push_into_stack_multi(lua.raw_lua()) }) + })) + } } impl IntoLua for WrappedFunction { diff --git a/src/lib.rs b/src/lib.rs index c402e11c..4d489aa5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,7 +115,7 @@ pub use crate::stdlib::StdLib; pub use crate::string::{BorrowedBytes, BorrowedStr, String}; pub use crate::table::{Table, TablePairs, TableSequence}; pub use crate::thread::{Thread, ThreadStatus}; -pub use crate::traits::ObjectLike; +pub use crate::traits::{LuaNativeFn, LuaNativeFnMut, ObjectLike}; pub use crate::types::{ AppDataRef, AppDataRefMut, Integer, LightUserData, MaybeSend, Number, RegistryKey, VmState, }; @@ -133,7 +133,7 @@ pub use crate::hook::HookTriggers; pub use crate::{chunk::Compiler, function::CoverageInfo, types::Vector}; #[cfg(feature = "async")] -pub use crate::thread::AsyncThread; +pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn}; #[cfg(feature = "serialize")] #[doc(inline)] diff --git a/src/prelude.rs b/src/prelude.rs index 329f2ee7..e26649ce 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -5,14 +5,15 @@ pub use crate::{ AnyUserData as LuaAnyUserData, Chunk as LuaChunk, Error as LuaError, ErrorContext as LuaErrorContext, ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti, Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, Integer as LuaInteger, - IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaOptions, MetaMethod as LuaMetaMethod, - MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, ObjectLike as LuaObjectLike, - RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, String as LuaString, - Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, Thread as LuaThread, - ThreadStatus as LuaThreadStatus, UserData as LuaUserData, UserDataFields as LuaUserDataFields, - UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, - UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut, - UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, VmState as LuaVmState, + IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions, + MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, + ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, + String as LuaString, Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, + Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData, + UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, + UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, + UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, + VmState as LuaVmState, }; #[cfg(not(feature = "luau"))] @@ -25,7 +26,7 @@ pub use crate::{CoverageInfo as LuaCoverageInfo, Vector as LuaVector}; #[cfg(feature = "async")] #[doc(no_inline)] -pub use crate::AsyncThread as LuaAsyncThread; +pub use crate::{AsyncThread as LuaAsyncThread, LuaNativeAsyncFn}; #[cfg(feature = "serialize")] #[doc(no_inline)] diff --git a/src/state.rs b/src/state.rs index d8f0b57a..0a93c798 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1553,6 +1553,12 @@ impl Lua { T::from_lua(value, self) } + /// Converts a value that implements `IntoLua` into a `FromLua` variant. + #[inline] + pub fn convert(&self, value: impl IntoLua) -> Result { + U::from_lua(value.into_lua(self)?, self) + } + /// Converts a value that implements `IntoLuaMulti` into a `MultiValue` instance. #[inline] pub fn pack_multi(&self, t: impl IntoLuaMulti) -> Result { diff --git a/src/traits.rs b/src/traits.rs index 0fd48d2b..2fd8dc64 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -2,6 +2,7 @@ use std::string::String as StdString; use crate::error::Result; use crate::private::Sealed; +use crate::types::MaybeSend; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; #[cfg(feature = "async")] @@ -76,3 +77,94 @@ pub trait ObjectLike: Sealed { /// This might invoke the `__tostring` metamethod. fn to_string(&self) -> Result; } + +/// A trait for types that can be used as Lua functions. +pub trait LuaNativeFn { + type Output: IntoLuaMulti; + + fn call(&self, args: A) -> Self::Output; +} + +/// A trait for types with mutable state that can be used as Lua functions. +pub trait LuaNativeFnMut { + type Output: IntoLuaMulti; + + fn call(&mut self, args: A) -> Self::Output; +} + +/// A trait for types that returns a future and can be used as Lua functions. +#[cfg(feature = "async")] +pub trait LuaNativeAsyncFn { + type Output: IntoLuaMulti; + + fn call(&self, args: A) -> impl Future + MaybeSend + 'static; +} + +macro_rules! impl_lua_native_fn { + ($($A:ident),*) => { + impl LuaNativeFn<($($A,)*)> for FN + where + FN: Fn($($A,)*) -> R + MaybeSend + 'static, + ($($A,)*): FromLuaMulti, + R: IntoLuaMulti, + { + type Output = R; + + #[allow(non_snake_case)] + fn call(&self, args: ($($A,)*)) -> Self::Output { + let ($($A,)*) = args; + self($($A,)*) + } + } + + impl LuaNativeFnMut<($($A,)*)> for FN + where + FN: FnMut($($A,)*) -> R + MaybeSend + 'static, + ($($A,)*): FromLuaMulti, + R: IntoLuaMulti, + { + type Output = R; + + #[allow(non_snake_case)] + fn call(&mut self, args: ($($A,)*)) -> Self::Output { + let ($($A,)*) = args; + self($($A,)*) + } + } + + #[cfg(feature = "async")] + impl LuaNativeAsyncFn<($($A,)*)> for FN + where + FN: Fn($($A,)*) -> Fut + MaybeSend + 'static, + ($($A,)*): FromLuaMulti, + Fut: Future + MaybeSend + 'static, + R: IntoLuaMulti, + { + type Output = R; + + #[allow(non_snake_case)] + fn call(&self, args: ($($A,)*)) -> impl Future + MaybeSend + 'static { + let ($($A,)*) = args; + self($($A,)*) + } + } + }; +} + +impl_lua_native_fn!(); +impl_lua_native_fn!(A); +impl_lua_native_fn!(A, B); +impl_lua_native_fn!(A, B, C); +impl_lua_native_fn!(A, B, C, D); +impl_lua_native_fn!(A, B, C, D, E); +impl_lua_native_fn!(A, B, C, D, E, F); +impl_lua_native_fn!(A, B, C, D, E, F, G); +impl_lua_native_fn!(A, B, C, D, E, F, G, H); +impl_lua_native_fn!(A, B, C, D, E, F, G, H, I); +impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J); +impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K); +impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L); +impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M); +impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N); +impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O); +impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); diff --git a/tests/async.rs b/tests/async.rs index 0f4101b4..f939a684 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -1,5 +1,6 @@ #![cfg(feature = "async")] +use std::string::String as StdString; use std::sync::Arc; use std::time::Duration; @@ -39,12 +40,38 @@ async fn test_async_function() -> Result<()> { async fn test_async_function_wrap() -> Result<()> { let lua = Lua::new(); - let f = Function::wrap_async(|_, s: String| async move { Ok(s) }); + let f = Function::wrap_async(|s: StdString| async move { + tokio::task::yield_now().await; + Ok(s) + }); lua.globals().set("f", f)?; + let res: String = lua.load(r#"f("hello")"#).eval_async().await?; + assert_eq!(res, "hello"); + Ok(()) +} + +#[tokio::test] +async fn test_async_function_wrap_raw() -> Result<()> { + let lua = Lua::new(); + + let f = Function::wrap_raw_async(|s: StdString| async move { + tokio::task::yield_now().await; + s + }); + lua.globals().set("f", f)?; let res: String = lua.load(r#"f("hello")"#).eval_async().await?; assert_eq!(res, "hello"); + // Return error + let ferr = Function::wrap_raw_async(|| async move { + tokio::task::yield_now().await; + Err::<(), _>("some error") + }); + lua.globals().set("ferr", ferr)?; + let (_, err): (Value, String) = lua.load(r#"ferr()"#).eval_async().await?; + assert_eq!(err, "some error"); + Ok(()) } diff --git a/tests/chunk.rs b/tests/chunk.rs index 403a6c56..910cfbff 100644 --- a/tests/chunk.rs +++ b/tests/chunk.rs @@ -42,7 +42,7 @@ fn test_chunk_macro() -> Result<()> { data.raw_set("num", 1)?; let ud = mlua::AnyUserData::wrap("hello"); - let f = mlua::Function::wrap(|_lua, ()| Ok(())); + let f = mlua::Function::wrap(|| Ok(())); lua.globals().set("g", 123)?; diff --git a/tests/function.rs b/tests/function.rs index 7415e5e9..cec11f38 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -1,4 +1,4 @@ -use mlua::{Function, Lua, Result, String, Table}; +use mlua::{Error, Function, Lua, Result, String, Table}; #[test] fn test_function() -> Result<()> { @@ -271,31 +271,101 @@ fn test_function_deep_clone() -> Result<()> { #[test] fn test_function_wrap() -> Result<()> { - use mlua::Error; - let lua = Lua::new(); - lua.globals().set("f", Function::wrap(|_, s: String| Ok(s)))?; - lua.load(r#"assert(f("hello") == "hello")"#).exec().unwrap(); - - let mut _i = false; - lua.globals().set( - "f", - Function::wrap_mut(move |lua, ()| { - _i = true; - lua.globals().get::("f")?.call::<()>(()) - }), - )?; - match lua.globals().get::("f")?.call::<()>(()) { - Err(Error::CallbackError { ref cause, .. }) => match *cause.as_ref() { - Error::CallbackError { ref cause, .. } => match *cause.as_ref() { - Error::RecursiveMutCallback { .. } => {} - ref other => panic!("incorrect result: {other:?}"), - }, - ref other => panic!("incorrect result: {other:?}"), + let f = Function::wrap(|s: String, n| Ok(s.to_str().unwrap().repeat(n))); + lua.globals().set("f", f)?; + lua.load(r#"assert(f("hello", 2) == "hellohello")"#) + .exec() + .unwrap(); + + // Return error + let ferr = Function::wrap(|| Err::<(), _>(Error::runtime("some error"))); + lua.globals().set("ferr", ferr)?; + lua.load( + r#" + local ok, err = pcall(ferr) + assert(not ok and tostring(err):find("some error")) + "#, + ) + .exec() + .unwrap(); + + // Mutable callback + let mut i = 0; + let fmut = Function::wrap_mut(move || { + i += 1; + Ok(i) + }); + lua.globals().set("fmut", fmut)?; + lua.load(r#"fmut(); fmut(); assert(fmut() == 3)"#).exec().unwrap(); + + // Check mutable callback with error + let fmut_err = Function::wrap_mut(|| Err::<(), _>(Error::runtime("some error"))); + lua.globals().set("fmut_err", fmut_err)?; + lua.load( + r#" + local ok, err = pcall(fmut_err) + assert(not ok and tostring(err):find("some error")) + "#, + ) + .exec() + .unwrap(); + + // Check recursive mut callback error + let fmut = Function::wrap_mut(|f: Function| match f.call::<()>(&f) { + Err(Error::CallbackError { cause, .. }) => match cause.as_ref() { + Error::RecursiveMutCallback { .. } => Ok(()), + other => panic!("incorrect result: {other:?}"), }, other => panic!("incorrect result: {other:?}"), - }; + }); + let fmut = lua.convert::(fmut)?; + assert!(fmut.call::<()>(&fmut).is_ok()); + + Ok(()) +} + +#[test] +fn test_function_wrap_raw() -> Result<()> { + let lua = Lua::new(); + + let f = Function::wrap_raw(|| "hello"); + lua.globals().set("f", f)?; + lua.load(r#"assert(f() == "hello")"#).exec().unwrap(); + + // Return error + let ferr = Function::wrap_raw(|| Err::<(), _>("some error")); + lua.globals().set("ferr", ferr)?; + lua.load( + r#" + local _, err = ferr() + assert(err == "some error") + "#, + ) + .exec() + .unwrap(); + + // Mutable callback + let mut i = 0; + let fmut = Function::wrap_raw_mut(move || { + i += 1; + i + }); + lua.globals().set("fmut", fmut)?; + lua.load(r#"fmut(); fmut(); assert(fmut() == 3)"#).exec().unwrap(); + + // Check mutable callback with error + let fmut_err = Function::wrap_raw_mut(|| Err::<(), _>("some error")); + lua.globals().set("fmut_err", fmut_err)?; + lua.load( + r#" + local _, err = fmut_err() + assert(err == "some error") + "#, + ) + .exec() + .unwrap(); Ok(()) } diff --git a/tests/types.rs b/tests/types.rs index 830851ae..27bbe04d 100644 --- a/tests/types.rs +++ b/tests/types.rs @@ -30,7 +30,7 @@ fn test_boolean_type_metatable() -> Result<()> { let lua = Lua::new(); let mt = lua.create_table()?; - mt.set("__add", Function::wrap(|_, (a, b): (bool, bool)| Ok(a || b)))?; + mt.set("__add", Function::wrap(|a, b| Ok(a || b)))?; lua.set_type_metatable::(Some(mt)); lua.load(r#"assert(true + true == true)"#).exec().unwrap(); @@ -48,7 +48,7 @@ fn test_lightuserdata_type_metatable() -> Result<()> { let mt = lua.create_table()?; mt.set( "__add", - Function::wrap(|_, (a, b): (LightUserData, LightUserData)| { + Function::wrap(|a: LightUserData, b: LightUserData| { Ok(LightUserData((a.0 as usize + b.0 as usize) as *mut c_void)) }), )?; @@ -76,7 +76,7 @@ fn test_number_type_metatable() -> Result<()> { let lua = Lua::new(); let mt = lua.create_table()?; - mt.set("__call", Function::wrap(|_, (n1, n2): (f64, f64)| Ok(n1 * n2)))?; + mt.set("__call", Function::wrap(|n1: f64, n2: f64| Ok(n1 * n2)))?; lua.set_type_metatable::(Some(mt)); lua.load(r#"assert((1.5)(3.0) == 4.5)"#).exec().unwrap(); lua.load(r#"assert((5)(5) == 25)"#).exec().unwrap(); @@ -91,7 +91,7 @@ fn test_string_type_metatable() -> Result<()> { let mt = lua.create_table()?; mt.set( "__add", - Function::wrap(|_, (a, b): (LuaString, LuaString)| Ok(format!("{}{}", a.to_str()?, b.to_str()?))), + Function::wrap(|a: String, b: String| Ok(format!("{a}{b}"))), )?; lua.set_type_metatable::(Some(mt)); @@ -107,7 +107,7 @@ fn test_function_type_metatable() -> Result<()> { let mt = lua.create_table()?; mt.set( "__index", - Function::wrap(|_, (_, key): (Function, String)| Ok(format!("function.{key}"))), + Function::wrap(|_: Function, key: String| Ok(format!("function.{key}"))), )?; lua.set_type_metatable::(Some(mt)); @@ -125,7 +125,7 @@ fn test_thread_type_metatable() -> Result<()> { let mt = lua.create_table()?; mt.set( "__index", - Function::wrap(|_, (_, key): (Thread, String)| Ok(format!("thread.{key}"))), + Function::wrap(|_: Thread, key: String| Ok(format!("thread.{key}"))), )?; lua.set_type_metatable::(Some(mt)); From b65901e444fdfc01dd67612229ca489db4efe193 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 24 Sep 2024 22:48:19 +0100 Subject: [PATCH 197/635] Add `Error::chain` method to return iterator over nested errors --- src/error.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ tests/error.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/src/error.rs b/src/error.rs index 59a80b27..452bf1a9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -365,6 +365,14 @@ impl Error { } } + /// An iterator over the chain of nested errors wrapped by this Error. + pub fn chain(&self) -> impl Iterator { + Chain { + root: self, + current: None, + } + } + pub(crate) fn bad_self_argument(to: &str, cause: Error) -> Self { Error::BadArgument { to: Some(to.to_string()), @@ -487,3 +495,44 @@ impl serde::de::Error for Error { Self::DeserializeError(msg.to_string()) } } + +struct Chain<'a> { + root: &'a Error, + current: Option<&'a (dyn StdError + 'static)>, +} + +impl<'a> Iterator for Chain<'a> { + type Item = &'a (dyn StdError + 'static); + + fn next(&mut self) -> Option { + loop { + let error: Option<&dyn StdError> = match self.current { + None => { + self.current = Some(self.root); + self.current + } + Some(current) => match current.downcast_ref::()? { + Error::BadArgument { cause, .. } + | Error::CallbackError { cause, .. } + | Error::WithContext { cause, .. } => { + self.current = Some(&**cause); + self.current + } + Error::ExternalError(err) => { + self.current = Some(&**err); + self.current + } + _ => None, + }, + }; + + // Skip `ExternalError` as it only wraps the underlying error + // without meaningful context + if let Some(Error::ExternalError(_)) = error?.downcast_ref::() { + continue; + } + + return self.current; + } + } +} diff --git a/tests/error.rs b/tests/error.rs index 922b83c6..ed1dcc23 100644 --- a/tests/error.rs +++ b/tests/error.rs @@ -46,3 +46,29 @@ fn test_error_context() -> Result<()> { Ok(()) } + +#[test] +fn test_error_chain() -> Result<()> { + let lua = Lua::new(); + + // Check that `Error::ExternalError` creates a chain with a single element + let io_err = io::Error::new(io::ErrorKind::Other, "other"); + assert_eq!(Error::external(io_err).chain().count(), 1); + + let func = lua.create_function(|_, ()| { + let err = Error::external(io::Error::new(io::ErrorKind::Other, "other")).context("io error"); + Err::<(), _>(err) + })?; + let err = func.call::<()>(()).err().unwrap(); + assert_eq!(err.chain().count(), 3); + for (i, err) in err.chain().enumerate() { + match i { + 0 => assert!(matches!(err.downcast_ref(), Some(Error::CallbackError { .. }))), + 1 => assert!(matches!(err.downcast_ref(), Some(Error::WithContext { .. }))), + 2 => assert!(matches!(err.downcast_ref(), Some(io::Error { .. }))), + _ => unreachable!(), + } + } + + Ok(()) +} From 235c32006c9f0afbd884e2c6730d2dc9c02540ec Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 24 Sep 2024 23:11:16 +0100 Subject: [PATCH 198/635] Rename `Lua::with_raw_state` to `Lua::exec_raw` --- src/state.rs | 2 +- tests/tests.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/state.rs b/src/state.rs index 0a93c798..cfcc81b6 100644 --- a/src/state.rs +++ b/src/state.rs @@ -288,7 +288,7 @@ impl Lua { /// This method ensures that the Lua instance is locked while the function is called /// and restores Lua stack after the function returns. #[allow(clippy::missing_safety_doc)] - pub unsafe fn with_raw_state( + pub unsafe fn exec_raw( &self, args: impl IntoLuaMulti, f: impl FnOnce(*mut ffi::lua_State), diff --git a/tests/tests.rs b/tests/tests.rs index 268cb80e..fb6e8931 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1291,7 +1291,7 @@ fn test_multi_thread() -> Result<()> { } #[test] -fn test_with_raw_state() -> Result<()> { +fn test_exec_raw() -> Result<()> { let lua = Lua::new(); let sum = lua.create_function(|_, args: Variadic| { @@ -1304,7 +1304,7 @@ fn test_with_raw_state() -> Result<()> { lua.globals().set("sum", sum)?; let n: i32 = unsafe { - lua.with_raw_state((), |state| { + lua.exec_raw((), |state| { ffi::lua_getglobal(state, b"sum\0".as_ptr() as _); ffi::lua_pushinteger(state, 1); ffi::lua_pushinteger(state, 7); @@ -1315,7 +1315,7 @@ fn test_with_raw_state() -> Result<()> { // Test error handling let res: Result<()> = unsafe { - lua.with_raw_state("test error", |state| { + lua.exec_raw("test error", |state| { ffi::lua_error(state); }) }; From fb0c0d9ee91114e082110ba9937dbc75f1e760d2 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 26 Sep 2024 18:58:28 +0100 Subject: [PATCH 199/635] Add `Either` enum to combine two types into a single one. It implements `FromLua` and `IntoLua` traits for easy type conversions.. --- mlua_derive/src/from_lua.rs | 2 +- src/conversion.rs | 59 ++++++++-------- src/error.rs | 8 +-- src/lib.rs | 2 +- src/prelude.rs | 18 ++--- src/string.rs | 2 +- src/traits.rs | 10 +++ src/types.rs | 2 + src/types/either.rs | 135 ++++++++++++++++++++++++++++++++++++ src/userdata/cell.rs | 2 +- tests/conversion.rs | 85 ++++++++++++++++++++++- 11 files changed, 278 insertions(+), 47 deletions(-) create mode 100644 src/types/either.rs diff --git a/mlua_derive/src/from_lua.rs b/mlua_derive/src/from_lua.rs index dfbb6746..e74eb868 100644 --- a/mlua_derive/src/from_lua.rs +++ b/mlua_derive/src/from_lua.rs @@ -20,7 +20,7 @@ pub fn from_lua(input: TokenStream) -> TokenStream { ::mlua::Value::UserData(ud) => Ok(ud.borrow::()?.clone()), _ => Err(::mlua::Error::FromLuaConversionError { from: value.type_name(), - to: #ident_str, + to: #ident_str.to_string(), message: None, }), } diff --git a/src/conversion.rs b/src/conversion.rs index 45e34cac..319b787a 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -15,6 +15,7 @@ use crate::state::{Lua, RawLua}; use crate::string::String; use crate::table::Table; use crate::thread::Thread; +use crate::traits::ShortTypeName as _; use crate::types::{LightUserData, MaybeSend, RegistryKey}; use crate::userdata::{AnyUserData, UserData}; use crate::value::{FromLua, IntoLua, Nil, Value}; @@ -72,7 +73,7 @@ impl FromLua for String { lua.coerce_string(value)? .ok_or_else(|| Error::FromLuaConversionError { from: ty, - to: "string", + to: "string".to_string(), message: Some("expected string or number".to_string()), }) } @@ -116,7 +117,7 @@ impl FromLua for Table { Value::Table(table) => Ok(table), _ => Err(Error::FromLuaConversionError { from: value.type_name(), - to: "table", + to: "table".to_string(), message: None, }), } @@ -150,7 +151,7 @@ impl FromLua for Function { Value::Function(table) => Ok(table), _ => Err(Error::FromLuaConversionError { from: value.type_name(), - to: "function", + to: "function".to_string(), message: None, }), } @@ -184,7 +185,7 @@ impl FromLua for Thread { Value::Thread(t) => Ok(t), _ => Err(Error::FromLuaConversionError { from: value.type_name(), - to: "thread", + to: "thread".to_string(), message: None, }), } @@ -218,7 +219,7 @@ impl FromLua for AnyUserData { Value::UserData(ud) => Ok(ud), _ => Err(Error::FromLuaConversionError { from: value.type_name(), - to: "userdata", + to: "userdata".to_string(), message: None, }), } @@ -336,7 +337,7 @@ impl FromLua for LightUserData { Value::LightUserData(ud) => Ok(ud), _ => Err(Error::FromLuaConversionError { from: value.type_name(), - to: "light userdata", + to: "lightuserdata".to_string(), message: None, }), } @@ -359,7 +360,7 @@ impl FromLua for crate::types::Vector { Value::Vector(v) => Ok(v), _ => Err(Error::FromLuaConversionError { from: value.type_name(), - to: "vector", + to: "vector".to_string(), message: None, }), } @@ -386,7 +387,7 @@ impl FromLua for StdString { .coerce_string(value)? .ok_or_else(|| Error::FromLuaConversionError { from: ty, - to: "String", + to: Self::type_name(), message: Some("expected string or number".to_string()), })? .to_str()? @@ -405,7 +406,7 @@ impl FromLua for StdString { .map(|s| s.to_owned()) .map_err(|e| Error::FromLuaConversionError { from: "string", - to: "String", + to: Self::type_name(), message: Some(e.to_string()), }); } @@ -448,7 +449,7 @@ impl FromLua for Box { .coerce_string(value)? .ok_or_else(|| Error::FromLuaConversionError { from: ty, - to: "Box", + to: Self::type_name(), message: Some("expected string or number".to_string()), })? .to_str()? @@ -472,7 +473,7 @@ impl FromLua for CString { .coerce_string(value)? .ok_or_else(|| Error::FromLuaConversionError { from: ty, - to: "CString", + to: Self::type_name(), message: Some("expected string or number".to_string()), })?; @@ -480,7 +481,7 @@ impl FromLua for CString { Ok(s) => Ok(s.into()), Err(_) => Err(Error::FromLuaConversionError { from: ty, - to: "CString", + to: Self::type_name(), message: Some("invalid C-style string".to_string()), }), } @@ -525,7 +526,7 @@ impl FromLua for BString { .coerce_string(value)? .ok_or_else(|| Error::FromLuaConversionError { from: ty, - to: "BString", + to: Self::type_name(), message: Some("expected string or number".to_string()), })? .as_bytes()) @@ -588,7 +589,7 @@ macro_rules! lua_convert_int { .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), + from: stringify!($x).to_string(), to: "number", message: Some("out of range".to_owned()), }) @@ -619,7 +620,7 @@ macro_rules! lua_convert_int { lua.coerce_number(value)? .ok_or_else(|| Error::FromLuaConversionError { from: ty, - to: stringify!($x), + to: stringify!($x).to_string(), message: Some( "expected number or string coercible to number".to_string(), ), @@ -630,7 +631,7 @@ macro_rules! lua_convert_int { }) .ok_or_else(|| Error::FromLuaConversionError { from: ty, - to: stringify!($x), + to: stringify!($x).to_string(), message: Some("out of range".to_owned()), }) } @@ -644,7 +645,7 @@ macro_rules! lua_convert_int { if ok != 0 { return cast(i).ok_or_else(|| Error::FromLuaConversionError { from: "integer", - to: stringify!($x), + to: stringify!($x).to_string(), message: Some("out of range".to_owned()), }); } @@ -676,7 +677,7 @@ macro_rules! lua_convert_float { fn into_lua(self, _: &Lua) -> Result { cast(self) .ok_or_else(|| Error::ToLuaConversionError { - from: stringify!($x), + from: stringify!($x).to_string(), to: "number", message: Some("out of range".to_string()), }) @@ -691,13 +692,13 @@ macro_rules! lua_convert_float { lua.coerce_number(value)? .ok_or_else(|| Error::FromLuaConversionError { from: ty, - to: stringify!($x), + 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: stringify!($x).to_string(), message: Some("number out of range".to_string()), }) }) @@ -712,7 +713,7 @@ macro_rules! lua_convert_float { if ok != 0 { return cast(i).ok_or_else(|| Error::FromLuaConversionError { from: "number", - to: stringify!($x), + to: stringify!($x).to_string(), message: Some("out of range".to_owned()), }); } @@ -771,13 +772,13 @@ where vec.try_into() .map_err(|vec: Vec| Error::FromLuaConversionError { from: "table", - to: "Array", - message: Some(format!("expected table of length {}, got {}", N, vec.len())), + to: Self::type_name(), + message: Some(format!("expected table of length {N}, got {}", vec.len())), }) } _ => Err(Error::FromLuaConversionError { from: value.type_name(), - to: "Array", + to: Self::type_name(), message: Some("expected table".to_string()), }), } @@ -812,7 +813,7 @@ impl FromLua for Vec { Value::Table(table) => table.sequence_values().collect(), _ => Err(Error::FromLuaConversionError { from: value.type_name(), - to: "Vec", + to: Self::type_name(), message: Some("expected table".to_string()), }), } @@ -834,7 +835,7 @@ impl FromLua for H } else { Err(Error::FromLuaConversionError { from: value.type_name(), - to: "HashMap", + to: Self::type_name(), message: Some("expected table".to_string()), }) } @@ -856,7 +857,7 @@ impl FromLua for BTreeMap { } else { Err(Error::FromLuaConversionError { from: value.type_name(), - to: "BTreeMap", + to: Self::type_name(), message: Some("expected table".to_string()), }) } @@ -880,7 +881,7 @@ impl FromLua for HashSet Value::Table(table) => table.pairs::().map(|res| res.map(|(k, _)| k)).collect(), _ => Err(Error::FromLuaConversionError { from: value.type_name(), - to: "HashSet", + to: Self::type_name(), message: Some("expected table".to_string()), }), } @@ -904,7 +905,7 @@ impl FromLua for BTreeSet { Value::Table(table) => table.pairs::().map(|res| res.map(|(k, _)| k)).collect(), _ => Err(Error::FromLuaConversionError { from: value.type_name(), - to: "BTreeSet", + to: Self::type_name(), message: Some("expected table".to_string()), }), } diff --git a/src/error.rs b/src/error.rs index 452bf1a9..8784778e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -83,7 +83,7 @@ pub enum Error { /// A Rust value could not be converted to a Lua value. ToLuaConversionError { /// Name of the Rust type that could not be converted. - from: &'static str, + from: String, /// Name of the Lua type that could not be created. to: &'static str, /// A message indicating why the conversion failed in more detail. @@ -94,7 +94,7 @@ pub enum Error { /// Name of the Lua type that could not be converted. from: &'static str, /// Name of the Rust type that could not be created. - to: &'static str, + to: String, /// A string containing more detailed error information. message: Option, }, @@ -384,12 +384,12 @@ impl Error { pub(crate) fn from_lua_conversion( from: &'static str, - to: &'static str, + to: impl ToString, message: impl Into>, ) -> Self { Error::FromLuaConversionError { from, - to, + to: to.to_string(), message: message.into(), } } diff --git a/src/lib.rs b/src/lib.rs index 4d489aa5..5f8a0261 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,7 +117,7 @@ pub use crate::table::{Table, TablePairs, TableSequence}; pub use crate::thread::{Thread, ThreadStatus}; pub use crate::traits::{LuaNativeFn, LuaNativeFnMut, ObjectLike}; pub use crate::types::{ - AppDataRef, AppDataRefMut, Integer, LightUserData, MaybeSend, Number, RegistryKey, VmState, + AppDataRef, AppDataRefMut, Either, Integer, LightUserData, MaybeSend, Number, RegistryKey, VmState, }; pub use crate::userdata::{ AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef, diff --git a/src/prelude.rs b/src/prelude.rs index e26649ce..68ba8f2f 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,15 +2,15 @@ #[doc(no_inline)] pub use crate::{ - AnyUserData as LuaAnyUserData, Chunk as LuaChunk, Error as LuaError, ErrorContext as LuaErrorContext, - ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti, - Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, Integer as LuaInteger, - IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions, - MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, - ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, - String as LuaString, Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, - Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData, - UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, + AnyUserData as LuaAnyUserData, Chunk as LuaChunk, Either as LuaEither, Error as LuaError, + ErrorContext as LuaErrorContext, ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, + FromLua, FromLuaMulti, Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, + Integer as LuaInteger, IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, + LuaNativeFnMut, LuaOptions, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, + Number as LuaNumber, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, + StdLib as LuaStdLib, String as LuaString, Table as LuaTable, TablePairs as LuaTablePairs, + TableSequence as LuaTableSequence, Thread as LuaThread, ThreadStatus as LuaThreadStatus, + UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, VmState as LuaVmState, diff --git a/src/string.rs b/src/string.rs index 5057b0be..0cc48a7f 100644 --- a/src/string.rs +++ b/src/string.rs @@ -45,7 +45,7 @@ impl String { let BorrowedBytes(bytes, guard) = self.as_bytes(); let s = str::from_utf8(bytes).map_err(|e| Error::FromLuaConversionError { from: "string", - to: "&str", + to: "&str".to_string(), message: Some(e.to_string()), })?; Ok(BorrowedStr(s, guard)) diff --git a/src/traits.rs b/src/traits.rs index 2fd8dc64..a8afc1cd 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -3,11 +3,21 @@ use std::string::String as StdString; use crate::error::Result; use crate::private::Sealed; use crate::types::MaybeSend; +use crate::util::short_type_name; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; #[cfg(feature = "async")] use std::future::Future; +pub(crate) trait ShortTypeName { + #[inline(always)] + fn type_name() -> StdString { + short_type_name::() + } +} + +impl ShortTypeName for T {} + /// A trait for types that can be used as Lua objects (usually table and userdata). pub trait ObjectLike: Sealed { /// Gets the value associated to `key` from the object, assuming it has `__index` metamethod. diff --git a/src/types.rs b/src/types.rs index be5b53b3..ad235f2a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -17,6 +17,7 @@ pub(crate) type BoxFuture<'a, T> = futures_util::future::BoxFuture<'a, T>; pub(crate) type BoxFuture<'a, T> = futures_util::future::LocalBoxFuture<'a, T>; pub use app_data::{AppData, AppDataRef, AppDataRefMut}; +pub use either::Either; pub use registry_key::RegistryKey; pub(crate) use value_ref::ValueRef; #[cfg(any(feature = "luau", doc))] @@ -132,6 +133,7 @@ impl LuaType for LightUserData { } mod app_data; +mod either; mod registry_key; mod sync; mod value_ref; diff --git a/src/types/either.rs b/src/types/either.rs new file mode 100644 index 00000000..ee818fe6 --- /dev/null +++ b/src/types/either.rs @@ -0,0 +1,135 @@ +use std::ffi::CStr; +use std::fmt; +use std::hash::Hash; +use std::os::raw::c_int; + +use crate::error::{Error, Result}; +use crate::state::{Lua, RawLua}; +use crate::traits::ShortTypeName as _; +use crate::value::{FromLua, IntoLua, Value}; + +/// Combination of two types into a single one. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Either { + Left(L), + Right(R), +} + +impl Either { + /// Return true if the value is the Left variant. + #[inline] + pub fn is_left(&self) -> bool { + matches!(self, Either::Left(_)) + } + + /// Return true if the value is the Right variant. + #[inline] + pub fn is_right(&self) -> bool { + matches!(self, Either::Right(_)) + } + + /// Convert the left side of `Either` to an `Option`. + #[inline] + pub fn left(self) -> Option { + match self { + Either::Left(l) => Some(l), + _ => None, + } + } + + /// Convert the right side of `Either` to an `Option`. + #[inline] + pub fn right(self) -> Option { + match self { + Either::Right(r) => Some(r), + _ => None, + } + } + + /// Convert `&Either` to `Either<&L, &R>`. + #[inline] + pub fn as_ref(&self) -> Either<&L, &R> { + match self { + Either::Left(l) => Either::Left(l), + Either::Right(r) => Either::Right(r), + } + } + + /// Convert `&mut Either` to `Either<&mut L, &mut R>`. + #[inline] + pub fn as_mut(&mut self) -> Either<&mut L, &mut R> { + match self { + Either::Left(l) => Either::Left(l), + Either::Right(r) => Either::Right(r), + } + } +} + +impl fmt::Display for Either +where + L: fmt::Display, + R: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Either::Left(a) => a.fmt(f), + Either::Right(b) => b.fmt(f), + } + } +} + +impl IntoLua for Either { + #[inline] + fn into_lua(self, lua: &Lua) -> Result { + match self { + Either::Left(l) => l.into_lua(lua), + Either::Right(r) => r.into_lua(lua), + } + } + + #[inline] + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + match self { + Either::Left(l) => l.push_into_stack(lua), + Either::Right(r) => r.push_into_stack(lua), + } + } +} + +impl FromLua for Either { + #[inline] + fn from_lua(value: Value, lua: &Lua) -> Result { + let value_type_name = value.type_name(); + // Try the left type first + match L::from_lua(value.clone(), lua) { + Ok(l) => Ok(Either::Left(l)), + // Try the right type + Err(_) => match R::from_lua(value, lua).map(Either::Right) { + Ok(r) => Ok(r), + Err(_) => Err(Error::FromLuaConversionError { + from: value_type_name, + to: Self::type_name(), + message: None, + }), + }, + } + } + + #[inline] + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { + match L::from_stack(idx, lua) { + Ok(l) => Ok(Either::Left(l)), + Err(_) => match R::from_stack(idx, lua).map(Either::Right) { + Ok(r) => Ok(r), + Err(_) => { + let value_type_name = CStr::from_ptr(ffi::luaL_typename(lua.state(), idx)); + Err(Error::FromLuaConversionError { + from: value_type_name.to_str().unwrap(), + to: Self::type_name(), + message: None, + }) + } + }, + } + } +} diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index b795b751..9cdcd5b4 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -348,7 +348,7 @@ fn try_value_to_userdata(value: Value) -> Result { Value::UserData(ud) => Ok(ud), _ => Err(Error::FromLuaConversionError { from: value.type_name(), - to: "userdata", + to: "userdata".to_string(), message: Some(format!("expected userdata of type {}", type_name::())), }), } diff --git a/tests/conversion.rs b/tests/conversion.rs index f975a01f..59418792 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -5,7 +5,8 @@ use std::ffi::{CStr, CString}; use bstr::BString; use maplit::{btreemap, btreeset, hashmap, hashset}; use mlua::{ - AnyUserData, Error, Function, IntoLua, Lua, RegistryKey, Result, Table, Thread, UserDataRef, Value, + AnyUserData, Either, Error, Function, IntoLua, Lua, RegistryKey, Result, Table, Thread, UserDataRef, + Value, }; #[test] @@ -413,3 +414,85 @@ fn test_option_into_from_lua() -> Result<()> { Ok(()) } + +#[test] +fn test_either_enum() -> Result<()> { + // Left + let mut either = Either::<_, String>::Left(42); + assert!(either.is_left()); + assert_eq!(*either.as_ref().left().unwrap(), 42); + *either.as_mut().left().unwrap() = 44; + assert_eq!(*either.as_ref().left().unwrap(), 44); + assert_eq!(format!("{either}"), "44"); + + // Right + either = Either::Right("hello".to_string()); + assert!(either.is_right()); + assert_eq!(*either.as_ref().right().unwrap(), "hello"); + *either.as_mut().right().unwrap() = "world".to_string(); + assert_eq!(*either.as_ref().right().unwrap(), "world"); + assert_eq!(format!("{either}"), "world"); + + Ok(()) +} + +#[test] +fn test_either_into_lua() -> Result<()> { + let lua = Lua::new(); + + // Direct conversion + let mut either = Either::::Left(42); + let value = either.into_lua(&lua)?; + assert_eq!(value, Value::Integer(42)); + + // Push into stack + let f = + lua.create_function(|_, either: Either| either.right().unwrap().set("hello", "world"))?; + let t = lua.create_table()?; + either = Either::Right(&t); + f.call::<()>(either)?; + assert_eq!(t.get::("hello")?, "world"); + + let f = lua.create_function(|_, either: Either| Ok(either.left().unwrap() + 1))?; + either = Either::Left(42); + assert_eq!(f.call::(either)?, 43); + + Ok(()) +} + +#[test] +fn test_either_from_lua() -> Result<()> { + let lua = Lua::new(); + + // From stack + let f = lua.create_function(|_, either: Either| Ok(either))?; + let either = f.call::>(42)?; + assert!(either.is_left()); + assert_eq!(*either.as_ref().left().unwrap(), 42); + + let either = f.call::>([5; 5])?; + assert!(either.is_right()); + assert_eq!(either.as_ref().right().unwrap(), &[5; 5]); + + // Check error message + match f.call::("hello") { + Ok(_) => panic!("expected error, got Ok"), + Err(ref err @ Error::CallbackError { ref cause, .. }) => { + match cause.as_ref() { + Error::BadArgument { cause, .. } => match cause.as_ref() { + Error::FromLuaConversionError { to, .. } => { + assert_eq!(to, "Either") + } + err => panic!("expected `Error::FromLuaConversionError`, got {err:?}"), + }, + err => panic!("expected `Error::BadArgument`, got {err:?}"), + } + assert!(err + .to_string() + .starts_with("bad argument #1: error converting Lua string to Either"),); + } + err => panic!("expected `Error::CallbackError`, got {err:?}"), + } + + Ok(()) +} From 4dddf3c18d4590f7c92214fa592c3faca3d2c812 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 26 Sep 2024 22:46:02 +0100 Subject: [PATCH 200/635] Use `fmt::Debug` implementation for Lua string from `bstr` --- src/string.rs | 19 ++----------------- tests/string.rs | 2 +- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/src/string.rs b/src/string.rs index 0cc48a7f..734cd810 100644 --- a/src/string.rs +++ b/src/string.rs @@ -143,23 +143,8 @@ impl fmt::Debug for String { } // Format as bytes - write!(f, "b\"")?; - for &b in bytes { - // https://doc.rust-lang.org/reference/tokens.html#byte-escapes - match b { - b'\n' => write!(f, "\\n")?, - b'\r' => write!(f, "\\r")?, - b'\t' => write!(f, "\\t")?, - b'\\' | b'"' => write!(f, "\\{}", b as char)?, - b'\0' => write!(f, "\\0")?, - // ASCII printable - 0x20..=0x7e => write!(f, "{}", b as char)?, - _ => write!(f, "\\x{b:02x}")?, - } - } - write!(f, "\"")?; - - Ok(()) + write!(f, "b")?; + ::fmt(bstr::BStr::new(&bytes), f) } } diff --git a/tests/string.rs b/tests/string.rs index a986c29c..456ad849 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -86,7 +86,7 @@ fn test_string_debug() -> Result<()> { // Invalid utf8 let s = lua.create_string(b"hello\0world\r\n\t\xF0\x90\x80")?; - assert_eq!(format!("{s:?}"), r#"b"hello\0world\r\n\t\xf0\x90\x80""#); + assert_eq!(format!("{s:?}"), r#"b"hello\0world\r\n\t\xF0\x90\x80""#); Ok(()) } From 529361fcbcd7e1c5ae57bc609f14ec75da0cffd2 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 1 Oct 2024 15:21:19 +0100 Subject: [PATCH 201/635] Add new `Buffer` type for Luau. Previously it was represented as `AnyUserData` which is not always convenient. --- src/buffer.rs | 86 +++++++++++++++++++++++++++++++++++++++++++++ src/conversion.rs | 45 ++++++++++++++++++++---- src/lib.rs | 3 +- src/serde/de.rs | 11 ++---- src/state.rs | 13 ++++--- src/state/raw.rs | 5 +-- src/table.rs | 2 +- src/types.rs | 2 -- src/types/vector.rs | 2 +- src/userdata.rs | 15 -------- src/value.rs | 51 +++++++++++++++++++++------ tests/buffer.rs | 56 +++++++++++++++++++++++++++++ tests/conversion.rs | 4 +-- tests/luau.rs | 26 -------------- tests/serde.rs | 18 +++++----- 15 files changed, 248 insertions(+), 91 deletions(-) create mode 100644 src/buffer.rs create mode 100644 tests/buffer.rs diff --git a/src/buffer.rs b/src/buffer.rs new file mode 100644 index 00000000..42b20088 --- /dev/null +++ b/src/buffer.rs @@ -0,0 +1,86 @@ +#[cfg(feature = "serialize")] +use serde::ser::{Serialize, Serializer}; + +use crate::types::ValueRef; + +/// A Luau buffer type. +/// +/// See the buffer [documentation] for more information. +/// +/// [documentation]: https://luau.org/library#buffer-library +#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] +#[derive(Clone, Debug, PartialEq)] +pub struct Buffer(pub(crate) ValueRef); + +#[cfg_attr(not(feature = "luau"), allow(unused))] +impl Buffer { + /// Copies the buffer data into a new `Vec`. + pub fn to_vec(&self) -> Vec { + unsafe { self.as_slice().to_vec() } + } + + /// Returns the length of the buffer. + pub fn len(&self) -> usize { + unsafe { self.as_slice().len() } + } + + /// Returns `true` if the buffer is empty. + #[doc(hidden)] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Reads given number of bytes from the buffer at the given offset. + /// + /// Offset is 0-based. + #[track_caller] + pub fn read_bytes(&self, offset: usize) -> [u8; N] { + let data = unsafe { self.as_slice() }; + let mut bytes = [0u8; N]; + bytes.copy_from_slice(&data[offset..offset + N]); + bytes + } + + /// Writes given bytes to the buffer at the given offset. + /// + /// Offset is 0-based. + #[track_caller] + pub fn write_bytes(&self, offset: usize, bytes: &[u8]) { + let data = unsafe { + let (buf, size) = self.as_raw_parts(); + std::slice::from_raw_parts_mut(buf, size) + }; + data[offset..offset + bytes.len()].copy_from_slice(bytes); + } + + pub(crate) unsafe fn as_slice(&self) -> &[u8] { + let (buf, size) = self.as_raw_parts(); + std::slice::from_raw_parts(buf, size) + } + + #[cfg(feature = "luau")] + unsafe fn as_raw_parts(&self) -> (*mut u8, usize) { + let lua = self.0.lua.lock(); + let mut size = 0usize; + let buf = ffi::lua_tobuffer(lua.ref_thread(), self.0.index, &mut size); + mlua_assert!(!buf.is_null(), "invalid Luau buffer"); + (buf as *mut u8, size) + } + + #[cfg(not(feature = "luau"))] + unsafe fn as_raw_parts(&self) -> (*mut u8, usize) { + unreachable!() + } +} + +#[cfg(feature = "serialize")] +impl Serialize for Buffer { + fn serialize(&self, serializer: S) -> std::result::Result { + serializer.serialize_bytes(unsafe { self.as_slice() }) + } +} + +#[cfg(feature = "luau")] +impl crate::types::LuaType for Buffer { + const TYPE_ID: std::os::raw::c_int = ffi::LUA_TBUFFER; +} diff --git a/src/conversion.rs b/src/conversion.rs index 319b787a..8bfd44b3 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -367,6 +367,43 @@ impl FromLua for crate::types::Vector { } } +#[cfg(feature = "luau")] +impl IntoLua for crate::Buffer { + #[inline] + fn into_lua(self, _: &Lua) -> Result { + Ok(Value::Buffer(self)) + } +} + +#[cfg(feature = "luau")] +impl IntoLua for &crate::Buffer { + #[inline] + fn into_lua(self, _: &Lua) -> Result { + Ok(Value::Buffer(self.clone())) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + lua.push_ref(&self.0); + Ok(()) + } +} + +#[cfg(feature = "luau")] +impl FromLua for crate::Buffer { + #[inline] + fn from_lua(value: Value, _: &Lua) -> Result { + match value { + Value::Buffer(buf) => Ok(buf), + _ => Err(Error::FromLuaConversionError { + from: value.type_name(), + to: "buffer".to_string(), + message: None, + }), + } + } +} + impl IntoLua for StdString { #[inline] fn into_lua(self, lua: &Lua) -> Result { @@ -515,13 +552,7 @@ impl FromLua for BString { match value { Value::String(s) => Ok((*s.as_bytes()).into()), #[cfg(feature = "luau")] - Value::UserData(ud) if ud.1 == crate::types::SubtypeId::Buffer => unsafe { - let lua = ud.0.lua.lock(); - let mut size = 0usize; - let buf = ffi::lua_tobuffer(lua.ref_thread(), ud.0.index, &mut size); - mlua_assert!(!buf.is_null(), "invalid Luau buffer"); - Ok(slice::from_raw_parts(buf as *const u8, size).into()) - }, + Value::Buffer(buf) => unsafe { Ok(buf.as_slice().into()) }, _ => Ok((*lua .coerce_string(value)? .ok_or_else(|| Error::FromLuaConversionError { diff --git a/src/lib.rs b/src/lib.rs index 5f8a0261..049f334b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,7 @@ #[macro_use] mod macros; +mod buffer; mod chunk; mod conversion; mod error; @@ -130,7 +131,7 @@ pub use crate::hook::HookTriggers; #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] -pub use crate::{chunk::Compiler, function::CoverageInfo, types::Vector}; +pub use crate::{buffer::Buffer, chunk::Compiler, function::CoverageInfo, types::Vector}; #[cfg(feature = "async")] pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn}; diff --git a/src/serde/de.rs b/src/serde/de.rs index c54241ae..6d3d5139 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -145,14 +145,7 @@ impl<'de> serde::Deserializer<'de> for Deserializer { serde_userdata(ud, |value| value.deserialize_any(visitor)) } #[cfg(feature = "luau")] - Value::UserData(ud) if ud.1 == crate::types::SubtypeId::Buffer => unsafe { - let lua = ud.0.lua.lock(); - let mut size = 0usize; - let buf = ffi::lua_tobuffer(lua.ref_thread(), ud.0.index, &mut size); - mlua_assert!(!buf.is_null(), "invalid Luau buffer"); - let buf = std::slice::from_raw_parts(buf as *const u8, size); - visitor.visit_bytes(buf) - }, + Value::Buffer(buf) => visitor.visit_bytes(unsafe { buf.as_slice() }), Value::Function(_) | Value::Thread(_) | Value::UserData(_) @@ -463,7 +456,7 @@ impl<'a> MapPairs<'a> { pub(crate) fn new(t: &'a Table, sort_keys: bool) -> Result { if sort_keys { let mut pairs = t.pairs::().collect::>>()?; - pairs.sort_by(|(a, _), (b, _)| b.cmp(a)); // reverse order as we pop values from the end + pairs.sort_by(|(a, _), (b, _)| b.sort_cmp(a)); // reverse order as we pop values from the end Ok(MapPairs::Vec(pairs)) } else { Ok(MapPairs::Iter(t.pairs::())) diff --git a/src/state.rs b/src/state.rs index cfcc81b6..645556b8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -31,7 +31,7 @@ use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil use crate::hook::HookTriggers; #[cfg(any(feature = "luau", doc))] -use crate::chunk::Compiler; +use crate::{buffer::Buffer, chunk::Compiler}; #[cfg(feature = "async")] use { @@ -996,22 +996,21 @@ impl Lua { /// Requires `feature = "luau"` /// /// [buffer]: https://luau-lang.org/library#buffer-library - #[cfg(feature = "luau")] - pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result { - use crate::types::SubtypeId; - + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + #[cfg(any(feature = "luau", doc))] + pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result { let lua = self.lock(); let state = lua.state(); unsafe { if lua.unlikely_memory_error() { crate::util::push_buffer(lua.ref_thread(), buf.as_ref(), false)?; - return Ok(AnyUserData(lua.pop_ref_thread(), SubtypeId::Buffer)); + return Ok(Buffer(lua.pop_ref_thread())); } let _sg = StackGuard::new(state); check_stack(state, 4)?; crate::util::push_buffer(state, buf.as_ref(), true)?; - Ok(AnyUserData(lua.pop_ref(), SubtypeId::Buffer)) + Ok(Buffer(lua.pop_ref())) } } diff --git a/src/state/raw.rs b/src/state/raw.rs index 61e70d3f..2b20e451 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -551,6 +551,8 @@ impl RawLua { Value::Function(f) => self.push_ref(&f.0), Value::Thread(t) => self.push_ref(&t.0), Value::UserData(ud) => self.push_ref(&ud.0), + #[cfg(feature = "luau")] + Value::Buffer(buf) => self.push_ref(&buf.0), Value::Error(err) => { let protect = !self.unlikely_memory_error(); push_internal_userdata(state, WrappedFailure::Error(*err.clone()), protect)?; @@ -652,9 +654,8 @@ impl RawLua { #[cfg(feature = "luau")] ffi::LUA_TBUFFER => { - // Buffer is represented as a userdata type ffi::lua_xpush(state, self.ref_thread(), idx); - Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::Buffer)) + Value::Buffer(crate::Buffer(self.pop_ref_thread())) } #[cfg(feature = "luajit")] diff --git a/src/table.rs b/src/table.rs index 002bfb4b..5fb6f201 100644 --- a/src/table.rs +++ b/src/table.rs @@ -785,7 +785,7 @@ impl Table { // Collect key/value pairs into a vector so we can sort them let mut pairs = self.pairs::().flatten().collect::>(); // Sort keys - pairs.sort_by(|(a, _), (b, _)| a.cmp(b)); + pairs.sort_by(|(a, _), (b, _)| a.sort_cmp(b)); if pairs.is_empty() { return write!(fmt, "{{}}"); } diff --git a/src/types.rs b/src/types.rs index ad235f2a..dee56311 100644 --- a/src/types.rs +++ b/src/types.rs @@ -32,8 +32,6 @@ pub type Number = ffi::lua_Number; #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub(crate) enum SubtypeId { None, - #[cfg(feature = "luau")] - Buffer, #[cfg(feature = "luajit")] CData, } diff --git a/src/types/vector.rs b/src/types/vector.rs index f65ba863..3aa8d126 100644 --- a/src/types/vector.rs +++ b/src/types/vector.rs @@ -10,7 +10,7 @@ use super::LuaType; /// By default vectors are 3-dimensional, but can be 4-dimensional /// if the `luau-vector4` feature is enabled. #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] -#[derive(Debug, Default, Clone, Copy, PartialEq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] pub struct Vector(pub(crate) [f32; Self::SIZE]); impl fmt::Display for Vector { diff --git a/src/userdata.rs b/src/userdata.rs index 78e68bef..e57e063d 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -937,8 +937,6 @@ impl AnyUserData { pub(crate) fn type_name(&self) -> Result> { match self.1 { SubtypeId::None => {} - #[cfg(feature = "luau")] - SubtypeId::Buffer => return Ok(Some("buffer".to_owned())), #[cfg(feature = "luajit")] SubtypeId::CData => return Ok(Some("cdata".to_owned())), } @@ -1111,19 +1109,6 @@ impl Serialize for AnyUserData { S: Serializer, { let lua = self.0.lua.lock(); - - // Special case for Luau buffer type - #[cfg(feature = "luau")] - if self.1 == SubtypeId::Buffer { - let buf = unsafe { - let mut size = 0usize; - let buf = ffi::lua_tobuffer(lua.ref_thread(), self.0.index, &mut size); - mlua_assert!(!buf.is_null(), "invalid Luau buffer"); - std::slice::from_raw_parts(buf as *const u8, size) - }; - return serializer.serialize_bytes(buf); - } - unsafe { let _ = lua .get_userdata_ref_type_id(&self.0) diff --git a/src/value.rs b/src/value.rs index 276904a0..2b718903 100644 --- a/src/value.rs +++ b/src/value.rs @@ -48,7 +48,7 @@ pub enum Value { /// A Luau vector. #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - Vector(crate::types::Vector), + Vector(crate::Vector), /// An interned string, managed by Lua. /// /// Unlike Rust strings, Lua strings may not be valid UTF-8. @@ -60,8 +60,13 @@ pub enum Value { /// Reference to a Lua thread (or coroutine). Thread(Thread), /// Reference to a userdata object that holds a custom type which implements `UserData`. + /// /// Special builtin userdata types will be represented as other `Value` variants. UserData(AnyUserData), + /// A Luau buffer. + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + Buffer(crate::Buffer), /// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned. Error(Box), } @@ -89,10 +94,10 @@ impl Value { Value::Function(_) => "function", Value::Thread(_) => "thread", Value::UserData(AnyUserData(_, SubtypeId::None)) => "userdata", - #[cfg(feature = "luau")] - Value::UserData(AnyUserData(_, SubtypeId::Buffer)) => "buffer", #[cfg(feature = "luajit")] Value::UserData(AnyUserData(_, SubtypeId::CData)) => "cdata", + #[cfg(feature = "luau")] + Value::Buffer(_) => "buffer", Value::Error(_) => "error", } } @@ -131,6 +136,8 @@ impl Value { | Value::Function(Function(r)) | Value::Thread(Thread(r, ..)) | Value::UserData(AnyUserData(r, ..)) => r.to_pointer(), + #[cfg(feature = "luau")] + Value::Buffer(crate::Buffer(r)) => r.to_pointer(), _ => ptr::null(), } } @@ -165,6 +172,8 @@ impl Value { })?; Ok(String(lua.pop_ref()).to_str()?.to_string()) }, + #[cfg(feature = "luau")] + Value::Buffer(buf) => StdString::from_utf8(buf.to_vec()).map_err(Error::external), Value::Error(err) => Ok(err.to_string()), } } @@ -416,15 +425,25 @@ impl Value { } } - /// Returns `true` if the value is a Buffer wrapped in [`AnyUserData`]. + /// Cast the value to a `Buffer`. + /// + /// If the value is `Buffer`, returns it or `None` otherwise. + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + #[inline] + pub fn as_buffer(&self) -> Option<&crate::Buffer> { + match self { + Value::Buffer(b) => Some(b), + _ => None, + } + } + + /// Returns `true` if the value is a `Buffer`. #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - #[doc(hidden)] #[inline] pub fn is_buffer(&self) -> bool { - self.as_userdata() - .map(|ud| ud.1 == SubtypeId::Buffer) - .unwrap_or_default() + self.as_buffer().is_some() } /// Returns `true` if the value is a CData wrapped in [`AnyUserData`]. @@ -450,7 +469,7 @@ impl Value { // Compares two values. // Used to sort values for Debug printing. - pub(crate) fn cmp(&self, other: &Self) -> Ordering { + pub(crate) fn sort_cmp(&self, other: &Self) -> Ordering { fn cmp_num(a: Number, b: Number) -> Ordering { match (a, b) { _ if a < b => Ordering::Less, @@ -479,11 +498,14 @@ impl Value { (&Value::Number(a), &Value::Number(b)) => cmp_num(a, b), (Value::Integer(_) | Value::Number(_), _) => Ordering::Less, (_, Value::Integer(_) | Value::Number(_)) => Ordering::Greater, + // Vector (Luau) + #[cfg(feature = "luau")] + (Value::Vector(a), Value::Vector(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal), // String (Value::String(a), Value::String(b)) => a.as_bytes().cmp(&b.as_bytes()), (Value::String(_), _) => Ordering::Less, (_, Value::String(_)) => Ordering::Greater, - // Other variants can be randomly ordered + // Other variants can be ordered by their pointer (a, b) => a.to_pointer().cmp(&b.to_pointer()), } } @@ -520,6 +542,8 @@ impl Value { .unwrap_or_else(|| format!("userdata: {:?}", u.to_pointer())); write!(fmt, "{s}") } + #[cfg(feature = "luau")] + buf @ Value::Buffer(_) => write!(fmt, "buffer: {:?}", buf.to_pointer()), Value::Error(e) if recursive => write!(fmt, "{e:?}"), Value::Error(_) => write!(fmt, "error"), } @@ -531,6 +555,7 @@ impl fmt::Debug for Value { if fmt.alternate() { return self.fmt_pretty(fmt, true, 0, &mut HashSet::new()); } + match self { Value::Nil => write!(fmt, "Nil"), Value::Boolean(b) => write!(fmt, "Boolean({b})"), @@ -544,6 +569,8 @@ impl fmt::Debug for Value { Value::Function(f) => write!(fmt, "{f:?}"), Value::Thread(t) => write!(fmt, "{t:?}"), Value::UserData(ud) => write!(fmt, "{ud:?}"), + #[cfg(feature = "luau")] + Value::Buffer(buf) => write!(fmt, "{buf:?}"), Value::Error(e) => write!(fmt, "Error({e:?})"), } } @@ -566,6 +593,8 @@ impl PartialEq for Value { (Value::Function(a), Value::Function(b)) => a == b, (Value::Thread(a), Value::Thread(b)) => a == b, (Value::UserData(a), Value::UserData(b)) => a == b, + #[cfg(feature = "luau")] + (Value::Buffer(a), Value::Buffer(b)) => a == b, _ => false, } } @@ -674,6 +703,8 @@ impl<'a> Serialize for SerializableValue<'a> { Value::UserData(ud) if ud.is_serializable() || self.options.deny_unsupported_types => { ud.serialize(serializer) } + #[cfg(feature = "luau")] + Value::Buffer(buf) => buf.serialize(serializer), Value::Function(_) | Value::Thread(_) | Value::UserData(_) diff --git a/tests/buffer.rs b/tests/buffer.rs new file mode 100644 index 00000000..4fb95020 --- /dev/null +++ b/tests/buffer.rs @@ -0,0 +1,56 @@ +#![cfg(feature = "luau")] + +use mlua::{Lua, Result, Value}; + +#[test] +fn test_buffer() -> Result<()> { + let lua = Lua::new(); + + let buf1 = lua + .load( + r#" + local buf = buffer.fromstring("hello") + assert(buffer.len(buf) == 5) + return buf + "#, + ) + .eval::()?; + assert!(buf1.is_buffer()); + assert_eq!(buf1.type_name(), "buffer"); + + let buf2 = lua.load("buffer.fromstring('hello')").eval::()?; + assert_ne!(buf1, buf2); + + // Check that we can pass buffer type to Lua + let buf1 = buf1.as_buffer().unwrap(); + let func = lua.create_function(|_, buf: Value| return buf.to_string())?; + assert_eq!(func.call::(buf1)?, "hello"); + + // Check buffer methods + assert_eq!(buf1.len(), 5); + assert_eq!(buf1.to_vec(), b"hello"); + assert_eq!(buf1.read_bytes::<3>(1), [b'e', b'l', b'l']); + buf1.write_bytes(1, b"i"); + assert_eq!(buf1.to_vec(), b"hillo"); + + let buf3 = lua.create_buffer(b"")?; + assert!(buf3.is_empty()); + + Ok(()) +} + +#[test] +#[should_panic(expected = "range end index 14 out of range for slice of length 13")] +fn test_buffer_out_of_bounds_read() { + let lua = Lua::new(); + let buf = lua.create_buffer(b"hello, world!").unwrap(); + _ = buf.read_bytes::<1>(13); +} + +#[test] +#[should_panic(expected = "range end index 16 out of range for slice of length 13")] +fn test_buffer_out_of_bounds_write() { + let lua = Lua::new(); + let buf = lua.create_buffer(b"hello, world!").unwrap(); + buf.write_bytes(14, b"!!"); +} diff --git a/tests/conversion.rs b/tests/conversion.rs index 59418792..5ca9982e 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -384,8 +384,8 @@ fn test_bstring_from_lua() -> Result<()> { fn test_bstring_from_lua_buffer() -> Result<()> { let lua = Lua::new(); - let b = lua.create_buffer("hello, world")?; - let bstr = lua.unpack::(Value::UserData(b))?; + let buf = lua.create_buffer("hello, world")?; + let bstr = lua.convert::(buf)?; assert_eq!(bstr, "hello, world"); // Test from stack diff --git a/tests/luau.rs b/tests/luau.rs index 3d7268b8..2c24c178 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -465,32 +465,6 @@ fn test_coverage() -> Result<()> { Ok(()) } -#[test] -fn test_buffer() -> Result<()> { - let lua = Lua::new(); - - let buf1 = lua - .load( - r#" - local buf = buffer.fromstring("hello") - assert(buffer.len(buf) == 5) - return buf - "#, - ) - .eval::()?; - assert!(buf1.is_userdata() && buf1.is_buffer()); - assert_eq!(buf1.type_name(), "buffer"); - - let buf2 = lua.load("buffer.fromstring('hello')").eval::()?; - assert_ne!(buf1, buf2); - - // Check that we can pass buffer type to Lua - let func = lua.create_function(|_, buf: Value| return buf.to_string())?; - assert!(func.call::(buf1)?.starts_with("buffer:")); - - Ok(()) -} - #[test] fn test_fflags() { // We cannot really on any particular feature flag to be present diff --git a/tests/serde.rs b/tests/serde.rs index fb50c04c..d3ed3726 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -717,27 +717,29 @@ fn test_arbitrary_precision() { #[cfg(feature = "luau")] #[test] -fn test_buffer_serialize() { +fn test_buffer_serialize() -> LuaResult<()> { let lua = Lua::new(); - let buf = lua.create_buffer(&[1, 2, 3, 4]).unwrap(); + let buf = lua.create_buffer(&[1, 2, 3, 4])?; let val = serde_value::to_value(&buf).unwrap(); assert_eq!(val, serde_value::Value::Bytes(vec![1, 2, 3, 4])); // Try empty buffer - let buf = lua.create_buffer(&[]).unwrap(); + let buf = lua.create_buffer(&[])?; let val = serde_value::to_value(&buf).unwrap(); assert_eq!(val, serde_value::Value::Bytes(vec![])); + + Ok(()) } #[cfg(feature = "luau")] #[test] -fn test_buffer_from_value() { +fn test_buffer_from_value() -> LuaResult<()> { let lua = Lua::new(); - let buf = lua.create_buffer(&[1, 2, 3, 4]).unwrap(); - let val = lua - .from_value::(Value::UserData(buf)) - .unwrap(); + let buf = lua.create_buffer(&[1, 2, 3, 4])?; + let val = lua.from_value::(Value::Buffer(buf)).unwrap(); assert_eq!(val, serde_value::Value::Bytes(vec![1, 2, 3, 4])); + + Ok(()) } From 7839c4438cef1eb30069b4d863b30424c58a2785 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 1 Oct 2024 22:28:47 +0100 Subject: [PATCH 202/635] Fix compilation warnings --- src/state/raw.rs | 11 +++++++---- src/thread.rs | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index 2b20e451..ae8095ef 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -18,7 +18,7 @@ use crate::table::Table; use crate::thread::Thread; use crate::types::{ AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData, - MaybeSend, ReentrantMutex, RegistryKey, SubtypeId, ValueRef, VmState, XRc, + MaybeSend, ReentrantMutex, RegistryKey, SubtypeId, ValueRef, XRc, }; use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataRegistry, UserDataStorage}; use crate::util::{ @@ -356,8 +356,11 @@ impl RawLua { triggers: HookTriggers, callback: F, ) where - F: Fn(&Lua, Debug) -> Result + MaybeSend + 'static, + F: Fn(&Lua, Debug) -> Result + MaybeSend + 'static, { + use crate::types::VmState; + use std::rc::Rc; + unsafe extern "C-unwind" fn hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) { let extra = ExtraData::get(state); if (*extra).hook_thread != state { @@ -368,7 +371,7 @@ impl RawLua { let result = callback_error_ext(state, extra, move |extra, _| { let hook_cb = (*extra).hook_callback.clone(); let hook_cb = mlua_expect!(hook_cb, "no hook callback set in hook_proc"); - if std::rc::Rc::strong_count(&hook_cb) > 2 { + if Rc::strong_count(&hook_cb) > 2 { return Ok(VmState::Continue); // Don't allow recursion } let rawlua = (*extra).raw_lua(); @@ -395,7 +398,7 @@ impl RawLua { } } - (*self.extra.get()).hook_callback = Some(std::rc::Rc::new(callback)); + (*self.extra.get()).hook_callback = Some(Rc::new(callback)); (*self.extra.get()).hook_thread = state; // Mark for what thread the hook is set ffi::lua_sethook(state, Some(hook_proc), triggers.mask(), triggers.count()); } diff --git a/src/thread.rs b/src/thread.rs index cbbd61fe..95531674 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -4,7 +4,7 @@ use crate::error::{Error, Result}; #[allow(unused)] use crate::state::Lua; use crate::state::RawLua; -use crate::types::{LuaType, ValueRef, VmState}; +use crate::types::{LuaType, ValueRef}; use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard}; use crate::value::{FromLuaMulti, IntoLuaMulti}; @@ -194,7 +194,7 @@ impl Thread { #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub fn set_hook(&self, triggers: HookTriggers, callback: F) where - F: Fn(&Lua, Debug) -> Result + MaybeSend + 'static, + F: Fn(&Lua, Debug) -> Result + MaybeSend + 'static, { let lua = self.0.lua.lock(); unsafe { From 4b8c26e682e258eeef3ae78b2e93592e88cfc538 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 1 Oct 2024 22:59:19 +0100 Subject: [PATCH 203/635] Invoke `__tostring` metamethod when calling `Value::to_string()` for Buffer type --- src/state.rs | 6 +++++- src/value.rs | 52 +++++++++++++++++++++++++------------------------ tests/buffer.rs | 2 +- tests/value.rs | 11 +++++++++++ 4 files changed, 44 insertions(+), 27 deletions(-) diff --git a/src/state.rs b/src/state.rs index 645556b8..470c299f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1390,7 +1390,11 @@ impl Lua { ffi::LUA_TTHREAD => { ffi::lua_newthread(state); } - _ => {} + #[cfg(feature = "luau")] + ffi::LUA_TBUFFER => { + ffi::lua_newbuffer(state, 0); + } + _ => return, } match metatable { Some(metatable) => lua.push_ref(&metatable.0), diff --git a/src/value.rs b/src/value.rs index 2b718903..50a1bd49 100644 --- a/src/value.rs +++ b/src/value.rs @@ -14,7 +14,7 @@ use crate::state::{Lua, RawLua}; use crate::string::{BorrowedStr, String}; use crate::table::Table; use crate::thread::Thread; -use crate::types::{Integer, LightUserData, Number, SubtypeId}; +use crate::types::{Integer, LightUserData, Number, SubtypeId, ValueRef}; use crate::userdata::AnyUserData; use crate::util::{check_stack, StackGuard}; @@ -131,22 +131,35 @@ impl Value { pub fn to_pointer(&self) -> *const c_void { match self { Value::LightUserData(ud) => ud.0, - Value::String(String(r)) - | Value::Table(Table(r)) - | Value::Function(Function(r)) - | Value::Thread(Thread(r, ..)) - | Value::UserData(AnyUserData(r, ..)) => r.to_pointer(), + Value::String(String(vref)) + | Value::Table(Table(vref)) + | Value::Function(Function(vref)) + | Value::Thread(Thread(vref, ..)) + | Value::UserData(AnyUserData(vref, ..)) => vref.to_pointer(), #[cfg(feature = "luau")] - Value::Buffer(crate::Buffer(r)) => r.to_pointer(), + Value::Buffer(crate::Buffer(vref)) => vref.to_pointer(), _ => ptr::null(), } } /// Converts the value to a string. /// - /// If the value has a metatable with a `__tostring` method, then it will be called to get the - /// result. + /// This might invoke the `__tostring` metamethod for non-primitive types (eg. tables, + /// functions). pub fn to_string(&self) -> Result { + unsafe fn invoke_to_string(vref: &ValueRef) -> Result { + let lua = vref.lua.lock(); + let state = lua.state(); + let _guard = StackGuard::new(state); + check_stack(state, 3)?; + + lua.push_ref(vref); + protect_lua!(state, 1, 1, fn(state) { + ffi::luaL_tolstring(state, -1, ptr::null_mut()); + })?; + Ok(String(lua.pop_ref()).to_str()?.to_string()) + } + match self { Value::Nil => Ok("nil".to_string()), Value::Boolean(b) => Ok(b.to_string()), @@ -157,23 +170,12 @@ impl Value { #[cfg(feature = "luau")] Value::Vector(v) => Ok(v.to_string()), Value::String(s) => Ok(s.to_str()?.to_string()), - Value::Table(Table(r)) - | Value::Function(Function(r)) - | Value::Thread(Thread(r, ..)) - | Value::UserData(AnyUserData(r, ..)) => unsafe { - let lua = r.lua.lock(); - let state = lua.state(); - let _guard = StackGuard::new(state); - check_stack(state, 3)?; - - lua.push_ref(r); - protect_lua!(state, 1, 1, fn(state) { - ffi::luaL_tolstring(state, -1, ptr::null_mut()); - })?; - Ok(String(lua.pop_ref()).to_str()?.to_string()) - }, + Value::Table(Table(vref)) + | Value::Function(Function(vref)) + | Value::Thread(Thread(vref, ..)) + | Value::UserData(AnyUserData(vref, ..)) => unsafe { invoke_to_string(vref) }, #[cfg(feature = "luau")] - Value::Buffer(buf) => StdString::from_utf8(buf.to_vec()).map_err(Error::external), + Value::Buffer(crate::Buffer(vref)) => unsafe { invoke_to_string(vref) }, Value::Error(err) => Ok(err.to_string()), } } diff --git a/tests/buffer.rs b/tests/buffer.rs index 4fb95020..cae34746 100644 --- a/tests/buffer.rs +++ b/tests/buffer.rs @@ -24,7 +24,7 @@ fn test_buffer() -> Result<()> { // Check that we can pass buffer type to Lua let buf1 = buf1.as_buffer().unwrap(); let func = lua.create_function(|_, buf: Value| return buf.to_string())?; - assert_eq!(func.call::(buf1)?, "hello"); + assert!(func.call::(buf1)?.starts_with("buffer:")); // Check buffer methods assert_eq!(buf1.len(), 5); diff --git a/tests/value.rs b/tests/value.rs index 024c5c59..e100d4f1 100644 --- a/tests/value.rs +++ b/tests/value.rs @@ -140,6 +140,17 @@ fn test_value_to_string() -> Result<()> { let err = Value::Error(Box::new(Error::runtime("test error"))); assert_eq!(err.to_string()?, "runtime error: test error"); + #[cfg(feature = "luau")] + { + let buf = Value::Buffer(lua.create_buffer(b"hello")?); + assert!(buf.to_string()?.starts_with("buffer:")); + + // Set `__tostring` metamethod for buffer + let mt = lua.load("{__tostring = buffer.tostring}").eval()?; + lua.set_type_metatable::(mt); + assert_eq!(buf.to_string()?, "hello"); + } + Ok(()) } From ad9bc3676418c046d7362867b0a6cc760072c424 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 1 Oct 2024 23:20:19 +0100 Subject: [PATCH 204/635] impl Eq/Ord for Lua String --- src/string.rs | 32 +++++++++++++++++++++++++++----- tests/conversion.rs | 2 +- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/string.rs b/src/string.rs index 734cd810..636c725a 100644 --- a/src/string.rs +++ b/src/string.rs @@ -164,19 +164,25 @@ where } } -impl PartialEq for String { +impl PartialEq for String { fn eq(&self, other: &String) -> bool { self.as_bytes() == other.as_bytes() } } -impl PartialEq<&String> for String { - fn eq(&self, other: &&String) -> bool { - self.as_bytes() == other.as_bytes() +impl Eq for String {} + +impl PartialOrd for String { + fn partial_cmp(&self, other: &String) -> Option { + self.as_bytes().partial_cmp(&other.as_bytes()) } } -impl Eq for String {} +impl Ord for String { + fn cmp(&self, other: &String) -> cmp::Ordering { + self.as_bytes().cmp(&other.as_bytes()) + } +} impl Hash for String { fn hash(&self, state: &mut H) { @@ -244,6 +250,8 @@ where } } +impl Eq for BorrowedStr<'_> {} + impl PartialOrd for BorrowedStr<'_> where T: AsRef, @@ -253,6 +261,12 @@ where } } +impl Ord for BorrowedStr<'_> { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.0.cmp(other.0) + } +} + /// A borrowed byte slice (`&[u8]`) that holds a strong reference to the Lua state. pub struct BorrowedBytes<'a>(&'a [u8], #[allow(unused)] Lua); @@ -294,6 +308,8 @@ where } } +impl Eq for BorrowedBytes<'_> {} + impl PartialOrd for BorrowedBytes<'_> where T: AsRef<[u8]>, @@ -303,6 +319,12 @@ where } } +impl Ord for BorrowedBytes<'_> { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.0.cmp(other.0) + } +} + impl<'a> IntoIterator for BorrowedBytes<'a> { type Item = &'a u8; type IntoIter = slice::Iter<'a, u8>; diff --git a/tests/conversion.rs b/tests/conversion.rs index 5ca9982e..d0e7a079 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -33,7 +33,7 @@ fn test_string_into_lua() -> Result<()> { // Direct conversion let s = lua.create_string("hello, world!")?; let s2 = (&s).into_lua(&lua)?; - assert_eq!(s, s2.as_string().unwrap()); + assert_eq!(s, *s2.as_string().unwrap()); // Push into stack let table = lua.create_table()?; From b6cdf32f16651db2d16af9f0bd5a3a247cda45e5 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 1 Oct 2024 23:21:28 +0100 Subject: [PATCH 205/635] Update MSRV in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e6debaa9..09921638 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [docs.rs]: https://docs.rs/mlua [Coverage Status]: https://codecov.io/gh/mlua-rs/mlua/branch/main/graph/badge.svg?token=99339FS1CG [codecov.io]: https://codecov.io/gh/mlua-rs/mlua -[MSRV]: https://img.shields.io/badge/rust-1.71+-brightgreen.svg?&logo=rust +[MSRV]: https://img.shields.io/badge/rust-1.79+-brightgreen.svg?&logo=rust [Guided Tour] | [Benchmarks] | [FAQ] From 04d81066765b2e8158cb8be3450bc3beb5e66246 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 2 Oct 2024 12:16:48 +0100 Subject: [PATCH 206/635] Remove `SubtypeId` from `AnyUserData` and instead add `Value::Other` variant that will cover any unknown types (eg. LuaJIT CData) --- src/scope.rs | 4 ++-- src/serde/de.rs | 3 ++- src/state/raw.rs | 15 ++++++--------- src/types.rs | 8 -------- src/userdata.rs | 10 ++-------- src/userdata/object.rs | 2 +- src/util/mod.rs | 7 ++++--- src/value.rs | 31 +++++++++++++------------------ tests/tests.rs | 3 +-- 9 files changed, 31 insertions(+), 52 deletions(-) diff --git a/src/scope.rs b/src/scope.rs index 57f6598f..725cd62b 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -6,7 +6,7 @@ use std::os::raw::c_void; use crate::error::{Error, Result}; use crate::function::Function; use crate::state::{Lua, LuaGuard, RawLua}; -use crate::types::{Callback, CallbackUpvalue, ScopedCallback, SubtypeId, ValueRef}; +use crate::types::{Callback, CallbackUpvalue, ScopedCallback, ValueRef}; use crate::userdata::{AnyUserData, UserData, UserDataRegistry, UserDataStorage}; use crate::util::{self, assert_stack, check_stack, get_userdata, take_userdata, StackGuard}; use crate::value::{FromLuaMulti, IntoLuaMulti}; @@ -186,7 +186,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { std::ptr::write(ud_ptr, UserDataStorage::new_scoped(data)); ffi::lua_setmetatable(state, -2); - let ud = AnyUserData(self.lua.pop_ref(), SubtypeId::None); + let ud = AnyUserData(self.lua.pop_ref()); let destructor: DestructorCallback = Box::new(|rawlua, vref| { let state = rawlua.state(); diff --git a/src/serde/de.rs b/src/serde/de.rs index 6d3d5139..4e4da584 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -150,7 +150,8 @@ impl<'de> serde::Deserializer<'de> for Deserializer { | Value::Thread(_) | Value::UserData(_) | Value::LightUserData(_) - | Value::Error(_) => { + | Value::Error(_) + | Value::Other(_) => { if self.options.deny_unsupported_types { let msg = format!("unsupported value type `{}`", self.value.type_name()); Err(de::Error::custom(msg)) diff --git a/src/state/raw.rs b/src/state/raw.rs index ae8095ef..36a54f94 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -18,7 +18,7 @@ use crate::table::Table; use crate::thread::Thread; use crate::types::{ AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData, - MaybeSend, ReentrantMutex, RegistryKey, SubtypeId, ValueRef, XRc, + MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc, }; use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataRegistry, UserDataStorage}; use crate::util::{ @@ -560,6 +560,7 @@ impl RawLua { let protect = !self.unlikely_memory_error(); push_internal_userdata(state, WrappedFailure::Error(*err.clone()), protect)?; } + Value::Other(vref) => self.push_ref(vref), } Ok(()) } @@ -644,7 +645,7 @@ impl RawLua { } _ => { ffi::lua_xpush(state, self.ref_thread(), idx); - Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::None)) + Value::UserData(AnyUserData(self.pop_ref_thread())) } } } @@ -661,14 +662,10 @@ impl RawLua { Value::Buffer(crate::Buffer(self.pop_ref_thread())) } - #[cfg(feature = "luajit")] - ffi::LUA_TCDATA => { - // CData is represented as a userdata type + _ => { ffi::lua_xpush(state, self.ref_thread(), idx); - Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::CData)) + Value::Other(self.pop_ref_thread()) } - - _ => mlua_panic!("unexpected value type on stack"), } } @@ -806,7 +803,7 @@ impl RawLua { ffi::lua_setuservalue(state, -2); } - Ok(AnyUserData(self.pop_ref(), SubtypeId::None)) + Ok(AnyUserData(self.pop_ref())) } pub(crate) unsafe fn create_userdata_metatable( diff --git a/src/types.rs b/src/types.rs index dee56311..12f40832 100644 --- a/src/types.rs +++ b/src/types.rs @@ -28,14 +28,6 @@ pub type Integer = ffi::lua_Integer; /// Type of Lua floating point numbers. pub type Number = ffi::lua_Number; -// Represents different subtypes wrapped in AnyUserData -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub(crate) enum SubtypeId { - None, - #[cfg(feature = "luajit")] - CData, -} - /// A "light" userdata value. Equivalent to an unmanaged raw pointer. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct LightUserData(pub *mut c_void); diff --git a/src/userdata.rs b/src/userdata.rs index e57e063d..71995eb9 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -19,7 +19,7 @@ use crate::function::Function; use crate::state::Lua; use crate::string::String; use crate::table::{Table, TablePairs}; -use crate::types::{MaybeSend, SubtypeId, ValueRef}; +use crate::types::{MaybeSend, ValueRef}; use crate::util::{check_stack, get_userdata, take_userdata, StackGuard}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; @@ -643,7 +643,7 @@ pub trait UserData: Sized { /// [`is`]: crate::AnyUserData::is /// [`borrow`]: crate::AnyUserData::borrow #[derive(Clone, Debug)] -pub struct AnyUserData(pub(crate) ValueRef, pub(crate) SubtypeId); +pub struct AnyUserData(pub(crate) ValueRef); impl AnyUserData { /// Checks whether the type of this userdata is `T`. @@ -935,12 +935,6 @@ impl AnyUserData { /// Returns a type name of this `UserData` (from a metatable field). pub(crate) fn type_name(&self) -> Result> { - match self.1 { - SubtypeId::None => {} - #[cfg(feature = "luajit")] - SubtypeId::CData => return Ok(Some("cdata".to_owned())), - } - let lua = self.0.lua.lock(); let state = lua.state(); unsafe { diff --git a/src/userdata/object.rs b/src/userdata/object.rs index faa68f72..2b9597f4 100644 --- a/src/userdata/object.rs +++ b/src/userdata/object.rs @@ -88,6 +88,6 @@ impl ObjectLike for AnyUserData { #[inline] fn to_string(&self) -> Result { - Value::UserData(AnyUserData(self.0.copy(), self.1)).to_string() + Value::UserData(AnyUserData(self.0.copy())).to_string() } } diff --git a/src/util/mod.rs b/src/util/mod.rs index 9722fbc4..148e1c8c 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -275,9 +275,10 @@ pub(crate) unsafe fn to_string(state: *mut ffi::lua_State, index: c_int) -> Stri ffi::LUA_TTHREAD => format!("", ffi::lua_topointer(state, index)), #[cfg(feature = "luau")] ffi::LUA_TBUFFER => format!("", ffi::lua_topointer(state, index)), - #[cfg(feature = "luajit")] - ffi::LUA_TCDATA => format!("", ffi::lua_topointer(state, index)), - _ => "".to_string(), + type_id => { + let type_name = CStr::from_ptr(ffi::lua_typename(state, type_id)).to_string_lossy(); + format!("<{type_name} {:?}>", ffi::lua_topointer(state, index)) + } } } diff --git a/src/value.rs b/src/value.rs index 50a1bd49..4d855e7a 100644 --- a/src/value.rs +++ b/src/value.rs @@ -14,7 +14,7 @@ use crate::state::{Lua, RawLua}; use crate::string::{BorrowedStr, String}; use crate::table::Table; use crate::thread::Thread; -use crate::types::{Integer, LightUserData, Number, SubtypeId, ValueRef}; +use crate::types::{Integer, LightUserData, Number, ValueRef}; use crate::userdata::AnyUserData; use crate::util::{check_stack, StackGuard}; @@ -28,7 +28,7 @@ use { /// A dynamically typed Lua value. /// -/// The `String`, `Table`, `Function`, `Thread`, and `UserData` variants contain handle types +/// The non-primitive variants (eg. string/table/function/thread/userdata) contain handle types /// into the internal Lua state. It is a logic error to mix handle types between separate /// `Lua` instances, and doing so will result in a panic. #[derive(Clone)] @@ -69,6 +69,9 @@ pub enum Value { Buffer(crate::Buffer), /// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned. Error(Box), + /// Any other value not known to mlua (eg. LuaJIT CData). + #[allow(private_interfaces)] + Other(ValueRef), } pub use self::Value::Nil; @@ -93,12 +96,11 @@ impl Value { Value::Table(_) => "table", Value::Function(_) => "function", Value::Thread(_) => "thread", - Value::UserData(AnyUserData(_, SubtypeId::None)) => "userdata", - #[cfg(feature = "luajit")] - Value::UserData(AnyUserData(_, SubtypeId::CData)) => "cdata", + Value::UserData(_) => "userdata", #[cfg(feature = "luau")] Value::Buffer(_) => "buffer", Value::Error(_) => "error", + Value::Other(_) => "other", } } @@ -173,7 +175,8 @@ impl Value { Value::Table(Table(vref)) | Value::Function(Function(vref)) | Value::Thread(Thread(vref, ..)) - | Value::UserData(AnyUserData(vref, ..)) => unsafe { invoke_to_string(vref) }, + | Value::UserData(AnyUserData(vref)) + | Value::Other(vref) => unsafe { invoke_to_string(vref) }, #[cfg(feature = "luau")] Value::Buffer(crate::Buffer(vref)) => unsafe { invoke_to_string(vref) }, Value::Error(err) => Ok(err.to_string()), @@ -448,17 +451,6 @@ impl Value { self.as_buffer().is_some() } - /// Returns `true` if the value is a CData wrapped in [`AnyUserData`]. - #[cfg(any(feature = "luajit", doc))] - #[cfg_attr(docsrs, doc(cfg(feature = "luajit")))] - #[doc(hidden)] - #[inline] - pub fn is_cdata(&self) -> bool { - self.as_userdata() - .map(|ud| ud.1 == SubtypeId::CData) - .unwrap_or_default() - } - /// Wrap reference to this Value into [`SerializableValue`]. /// /// This allows customizing serialization behavior using serde. @@ -548,6 +540,7 @@ impl Value { buf @ Value::Buffer(_) => write!(fmt, "buffer: {:?}", buf.to_pointer()), Value::Error(e) if recursive => write!(fmt, "{e:?}"), Value::Error(_) => write!(fmt, "error"), + Value::Other(v) => write!(fmt, "other: {:?}", v.to_pointer()), } } } @@ -574,6 +567,7 @@ impl fmt::Debug for Value { #[cfg(feature = "luau")] Value::Buffer(buf) => write!(fmt, "{buf:?}"), Value::Error(e) => write!(fmt, "Error({e:?})"), + Value::Other(v) => write!(fmt, "Other({v:?})"), } } } @@ -711,7 +705,8 @@ impl<'a> Serialize for SerializableValue<'a> { | Value::Thread(_) | Value::UserData(_) | Value::LightUserData(_) - | Value::Error(_) => { + | Value::Error(_) + | Value::Other(_) => { if self.options.deny_unsupported_types { let msg = format!("cannot serialize <{}>", self.value.type_name()); Err(ser::Error::custom(msg)) diff --git a/tests/tests.rs b/tests/tests.rs index fb6e8931..a819d06f 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1256,8 +1256,7 @@ fn test_luajit_cdata() -> Result<()> { "#, ) .eval::()?; - assert!(cdata.is_userdata() && cdata.is_cdata()); - assert_eq!(cdata.type_name(), "cdata"); + assert_eq!(cdata.type_name(), "other"); assert!(cdata.to_string()?.starts_with("cdata:")); Ok(()) From 4ac87c72082d7dcac2e86b52cfc6cb8042d481c1 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 2 Oct 2024 12:21:04 +0100 Subject: [PATCH 207/635] Derive `PartialEq` instead of implementing manually --- src/function.rs | 8 +------- src/table.rs | 10 ++-------- src/thread.rs | 9 ++++++++- src/userdata.rs | 10 ++-------- 4 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/function.rs b/src/function.rs index 37211122..d981f0bb 100644 --- a/src/function.rs +++ b/src/function.rs @@ -20,7 +20,7 @@ use { }; /// Handle to an internal Lua function. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct Function(pub(crate) ValueRef); /// Contains information about a function. @@ -509,12 +509,6 @@ impl Function { } } -impl PartialEq for Function { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - pub(crate) struct WrappedFunction(pub(crate) Callback); #[cfg(feature = "async")] diff --git a/src/table.rs b/src/table.rs index 5fb6f201..5fc3ae6e 100644 --- a/src/table.rs +++ b/src/table.rs @@ -23,7 +23,7 @@ use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Nil, Value}; use futures_util::future::{self, Either, Future}; /// Handle to an internal Lua table. -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct Table(pub(crate) ValueRef); impl Table { @@ -806,13 +806,7 @@ impl fmt::Debug for Table { if fmt.alternate() { return self.fmt_pretty(fmt, 0, &mut HashSet::new()); } - fmt.write_fmt(format_args!("Table({:?})", self.0)) - } -} - -impl PartialEq for Table { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 + fmt.debug_tuple("Table").field(&self.0).finish() } } diff --git a/src/thread.rs b/src/thread.rs index 95531674..8a0f1c3e 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -1,3 +1,4 @@ +use std::fmt; use std::os::raw::{c_int, c_void}; use crate::error::{Error, Result}; @@ -42,7 +43,7 @@ pub enum ThreadStatus { } /// Handle to an internal Lua thread (coroutine). -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct Thread(pub(crate) ValueRef, pub(crate) *mut ffi::lua_State); #[cfg(feature = "send")] @@ -366,6 +367,12 @@ impl Thread { } } +impl fmt::Debug for Thread { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_tuple("Thread").field(&self.0).finish() + } +} + impl PartialEq for Thread { fn eq(&self, other: &Self) -> bool { self.0 == other.0 diff --git a/src/userdata.rs b/src/userdata.rs index 71995eb9..df299bfb 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -128,7 +128,7 @@ pub enum MetaMethod { /// /// Executed when a variable, that marked as to-be-closed, goes out of scope. /// - /// More information about to-be-closed variabled can be found in the Lua 5.4 + /// More information about to-be-closed variables can be found in the Lua 5.4 /// [documentation][lua_doc]. /// /// Requires `feature = "lua54"` @@ -642,7 +642,7 @@ pub trait UserData: Sized { /// [`UserData`]: crate::UserData /// [`is`]: crate::AnyUserData::is /// [`borrow`]: crate::AnyUserData::borrow -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct AnyUserData(pub(crate) ValueRef); impl AnyUserData { @@ -1009,12 +1009,6 @@ impl AnyUserData { } } -impl PartialEq for AnyUserData { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - impl AsRef for AnyUserData { #[inline] fn as_ref(&self) -> &Self { From ae4897ab2eb056c39324cf56e56d1a44b3f154ed Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 2 Oct 2024 12:59:04 +0100 Subject: [PATCH 208/635] Add `Value::is_error` and `Value::as_error` helpers --- src/value.rs | 16 ++++++++++++++++ tests/value.rs | 8 ++++++++ 2 files changed, 24 insertions(+) diff --git a/src/value.rs b/src/value.rs index 4d855e7a..e68f7532 100644 --- a/src/value.rs +++ b/src/value.rs @@ -451,6 +451,22 @@ impl Value { self.as_buffer().is_some() } + /// Returns `true` if the value is an [`Error`]. + #[inline] + pub fn is_error(&self) -> bool { + self.as_error().is_some() + } + + /// Cast the value to [`Error`]. + /// + /// If the value is an [`Error`], returns it or `None` otherwise. + pub fn as_error(&self) -> Option<&Error> { + match self { + Value::Error(e) => Some(e), + _ => None, + } + } + /// Wrap reference to this Value into [`SerializableValue`]. /// /// This allows customizing serialization behavior using serde. diff --git a/tests/value.rs b/tests/value.rs index e100d4f1..8f913fde 100644 --- a/tests/value.rs +++ b/tests/value.rs @@ -231,5 +231,13 @@ fn test_value_conversions() -> Result<()> { Some(&"hello") ); + assert!(Value::Error(Box::new(Error::runtime("some error"))).is_error()); + assert_eq!( + (Value::Error(Box::new(Error::runtime("some error"))).as_error()) + .unwrap() + .to_string(), + "runtime error: some error" + ); + Ok(()) } From a3ca95fc8ff982c1b0fbe853d9003900ea55c188 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 4 Oct 2024 10:33:56 +0100 Subject: [PATCH 209/635] Include `Value::Other` variant into `Value::to_pointer()` helper. Closes #465 --- src/value.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/value.rs b/src/value.rs index e68f7532..a645cf91 100644 --- a/src/value.rs +++ b/src/value.rs @@ -137,7 +137,8 @@ impl Value { | Value::Table(Table(vref)) | Value::Function(Function(vref)) | Value::Thread(Thread(vref, ..)) - | Value::UserData(AnyUserData(vref, ..)) => vref.to_pointer(), + | Value::UserData(AnyUserData(vref)) + | Value::Other(vref) => vref.to_pointer(), #[cfg(feature = "luau")] Value::Buffer(crate::Buffer(vref)) => vref.to_pointer(), _ => ptr::null(), From 4bc846a1194d1876110ec18dd95d6cb5f7e015ae Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 5 Oct 2024 23:16:36 +0100 Subject: [PATCH 210/635] Add optional `anyhow` dependency (under the same feature flag) to implement `IntoLua` for `anyhow::Error` --- Cargo.toml | 2 ++ src/conversion.rs | 8 ++++++++ src/error.rs | 12 +++++++++++- tests/error.rs | 23 +++++++++++++++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 027afa52..8abda75c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ async = ["dep:futures-util"] send = ["parking_lot/send_guard"] serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value"] macros = ["mlua_derive/macros"] +anyhow = ["dep:anyhow"] [dependencies] mlua_derive = { version = "=0.10.0-beta.1", optional = true, path = "mlua_derive" } @@ -52,6 +53,7 @@ serde = { version = "1.0", optional = true } erased-serde = { version = "0.4", optional = true } serde-value = { version = "0.7", optional = true } parking_lot = { version = "0.12", features = ["arc_lock"] } +anyhow = { version = "1.0", optional = true } ffi = { package = "mlua-sys", version = "0.6.3", path = "mlua-sys" } diff --git a/src/conversion.rs b/src/conversion.rs index 8bfd44b3..a06abdcd 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -254,6 +254,14 @@ impl FromLua for Error { } } +#[cfg(feature = "anyhow")] +impl IntoLua for anyhow::Error { + #[inline] + fn into_lua(self, _: &Lua) -> Result { + Ok(Value::Error(Box::new(Error::from(self)))) + } +} + impl IntoLua for RegistryKey { #[inline] fn into_lua(self, lua: &Lua) -> Result { diff --git a/src/error.rs b/src/error.rs index 8784778e..d18b1a39 100644 --- a/src/error.rs +++ b/src/error.rs @@ -454,7 +454,7 @@ impl ErrorContext for Error { } } -impl ErrorContext for StdResult { +impl ErrorContext for Result { fn context(self, context: C) -> Self { self.map_err(|err| err.context(context)) } @@ -496,6 +496,16 @@ impl serde::de::Error for Error { } } +#[cfg(feature = "anyhow")] +impl From for Error { + fn from(err: anyhow::Error) -> Self { + match err.downcast::() { + Ok(err) => err, + Err(err) => Error::external(err), + } + } +} + struct Chain<'a> { root: &'a Error, current: Option<&'a (dyn StdError + 'static)>, diff --git a/tests/error.rs b/tests/error.rs index ed1dcc23..a61e2aa2 100644 --- a/tests/error.rs +++ b/tests/error.rs @@ -72,3 +72,26 @@ fn test_error_chain() -> Result<()> { Ok(()) } + +#[cfg(feature = "anyhow")] +#[test] +fn test_error_anyhow() -> Result<()> { + use mlua::IntoLua; + + let lua = Lua::new(); + + let err = anyhow::Error::msg("anyhow error"); + let val = err.into_lua(&lua)?; + assert!(val.is_error()); + assert_eq!(val.as_error().unwrap().to_string(), "anyhow error"); + + // Try Error -> anyhow::Error -> Error roundtrip + let err = Error::runtime("runtime error"); + let err = anyhow::Error::new(err); + let err = err.into_lua(&lua)?; + assert!(err.is_error()); + let err = err.as_error().unwrap(); + assert!(matches!(err, Error::RuntimeError(msg) if msg == "runtime error")); + + Ok(()) +} From f95161c6e0661e089802b0e9f19df3580e85d6a3 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 5 Oct 2024 23:26:03 +0100 Subject: [PATCH 211/635] Add missing documentation for `Function::wrap_raw*` functions --- src/function.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/function.rs b/src/function.rs index d981f0bb..7379e11a 100644 --- a/src/function.rs +++ b/src/function.rs @@ -545,6 +545,11 @@ impl Function { })) } + /// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`] + /// trait. + /// + /// This function is similar to [`Function::wrap`] but any returned `Result` will be converted + /// to a `ok, err` tuple without throwing an exception. #[inline] pub fn wrap_raw(func: F) -> impl IntoLua where @@ -557,6 +562,10 @@ impl Function { })) } + /// Wraps a Rust mutable closure, returning an opaque type that implements [`IntoLua`] trait. + /// + /// This function is similar to [`Function::wrap_mut`] but any returned `Result` will be + /// converted to a `ok, err` tuple without throwing an exception. #[inline] pub fn wrap_raw_mut(func: F) -> impl IntoLua where @@ -592,6 +601,11 @@ impl Function { })) } + /// Wraps a Rust async function or closure, returning an opaque type that implements [`IntoLua`] + /// trait. + /// + /// This function is similar to [`Function::wrap_async`] but any returned `Result` will be + /// converted to a `ok, err` tuple without throwing an exception. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] pub fn wrap_raw_async(func: F) -> impl IntoLua From ac315fd80bbf1acf95160f4ddd46575deec9f211 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 6 Oct 2024 11:09:34 +0100 Subject: [PATCH 212/635] Add `AnyUserData::wrap_ser` function --- src/userdata.rs | 10 ++++++++++ tests/serde.rs | 24 ++++++++++++++++++------ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/userdata.rs b/src/userdata.rs index df299bfb..5fa97662 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -1116,6 +1116,16 @@ impl AnyUserData { pub fn wrap(data: T) -> impl IntoLua { WrappedUserdata(move |lua| lua.create_any_userdata(data)) } + + /// Wraps any Rust type that implements [`Serialize`], returning an opaque type that implements + /// [`IntoLua`] trait. + /// + /// This function uses [`Lua::create_ser_any_userdata()`] under the hood. + #[cfg(feature = "serialize")] + #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] + pub fn wrap_ser(data: T) -> impl IntoLua { + WrappedUserdata(move |lua| lua.create_ser_any_userdata(data)) + } } impl IntoLua for WrappedUserdata diff --git a/tests/serde.rs b/tests/serde.rs index d3ed3726..cafbe70b 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -4,8 +4,8 @@ use std::collections::HashMap; use std::error::Error as StdError; use mlua::{ - DeserializeOptions, Error, ExternalResult, Lua, LuaSerdeExt, Result as LuaResult, SerializeOptions, - UserData, Value, + AnyUserData, DeserializeOptions, Error, ExternalResult, IntoLua, Lua, LuaSerdeExt, Result as LuaResult, + SerializeOptions, UserData, Value, }; use serde::{Deserialize, Serialize}; @@ -72,18 +72,30 @@ fn test_serialize() -> Result<(), Box> { } #[test] -fn test_serialize_any_userdata() -> Result<(), Box> { +fn test_serialize_any_userdata() { let lua = Lua::new(); let json_val = serde_json::json!({ "a": 1, "b": "test", }); - let json_ud = lua.create_ser_any_userdata(json_val)?; - let json_str = serde_json::to_string_pretty(&json_ud)?; + let json_ud = lua.create_ser_any_userdata(json_val).unwrap(); + let json_str = serde_json::to_string_pretty(&json_ud).unwrap(); assert_eq!(json_str, "{\n \"a\": 1,\n \"b\": \"test\"\n}"); +} - Ok(()) +#[test] +fn test_serialize_wrapped_any_userdata() { + let lua = Lua::new(); + + let json_val = serde_json::json!({ + "a": 1, + "b": "test", + }); + let ud = AnyUserData::wrap_ser(json_val); + let json_ud = ud.into_lua(&lua).unwrap(); + let json_str = serde_json::to_string(&json_ud).unwrap(); + assert_eq!(json_str, "{\"a\":1,\"b\":\"test\"}"); } #[test] From 6d5e735bedca76d3f0327f5c3f9a54c224ff747c Mon Sep 17 00:00:00 2001 From: psentee <135014396+psentee@users.noreply.github.com> Date: Sun, 6 Oct 2024 14:43:20 +0200 Subject: [PATCH 213/635] Add IntoLua/FromLua for OsString/OsStr and PathBuf/Path (#459) --- src/conversion.rs | 62 +++++++++++++++++++++++++++++++++++++++++++-- tests/conversion.rs | 52 ++++++++++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 3 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index a06abdcd..da3a7d9a 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -1,12 +1,13 @@ use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; -use std::ffi::{CStr, CString}; +use std::ffi::{CStr, CString, OsStr, OsString}; use std::hash::{BuildHasher, Hash}; use std::os::raw::c_int; +use std::path::{Path, PathBuf}; use std::string::String as StdString; use std::{slice, str}; -use bstr::{BStr, BString}; +use bstr::{BStr, BString, ByteVec}; use num_traits::cast; use crate::error::{Error, Result}; @@ -603,6 +604,63 @@ impl IntoLua for &BStr { } } +impl IntoLua for OsString { + #[inline] + fn into_lua(self, lua: &Lua) -> Result { + BString::from( + Vec::from_os_string(self).map_err(|val| Error::ToLuaConversionError { + from: "OsString".into(), + to: "string", + message: Some(format!("Invalid encoding: {:?}", val)), + })?, + ) + .into_lua(lua) + } +} + +impl FromLua for OsString { + #[inline] + fn from_lua(value: Value, lua: &Lua) -> Result { + let ty = value.type_name(); + let bs = BString::from_lua(value, lua)?; + Vec::from(bs) + .into_os_string() + .map_err(|err| Error::FromLuaConversionError { + from: ty, + to: "OsString".into(), + message: Some(format!("{}", err)), + }) + } +} + +impl IntoLua for &OsStr { + #[inline] + fn into_lua(self, lua: &Lua) -> Result { + OsString::from(self).into_lua(lua) + } +} + +impl IntoLua for PathBuf { + #[inline] + fn into_lua(self, lua: &Lua) -> Result { + self.into_os_string().into_lua(lua) + } +} + +impl FromLua for PathBuf { + #[inline] + fn from_lua(value: Value, lua: &Lua) -> Result { + OsString::from_lua(value, lua).map(PathBuf::from) + } +} + +impl IntoLua for &Path { + #[inline] + fn into_lua(self, lua: &Lua) -> Result { + PathBuf::from(self).into_lua(lua) + } +} + #[inline] unsafe fn push_bytes_into_stack(this: T, lua: &RawLua) -> Result<()> where diff --git a/tests/conversion.rs b/tests/conversion.rs index d0e7a079..e61dd32a 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; -use std::ffi::{CStr, CString}; +use std::ffi::{CStr, CString, OsStr, OsString}; +use std::path::{Path, PathBuf}; use bstr::BString; use maplit::{btreemap, btreeset, hashmap, hashset}; @@ -397,6 +398,55 @@ fn test_bstring_from_lua_buffer() -> Result<()> { Ok(()) } +#[test] +fn test_osstring_into_from_lua() -> Result<()> { + let lua = Lua::new(); + + let s = OsString::from("hello, world"); + + let v = lua.pack(s.as_os_str())?; + assert!(v.is_string()); + assert_eq!(v.as_str().unwrap(), "hello, world"); + + let v = lua.pack(s)?; + assert!(v.is_string()); + assert_eq!(v.as_str().unwrap(), "hello, world"); + + let s = lua.create_string("hello, world")?; + let bstr = lua.unpack::(Value::String(s))?; + assert_eq!(bstr, "hello, world"); + + let bstr = lua.unpack::(Value::Integer(123))?; + assert_eq!(bstr, "123"); + + let bstr = lua.unpack::(Value::Number(-123.55))?; + assert_eq!(bstr, "-123.55"); + + Ok(()) +} + +#[test] +fn test_pathbuf_into_from_lua() -> Result<()> { + let lua = Lua::new(); + + let pb = PathBuf::from(env!("CARGO_TARGET_TMPDIR")); + let pb_str = pb.to_str().unwrap(); + + let v = lua.pack(pb.as_path())?; + assert!(v.is_string()); + assert_eq!(v.as_str().unwrap(), pb_str); + + let v = lua.pack(pb.clone())?; + assert!(v.is_string()); + assert_eq!(v.as_str().unwrap(), pb_str); + + let s = lua.create_string(pb_str)?; + let bstr = lua.unpack::(Value::String(s))?; + assert_eq!(bstr, pb); + + Ok(()) +} + #[test] fn test_option_into_from_lua() -> Result<()> { let lua = Lua::new(); From fa343c2c69d9e1df2c1a59d35c0f9516ae057d3e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 6 Oct 2024 22:45:36 +0100 Subject: [PATCH 214/635] More optimal OsStr/Path conversion to Lua --- src/conversion.rs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index da3a7d9a..20d99cee 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -7,7 +7,7 @@ use std::path::{Path, PathBuf}; use std::string::String as StdString; use std::{slice, str}; -use bstr::{BStr, BString, ByteVec}; +use bstr::{BStr, BString, ByteSlice, ByteVec}; use num_traits::cast; use crate::error::{Error, Result}; @@ -607,14 +607,7 @@ impl IntoLua for &BStr { impl IntoLua for OsString { #[inline] fn into_lua(self, lua: &Lua) -> Result { - BString::from( - Vec::from_os_string(self).map_err(|val| Error::ToLuaConversionError { - from: "OsString".into(), - to: "string", - message: Some(format!("Invalid encoding: {:?}", val)), - })?, - ) - .into_lua(lua) + self.as_os_str().into_lua(lua) } } @@ -628,7 +621,7 @@ impl FromLua for OsString { .map_err(|err| Error::FromLuaConversionError { from: ty, to: "OsString".into(), - message: Some(format!("{}", err)), + message: Some(err.to_string()), }) } } @@ -636,14 +629,19 @@ impl FromLua for OsString { impl IntoLua for &OsStr { #[inline] fn into_lua(self, lua: &Lua) -> Result { - OsString::from(self).into_lua(lua) + let s = <[u8]>::from_os_str(self).ok_or_else(|| Error::ToLuaConversionError { + from: "OsStr".into(), + to: "string", + message: Some("invalid utf-8 encoding".into()), + })?; + Ok(Value::String(lua.create_string(s)?)) } } impl IntoLua for PathBuf { #[inline] fn into_lua(self, lua: &Lua) -> Result { - self.into_os_string().into_lua(lua) + self.as_os_str().into_lua(lua) } } @@ -657,7 +655,7 @@ impl FromLua for PathBuf { impl IntoLua for &Path { #[inline] fn into_lua(self, lua: &Lua) -> Result { - PathBuf::from(self).into_lua(lua) + self.as_os_str().into_lua(lua) } } From 4891b6535cd59d18279c25dc3e71bf7f22a05d28 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 6 Oct 2024 11:10:47 +0100 Subject: [PATCH 215/635] Remove const from `Value::type_name` --- src/value.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/value.rs b/src/value.rs index a645cf91..38d0ff80 100644 --- a/src/value.rs +++ b/src/value.rs @@ -83,7 +83,7 @@ impl Value { pub const NULL: Value = Value::LightUserData(LightUserData(ptr::null_mut())); /// Returns type name of this value. - pub const fn type_name(&self) -> &'static str { + pub fn type_name(&self) -> &'static str { match *self { Value::Nil => "nil", Value::Boolean(_) => "boolean", From 8aecc83f53646da843031dd01b43e3354d5356de Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 6 Oct 2024 22:48:58 +0100 Subject: [PATCH 216/635] clippy --- src/string.rs | 2 +- tests/conversion.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/string.rs b/src/string.rs index 636c725a..264d51fa 100644 --- a/src/string.rs +++ b/src/string.rs @@ -174,7 +174,7 @@ impl Eq for String {} impl PartialOrd for String { fn partial_cmp(&self, other: &String) -> Option { - self.as_bytes().partial_cmp(&other.as_bytes()) + Some(self.cmp(other)) } } diff --git a/tests/conversion.rs b/tests/conversion.rs index e61dd32a..0bad4958 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; -use std::ffi::{CStr, CString, OsStr, OsString}; -use std::path::{Path, PathBuf}; +use std::ffi::{CStr, CString, OsString}; +use std::path::PathBuf; use bstr::BString; use maplit::{btreemap, btreeset, hashmap, hashset}; From 9f6c78532f1be12e6024a6d8c0c1b38910557950 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 7 Oct 2024 10:32:50 +0100 Subject: [PATCH 217/635] Move IntoLua/FromLua and IntoLuaMulti/FromLuaMulti to traits module --- src/chunk.rs | 2 +- src/conversion.rs | 4 +- src/function.rs | 4 +- src/lib.rs | 6 +- src/luau/package.rs | 3 +- src/multi.rs | 3 +- src/scope.rs | 2 +- src/serde/ser.rs | 3 +- src/state.rs | 3 +- src/state/raw.rs | 6 +- src/table.rs | 4 +- src/thread.rs | 2 +- src/traits.rs | 149 ++++++++++++++++++++++++++++++++++++--- src/types/either.rs | 4 +- src/userdata.rs | 3 +- src/userdata/cell.rs | 3 +- src/userdata/object.rs | 4 +- src/userdata/registry.rs | 3 +- src/value.rs | 136 +---------------------------------- 19 files changed, 179 insertions(+), 165 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index 8722a063..b596b919 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -9,7 +9,7 @@ use crate::error::{Error, Result}; use crate::function::Function; use crate::state::{Lua, WeakLua}; use crate::table::Table; -use crate::value::{FromLuaMulti, IntoLuaMulti}; +use crate::traits::{FromLuaMulti, IntoLuaMulti}; /// Trait for types [loadable by Lua] and convertible to a [`Chunk`] /// diff --git a/src/conversion.rs b/src/conversion.rs index 20d99cee..f9ba62d2 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -16,10 +16,10 @@ use crate::state::{Lua, RawLua}; use crate::string::String; use crate::table::Table; use crate::thread::Thread; -use crate::traits::ShortTypeName as _; +use crate::traits::{FromLua, IntoLua, ShortTypeName as _}; use crate::types::{LightUserData, MaybeSend, RegistryKey}; use crate::userdata::{AnyUserData, UserData}; -use crate::value::{FromLua, IntoLua, Nil, Value}; +use crate::value::{Nil, Value}; impl IntoLua for Value { #[inline] diff --git a/src/function.rs b/src/function.rs index 7379e11a..19e52c36 100644 --- a/src/function.rs +++ b/src/function.rs @@ -5,12 +5,12 @@ use std::{mem, ptr, slice}; use crate::error::{Error, Result}; use crate::state::Lua; use crate::table::Table; -use crate::traits::{LuaNativeFn, LuaNativeFnMut}; +use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut}; use crate::types::{Callback, LuaType, MaybeSend, ValueRef}; use crate::util::{ assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str, StackGuard, }; -use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti, Value}; +use crate::value::Value; #[cfg(feature = "async")] use { diff --git a/src/lib.rs b/src/lib.rs index 049f334b..e4c33958 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,7 +116,9 @@ pub use crate::stdlib::StdLib; pub use crate::string::{BorrowedBytes, BorrowedStr, String}; pub use crate::table::{Table, TablePairs, TableSequence}; pub use crate::thread::{Thread, ThreadStatus}; -pub use crate::traits::{LuaNativeFn, LuaNativeFnMut, ObjectLike}; +pub use crate::traits::{ + FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut, ObjectLike, +}; pub use crate::types::{ AppDataRef, AppDataRefMut, Either, Integer, LightUserData, MaybeSend, Number, RegistryKey, VmState, }; @@ -124,7 +126,7 @@ pub use crate::userdata::{ AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef, UserDataRefMut, UserDataRegistry, }; -pub use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value}; +pub use crate::value::{MultiValue, Nil, Value}; #[cfg(not(feature = "luau"))] pub use crate::hook::HookTriggers; diff --git a/src/luau/package.rs b/src/luau/package.rs index e0401881..e3e6e780 100644 --- a/src/luau/package.rs +++ b/src/luau/package.rs @@ -9,7 +9,8 @@ use crate::chunk::ChunkMode; use crate::error::Result; use crate::state::Lua; use crate::table::Table; -use crate::value::{IntoLua, Value}; +use crate::traits::IntoLua; +use crate::value::Value; #[cfg(unix)] use {libloading::Library, rustc_hash::FxHashMap}; diff --git a/src/multi.rs b/src/multi.rs index 29d2f383..3aabe567 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -5,8 +5,9 @@ use std::result::Result as StdResult; use crate::error::Result; use crate::state::{Lua, RawLua}; +use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::util::check_stack; -use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil}; +use crate::value::{MultiValue, Nil}; /// Result is convertible to `MultiValue` following the common Lua idiom of returning the result /// on success, or in the case of an error, returning `nil` and an error message. diff --git a/src/scope.rs b/src/scope.rs index 725cd62b..4a8ec76d 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -6,10 +6,10 @@ use std::os::raw::c_void; use crate::error::{Error, Result}; use crate::function::Function; use crate::state::{Lua, LuaGuard, RawLua}; +use crate::traits::{FromLuaMulti, IntoLuaMulti}; use crate::types::{Callback, CallbackUpvalue, ScopedCallback, ValueRef}; use crate::userdata::{AnyUserData, UserData, UserDataRegistry, UserDataStorage}; use crate::util::{self, assert_stack, check_stack, get_userdata, take_userdata, StackGuard}; -use crate::value::{FromLuaMulti, IntoLuaMulti}; /// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and /// callbacks that are not required to be `Send` or `'static`. diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 3f207431..1292e4d6 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -4,7 +4,8 @@ use super::LuaSerdeExt; use crate::error::{Error, Result}; use crate::state::Lua; use crate::table::Table; -use crate::value::{IntoLua, Value}; +use crate::traits::IntoLua; +use crate::value::Value; /// A struct for serializing Rust values into Lua values. #[derive(Debug)] diff --git a/src/state.rs b/src/state.rs index 470c299f..c42523ea 100644 --- a/src/state.rs +++ b/src/state.rs @@ -17,6 +17,7 @@ use crate::stdlib::StdLib; use crate::string::String; use crate::table::Table; use crate::thread::Thread; +use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::types::{ AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LuaType, MaybeSend, Number, ReentrantMutex, ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak, @@ -25,7 +26,7 @@ use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, Us use crate::util::{ assert_stack, check_stack, protect_lua_closure, push_string, push_table, rawset_field, StackGuard, }; -use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value}; +use crate::value::{MultiValue, Nil, Value}; #[cfg(not(feature = "luau"))] use crate::hook::HookTriggers; diff --git a/src/state/raw.rs b/src/state/raw.rs index 36a54f94..9c0d7f87 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -16,6 +16,7 @@ use crate::stdlib::StdLib; use crate::string::String; use crate::table::Table; use crate::thread::Thread; +use crate::traits::IntoLua; use crate::types::{ AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData, MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc, @@ -27,7 +28,7 @@ use crate::util::{ push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name, StackGuard, WrappedFailure, }; -use crate::value::{IntoLua, Nil, Value}; +use crate::value::{Nil, Value}; use super::extra::ExtraData; use super::{Lua, LuaOptions, WeakLua}; @@ -37,8 +38,9 @@ use crate::hook::{Debug, HookTriggers}; #[cfg(feature = "async")] use { + crate::traits::FromLuaMulti, crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue}, - crate::value::{FromLuaMulti, MultiValue}, + crate::value::MultiValue, std::ptr::NonNull, std::task::{Context, Poll, Waker}, }; diff --git a/src/table.rs b/src/table.rs index 5fc3ae6e..c8fdcf2b 100644 --- a/src/table.rs +++ b/src/table.rs @@ -14,10 +14,10 @@ use { use crate::error::{Error, Result}; use crate::function::Function; use crate::state::{LuaGuard, RawLua}; -use crate::traits::ObjectLike; +use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike}; use crate::types::{Integer, LuaType, ValueRef}; use crate::util::{assert_stack, check_stack, StackGuard}; -use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Nil, Value}; +use crate::value::{Nil, Value}; #[cfg(feature = "async")] use futures_util::future::{self, Either, Future}; diff --git a/src/thread.rs b/src/thread.rs index 8a0f1c3e..d73a87e2 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -5,9 +5,9 @@ use crate::error::{Error, Result}; #[allow(unused)] use crate::state::Lua; use crate::state::RawLua; +use crate::traits::{FromLuaMulti, IntoLuaMulti}; use crate::types::{LuaType, ValueRef}; use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard}; -use crate::value::{FromLuaMulti, IntoLuaMulti}; #[cfg(not(feature = "luau"))] use crate::{ diff --git a/src/traits.rs b/src/traits.rs index a8afc1cd..162eb38e 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,22 +1,146 @@ +use std::os::raw::c_int; use std::string::String as StdString; +use std::sync::Arc; -use crate::error::Result; +use crate::error::{Error, Result}; use crate::private::Sealed; +use crate::state::{Lua, RawLua}; use crate::types::MaybeSend; -use crate::util::short_type_name; -use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; +use crate::util::{check_stack, short_type_name}; +use crate::value::{MultiValue, Value}; #[cfg(feature = "async")] use std::future::Future; -pub(crate) trait ShortTypeName { - #[inline(always)] - fn type_name() -> StdString { - short_type_name::() +/// Trait for types convertible to `Value`. +pub trait IntoLua: Sized { + /// Performs the conversion. + fn into_lua(self, lua: &Lua) -> Result; + + /// Pushes the value into the Lua stack. + /// + /// # Safety + /// This method does not check Lua stack space. + #[doc(hidden)] + #[inline] + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + lua.push_value(&self.into_lua(lua.lua())?) } } -impl ShortTypeName for T {} +/// Trait for types convertible from `Value`. +pub trait FromLua: Sized { + /// Performs the conversion. + fn from_lua(value: Value, lua: &Lua) -> Result; + + /// Performs the conversion for an argument (eg. function argument). + /// + /// `i` is the argument index (position), + /// `to` is a function name that received the argument. + #[doc(hidden)] + #[inline] + fn from_lua_arg(arg: Value, i: usize, to: Option<&str>, lua: &Lua) -> Result { + Self::from_lua(arg, lua).map_err(|err| Error::BadArgument { + to: to.map(|s| s.to_string()), + pos: i, + name: None, + cause: Arc::new(err), + }) + } + + /// Performs the conversion for a value in the Lua stack at index `idx`. + #[doc(hidden)] + #[inline] + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { + Self::from_lua(lua.stack_value(idx, None), lua.lua()) + } + + /// Same as `from_lua_arg` but for a value in the Lua stack at index `idx`. + #[doc(hidden)] + #[inline] + unsafe fn from_stack_arg(idx: c_int, i: usize, to: Option<&str>, lua: &RawLua) -> Result { + Self::from_stack(idx, lua).map_err(|err| Error::BadArgument { + to: to.map(|s| s.to_string()), + pos: i, + name: None, + cause: Arc::new(err), + }) + } +} + +/// Trait for types convertible to any number of Lua values. +/// +/// This is a generalization of `IntoLua`, allowing any number of resulting Lua values instead of +/// just one. Any type that implements `IntoLua` will automatically implement this trait. +pub trait IntoLuaMulti: Sized { + /// Performs the conversion. + fn into_lua_multi(self, lua: &Lua) -> Result; + + /// Pushes the values into the Lua stack. + /// + /// Returns number of pushed values. + #[doc(hidden)] + #[inline] + unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { + let values = self.into_lua_multi(lua.lua())?; + let len: c_int = values.len().try_into().unwrap(); + unsafe { + check_stack(lua.state(), len + 1)?; + for val in &values { + lua.push_value(val)?; + } + } + Ok(len) + } +} + +/// Trait for types that can be created from an arbitrary number of Lua values. +/// +/// This is a generalization of `FromLua`, allowing an arbitrary number of Lua values to participate +/// in the conversion. Any type that implements `FromLua` will automatically implement this trait. +pub trait FromLuaMulti: Sized { + /// Performs the conversion. + /// + /// In case `values` contains more values than needed to perform the conversion, the excess + /// values should be ignored. This reflects the semantics of Lua when calling a function or + /// assigning values. Similarly, if not enough values are given, conversions should assume that + /// any missing values are nil. + fn from_lua_multi(values: MultiValue, lua: &Lua) -> Result; + + /// Performs the conversion for a list of arguments. + /// + /// `i` is an index (position) of the first argument, + /// `to` is a function name that received the arguments. + #[doc(hidden)] + #[inline] + fn from_lua_args(args: MultiValue, i: usize, to: Option<&str>, lua: &Lua) -> Result { + let _ = (i, to); + Self::from_lua_multi(args, lua) + } + + /// Performs the conversion for a number of values in the Lua stack. + #[doc(hidden)] + #[inline] + unsafe fn from_stack_multi(nvals: c_int, lua: &RawLua) -> Result { + let mut values = MultiValue::with_capacity(nvals as usize); + for idx in 0..nvals { + values.push_back(lua.stack_value(-nvals + idx, None)); + } + if nvals > 0 { + // It's safe to clear the stack as all references moved to ref thread + ffi::lua_pop(lua.state(), nvals); + } + Self::from_lua_multi(values, lua.lua()) + } + + /// Same as `from_lua_args` but for a number of values in the Lua stack. + #[doc(hidden)] + #[inline] + unsafe fn from_stack_args(nargs: c_int, i: usize, to: Option<&str>, lua: &RawLua) -> Result { + let _ = (i, to); + Self::from_stack_multi(nargs, lua) + } +} /// A trait for types that can be used as Lua objects (usually table and userdata). pub trait ObjectLike: Sealed { @@ -178,3 +302,12 @@ impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M); impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N); impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O); impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); + +pub(crate) trait ShortTypeName { + #[inline(always)] + fn type_name() -> StdString { + short_type_name::() + } +} + +impl ShortTypeName for T {} diff --git a/src/types/either.rs b/src/types/either.rs index ee818fe6..fdec083d 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -5,8 +5,8 @@ use std::os::raw::c_int; use crate::error::{Error, Result}; use crate::state::{Lua, RawLua}; -use crate::traits::ShortTypeName as _; -use crate::value::{FromLua, IntoLua, Value}; +use crate::traits::{FromLua, IntoLua, ShortTypeName as _}; +use crate::value::Value; /// Combination of two types into a single one. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] diff --git a/src/userdata.rs b/src/userdata.rs index 5fa97662..1b28dc68 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -19,9 +19,10 @@ use crate::function::Function; use crate::state::Lua; use crate::string::String; use crate::table::{Table, TablePairs}; +use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::types::{MaybeSend, ValueRef}; use crate::util::{check_stack, get_userdata, take_userdata, StackGuard}; -use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; +use crate::value::Value; // Re-export for convenience pub(crate) use cell::UserDataStorage; diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index 9cdcd5b4..a9f25b67 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -9,10 +9,11 @@ use serde::ser::{Serialize, Serializer}; use crate::error::{Error, Result}; use crate::state::{Lua, RawLua}; +use crate::traits::FromLua; use crate::types::XRc; use crate::userdata::AnyUserData; use crate::util::get_userdata; -use crate::value::{FromLua, Value}; +use crate::value::Value; use super::lock::{RawLock, UserDataLock}; diff --git a/src/userdata/object.rs b/src/userdata/object.rs index 2b9597f4..6418f1e9 100644 --- a/src/userdata/object.rs +++ b/src/userdata/object.rs @@ -2,9 +2,9 @@ use std::string::String as StdString; use crate::error::{Error, Result}; use crate::table::Table; -use crate::traits::ObjectLike; +use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike}; use crate::userdata::AnyUserData; -use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; +use crate::value::Value; use crate::Function; #[cfg(feature = "async")] diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index 072562dd..4c7eee82 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -8,10 +8,11 @@ use std::string::String as StdString; use crate::error::{Error, Result}; use crate::state::{Lua, RawLua}; +use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::types::{Callback, MaybeSend}; use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataStorage}; use crate::util::{get_userdata, short_type_name}; -use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; +use crate::value::Value; #[cfg(feature = "async")] use { diff --git a/src/value.rs b/src/value.rs index 38d0ff80..a2a4a4de 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,19 +1,19 @@ use std::cmp::Ordering; use std::collections::{vec_deque, HashSet, VecDeque}; use std::ops::{Deref, DerefMut}; -use std::os::raw::{c_int, c_void}; +use std::os::raw::c_void; use std::string::String as StdString; -use std::sync::Arc; use std::{fmt, mem, ptr, str}; use num_traits::FromPrimitive; use crate::error::{Error, Result}; use crate::function::Function; -use crate::state::{Lua, RawLua}; +use crate::state::Lua; use crate::string::{BorrowedStr, String}; use crate::table::Table; use crate::thread::Thread; +use crate::traits::IntoLua; use crate::types::{Integer, LightUserData, Number, ValueRef}; use crate::userdata::AnyUserData; use crate::util::{check_stack, StackGuard}; @@ -735,62 +735,6 @@ impl<'a> Serialize for SerializableValue<'a> { } } -/// Trait for types convertible to `Value`. -pub trait IntoLua: Sized { - /// Performs the conversion. - fn into_lua(self, lua: &Lua) -> Result; - - /// Pushes the value into the Lua stack. - /// - /// # Safety - /// This method does not check Lua stack space. - #[doc(hidden)] - #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - lua.push_value(&self.into_lua(lua.lua())?) - } -} - -/// Trait for types convertible from `Value`. -pub trait FromLua: Sized { - /// Performs the conversion. - fn from_lua(value: Value, lua: &Lua) -> Result; - - /// Performs the conversion for an argument (eg. function argument). - /// - /// `i` is the argument index (position), - /// `to` is a function name that received the argument. - #[doc(hidden)] - #[inline] - fn from_lua_arg(arg: Value, i: usize, to: Option<&str>, lua: &Lua) -> Result { - Self::from_lua(arg, lua).map_err(|err| Error::BadArgument { - to: to.map(|s| s.to_string()), - pos: i, - name: None, - cause: Arc::new(err), - }) - } - - /// Performs the conversion for a value in the Lua stack at index `idx`. - #[doc(hidden)] - #[inline] - unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - Self::from_lua(lua.stack_value(idx, None), lua.lua()) - } - - /// Same as `from_lua_arg` but for a value in the Lua stack at index `idx`. - #[doc(hidden)] - #[inline] - unsafe fn from_stack_arg(idx: c_int, i: usize, to: Option<&str>, lua: &RawLua) -> Result { - Self::from_stack(idx, lua).map_err(|err| Error::BadArgument { - to: to.map(|s| s.to_string()), - pos: i, - name: None, - cause: Arc::new(err), - }) - } -} - /// Multiple Lua values used for both argument passing and also for multiple return values. #[derive(Default, Debug, Clone)] pub struct MultiValue(VecDeque); @@ -865,80 +809,6 @@ impl<'a> IntoIterator for &'a MultiValue { } } -/// Trait for types convertible to any number of Lua values. -/// -/// This is a generalization of `IntoLua`, allowing any number of resulting Lua values instead of -/// just one. Any type that implements `IntoLua` will automatically implement this trait. -pub trait IntoLuaMulti: Sized { - /// Performs the conversion. - fn into_lua_multi(self, lua: &Lua) -> Result; - - /// Pushes the values into the Lua stack. - /// - /// Returns number of pushed values. - #[doc(hidden)] - #[inline] - unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { - let values = self.into_lua_multi(lua.lua())?; - let len: c_int = values.len().try_into().unwrap(); - unsafe { - check_stack(lua.state(), len + 1)?; - for val in &values { - lua.push_value(val)?; - } - } - Ok(len) - } -} - -/// Trait for types that can be created from an arbitrary number of Lua values. -/// -/// This is a generalization of `FromLua`, allowing an arbitrary number of Lua values to participate -/// in the conversion. Any type that implements `FromLua` will automatically implement this trait. -pub trait FromLuaMulti: Sized { - /// Performs the conversion. - /// - /// In case `values` contains more values than needed to perform the conversion, the excess - /// values should be ignored. This reflects the semantics of Lua when calling a function or - /// assigning values. Similarly, if not enough values are given, conversions should assume that - /// any missing values are nil. - fn from_lua_multi(values: MultiValue, lua: &Lua) -> Result; - - /// Performs the conversion for a list of arguments. - /// - /// `i` is an index (position) of the first argument, - /// `to` is a function name that received the arguments. - #[doc(hidden)] - #[inline] - fn from_lua_args(args: MultiValue, i: usize, to: Option<&str>, lua: &Lua) -> Result { - let _ = (i, to); - Self::from_lua_multi(args, lua) - } - - /// Performs the conversion for a number of values in the Lua stack. - #[doc(hidden)] - #[inline] - unsafe fn from_stack_multi(nvals: c_int, lua: &RawLua) -> Result { - let mut values = MultiValue::with_capacity(nvals as usize); - for idx in 0..nvals { - values.push_back(lua.stack_value(-nvals + idx, None)); - } - if nvals > 0 { - // It's safe to clear the stack as all references moved to ref thread - ffi::lua_pop(lua.state(), nvals); - } - Self::from_lua_multi(values, lua.lua()) - } - - /// Same as `from_lua_args` but for a number of values in the Lua stack. - #[doc(hidden)] - #[inline] - unsafe fn from_stack_args(nargs: c_int, i: usize, to: Option<&str>, lua: &RawLua) -> Result { - let _ = (i, to); - Self::from_stack_multi(nargs, lua) - } -} - #[cfg(test)] mod assertions { use super::*; From 03a4068d55329241e64fe50b9da5a24d5dd53247 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 7 Oct 2024 12:06:13 +0100 Subject: [PATCH 218/635] Move `MultiValue` from `value` to `multi` module --- src/lib.rs | 4 +-- src/multi.rs | 88 +++++++++++++++++++++++++++++++++++++++++++++++- src/state.rs | 3 +- src/state/raw.rs | 2 +- src/traits.rs | 3 +- src/value.rs | 86 ++-------------------------------------------- 6 files changed, 96 insertions(+), 90 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e4c33958..3769d181 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,7 +109,7 @@ pub use crate::chunk::{AsChunk, Chunk, ChunkMode}; pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result}; pub use crate::function::{Function, FunctionInfo}; pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack}; -pub use crate::multi::Variadic; +pub use crate::multi::{MultiValue, Variadic}; pub use crate::scope::Scope; pub use crate::state::{GCMode, Lua, LuaOptions}; pub use crate::stdlib::StdLib; @@ -126,7 +126,7 @@ pub use crate::userdata::{ AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef, UserDataRefMut, UserDataRegistry, }; -pub use crate::value::{MultiValue, Nil, Value}; +pub use crate::value::{Nil, Value}; #[cfg(not(feature = "luau"))] pub use crate::hook::HookTriggers; diff --git a/src/multi.rs b/src/multi.rs index 3aabe567..3619588f 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -1,4 +1,6 @@ +use std::collections::{vec_deque, VecDeque}; use std::iter::FromIterator; +use std::mem; use std::ops::{Deref, DerefMut}; use std::os::raw::c_int; use std::result::Result as StdResult; @@ -7,7 +9,7 @@ use crate::error::Result; use crate::state::{Lua, RawLua}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::util::check_stack; -use crate::value::{MultiValue, Nil}; +use crate::value::{Nil, Value}; /// Result is convertible to `MultiValue` following the common Lua idiom of returning the result /// on success, or in the case of an error, returning `nil` and an error message. @@ -90,6 +92,80 @@ impl FromLuaMulti for T { } } +/// Multiple Lua values used for both argument passing and also for multiple return values. +#[derive(Default, Debug, Clone)] +pub struct MultiValue(VecDeque); + +impl Deref for MultiValue { + type Target = VecDeque; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for MultiValue { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl MultiValue { + /// Creates an empty `MultiValue` containing no values. + #[inline] + pub const fn new() -> MultiValue { + MultiValue(VecDeque::new()) + } + + /// Creates an empty `MultiValue` container with space for at least `capacity` elements. + pub fn with_capacity(capacity: usize) -> MultiValue { + MultiValue(VecDeque::with_capacity(capacity)) + } + + #[inline] + pub(crate) fn from_lua_iter(lua: &Lua, iter: impl IntoIterator) -> Result { + let iter = iter.into_iter(); + let mut multi_value = MultiValue::with_capacity(iter.size_hint().0); + for value in iter { + multi_value.push_back(value.into_lua(lua)?); + } + Ok(multi_value) + } +} + +impl FromIterator for MultiValue { + #[inline] + fn from_iter>(iter: I) -> Self { + let mut multi_value = MultiValue::new(); + multi_value.extend(iter); + multi_value + } +} + +impl IntoIterator for MultiValue { + type Item = Value; + type IntoIter = vec_deque::IntoIter; + + #[inline] + fn into_iter(mut self) -> Self::IntoIter { + let deque = mem::take(&mut self.0); + mem::forget(self); + deque.into_iter() + } +} + +impl<'a> IntoIterator for &'a MultiValue { + type Item = &'a Value; + type IntoIter = vec_deque::Iter<'a, Value>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + impl IntoLuaMulti for MultiValue { #[inline] fn into_lua_multi(self, _: &Lua) -> Result { @@ -343,3 +419,13 @@ impl_tuple!(A B C D E F G H I J K L M); impl_tuple!(A B C D E F G H I J K L M N); impl_tuple!(A B C D E F G H I J K L M N O); impl_tuple!(A B C D E F G H I J K L M N O P); + +#[cfg(test)] +mod assertions { + use super::*; + + #[cfg(not(feature = "send"))] + static_assertions::assert_not_impl_any!(MultiValue: Send); + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(MultiValue: Send, Sync); +} diff --git a/src/state.rs b/src/state.rs index c42523ea..b038e303 100644 --- a/src/state.rs +++ b/src/state.rs @@ -12,6 +12,7 @@ use crate::error::{Error, Result}; use crate::function::Function; use crate::hook::Debug; use crate::memory::MemoryState; +use crate::multi::MultiValue; use crate::scope::Scope; use crate::stdlib::StdLib; use crate::string::String; @@ -26,7 +27,7 @@ use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, Us use crate::util::{ assert_stack, check_stack, protect_lua_closure, push_string, push_table, rawset_field, StackGuard, }; -use crate::value::{MultiValue, Nil, Value}; +use crate::value::{Nil, Value}; #[cfg(not(feature = "luau"))] use crate::hook::HookTriggers; diff --git a/src/state/raw.rs b/src/state/raw.rs index 9c0d7f87..2c132c51 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -38,9 +38,9 @@ use crate::hook::{Debug, HookTriggers}; #[cfg(feature = "async")] use { + crate::multi::MultiValue, crate::traits::FromLuaMulti, crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue}, - crate::value::MultiValue, std::ptr::NonNull, std::task::{Context, Poll, Waker}, }; diff --git a/src/traits.rs b/src/traits.rs index 162eb38e..46adfbf4 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -3,11 +3,12 @@ use std::string::String as StdString; use std::sync::Arc; use crate::error::{Error, Result}; +use crate::multi::MultiValue; use crate::private::Sealed; use crate::state::{Lua, RawLua}; use crate::types::MaybeSend; use crate::util::{check_stack, short_type_name}; -use crate::value::{MultiValue, Value}; +use crate::value::Value; #[cfg(feature = "async")] use std::future::Future; diff --git a/src/value.rs b/src/value.rs index a2a4a4de..e9f0ecb6 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,19 +1,16 @@ use std::cmp::Ordering; -use std::collections::{vec_deque, HashSet, VecDeque}; -use std::ops::{Deref, DerefMut}; +use std::collections::HashSet; use std::os::raw::c_void; use std::string::String as StdString; -use std::{fmt, mem, ptr, str}; +use std::{fmt, ptr, str}; use num_traits::FromPrimitive; use crate::error::{Error, Result}; use crate::function::Function; -use crate::state::Lua; use crate::string::{BorrowedStr, String}; use crate::table::Table; use crate::thread::Thread; -use crate::traits::IntoLua; use crate::types::{Integer, LightUserData, Number, ValueRef}; use crate::userdata::AnyUserData; use crate::util::{check_stack, StackGuard}; @@ -735,91 +732,12 @@ impl<'a> Serialize for SerializableValue<'a> { } } -/// Multiple Lua values used for both argument passing and also for multiple return values. -#[derive(Default, Debug, Clone)] -pub struct MultiValue(VecDeque); - -impl Deref for MultiValue { - type Target = VecDeque; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for MultiValue { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl MultiValue { - /// Creates an empty `MultiValue` containing no values. - #[inline] - pub const fn new() -> MultiValue { - MultiValue(VecDeque::new()) - } - - /// Creates an empty `MultiValue` container with space for at least `capacity` elements. - pub fn with_capacity(capacity: usize) -> MultiValue { - MultiValue(VecDeque::with_capacity(capacity)) - } - - #[inline] - pub(crate) fn from_lua_iter(lua: &Lua, iter: impl IntoIterator) -> Result { - let iter = iter.into_iter(); - let mut multi_value = MultiValue::with_capacity(iter.size_hint().0); - for value in iter { - multi_value.push_back(value.into_lua(lua)?); - } - Ok(multi_value) - } -} - -impl FromIterator for MultiValue { - #[inline] - fn from_iter>(iter: I) -> Self { - let mut multi_value = MultiValue::new(); - multi_value.extend(iter); - multi_value - } -} - -impl IntoIterator for MultiValue { - type Item = Value; - type IntoIter = vec_deque::IntoIter; - - #[inline] - fn into_iter(mut self) -> Self::IntoIter { - let deque = mem::take(&mut self.0); - mem::forget(self); - deque.into_iter() - } -} - -impl<'a> IntoIterator for &'a MultiValue { - type Item = &'a Value; - type IntoIter = vec_deque::Iter<'a, Value>; - - #[inline] - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - #[cfg(test)] mod assertions { use super::*; #[cfg(not(feature = "send"))] static_assertions::assert_not_impl_any!(Value: Send); - #[cfg(not(feature = "send"))] - static_assertions::assert_not_impl_any!(MultiValue: Send); - #[cfg(feature = "send")] static_assertions::assert_impl_all!(Value: Send, Sync); - #[cfg(feature = "send")] - static_assertions::assert_impl_all!(MultiValue: Send, Sync); } From 640cb2c182eaee9cab9960266913531ccfc8d649 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 7 Oct 2024 13:00:57 +0100 Subject: [PATCH 219/635] Add _unguarded to `RawLua::app_data_ref` (for internal use only) --- src/chunk.rs | 4 ++-- src/state/raw.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index b596b919..37176adf 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -481,7 +481,7 @@ impl<'a> Chunk<'a> { if let Ok(ref source) = self.source { if self.detect_mode() == ChunkMode::Text { let lua = self.lua.lock(); - if let Some(cache) = lua.app_data_ref::() { + if let Some(cache) = lua.app_data_ref_unguarded::() { if let Some(data) = cache.0.get(source.as_ref()) { self.source = Ok(Cow::Owned(data.clone())); self.mode = Some(ChunkMode::Binary); @@ -498,7 +498,7 @@ impl<'a> Chunk<'a> { if let Ok(ref binary_source) = self.source { if self.detect_mode() == ChunkMode::Binary { let lua = self.lua.lock(); - if let Some(mut cache) = lua.app_data_mut::() { + if let Some(mut cache) = lua.app_data_mut_unguarded::() { cache.0.insert(text_source, binary_source.as_ref().to_vec()); } else { let mut cache = ChunksCache(HashMap::new()); diff --git a/src/state/raw.rs b/src/state/raw.rs index 2c132c51..67657721 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -285,7 +285,7 @@ impl RawLua { /// See [`Lua::app_data_ref`] #[track_caller] #[inline] - pub(crate) fn app_data_ref(&self) -> Option> { + pub(crate) fn app_data_ref_unguarded(&self) -> Option> { let extra = unsafe { &*self.extra.get() }; extra.app_data.borrow(None) } @@ -293,7 +293,7 @@ impl RawLua { /// See [`Lua::app_data_mut`] #[track_caller] #[inline] - pub(crate) fn app_data_mut(&self) -> Option> { + pub(crate) fn app_data_mut_unguarded(&self) -> Option> { let extra = unsafe { &*self.extra.get() }; extra.app_data.borrow_mut(None) } From c086c144d0b554af47bae9c7f395ff2f4e6bc3da Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 8 Oct 2024 11:53:31 +0100 Subject: [PATCH 220/635] Use `impl IntoIterator` in Lua::create_table_from/create_sequence_from --- src/state.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/state.rs b/src/state.rs index b038e303..18eb335c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1030,11 +1030,10 @@ impl Lua { } /// Creates a table and fills it with values from an iterator. - pub fn create_table_from(&self, iter: I) -> Result
+ pub fn create_table_from(&self, iter: impl IntoIterator) -> Result
where K: IntoLua, V: IntoLua, - I: IntoIterator, { let lua = self.lock(); let state = lua.state(); @@ -1061,10 +1060,9 @@ impl Lua { } /// Creates a table from an iterator of values, using `1..` as the keys. - pub fn create_sequence_from(&self, iter: I) -> Result
+ pub fn create_sequence_from(&self, iter: impl IntoIterator) -> Result
where T: IntoLua, - I: IntoIterator, { unsafe { self.lock().create_sequence_from(iter) } } From 669349d704ec5a4769256c7f8484e742fb1b0410 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 8 Oct 2024 22:40:07 +0100 Subject: [PATCH 221/635] mlua_derive: v0.10.0-rc.1 --- mlua_derive/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlua_derive/Cargo.toml b/mlua_derive/Cargo.toml index 71cdb08b..c17ae158 100644 --- a/mlua_derive/Cargo.toml +++ b/mlua_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua_derive" -version = "0.10.0-beta.1" +version = "0.10.0-rc.1" authors = ["Aleksandr Orlenko "] edition = "2021" description = "Procedural macros for the mlua crate." From c6cd1c53c326a38431edfa80d7e4a973e222716d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 8 Oct 2024 22:57:18 +0100 Subject: [PATCH 222/635] Update CHANGELOG --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 710c0efb..787825d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## v0.10.0-rc.1 + +- `Lua::scope` is back +- Support yielding from hooks for Lua 5.3+ +- Support setting metatable for Lua builtin types (number/string/function/etc) +- Added `LuaNativeFn`/`LuaNativeFnMut`/`LuaNativeAsyncFn` traits for using in `Function::wrap` +- Added `Error::chain` method to return iterator over nested errors +- Added `Lua::exec_raw` helper to execute low-level Lua C API code +- Added `Either` enum to combine two types into a single one +- Added a new `Buffer` type for Luau +- Added `Value::is_error` and `Value::as_error` helpers +- Added `Value::Other` variant to represent unknown Lua types (eg LuaJIT CDATA) +- Added (optional) `anyhow` feature to implement `IntoLua` for `anyhow::Error` +- Added `IntoLua`/`FromLua` for `OsString`/`OsStr` and `PathBuf`/`Path` + ## v0.10.0-beta.2 - Updated `ThreadStatus` enum to include `Running` and `Finished` variants. From 7b777d074e69b623a4776dbf25acafc35b54f2ab Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 8 Oct 2024 22:57:32 +0100 Subject: [PATCH 223/635] Update README --- README.md | 9 ++- docs/release_notes/v0.10.md | 123 ++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 docs/release_notes/v0.10.md diff --git a/README.md b/README.md index 09921638..3a0efe89 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,10 @@ # The main branch is the v0.10, development version of `mlua`. Please see the [v0.9](https://github.com/mlua-rs/mlua/tree/v0.9) branch for the stable versions of `mlua`. +> **Note** +> +> See (upcoming) v0.10 [release notes](https://github.com/khvzak/mlua/blob/main/docs/release_notes/v0.10.md). + `mlua` is bindings to [Lua](https://www.lua.org) programming language for Rust with a goal to provide _safe_ (as far as it's possible), high level, easy to use, practical and flexible API. @@ -53,6 +57,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl * `send`: make `mlua::Lua: Send + Sync` (adds [`Send`] requirement to `mlua::Function` and `mlua::UserData`) * `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework * `macros`: enable procedural macros (such as `chunk!`) +* `anyhow`: enable `anyhow::Error` conversion into Lua [5.4]: https://www.lua.org/manual/5.4/manual.html [5.3]: https://www.lua.org/manual/5.3/manual.html @@ -128,7 +133,7 @@ Add to `Cargo.toml` : ``` toml [dependencies] -mlua = { version = "0.9.9", features = ["lua54", "vendored"] } +mlua = { version = "0.10.0-rc.1", features = ["lua54", "vendored"] } ``` `main.rs` @@ -163,7 +168,7 @@ Add to `Cargo.toml` : crate-type = ["cdylib"] [dependencies] -mlua = { version = "0.9.9", features = ["lua54", "module"] } +mlua = { version = "0.10.0-rc.1", features = ["lua54", "module"] } ``` `lib.rs` : diff --git a/docs/release_notes/v0.10.md b/docs/release_notes/v0.10.md new file mode 100644 index 00000000..466929d7 --- /dev/null +++ b/docs/release_notes/v0.10.md @@ -0,0 +1,123 @@ +## mlua v0.10 release notes + +The v0.10 version of mlua has goal to improve the user experience while keeping the same performance and safety guarantees. +This document highlights the most notable features. For a full list of changes, see the [CHANGELOG]. + +[CHANGELOG]: https://github.com/khvzak/mlua/blob/main/CHANGELOG.md + +### New features + +#### `'static` Lua types + +In previous mlua versions, it was required to have a `'lua` lifetime attached to every Lua value. v0.9 introduced (experimental) owned types that are `'static` without a lifetime attached, but they kept strong references to the Lua instance. +In v0.10 all Lua types are `'static` and have only weak reference to the Lua instance. It means they are more flexible and can be used in more places without worrying about memory leaks. + +#### Truly `send` feature + +In this version Lua is `Send + Sync` when the `send` feature flag is enabled (previously was only `Send`). It means Lua instance and their values can be safely shared between threads and used in multi threaded async contexts. + +```rust +let lua = Lua::new(); + +lua.globals().set("i", 0)?; +let func = lua.load("i = i + ...").into_function()?; + +std::thread::scope(|s| { + s.spawn(|| { + for i in 0..5 { + func.call::<()>(i).unwrap(); + } + }); + s.spawn(|| { + for i in 0..5 { + func.call::<()>(i).unwrap(); + } + }); +}); + +assert_eq!(lua.globals().get::("i")?, 20); +``` + +Under the hood, to synchronize access to the Lua state, mlua uses [`ReentrantMutex`] which can be recursively locked by a single thread. Only one thread can execute Lua code at a time, but it's possible to share Lua values between threads. + +This has some performance penalties (about 10-20%) compared to the lock free mode. This flag is disabled by default and does not supported in module mode. + +[`ReentrantMutex`]: https://docs.rs/parking_lot/latest/parking_lot/type.ReentrantMutex.html + +#### Register Rust functions with variable number of arguments + +The new traits `LuaNativeFn`/`LuaNativeFnMut`/`LuaNativeAsyncFn` have been introduced to provide a way to register Rust functions with variable number of arguments in Lua, without needing to pass all arguments as a tuple. + +They are used by `Function::wrap`/`Function::wrap_mut`/`Function::wrap_async` methods: + +```rust +let add = Function::wrap(|a: i64, b: i64| Ok(a + b)); + +lua.globals().set("add", add).unwrap(); + +// Prints 50 +lua.load(r#"print(add(5, 45))"#).exec().unwrap(); +``` + +To wrap functions that return direct value (non-`Result`) you can use `Function::wrap_raw` method. + +#### Setting metatable for Lua builtin types + +For Lua builtin types (like `string`, `function`, `number`, etc.) that have a shared metatable for all instances, it's now possible to set a custom metatable for them. + +```rust +let mt = lua.create_table()?; +mt.set("__tostring", lua.create_function(|_, b: bool| Ok(if b { "2" } else { "0" }))?)?; +lua.set_type_metatable::(Some(mt)); +lua.load("assert(tostring(true) == '2')").exec().unwrap(); +``` + +### Improvements + +#### New `ObjectLike` trait + +The `ObjectLike` trait is a combination of the `AnyUserDataExt` and `TableExt` traits used in previous versions. It provides a unified interface for working with Lua tables and userdata. + +#### `Either` enum + +The `Either` enum is a simple enum that can hold either `L` or `R` value. It's useful when you need to return or receive one of two types in a function. +This type implements `IntoLua` and `FromLua` traits and can generate a meaningful error message when conversion fails. + +```rust +let func = Function::wrap(|x: Either| Ok(format!("received: {x}"))); + +lua.globals().set("func", func).unwrap(); + +// Prints: received: 123 +lua.load(r#"print(func(123))"#).exec().unwrap(); + +// Prints: bad argument #1: error converting Lua table to Either +lua.load(r#"print(pcall(func, {}))"#).exec().unwrap(); +``` + +#### `Lua::exec_raw` helper to execute low-level Lua C API code + +For advanced users, it's now possible to execute low-level Lua C API code using the `Lua::exec_raw` method. + +```rust +let t = lua.create_sequence_from([1, 2, 3, 4, 5])?; +let sum: i64 = unsafe { + lua.exec_raw(&t, |state| { + // top of the stack: table `t` + let mut sum = 0; + // push nil as the first key + mlua::ffi::lua_pushnil(state); + while mlua::ffi::lua_next(state, -2) != 0 { + sum += mlua::ffi::lua_tointeger(state, -1); + // Remove the value, keep the key for the next iteration + mlua::ffi::lua_pop(state, 1); + } + mlua::ffi::lua_pop(state, 1); + mlua::ffi::lua_pushinteger(state, sum); + // top of the stack: sum + }) +}?; +assert_eq!(sum, 15); +``` + +The `exec_raw` method is longjmp-safe. It's not recommended to move `Drop` types into the closure to avoid possible memory leaks. From 045302976586ebe8b840880aa427eb90646a5cd4 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 8 Oct 2024 23:00:04 +0100 Subject: [PATCH 224/635] v0.10.0-rc.1 --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8abda75c..aecf0a7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.10.0-beta.2" # remember to update mlua_derive +version = "0.10.0-rc.1" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.79.0" edition = "2021" @@ -44,7 +44,7 @@ macros = ["mlua_derive/macros"] anyhow = ["dep:anyhow"] [dependencies] -mlua_derive = { version = "=0.10.0-beta.1", optional = true, path = "mlua_derive" } +mlua_derive = { version = "=0.10.0-rc.1", optional = true, path = "mlua_derive" } bstr = { version = "1.0", features = ["std"], default-features = false } num-traits = { version = "0.2.14" } rustc-hash = "2.0" From 81d7c81532d003a72354a0966225468c55541be7 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 12 Oct 2024 21:30:56 +0100 Subject: [PATCH 225/635] Update Luau to 0.647 --- mlua-sys/Cargo.toml | 2 +- mlua-sys/src/luau/lua.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 2da25a09..66d7e2f6 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -40,7 +40,7 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 547.0.0, < 547.1.0", optional = true } luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } -luau0-src = { version = "0.10.0", optional = true } +luau0-src = { version = "0.11.0", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 540dee46..53d1c223 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -526,6 +526,9 @@ pub struct lua_Callbacks { pub debuginterrupt: Option, /// gets called when protected call results in an error pub debugprotectederror: Option, + + /// gets called when memory is allocated + pub onallocate: Option, } extern "C" { @@ -535,4 +538,5 @@ extern "C" { // Functions from customization lib extern "C" { pub fn luau_setfflag(name: *const c_char, value: c_int) -> c_int; + pub fn lua_getmetatablepointer(L: *mut lua_State, idx: c_int) -> *const c_void; } From 0a2a70c15acf5dae747b5a30d6d45737deadb40c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 12 Oct 2024 21:32:08 +0100 Subject: [PATCH 226/635] mlua-sys: v0.6.4 --- mlua-sys/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 66d7e2f6..32f7c878 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.6.3" +version = "0.6.4" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" From 3787ff9e8c907843b1dfe920e269d188b637f9f3 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 12 Oct 2024 21:37:18 +0100 Subject: [PATCH 227/635] Optimize metatable pointer lookup for userdata (Luau) --- Cargo.toml | 2 +- src/scope.rs | 8 ++++---- src/state/raw.rs | 11 +++++------ src/util/mod.rs | 17 ++++++++++++++++- src/util/userdata.rs | 25 ++++++++++--------------- 5 files changed, 36 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aecf0a7d..42365634 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ serde-value = { version = "0.7", optional = true } parking_lot = { version = "0.12", features = ["arc_lock"] } anyhow = { version = "1.0", optional = true } -ffi = { package = "mlua-sys", version = "0.6.3", path = "mlua-sys" } +ffi = { package = "mlua-sys", version = "0.6.4", path = "mlua-sys" } [target.'cfg(unix)'.dependencies] libloading = { version = "0.8", optional = true } diff --git a/src/scope.rs b/src/scope.rs index 4a8ec76d..0a0e5beb 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -9,7 +9,9 @@ use crate::state::{Lua, LuaGuard, RawLua}; use crate::traits::{FromLuaMulti, IntoLuaMulti}; use crate::types::{Callback, CallbackUpvalue, ScopedCallback, ValueRef}; use crate::userdata::{AnyUserData, UserData, UserDataRegistry, UserDataStorage}; -use crate::util::{self, assert_stack, check_stack, get_userdata, take_userdata, StackGuard}; +use crate::util::{ + self, assert_stack, check_stack, get_metatable_ptr, get_userdata, take_userdata, StackGuard, +}; /// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and /// callbacks that are not required to be `Send` or `'static`. @@ -199,9 +201,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { } // Deregister metatable - ffi::lua_getmetatable(state, -1); - let mt_ptr = ffi::lua_topointer(state, -1); - ffi::lua_pop(state, 1); + let mt_ptr = get_metatable_ptr(state, -1); rawlua.deregister_userdata_metatable(mt_ptr); let ud = take_userdata::>(state); diff --git a/src/state/raw.rs b/src/state/raw.rs index 67657721..c756356e 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -24,9 +24,9 @@ use crate::types::{ use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataRegistry, UserDataStorage}; use crate::util::{ assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state, - get_userdata, init_error_registry, init_internal_metatable, init_userdata_metatable, pop_error, - push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name, - StackGuard, WrappedFailure, + get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, init_userdata_metatable, + pop_error, push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, + short_type_name, StackGuard, WrappedFailure, }; use crate::value::{Nil, Value}; @@ -1026,11 +1026,10 @@ impl RawLua { state: *mut ffi::lua_State, idx: c_int, ) -> Result> { - if ffi::lua_getmetatable(state, idx) == 0 { + let mt_ptr = get_metatable_ptr(state, idx); + if mt_ptr.is_null() { return Err(Error::UserDataTypeMismatch); } - let mt_ptr = ffi::lua_topointer(state, -1); - ffi::lua_pop(state, 1); // Fast path to skip looking up the metatable in the map let (last_mt, last_type_id) = (*self.extra.get()).last_checked_userdata_mt; diff --git a/src/util/mod.rs b/src/util/mod.rs index 148e1c8c..f81e9e68 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; use std::ffi::CStr; -use std::os::raw::{c_char, c_int}; +use std::os::raw::{c_char, c_int, c_void}; use std::{ptr, slice, str}; use crate::error::{Error, Result}; @@ -282,6 +282,21 @@ pub(crate) unsafe fn to_string(state: *mut ffi::lua_State, index: c_int) -> Stri } } +#[inline(always)] +pub(crate) unsafe fn get_metatable_ptr(state: *mut ffi::lua_State, index: c_int) -> *const c_void { + #[cfg(feature = "luau")] + return ffi::lua_getmetatablepointer(state, index); + + #[cfg(not(feature = "luau"))] + if ffi::lua_getmetatable(state, index) == 0 { + ptr::null() + } else { + let p = ffi::lua_topointer(state, -1); + ffi::lua_pop(state, 1); + p + } +} + pub(crate) unsafe fn ptr_to_str<'a>(input: *const c_char) -> Option<&'a str> { if input.is_null() { return None; diff --git a/src/util/userdata.rs b/src/util/userdata.rs index 321bb903..24c4a601 100644 --- a/src/util/userdata.rs +++ b/src/util/userdata.rs @@ -3,7 +3,7 @@ use std::os::raw::{c_int, c_void}; use std::{ptr, str}; use crate::error::Result; -use crate::util::{check_stack, push_string, push_table, rawset_field, TypeKey}; +use crate::util::{check_stack, get_metatable_ptr, push_string, push_table, rawset_field, TypeKey}; // Pushes the userdata and attaches a metatable with __gc method. // Internally uses 3 stack spaces, does not call checkstack. @@ -58,25 +58,20 @@ pub(crate) unsafe fn init_internal_metatable( pub(crate) unsafe fn get_internal_userdata( state: *mut ffi::lua_State, index: c_int, - type_mt_ptr: *const c_void, + mut type_mt_ptr: *const c_void, ) -> *mut T { let ud = ffi::lua_touserdata(state, index) as *mut T; - if ud.is_null() || ffi::lua_getmetatable(state, index) == 0 { + if ud.is_null() { return ptr::null_mut(); } - if !type_mt_ptr.is_null() { - let ud_mt_ptr = ffi::lua_topointer(state, -1); - ffi::lua_pop(state, 1); - if ud_mt_ptr != type_mt_ptr { - return ptr::null_mut(); - } - } else { + let mt_ptr = get_metatable_ptr(state, index); + if type_mt_ptr.is_null() { get_internal_metatable::(state); - let res = ffi::lua_rawequal(state, -1, -2); - ffi::lua_pop(state, 2); - if res == 0 { - return ptr::null_mut(); - } + type_mt_ptr = ffi::lua_topointer(state, -1); + ffi::lua_pop(state, 1); + } + if mt_ptr != type_mt_ptr { + return ptr::null_mut(); } ud } From 7535a23fa2eadfcd02102be937f1b0ee8555aa23 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 16 Oct 2024 15:46:37 +0100 Subject: [PATCH 228/635] Update function tests --- tests/function.rs | 191 +++++++++++++++++++++++++++++----------------- tests/luau.rs | 78 +------------------ tests/tests.rs | 20 ----- 3 files changed, 122 insertions(+), 167 deletions(-) diff --git a/tests/function.rs b/tests/function.rs index cec11f38..b8c10703 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -1,27 +1,34 @@ -use mlua::{Error, Function, Lua, Result, String, Table}; +use mlua::{Error, Function, Lua, Result, String, Table, Variadic}; #[test] -fn test_function() -> Result<()> { +fn test_function_call() -> Result<()> { let lua = Lua::new(); - let globals = lua.globals(); - lua.load( - r#" - function concat(arg1, arg2) - return arg1 .. arg2 - end - "#, - ) - .exec()?; - - let concat = globals.get::("concat")?; + let concat = lua + .load(r#"function(arg1, arg2) return arg1 .. arg2 end"#) + .eval::()?; assert_eq!(concat.call::(("foo", "bar"))?, "foobar"); Ok(()) } #[test] -fn test_bind() -> Result<()> { +fn test_function_call_error() -> Result<()> { + let lua = Lua::new(); + + let concat_err = lua + .load(r#"function(arg1, arg2) error("concat error") end"#) + .eval::()?; + match concat_err.call::(("foo", "bar")) { + Err(Error::RuntimeError(msg)) if msg.contains("concat error") => {} + other => panic!("unexpected result: {other:?}"), + } + + Ok(()) +} + +#[test] +fn test_function_bind() -> Result<()> { let lua = Lua::new(); let globals = lua.globals(); @@ -54,59 +61,13 @@ fn test_bind() -> Result<()> { } #[test] -fn test_rust_function() -> Result<()> { +#[cfg(not(target_arch = "wasm32"))] +fn test_function_bind_error() -> Result<()> { let lua = Lua::new(); - let globals = lua.globals(); - lua.load( - r#" - function lua_function() - return rust_function() - end - - -- Test to make sure chunk return is ignored - return 1 - "#, - ) - .exec()?; - - let lua_function = globals.get::("lua_function")?; - let rust_function = lua.create_function(|_, ()| Ok("hello"))?; - - globals.set("rust_function", rust_function)?; - assert_eq!(lua_function.call::(())?, "hello"); - - Ok(()) -} - -#[test] -fn test_c_function() -> Result<()> { - let lua = Lua::new(); - - unsafe extern "C-unwind" fn c_function(state: *mut mlua::lua_State) -> std::os::raw::c_int { - ffi::lua_pushboolean(state, 1); - ffi::lua_setglobal(state, b"c_function\0" as *const _ as *const _); - 0 - } - - let func = unsafe { lua.create_c_function(c_function)? }; - func.call::<()>(())?; - assert_eq!(lua.globals().get::("c_function")?, true); - - Ok(()) -} - -#[cfg(not(feature = "luau"))] -#[test] -fn test_dump() -> Result<()> { - let lua = unsafe { Lua::unsafe_new() }; - - let concat_lua = lua - .load(r#"function(arg1, arg2) return arg1 .. arg2 end"#) - .eval::()?; - let concat = lua.load(&concat_lua.dump(false)).into_function()?; - - assert_eq!(concat.call::(("foo", "bar"))?, "foobar"); + let func = lua.load(r#"function(...) end"#).eval::()?; + assert!(func.bind(Variadic::from_iter(1..1000000)).is_err()); + assert!(func.call::<()>(Variadic::from_iter(1..1000000)).is_err()); Ok(()) } @@ -114,14 +75,15 @@ fn test_dump() -> Result<()> { #[test] fn test_function_environment() -> Result<()> { let lua = Lua::new(); + let globals = lua.globals(); // We must not get or set environment for C functions let rust_func = lua.create_function(|_, ()| Ok("hello"))?; assert_eq!(rust_func.environment(), None); - assert_eq!(rust_func.set_environment(lua.globals()).ok(), Some(false)); + assert_eq!(rust_func.set_environment(globals.clone()).ok(), Some(false)); // Test getting Lua function environment - lua.globals().set("hello", "global")?; + globals.set("hello", "global")?; let lua_func = lua .load( r#" @@ -135,7 +97,7 @@ fn test_function_environment() -> Result<()> { .eval::()?; let lua_func2 = lua.load("return hello").into_function()?; assert_eq!(lua_func.call::(())?, "global"); - assert_eq!(lua_func.environment(), Some(lua.globals())); + assert_eq!(lua_func.environment().as_ref(), Some(&globals)); // Test changing the environment let env = lua.create_table_from([("hello", "local")])?; @@ -154,9 +116,9 @@ fn test_function_environment() -> Result<()> { "#, ) .exec()?; - let lucky = lua.globals().get::("lucky")?; + let lucky = globals.get::("lucky")?; assert_eq!(lucky.call::(())?, "number is 15"); - let new_env = lua.globals().get::
("new_env")?; + let new_env = globals.get::
("new_env")?; lucky.set_environment(new_env)?; assert_eq!(lucky.call::(())?, "15"); @@ -235,6 +197,95 @@ fn test_function_info() -> Result<()> { Ok(()) } +#[cfg(not(feature = "luau"))] +#[test] +fn test_function_dump() -> Result<()> { + let lua = unsafe { Lua::unsafe_new() }; + + let concat_lua = lua + .load(r#"function(arg1, arg2) return arg1 .. arg2 end"#) + .eval::()?; + let concat = lua.load(&concat_lua.dump(false)).into_function()?; + + assert_eq!(concat.call::(("foo", "bar"))?, "foobar"); + + Ok(()) +} + +#[cfg(feature = "luau")] +#[test] +fn test_finction_coverage() -> Result<()> { + let lua = Lua::new(); + + lua.set_compiler(mlua::Compiler::default().set_coverage_level(1)); + + let f = lua + .load( + r#"local s = "abc" + assert(#s == 3) + + function abc(i) + if i < 5 then + return 0 + else + return 1 + end + end + + (function() + (function() abc(10) end)() + end)() + "#, + ) + .into_function()?; + + f.call::<()>(())?; + + let mut report = Vec::new(); + f.coverage(|cov| { + report.push(cov); + }); + + assert_eq!( + report[0], + mlua::CoverageInfo { + function: None, + line_defined: 1, + depth: 0, + hits: vec![-1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1], + } + ); + assert_eq!( + report[1], + mlua::CoverageInfo { + function: Some("abc".into()), + line_defined: 4, + depth: 1, + hits: vec![-1, -1, -1, -1, -1, 1, 0, -1, 1, -1, -1, -1, -1, -1, -1, -1], + } + ); + assert_eq!( + report[2], + mlua::CoverageInfo { + function: None, + line_defined: 12, + depth: 1, + hits: vec![-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1], + } + ); + assert_eq!( + report[3], + mlua::CoverageInfo { + function: None, + line_defined: 13, + depth: 2, + hits: vec![-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1], + } + ); + + Ok(()) +} + #[test] fn test_function_pointer() -> Result<()> { let lua = Lua::new(); diff --git a/tests/luau.rs b/tests/luau.rs index 2c24c178..0b37f7d6 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -6,10 +6,7 @@ use std::panic::{catch_unwind, AssertUnwindSafe}; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; -use mlua::{ - Compiler, CoverageInfo, Error, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, Vector, - VmState, -}; +use mlua::{Compiler, Error, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, Vector, VmState}; #[test] fn test_version() -> Result<()> { @@ -392,79 +389,6 @@ fn test_interrupts() -> Result<()> { Ok(()) } -#[test] -fn test_coverage() -> Result<()> { - let lua = Lua::new(); - - lua.set_compiler(Compiler::default().set_coverage_level(1)); - - let f = lua - .load( - r#"local s = "abc" - assert(#s == 3) - - function abc(i) - if i < 5 then - return 0 - else - return 1 - end - end - - (function() - (function() abc(10) end)() - end)() - "#, - ) - .into_function()?; - - f.call::<()>(())?; - - let mut report = Vec::new(); - f.coverage(|cov| { - report.push(cov); - }); - - assert_eq!( - report[0], - CoverageInfo { - function: None, - line_defined: 1, - depth: 0, - hits: vec![-1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1], - } - ); - assert_eq!( - report[1], - CoverageInfo { - function: Some("abc".into()), - line_defined: 4, - depth: 1, - hits: vec![-1, -1, -1, -1, -1, 1, 0, -1, 1, -1, -1, -1, -1, -1, -1, -1], - } - ); - assert_eq!( - report[2], - CoverageInfo { - function: None, - line_defined: 12, - depth: 1, - hits: vec![-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1], - } - ); - assert_eq!( - report[3], - CoverageInfo { - function: None, - line_defined: 13, - depth: 2, - hits: vec![-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1], - } - ); - - Ok(()) -} - #[test] fn test_fflags() { // We cannot really on any particular feature flag to be present diff --git a/tests/tests.rs b/tests/tests.rs index a819d06f..f4faac5c 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -925,26 +925,6 @@ fn test_too_many_recursions() -> Result<()> { Ok(()) } -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn test_too_many_binds() -> Result<()> { - let lua = Lua::new(); - let globals = lua.globals(); - lua.load( - r#" - function f(...) - end - "#, - ) - .exec()?; - - let concat = globals.get::("f")?; - assert!(concat.bind(Variadic::from_iter(1..1000000)).is_err()); - assert!(concat.call::<()>(Variadic::from_iter(1..1000000)).is_err()); - - Ok(()) -} - #[test] #[cfg(not(target_arch = "wasm32"))] fn test_ref_stack_exhaustion() { From 9e16e181320529b6074dfabac7644ea8a0283390 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 16 Oct 2024 15:58:52 +0100 Subject: [PATCH 229/635] Update string tests --- src/string.rs | 9 +++++++++ tests/string.rs | 15 +++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/string.rs b/src/string.rs index 264d51fa..364010f9 100644 --- a/src/string.rs +++ b/src/string.rs @@ -172,6 +172,15 @@ impl PartialEq for String { impl Eq for String {} +impl PartialOrd for String +where + T: AsRef<[u8]> + ?Sized, +{ + fn partial_cmp(&self, other: &T) -> Option { + self.as_bytes().partial_cmp(&other.as_ref()) + } +} + impl PartialOrd for String { fn partial_cmp(&self, other: &String) -> Option { Some(self.cmp(other)) diff --git a/tests/string.rs b/tests/string.rs index 456ad849..34a32de6 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -17,6 +17,15 @@ fn test_string_compare() { with_str("teststring", |t| assert_eq!(t, t)); // mlua::String with_str("teststring", |t| assert_eq!(t, Cow::from(b"teststring".as_ref()))); // Cow (borrowed) with_str("bla", |t| assert_eq!(t, Cow::from(b"bla".to_vec()))); // Cow (owned) + + // Test ordering + with_str("a", |a| { + assert!(!(a < a)); + assert!(!(a > a)); + }); + with_str("a", |a| assert!(a < "b")); + with_str("a", |a| assert!(a < b"b")); + with_str("a", |a| with_str("b", |b| assert!(a < b))); } #[test] @@ -52,7 +61,7 @@ fn test_string_views() -> Result<()> { } #[test] -fn test_raw_string() -> Result<()> { +fn test_string_from_bytes() -> Result<()> { let lua = Lua::new(); let rs = lua.create_string(&[0, 1, 2, 3, 0, 1, 2, 3])?; @@ -77,12 +86,14 @@ fn test_string_hash() -> Result<()> { } #[test] -fn test_string_debug() -> Result<()> { +fn test_string_fmt_debug() -> Result<()> { let lua = Lua::new(); // Valid utf8 let s = lua.create_string("hello")?; assert_eq!(format!("{s:?}"), r#""hello""#); + assert_eq!(format!("{:?}", s.to_str()?), r#""hello""#); + assert_eq!(format!("{:?}", s.as_bytes()), "[104, 101, 108, 108, 111]"); // Invalid utf8 let s = lua.create_string(b"hello\0world\r\n\t\xF0\x90\x80")?; From f9ae4bf05f36bc453939f1052be51332fc4d2e42 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 16 Oct 2024 16:00:01 +0100 Subject: [PATCH 230/635] Update table tests --- src/table.rs | 1 + tests/table.rs | 124 ++++++++++++++++++++++++++----------------------- 2 files changed, 66 insertions(+), 59 deletions(-) diff --git a/src/table.rs b/src/table.rs index c8fdcf2b..01656b40 100644 --- a/src/table.rs +++ b/src/table.rs @@ -511,6 +511,7 @@ impl Table { #[doc(hidden)] #[deprecated(since = "0.10.0", note = "please use `metatable` instead")] + #[cfg(not(tarpaulin_include))] pub fn get_metatable(&self) -> Option
{ self.metatable() } diff --git a/tests/table.rs b/tests/table.rs index e6410e53..5cb3f478 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -1,4 +1,4 @@ -use mlua::{Error, Lua, Nil, ObjectLike, Result, Table, Value}; +use mlua::{Error, Lua, ObjectLike, Result, Table, Value}; #[test] fn test_globals_set_get() -> Result<()> { @@ -10,6 +10,8 @@ fn test_globals_set_get() -> Result<()> { assert_eq!(globals.get::("foo")?, "bar"); assert_eq!(globals.get::("baz")?, "baf"); + lua.load(r#"assert(foo == "bar")"#).exec().unwrap(); + Ok(()) } @@ -19,16 +21,6 @@ fn test_table() -> Result<()> { let globals = lua.globals(); - globals.set("table", lua.create_table()?)?; - let table1: Table = globals.get("table")?; - let table2: Table = globals.get("table")?; - - table1.set("foo", "bar")?; - table2.set("baz", "baf")?; - - assert_eq!(table2.get::("foo")?, "bar"); - assert_eq!(table1.get::("baz")?, "baf"); - lua.load( r#" table1 = {1, 2, 3, 4, 5} @@ -39,29 +31,26 @@ fn test_table() -> Result<()> { .exec()?; let table1 = globals.get::
("table1")?; - let table2 = globals.get::
("table2")?; - let table3 = globals.get::
("table3")?; - assert_eq!(table1.len()?, 5); assert!(!table1.is_empty()); assert_eq!( - table1.clone().pairs().collect::>>()?, + table1.pairs().collect::>>()?, vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)] ); assert_eq!( - table1.clone().sequence_values().collect::>>()?, + table1.sequence_values().collect::>>()?, vec![1, 2, 3, 4, 5] ); assert_eq!(table1, [1, 2, 3, 4, 5]); + assert_eq!(table1, [1, 2, 3, 4, 5].as_slice()); + let table2 = globals.get::
("table2")?; assert_eq!(table2.len()?, 0); assert!(table2.is_empty()); - assert_eq!( - table2.clone().pairs().collect::>>()?, - vec![] - ); + assert_eq!(table2.pairs().collect::>>()?, vec![]); assert_eq!(table2, [0; 0]); + let table3 = globals.get::
("table3")?; // sequence_values should only iterate until the first border assert_eq!(table3, [1, 2]); assert_eq!( @@ -69,26 +58,6 @@ fn test_table() -> Result<()> { vec![1, 2] ); - globals.set("table4", lua.create_sequence_from(vec![1, 2, 3, 4, 5])?)?; - let table4 = globals.get::
("table4")?; - assert_eq!( - table4.clone().pairs().collect::>>()?, - vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)] - ); - - table4.raw_insert(4, 35)?; - table4.raw_insert(7, 7)?; - assert_eq!( - table4.clone().pairs().collect::>>()?, - vec![(1, 1), (2, 2), (3, 3), (4, 35), (5, 4), (6, 5), (7, 7)] - ); - - table4.raw_remove(1)?; - assert_eq!( - table4.clone().pairs().collect::>>()?, - vec![(1, 2), (2, 3), (3, 35), (4, 4), (5, 5), (6, 7)] - ); - Ok(()) } @@ -97,7 +66,7 @@ fn test_table_push_pop() -> Result<()> { let lua = Lua::new(); // Test raw access - let table1 = lua.create_sequence_from(vec![123])?; + let table1 = lua.create_sequence_from([123])?; table1.raw_push(321)?; assert_eq!(table1, [123, 321]); assert_eq!(table1.raw_pop::()?, 321); @@ -123,10 +92,7 @@ fn test_table_push_pop() -> Result<()> { table2.push(345)?; assert_eq!(table2.len()?, 2); assert_eq!( - table2 - .clone() - .sequence_values::() - .collect::>>()?, + table2.sequence_values::().collect::>>()?, vec![] ); assert_eq!(table2.pop::()?, 345); @@ -137,22 +103,53 @@ fn test_table_push_pop() -> Result<()> { Ok(()) } +#[test] +fn test_table_insert_remove() -> Result<()> { + let lua = Lua::new(); + + let globals = lua.globals(); + + globals.set("table4", [1, 2, 3, 4, 5])?; + let table4 = globals.get::
("table4")?; + assert_eq!( + table4.pairs().collect::>>()?, + vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)] + ); + table4.raw_insert(4, 35)?; + table4.raw_insert(7, 7)?; + assert_eq!( + table4.pairs().collect::>>()?, + vec![(1, 1), (2, 2), (3, 3), (4, 35), (5, 4), (6, 5), (7, 7)] + ); + table4.raw_remove(1)?; + assert_eq!( + table4.pairs().collect::>>()?, + vec![(1, 2), (2, 3), (3, 35), (4, 4), (5, 5), (6, 7)] + ); + + // Wrong index, tables are 1-indexed + assert!(table4.raw_insert(0, "123").is_err()); + + Ok(()) +} + #[test] fn test_table_clear() -> Result<()> { let lua = Lua::new(); + let t = lua.create_table()?; + // Check readonly error #[cfg(feature = "luau")] { - let t = lua.create_table()?; t.set_readonly(true); assert!(matches!( t.clear(), Err(Error::RuntimeError(err)) if err.contains("attempt to modify a readonly table") )); + t.set_readonly(false); } - let t = lua.create_table()?; // Set array and hash parts t.push("abc")?; t.push("bcd")?; @@ -217,15 +214,14 @@ fn test_table_pairs() -> Result<()> { ) .eval::
()?; - let table2 = table.clone(); for (i, kv) in table.pairs::().enumerate() { let (k, _v) = kv.unwrap(); match i { // Try to add a new key - 0 => table2.set("new_key", "new_value")?, + 0 => table.set("new_key", "new_value")?, // Try to delete the 2nd key 1 => { - table2.set(k, Value::Nil)?; + table.set(k, Value::Nil)?; lua.gc_collect()?; } _ => {} @@ -304,21 +300,15 @@ fn test_metatable() -> Result<()> { metatable.set("__index", lua.create_function(|_, ()| Ok("index_value"))?)?; table.set_metatable(Some(metatable)); assert_eq!(table.get::("any_key")?, "index_value"); - match table.raw_get::("any_key")? { - Nil => {} - _ => panic!(), - } + assert_eq!(table.raw_get::("any_key")?, Value::Nil); table.set_metatable(None); - match table.get::("any_key")? { - Nil => {} - _ => panic!(), - }; + assert_eq!(table.get::("any_key")?, Value::Nil); Ok(()) } #[test] -fn test_table_eq() -> Result<()> { +fn test_table_equals() -> Result<()> { let lua = Lua::new(); let globals = lua.globals(); @@ -358,6 +348,7 @@ fn test_table_pointer() -> Result<()> { let table1 = lua.create_table()?; let table2 = lua.create_table()?; + // Clone should not create a new table assert_eq!(table1.to_pointer(), table1.clone().to_pointer()); assert_ne!(table1.to_pointer(), table2.to_pointer()); @@ -398,6 +389,21 @@ fn test_table_error() -> Result<()> { Ok(()) } +#[test] +fn test_table_fmt() -> Result<()> { + let lua = Lua::new(); + + let table = lua.load(r#"{1, 2, 3, a = 5, b = { 6 }}"#).eval::
()?; + // assert_eq!(format!("{:?}", table), "{1, 2, 3, a = 5, b = {6}}"); + assert!(format!("{table:?}").starts_with("Table(Ref(")); + assert_eq!( + format!("{table:#?}"), + "{\n [1] = 1,\n [2] = 2,\n [3] = 3,\n [\"a\"] = 5,\n [\"b\"] = {\n [1] = 6,\n },\n}" + ); + + Ok(()) +} + #[test] fn test_table_object_like() -> Result<()> { let lua = Lua::new(); From 179c54f29773172d849686d97ef5895185ea3cef Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 16 Oct 2024 16:11:39 +0100 Subject: [PATCH 231/635] Remove generic from `Table::equals` and `Value::equals` --- src/table.rs | 12 ++---------- src/userdata.rs | 18 +++++------------- src/value.rs | 15 ++++----------- tests/userdata.rs | 2 +- tests/value.rs | 2 +- 5 files changed, 13 insertions(+), 36 deletions(-) diff --git a/src/table.rs b/src/table.rs index 01656b40..55471331 100644 --- a/src/table.rs +++ b/src/table.rs @@ -218,13 +218,12 @@ impl Table { /// # Ok(()) /// # } /// ``` - pub fn equals>(&self, other: T) -> Result { - let other = other.as_ref(); + pub fn equals(&self, other: &Self) -> Result { if self == other { return Ok(true); } - // Compare using __eq metamethod if exists + // Compare using `__eq` metamethod if exists // First, check the self for the metamethod. // If self does not define it, then check the other table. if let Some(mt) = self.metatable() { @@ -811,13 +810,6 @@ impl fmt::Debug for Table { } } -impl AsRef
for Table { - #[inline] - fn as_ref(&self) -> &Self { - self - } -} - impl PartialEq<[T]> for Table where T: IntoLua + Clone, diff --git a/src/userdata.rs b/src/userdata.rs index 1b28dc68..dbdc69f5 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -902,7 +902,7 @@ impl AnyUserData { /// [`UserDataMetatable`]: crate::UserDataMetatable #[inline] pub fn metatable(&self) -> Result { - self.get_raw_metatable().map(UserDataMetatable) + self.raw_metatable().map(UserDataMetatable) } #[doc(hidden)] @@ -911,7 +911,7 @@ impl AnyUserData { self.metatable() } - fn get_raw_metatable(&self) -> Result
{ + fn raw_metatable(&self) -> Result
{ let lua = self.0.lua.lock(); let state = lua.state(); unsafe { @@ -958,15 +958,14 @@ impl AnyUserData { } } - pub(crate) fn equals>(&self, other: T) -> Result { - let other = other.as_ref(); + pub(crate) fn equals(&self, other: &Self) -> Result { // Uses lua_rawequal() under the hood if self == other { return Ok(true); } - let mt = self.get_raw_metatable()?; - if mt != other.get_raw_metatable()? { + let mt = self.raw_metatable()?; + if mt != other.raw_metatable()? { return Ok(false); } @@ -1010,13 +1009,6 @@ impl AnyUserData { } } -impl AsRef for AnyUserData { - #[inline] - fn as_ref(&self) -> &Self { - self - } -} - /// Handle to a `UserData` metatable. #[derive(Clone, Debug)] pub struct UserDataMetatable(pub(crate) Table); diff --git a/src/value.rs b/src/value.rs index e9f0ecb6..5763661a 100644 --- a/src/value.rs +++ b/src/value.rs @@ -104,15 +104,15 @@ impl Value { /// Compares two values for equality. /// /// Equality comparisons do not convert strings to numbers or vice versa. - /// Tables, Functions, Threads, and Userdata are compared by reference: + /// Tables, Functions, Threads, and UserData are compared by reference: /// two objects are considered equal only if they are the same object. /// - /// If Tables or Userdata have `__eq` metamethod then mlua will try to invoke it. + /// If Tables or UserData have `__eq` metamethod then mlua will try to invoke it. /// The first value is checked first. If that value does not define a metamethod /// for `__eq`, then mlua will check the second value. /// Then mlua calls the metamethod with the two values as arguments, if found. - pub fn equals>(&self, other: T) -> Result { - match (self, other.as_ref()) { + pub fn equals(&self, other: &Self) -> Result { + match (self, other) { (Value::Table(a), Value::Table(b)) => a.equals(b), (Value::UserData(a), Value::UserData(b)) => a.equals(b), (a, b) => Ok(a == b), @@ -610,13 +610,6 @@ impl PartialEq for Value { } } -impl AsRef for Value { - #[inline] - fn as_ref(&self) -> &Self { - self - } -} - /// A wrapped [`Value`] with customized serialization behavior. #[cfg(feature = "serialize")] #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] diff --git a/tests/userdata.rs b/tests/userdata.rs index b71f2c08..5fd127d5 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -195,7 +195,7 @@ fn test_metamethods() -> Result<()> { assert!(lua.load("userdata2 == userdata3").eval::()?); assert!(userdata2 != userdata3); // because references are differ - assert!(userdata2.equals(userdata3)?); + assert!(userdata2.equals(&userdata3)?); let userdata1: AnyUserData = globals.get("userdata1")?; assert!(userdata1.metatable()?.contains(MetaMethod::Add)?); diff --git a/tests/value.rs b/tests/value.rs index 8f913fde..96afbf52 100644 --- a/tests/value.rs +++ b/tests/value.rs @@ -52,7 +52,7 @@ fn test_value_eq() -> Result<()> { assert!(string1 == string2); assert!(string1.equals(&string2)?); assert!(num1 == num2); - assert!(num1.equals(num2)?); + assert!(num1.equals(&num2)?); assert!(num1 != num3); assert!(func1 == func2); assert!(func1 != func3); From 5479546b2755b2e0b9d674c7e8ded5ea432bb3ed Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 16 Oct 2024 21:54:22 +0100 Subject: [PATCH 232/635] Update chunk tests --- src/luau/package.rs | 2 +- tests/chunk.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/luau/package.rs b/src/luau/package.rs index e3e6e780..97060e7c 100644 --- a/src/luau/package.rs +++ b/src/luau/package.rs @@ -203,7 +203,7 @@ fn lua_loader(lua: &Lua, modname: StdString) -> Result { match fs::read(&file_path) { Ok(buf) => { return lua - .load(&buf) + .load(buf) .set_name(format!("={}", file_path.display())) .set_mode(ChunkMode::Text) .into_function() diff --git a/tests/chunk.rs b/tests/chunk.rs index 910cfbff..faa93f05 100644 --- a/tests/chunk.rs +++ b/tests/chunk.rs @@ -19,7 +19,7 @@ fn test_chunk_path() -> Result<()> { return 321 "#, )?; - let i: i32 = lua.load(&*temp_dir.path().join("module.lua")).eval()?; + let i: i32 = lua.load(temp_dir.path().join("module.lua")).eval()?; assert_eq!(i, 321); match lua.load(&*temp_dir.path().join("module2.lua")).exec() { @@ -27,6 +27,30 @@ fn test_chunk_path() -> Result<()> { res => panic!("expected io::Error, got {:?}", res), }; + // &Path + assert_eq!( + (lua.load(&*temp_dir.path().join("module.lua").as_path())).eval::()?, + 321 + ); + + Ok(()) +} + +#[test] +fn test_chunk_impls() -> Result<()> { + let lua = Lua::new(); + + // StdString + assert_eq!(lua.load(String::from("1")).eval::()?, 1); + assert_eq!(lua.load(&*String::from("2")).eval::()?, 2); + + // &[u8] + assert_eq!(lua.load(&b"3"[..]).eval::()?, 3); + + // Vec + assert_eq!(lua.load(b"4".to_vec()).eval::()?, 4); + assert_eq!(lua.load(&b"5".to_vec()).eval::()?, 5); + Ok(()) } @@ -68,3 +92,32 @@ fn test_chunk_macro() -> Result<()> { Ok(()) } + +#[cfg(feature = "luau")] +#[test] +fn test_compiler() -> Result<()> { + use std::vec; + + let compiler = mlua::Compiler::new() + .set_optimization_level(2) + .set_debug_level(2) + .set_type_info_level(1) + .set_coverage_level(2) + .set_vector_lib("vector") + .set_vector_ctor("new") + .set_vector_type("vector") + .set_mutable_globals(vec!["mutable_global".into()]) + .set_userdata_types(vec!["MyUserdata".into()]); + + assert!(compiler.compile("return vector.new(1, 2, 3)").is_ok()); + + // Error + match compiler.compile("%") { + Err(mlua::Error::SyntaxError { ref message, .. }) => { + assert!(message.contains("Expected identifier when parsing expression, got '%'"),); + } + res => panic!("expected result: {res:?}"), + } + + Ok(()) +} From 735aa22be9de332791d748e73e105fabe27c168f Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 16 Oct 2024 23:57:07 +0100 Subject: [PATCH 233/635] Fix typo in chunk tests --- tests/chunk.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/chunk.rs b/tests/chunk.rs index faa93f05..1a5270ec 100644 --- a/tests/chunk.rs +++ b/tests/chunk.rs @@ -42,7 +42,7 @@ fn test_chunk_impls() -> Result<()> { // StdString assert_eq!(lua.load(String::from("1")).eval::()?, 1); - assert_eq!(lua.load(&*String::from("2")).eval::()?, 2); + assert_eq!(lua.load(&String::from("2")).eval::()?, 2); // &[u8] assert_eq!(lua.load(&b"3"[..]).eval::()?, 3); From 084a85c3d83f20d36ce7223bc15e9a74fe265d39 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 16 Oct 2024 23:57:48 +0100 Subject: [PATCH 234/635] Update error tests --- .github/workflows/main.yml | 10 +++++----- src/error.rs | 22 +++++++++++++--------- tarpaulin.toml | 2 +- tests/error.rs | 24 ++++++++++++++---------- 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 84331793..a4795c1b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,8 +27,8 @@ jobs: - name: Build ${{ matrix.lua }} vendored run: | cargo build --features "${{ matrix.lua }},vendored" - cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros" - cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,send" + cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow" + cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,send" shell: bash - name: Build ${{ matrix.lua }} pkg-config if: ${{ matrix.os == 'ubuntu-latest' }} @@ -123,8 +123,8 @@ jobs: - name: Run ${{ matrix.lua }} tests run: | cargo test --features "${{ matrix.lua }},vendored" - cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros" - cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,send" + cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow" + cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,send" shell: bash - name: Run compile tests (macos lua54) if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }} @@ -281,4 +281,4 @@ jobs: - uses: giraffate/clippy-action@v1 with: reporter: 'github-pr-review' - clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros" + clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow" diff --git a/src/error.rs b/src/error.rs index d18b1a39..d84d41ce 100644 --- a/src/error.rs +++ b/src/error.rs @@ -309,7 +309,7 @@ impl fmt::Display for Error { Error::DeserializeError(err) => { write!(fmt, "deserialize error: {err}") }, - Error::ExternalError(err) => write!(fmt, "{err}"), + Error::ExternalError(err) => err.fmt(fmt), Error::WithContext { context, cause } => { writeln!(fmt, "{context}")?; write!(fmt, "{cause}") @@ -328,10 +328,7 @@ impl StdError for Error { // returns nothing. Error::CallbackError { .. } => None, Error::ExternalError(err) => err.source(), - Error::WithContext { cause, .. } => match cause.as_ref() { - Error::ExternalError(err) => err.source(), - _ => None, - }, + Error::WithContext { cause, .. } => Self::source(&cause), _ => None, } } @@ -357,10 +354,7 @@ impl Error { { match self { Error::ExternalError(err) => err.downcast_ref(), - Error::WithContext { cause, .. } => match cause.as_ref() { - Error::ExternalError(err) => err.downcast_ref(), - _ => None, - }, + Error::WithContext { cause, .. } => Self::downcast_ref(&cause), _ => None, } } @@ -373,6 +367,16 @@ impl Error { } } + /// Returns the parent of this error. + #[doc(hidden)] + pub fn parent(&self) -> Option<&Error> { + match self { + Error::CallbackError { cause, .. } => Some(cause.as_ref()), + Error::WithContext { cause, .. } => Some(cause.as_ref()), + _ => None, + } + } + pub(crate) fn bad_self_argument(to: &str, cause: Error) -> Self { Error::BadArgument { to: Some(to.to_string()), diff --git a/tarpaulin.toml b/tarpaulin.toml index e6b58889..26ede626 100644 --- a/tarpaulin.toml +++ b/tarpaulin.toml @@ -1,5 +1,5 @@ [lua54_coverage] -features = "lua54,vendored,async,send,serialize,macros" +features = "lua54,vendored,async,send,serialize,macros,anyhow" [lua54_with_memory_limit_coverage] features = "lua54,vendored,async,send,serialize,macros" diff --git a/tests/error.rs b/tests/error.rs index a61e2aa2..09bdd5b1 100644 --- a/tests/error.rs +++ b/tests/error.rs @@ -1,4 +1,5 @@ -use std::io; +use std::error::Error as _; +use std::{fmt, io}; use mlua::{Error, ErrorContext, Lua, Result}; @@ -27,7 +28,6 @@ fn test_error_context() -> Result<()> { .load("local _, err = pcall(func2); return tostring(err)") .eval::()?; assert!(msg2.contains("failed to find global")); - println!("{msg2}"); assert!(msg2.contains("error converting Lua nil to String")); // Rewrite context message and test `downcast_ref` @@ -36,13 +36,12 @@ fn test_error_context() -> Result<()> { .context("some context") .context("some new context") })?; - let res = func3.call::<()>(()).err().unwrap(); - let Error::CallbackError { cause, .. } = &res else { - unreachable!() - }; - assert!(!res.to_string().contains("some context")); - assert!(res.to_string().contains("some new context")); - assert!(cause.downcast_ref::().is_some()); + let err = func3.call::<()>(()).unwrap_err(); + let err = err.parent().unwrap(); + assert!(!err.to_string().contains("some context")); + assert!(err.to_string().contains("some new context")); + assert!(err.downcast_ref::().is_some()); + assert!(err.downcast_ref::().is_none()); Ok(()) } @@ -59,7 +58,7 @@ fn test_error_chain() -> Result<()> { let err = Error::external(io::Error::new(io::ErrorKind::Other, "other")).context("io error"); Err::<(), _>(err) })?; - let err = func.call::<()>(()).err().unwrap(); + let err = func.call::<()>(()).unwrap_err(); assert_eq!(err.chain().count(), 3); for (i, err) in err.chain().enumerate() { match i { @@ -70,6 +69,11 @@ fn test_error_chain() -> Result<()> { } } + let err = err.parent().unwrap(); + assert!(err.source().is_none()); // The source is included to the `Display` output + assert!(err.to_string().contains("io error")); + assert!(err.to_string().contains("other")); + Ok(()) } From c07bdce250feb3e4c1428082f211d21685fb9de5 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 17 Oct 2024 17:03:31 +0100 Subject: [PATCH 235/635] More scope tests --- tests/scope.rs | 69 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/tests/scope.rs b/tests/scope.rs index aa96c48d..4ff9a94e 100644 --- a/tests/scope.rs +++ b/tests/scope.rs @@ -332,7 +332,15 @@ fn test_scope_userdata_ref() -> Result<()> { let data = MyUserData(Cell::new(1)); lua.scope(|scope| { let ud = scope.create_userdata_ref(&data)?; - modify_userdata(&lua, ud) + modify_userdata(&lua, &ud)?; + + // We can only borrow userdata scoped + assert!((matches!(ud.borrow::(), Err(Error::UserDataTypeMismatch)))); + ud.borrow_scoped::(|ud_inst| { + assert_eq!(ud_inst.0.get(), 2); + })?; + + Ok(()) })?; assert_eq!(data.0.get(), 2); @@ -362,9 +370,16 @@ fn test_scope_userdata_ref_mut() -> Result<()> { let mut data = MyUserData(1); lua.scope(|scope| { let ud = scope.create_userdata_ref_mut(&mut data)?; - modify_userdata(&lua, ud) + modify_userdata(&lua, &ud)?; + + assert!((matches!(ud.borrow_mut::(), Err(Error::UserDataTypeMismatch)))); + ud.borrow_mut_scoped::(|ud_inst| { + ud_inst.0 += 10; + })?; + + Ok(()) })?; - assert_eq!(data.0, 2); + assert_eq!(data.0, 12); Ok(()) } @@ -415,27 +430,47 @@ fn test_scope_any_userdata_ref() -> Result<()> { let data = Cell::new(1i64); lua.scope(|scope| { let ud = scope.create_any_userdata_ref(&data)?; - modify_userdata(&lua, ud) + modify_userdata(&lua, &ud) })?; assert_eq!(data.get(), 2); Ok(()) } -fn modify_userdata(lua: &Lua, ud: AnyUserData) -> Result<()> { - let f: Function = lua - .load( - r#" - function(u) - u:inc() - u:dec() - u:inc() - end -"#, - ) - .eval()?; +#[test] +fn test_scope_any_userdata_ref_mut() -> Result<()> { + let lua = Lua::new(); - f.call::<()>(ud)?; + lua.register_userdata_type::(|reg| { + reg.add_method_mut("inc", |_, data, ()| { + *data += 1; + Ok(()) + }); + + reg.add_method_mut("dec", |_, data, ()| { + *data -= 1; + Ok(()) + }); + })?; + + let mut data = 1i64; + lua.scope(|scope| { + let ud = scope.create_any_userdata_ref_mut(&mut data)?; + modify_userdata(&lua, &ud) + })?; + assert_eq!(data, 2); Ok(()) } + +fn modify_userdata(lua: &Lua, ud: &AnyUserData) -> Result<()> { + lua.load( + r#" + local u = ... + u:inc() + u:dec() + u:inc() +"#, + ) + .call(ud) +} From 2331995e288459145c43f354bc2784ec0aa1e45c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 18 Oct 2024 21:36:49 +0100 Subject: [PATCH 236/635] Update coverage ci options --- .github/workflows/coverage.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index e944f62f..f64ab1d3 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -6,7 +6,7 @@ jobs: name: coverage runs-on: ubuntu-latest container: - image: xd009642/tarpaulin + image: xd009642/tarpaulin:develop-nightly options: --security-opt seccomp=unconfined steps: - name: Checkout repository @@ -14,7 +14,7 @@ jobs: - name: Generate coverage report run: | - cargo tarpaulin --out xml --tests --exclude-files benches/* --exclude-files mlua-sys/src/*/* + cargo +nightly tarpaulin --verbose --out xml --tests --exclude-files benches/* --exclude-files mlua-sys/src/*/* - name: Upload report to codecov.io uses: codecov/codecov-action@v4 From 98339c57e6a3fac3e1b3a8fe54f9e03391a69c38 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 18 Oct 2024 21:38:02 +0100 Subject: [PATCH 237/635] Update userdata tests --- src/userdata.rs | 28 +++++++++++----------------- src/userdata/lock.rs | 1 + tests/userdata.rs | 1 + 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/userdata.rs b/src/userdata.rs index dbdc69f5..2934f44e 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -21,7 +21,7 @@ use crate::string::String; use crate::table::{Table, TablePairs}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::types::{MaybeSend, ValueRef}; -use crate::util::{check_stack, get_userdata, take_userdata, StackGuard}; +use crate::util::{check_stack, get_userdata, push_string, take_userdata, StackGuard}; use crate::value::Value; // Re-export for convenience @@ -809,13 +809,10 @@ impl AnyUserData { lua.push_userdata_ref(&self.0)?; // Multiple (extra) user values are emulated by storing them in a table - protect_lua!(state, 1, 1, |state| { - if ffi::lua_getuservalue(state, -1) != ffi::LUA_TTABLE { - ffi::lua_pushnil(state); - return; - } - ffi::lua_rawgeti(state, -1, n as ffi::lua_Integer); - })?; + if ffi::lua_getuservalue(state, -1) != ffi::LUA_TTABLE { + return V::from_lua(Value::Nil, lua.lua()); + } + ffi::lua_rawgeti(state, -1, n as ffi::lua_Integer); V::from_lua(lua.pop_value(), lua.lua()) } @@ -873,16 +870,13 @@ impl AnyUserData { lua.push_userdata_ref(&self.0)?; // Multiple (extra) user values are emulated by storing them in a table - protect_lua!(state, 1, 1, |state| { - if ffi::lua_getuservalue(state, -1) != ffi::LUA_TTABLE { - ffi::lua_pushnil(state); - return; - } - ffi::lua_pushlstring(state, name.as_ptr() as *const c_char, name.len()); - ffi::lua_rawget(state, -2); - })?; + if ffi::lua_getuservalue(state, -1) != ffi::LUA_TTABLE { + return V::from_lua(Value::Nil, lua.lua()); + } + push_string(state, name.as_bytes(), !lua.unlikely_memory_error())?; + ffi::lua_rawget(state, -2); - V::from_lua(lua.pop_value(), lua.lua()) + V::from_stack(-1, &lua) } } diff --git a/src/userdata/lock.rs b/src/userdata/lock.rs index 8845f332..02cf005d 100644 --- a/src/userdata/lock.rs +++ b/src/userdata/lock.rs @@ -11,6 +11,7 @@ pub(crate) trait UserDataLock { pub(crate) use lock_impl::RawLock; #[cfg(not(feature = "send"))] +#[cfg(not(tarpaulin_include))] mod lock_impl { use std::cell::Cell; diff --git a/tests/userdata.rs b/tests/userdata.rs index 5fd127d5..cba4f318 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -418,6 +418,7 @@ fn test_user_values() -> Result<()> { assert!(ud.nth_user_value::(65536).is_err()); // Named user values + let ud = lua.create_userdata(MyUserData)?; ud.set_named_user_value("name", "alex")?; ud.set_named_user_value("age", 10)?; From 02d4ceff342327da205f8bf591c2fbfb640677db Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 18 Oct 2024 21:50:57 +0100 Subject: [PATCH 238/635] Make Thread::state non-const (private api) --- src/thread.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/thread.rs b/src/thread.rs index d73a87e2..1a5785a6 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -69,7 +69,7 @@ pub struct AsyncThread { impl Thread { #[inline(always)] - const fn state(&self) -> *mut ffi::lua_State { + fn state(&self) -> *mut ffi::lua_State { self.1 } From c68e3c4f41f5bdb03951ecb14da6e711dabd1dca Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 18 Oct 2024 22:45:26 +0100 Subject: [PATCH 239/635] Some `DebugStack` improvements --- src/hook.rs | 17 +++++++++++------ tests/tests.rs | 21 +++++++++++++++++---- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/hook.rs b/src/hook.rs index 0d327445..3f38c6d5 100644 --- a/src/hook.rs +++ b/src/hook.rs @@ -184,8 +184,8 @@ impl<'a> Debug<'a> { ); #[cfg(feature = "luau")] mlua_assert!( - ffi::lua_getinfo(self.lua.state(), self.level, cstr!("a"), self.ar.get()) != 0, - "lua_getinfo failed with `a`" + ffi::lua_getinfo(self.lua.state(), self.level, cstr!("au"), self.ar.get()) != 0, + "lua_getinfo failed with `au`" ); #[cfg(not(feature = "luau"))] @@ -198,8 +198,8 @@ impl<'a> Debug<'a> { }; #[cfg(feature = "luau")] let stack = DebugStack { - num_ups: (*self.ar.get()).nupvals as i32, - num_params: (*self.ar.get()).nparams as i32, + num_ups: (*self.ar.get()).nupvals, + num_params: (*self.ar.get()).nparams, is_vararg: (*self.ar.get()).isvararg != 0, }; stack @@ -262,10 +262,15 @@ pub struct DebugSource<'a> { #[derive(Copy, Clone, Debug)] pub struct DebugStack { - pub num_ups: i32, + /// Number of upvalues. + pub num_ups: u8, + /// Number of parameters. + /// /// Requires `feature = "lua54/lua53/lua52/luau"` #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] - pub num_params: i32, + pub num_params: u8, + /// Whether the function is a vararg function. + /// /// Requires `feature = "lua54/lua53/lua52/luau"` #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] pub is_vararg: bool, diff --git a/tests/tests.rs b/tests/tests.rs index f4faac5c..4e4d161b 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -106,7 +106,6 @@ fn test_exec() -> Result<()> { "#, ) .eval()?; - println!("checkpoint"); assert!(module.contains_key("func")?); assert_eq!(module.get::("func")?.call::(())?, "hello"); @@ -631,8 +630,7 @@ fn test_recursive_mut_callback_error() -> Result<()> { // Whoops, this will recurse into the function and produce another mutable reference! lua.globals().get::("f")?.call::<()>(true)?; println!("Should not get here, mutable aliasing has occurred!"); - println!("value at {:p}", r as *mut _); - println!("value is {}", r); + println!("value at {:p} is {r}", r as *mut _); } Ok(()) @@ -1134,6 +1132,13 @@ fn test_inspect_stack() -> Result<()> { })?; lua.globals().set("logline", logline)?; + let stack_info = lua.create_function(|lua, ()| { + let debug = lua.inspect_stack(1).unwrap(); // caller + let stack_info = debug.stack(); + Ok(format!("{stack_info:?}")) + })?; + lua.globals().set("stack_info", stack_info)?; + lua.load( r#" local function foo() @@ -1143,10 +1148,18 @@ fn test_inspect_stack() -> Result<()> { local function bar() return foo() end + local stack_info = stack_info + local function baz(a, b, c, ...) + return stack_info() + end assert(foo() == '[string "chunk"]:3 hello') assert(bar() == '[string "chunk"]:3 hello') - assert(logline("world") == '[string "chunk"]:12 world') + assert(logline("world") == '[string "chunk"]:16 world') + assert( + baz() == 'DebugStack { num_ups: 1, num_params: 3, is_vararg: true }' or + baz() == 'DebugStack { num_ups: 1 }' + ) "#, ) .set_name("chunk") From cbae4fe59c0cb0ef56d99fe7b524bd2b3ba95bca Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 18 Oct 2024 22:50:15 +0100 Subject: [PATCH 240/635] More async tests --- tests/async.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/async.rs b/tests/async.rs index f939a684..4ce1bdd6 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -48,6 +48,19 @@ async fn test_async_function_wrap() -> Result<()> { let res: String = lua.load(r#"f("hello")"#).eval_async().await?; assert_eq!(res, "hello"); + // Return error + let ferr = Function::wrap_async(|| async move { Err::<(), _>(Error::runtime("some async error")) }); + lua.globals().set("ferr", ferr)?; + lua.load( + r#" + local ok, err = pcall(ferr) + assert(not ok and tostring(err):find("some async error")) + "#, + ) + .exec_async() + .await + .unwrap(); + Ok(()) } @@ -376,6 +389,13 @@ async fn test_async_table_object_like() -> Result<()> { table.set_metatable(Some(metatable)); assert_eq!(table.call_async::(()).await.unwrap(), 15); + match table.call_async_method::<()>("non_existent", ()).await { + Err(Error::RuntimeError(err)) => { + assert!(err.contains("attempt to call a nil value (function 'non_existent')")) + } + r => panic!("expected RuntimeError, got {r:?}"), + } + Ok(()) } From 2a8db8713231bb7b43ac180eea6017b8d1c8bcc6 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 18 Oct 2024 22:50:31 +0100 Subject: [PATCH 241/635] Update Value tests --- src/value.rs | 6 ++--- tests/buffer.rs | 1 + tests/table.rs | 18 ++++++++++++--- tests/userdata.rs | 1 - tests/value.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 75 insertions(+), 8 deletions(-) diff --git a/src/value.rs b/src/value.rs index 5763661a..4dfa0c12 100644 --- a/src/value.rs +++ b/src/value.rs @@ -501,9 +501,9 @@ impl Value { (_, Value::Boolean(_)) => Ordering::Greater, // Integer && Number (Value::Integer(a), Value::Integer(b)) => a.cmp(b), - (&Value::Integer(a), &Value::Number(b)) => cmp_num(a as Number, b), - (&Value::Number(a), &Value::Integer(b)) => cmp_num(a, b as Number), - (&Value::Number(a), &Value::Number(b)) => cmp_num(a, b), + (Value::Integer(a), Value::Number(b)) => cmp_num(*a as Number, *b), + (Value::Number(a), Value::Integer(b)) => cmp_num(*a, *b as Number), + (Value::Number(a), Value::Number(b)) => cmp_num(*a, *b), (Value::Integer(_) | Value::Number(_), _) => Ordering::Less, (_, Value::Integer(_) | Value::Number(_)) => Ordering::Greater, // Vector (Luau) diff --git a/tests/buffer.rs b/tests/buffer.rs index cae34746..8e82cfd0 100644 --- a/tests/buffer.rs +++ b/tests/buffer.rs @@ -35,6 +35,7 @@ fn test_buffer() -> Result<()> { let buf3 = lua.create_buffer(b"")?; assert!(buf3.is_empty()); + assert!(!Value::Buffer(buf3).to_pointer().is_null()); Ok(()) } diff --git a/tests/table.rs b/tests/table.rs index 5cb3f478..d7a425e3 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -393,12 +393,24 @@ fn test_table_error() -> Result<()> { fn test_table_fmt() -> Result<()> { let lua = Lua::new(); - let table = lua.load(r#"{1, 2, 3, a = 5, b = { 6 }}"#).eval::
()?; - // assert_eq!(format!("{:?}", table), "{1, 2, 3, a = 5, b = {6}}"); + let table = lua + .load( + r#" + local t = {1, 2, 3, a = 5, b = { 6 }} + t[9.2] = 9.2 + t[1.99] = 1.99 + t[true] = true + t[false] = false + return t + "#, + ) + .eval::
()?; assert!(format!("{table:?}").starts_with("Table(Ref(")); + + // Pretty print assert_eq!( format!("{table:#?}"), - "{\n [1] = 1,\n [2] = 2,\n [3] = 3,\n [\"a\"] = 5,\n [\"b\"] = {\n [1] = 6,\n },\n}" + "{\n [false] = false,\n [true] = true,\n [1] = 1,\n [1.99] = 1.99,\n [2] = 2,\n [3] = 3,\n [9.2] = 9.2,\n [\"a\"] = 5,\n [\"b\"] = {\n [1] = 6,\n },\n}" ); Ok(()) diff --git a/tests/userdata.rs b/tests/userdata.rs index cba4f318..d8c06b9e 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -803,7 +803,6 @@ fn test_userdata_method_errors() -> Result<()> { } => { assert_eq!(to.as_deref(), Some("MyUserData.get_value")); assert_eq!(name.as_deref(), Some("self")); - println!("{}", cause2.to_string()); assert_eq!( cause2.to_string(), "error converting Lua string to userdata (expected userdata of type 'MyUserData')" diff --git a/tests/value.rs b/tests/value.rs index 96afbf52..98fc222f 100644 --- a/tests/value.rs +++ b/tests/value.rs @@ -88,30 +88,79 @@ fn test_multi_value() { assert!(multi_value.is_empty()); } +#[test] +fn test_value_to_pointer() -> Result<()> { + let lua = Lua::new(); + + let globals = lua.globals(); + lua.load( + r#" + table = {} + string = "hello" + num = 1 + func = function() end + thread = coroutine.create(function() end) + "#, + ) + .exec()?; + globals.set("null", Value::NULL)?; + + let table: Value = globals.get("table")?; + let string: Value = globals.get("string")?; + let num: Value = globals.get("num")?; + let func: Value = globals.get("func")?; + let thread: Value = globals.get("thread")?; + let null: Value = globals.get("null")?; + let ud: Value = Value::UserData(lua.create_any_userdata(())?); + + assert!(!table.to_pointer().is_null()); + assert!(!string.to_pointer().is_null()); + assert!(num.to_pointer().is_null()); + assert!(!func.to_pointer().is_null()); + assert!(!thread.to_pointer().is_null()); + assert!(null.to_pointer().is_null()); + assert!(!ud.to_pointer().is_null()); + + Ok(()) +} + #[test] fn test_value_to_string() -> Result<()> { let lua = Lua::new(); assert_eq!(Value::Nil.to_string()?, "nil"); + assert_eq!(Value::Nil.type_name(), "nil"); assert_eq!(Value::Boolean(true).to_string()?, "true"); + assert_eq!(Value::Boolean(true).type_name(), "boolean"); assert_eq!(Value::NULL.to_string()?, "null"); + assert_eq!(Value::NULL.type_name(), "lightuserdata"); assert_eq!( Value::LightUserData(LightUserData(0x1 as *const c_void as *mut _)).to_string()?, "lightuserdata: 0x1" ); assert_eq!(Value::Integer(1).to_string()?, "1"); + assert_eq!(Value::Integer(1).type_name(), "integer"); assert_eq!(Value::Number(34.59).to_string()?, "34.59"); + assert_eq!(Value::Number(34.59).type_name(), "number"); #[cfg(all(feature = "luau", not(feature = "luau-vector4")))] assert_eq!( Value::Vector(mlua::Vector::new(10.0, 11.1, 12.2)).to_string()?, "vector(10, 11.1, 12.2)" ); + #[cfg(all(feature = "luau", not(feature = "luau-vector4")))] + assert_eq!( + Value::Vector(mlua::Vector::new(10.0, 11.1, 12.2)).type_name(), + "vector" + ); #[cfg(feature = "luau-vector4")] assert_eq!( Value::Vector(mlua::Vector::new(10.0, 11.1, 12.2, 13.3)).to_string()?, "vector(10, 11.1, 12.2, 13.3)" ); - assert_eq!(Value::String(lua.create_string("hello")?).to_string()?, "hello"); + + let s = Value::String(lua.create_string("hello")?); + assert_eq!(s.to_string()?, "hello"); + assert_eq!(s.type_name(), "string"); let table: Value = lua.load("{}").eval()?; assert!(table.to_string()?.starts_with("table:")); @@ -119,18 +168,22 @@ fn test_value_to_string() -> Result<()> { .load("setmetatable({}, {__tostring = function() return 'test table' end})") .eval()?; assert_eq!(table.to_string()?, "test table"); + assert_eq!(table.type_name(), "table"); let func: Value = lua.load("function() end").eval()?; assert!(func.to_string()?.starts_with("function:")); + assert_eq!(func.type_name(), "function"); let thread: Value = lua.load("coroutine.create(function() end)").eval()?; assert!(thread.to_string()?.starts_with("thread:")); + assert_eq!(thread.type_name(), "thread"); lua.register_userdata_type::(|reg| { reg.add_meta_method("__tostring", |_, this, ()| Ok(this.clone())); })?; let ud: Value = Value::UserData(lua.create_any_userdata(String::from("string userdata"))?); assert_eq!(ud.to_string()?, "string userdata"); + assert_eq!(ud.type_name(), "userdata"); struct MyUserData; impl UserData for MyUserData {} @@ -139,11 +192,13 @@ fn test_value_to_string() -> Result<()> { let err = Value::Error(Box::new(Error::runtime("test error"))); assert_eq!(err.to_string()?, "runtime error: test error"); + assert_eq!(err.type_name(), "error"); #[cfg(feature = "luau")] { let buf = Value::Buffer(lua.create_buffer(b"hello")?); assert!(buf.to_string()?.starts_with("buffer:")); + assert_eq!(buf.type_name(), "buffer"); // Set `__tostring` metamethod for buffer let mt = lua.load("{__tostring = buffer.tostring}").eval()?; From 930fd9c00fd1d31f6ab49f7bc2083a9e8d5146de Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 18 Oct 2024 23:08:43 +0100 Subject: [PATCH 242/635] Fix `Value::String::to_pointer` for Lua < 5.4 --- src/value.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/value.rs b/src/value.rs index 4dfa0c12..4c8251b5 100644 --- a/src/value.rs +++ b/src/value.rs @@ -129,9 +129,14 @@ impl Value { #[inline] pub fn to_pointer(&self) -> *const c_void { match self { + Value::String(String(vref)) => { + // In Lua < 5.4 (excluding Luau), string pointers are NULL + // Use alternative approach + let lua = vref.lua.lock(); + unsafe { ffi::lua_tostring(lua.ref_thread(), vref.index) as *const c_void } + } Value::LightUserData(ud) => ud.0, - Value::String(String(vref)) - | Value::Table(Table(vref)) + Value::Table(Table(vref)) | Value::Function(Function(vref)) | Value::Thread(Thread(vref, ..)) | Value::UserData(AnyUserData(vref)) From c638d90b02632eb029e5b8ffcb70907e951202bb Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 19 Oct 2024 00:11:05 +0100 Subject: [PATCH 243/635] Fix test_inspect_stack --- tests/tests.rs | 47 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/tests/tests.rs b/tests/tests.rs index 4e4d161b..65335845 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1132,13 +1132,6 @@ fn test_inspect_stack() -> Result<()> { })?; lua.globals().set("logline", logline)?; - let stack_info = lua.create_function(|lua, ()| { - let debug = lua.inspect_stack(1).unwrap(); // caller - let stack_info = debug.stack(); - Ok(format!("{stack_info:?}")) - })?; - lua.globals().set("stack_info", stack_info)?; - lua.load( r#" local function foo() @@ -1148,21 +1141,45 @@ fn test_inspect_stack() -> Result<()> { local function bar() return foo() end + + assert(foo() == '[string "chunk"]:3 hello') + assert(bar() == '[string "chunk"]:3 hello') + assert(logline("world") == '[string "chunk"]:12 world') + "#, + ) + .set_name("chunk") + .exec()?; + + let stack_info = lua.create_function(|lua, ()| { + let debug = lua.inspect_stack(1).unwrap(); // caller + let stack_info = debug.stack(); + Ok(format!("{stack_info:?}")) + })?; + lua.globals().set("stack_info", stack_info)?; + + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] + lua.load( + r#" local stack_info = stack_info local function baz(a, b, c, ...) return stack_info() end + assert(baz() == 'DebugStack { num_ups: 1, num_params: 3, is_vararg: true }') + "#, + ) + .exec()?; - assert(foo() == '[string "chunk"]:3 hello') - assert(bar() == '[string "chunk"]:3 hello') - assert(logline("world") == '[string "chunk"]:16 world') - assert( - baz() == 'DebugStack { num_ups: 1, num_params: 3, is_vararg: true }' or - baz() == 'DebugStack { num_ups: 1 }' - ) + // LuaJIT does not pass this test for some reason + #[cfg(feature = "lua51")] + lua.load( + r#" + local stack_info = stack_info + local function baz(a, b, c, ...) + return stack_info() + end + assert(baz() == 'DebugStack { num_ups: 1 }') "#, ) - .set_name("chunk") .exec()?; Ok(()) From e122f9083775b38a03b14a9b8b282913f433721f Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 19 Oct 2024 11:20:25 +0100 Subject: [PATCH 244/635] More `Either` tests --- tests/conversion.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/conversion.rs b/tests/conversion.rs index 0bad4958..a9671e0e 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -474,6 +474,7 @@ fn test_either_enum() -> Result<()> { *either.as_mut().left().unwrap() = 44; assert_eq!(*either.as_ref().left().unwrap(), 44); assert_eq!(format!("{either}"), "44"); + assert_eq!(either.right(), None); // Right either = Either::Right("hello".to_string()); @@ -482,6 +483,7 @@ fn test_either_enum() -> Result<()> { *either.as_mut().right().unwrap() = "world".to_string(); assert_eq!(*either.as_ref().right().unwrap(), "world"); assert_eq!(format!("{either}"), "world"); + assert_eq!(either.left(), None); Ok(()) } @@ -492,8 +494,10 @@ fn test_either_into_lua() -> Result<()> { // Direct conversion let mut either = Either::::Left(42); - let value = either.into_lua(&lua)?; - assert_eq!(value, Value::Integer(42)); + assert_eq!(either.into_lua(&lua)?, Value::Integer(42)); + let t = lua.create_table()?; + either = Either::Right(&t); + assert!(matches!(either.into_lua(&lua)?, Value::Table(_))); // Push into stack let f = @@ -514,6 +518,19 @@ fn test_either_into_lua() -> Result<()> { fn test_either_from_lua() -> Result<()> { let lua = Lua::new(); + // From value + let mut either = lua.unpack::>(Value::Integer(42))?; + assert!(either.is_left()); + assert_eq!(*either.as_ref().left().unwrap(), 42); + let t = lua.create_table()?; + either = lua.unpack::>(Value::Table(t.clone()))?; + assert!(either.is_right()); + assert_eq!(either.as_ref().right().unwrap(), &t); + match lua.unpack::>(Value::String(lua.create_string("abc")?)) { + Err(Error::FromLuaConversionError { to, .. }) => assert_eq!(to, "Either"), + _ => panic!("expected `Error::FromLuaConversionError`"), + } + // From stack let f = lua.create_function(|_, either: Either| Ok(either))?; let either = f.call::>(42)?; From 08545224f4e81032356bcae15de73ad4b24a9bd2 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 19 Oct 2024 11:49:40 +0100 Subject: [PATCH 245/635] clippy --- src/error.rs | 4 ++-- src/serde/de.rs | 2 +- src/state.rs | 2 +- src/state/util.rs | 2 +- src/table.rs | 6 +++--- src/thread.rs | 2 +- src/userdata.rs | 2 +- src/userdata/cell.rs | 10 +++++----- src/value.rs | 2 +- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/error.rs b/src/error.rs index d84d41ce..db433e66 100644 --- a/src/error.rs +++ b/src/error.rs @@ -328,7 +328,7 @@ impl StdError for Error { // returns nothing. Error::CallbackError { .. } => None, Error::ExternalError(err) => err.source(), - Error::WithContext { cause, .. } => Self::source(&cause), + Error::WithContext { cause, .. } => Self::source(cause), _ => None, } } @@ -354,7 +354,7 @@ impl Error { { match self { Error::ExternalError(err) => err.downcast_ref(), - Error::WithContext { cause, .. } => Self::downcast_ref(&cause), + Error::WithContext { cause, .. } => Self::downcast_ref(cause), _ => None, } } diff --git a/src/serde/de.rs b/src/serde/de.rs index 4e4da584..6c2b2b20 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -498,7 +498,7 @@ struct MapDeserializer<'a> { processed: usize, } -impl<'a> MapDeserializer<'a> { +impl MapDeserializer<'_> { fn next_key_deserializer(&mut self) -> Result> { loop { match self.pairs.next() { diff --git a/src/state.rs b/src/state.rs index 18eb335c..7c9f66e0 100644 --- a/src/state.rs +++ b/src/state.rs @@ -306,7 +306,7 @@ impl Lua { R::from_stack_multi(nresults, &lua) } - /// FIXME: Deprecated load_from_std_lib + // FIXME: Deprecated load_from_std_lib /// Loads the specified subset of the standard libraries into an existing Lua state. /// diff --git a/src/state/util.rs b/src/state/util.rs index 49c36a23..ba6339b1 100644 --- a/src/state/util.rs +++ b/src/state/util.rs @@ -18,7 +18,7 @@ impl<'a> StateGuard<'a> { } } -impl<'a> Drop for StateGuard<'a> { +impl Drop for StateGuard<'_> { fn drop(&mut self) { self.0.state.set(self.1); } diff --git a/src/table.rs b/src/table.rs index 55471331..2ad512f8 100644 --- a/src/table.rs +++ b/src/table.rs @@ -975,7 +975,7 @@ impl<'a> SerializableTable<'a> { } #[cfg(feature = "serialize")] -impl<'a> Serialize for SerializableTable<'a> { +impl Serialize for SerializableTable<'_> { fn serialize(&self, serializer: S) -> StdResult where S: Serializer, @@ -1066,7 +1066,7 @@ pub struct TablePairs<'a, K, V> { _phantom: PhantomData<(K, V)>, } -impl<'a, K, V> Iterator for TablePairs<'a, K, V> +impl Iterator for TablePairs<'_, K, V> where K: FromLua, V: FromLua, @@ -1126,7 +1126,7 @@ pub struct TableSequence<'a, V> { _phantom: PhantomData, } -impl<'a, V> Iterator for TableSequence<'a, V> +impl Iterator for TableSequence<'_, V> where V: FromLua, { diff --git a/src/thread.rs b/src/thread.rs index 1a5785a6..1669a0b4 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -522,7 +522,7 @@ impl<'lua, 'a> WakerGuard<'lua, 'a> { } #[cfg(feature = "async")] -impl<'lua, 'a> Drop for WakerGuard<'lua, 'a> { +impl Drop for WakerGuard<'_, '_> { fn drop(&mut self) { unsafe { self.lua.set_waker(self.prev) }; } diff --git a/src/userdata.rs b/src/userdata.rs index 2934f44e..ea7211e8 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -1056,7 +1056,7 @@ impl UserDataMetatable { /// [`UserDataMetatable::pairs`]: crate::UserDataMetatable::method.pairs pub struct UserDataMetatablePairs<'a, V>(TablePairs<'a, StdString, V>); -impl<'a, V> Iterator for UserDataMetatablePairs<'a, V> +impl Iterator for UserDataMetatablePairs<'_, V> where V: FromLua, { diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index a9f25b67..c48ae2fa 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -277,14 +277,14 @@ impl FromLua for UserDataRefMut { /// A type that provides read access to a userdata value (borrowing the value). pub(crate) struct UserDataBorrowRef<'a, T>(&'a UserDataVariant); -impl<'a, T> Drop for UserDataBorrowRef<'a, T> { +impl Drop for UserDataBorrowRef<'_, T> { #[inline] fn drop(&mut self) { unsafe { self.0.raw_lock().unlock_shared() }; } } -impl<'a, T> Deref for UserDataBorrowRef<'a, T> { +impl Deref for UserDataBorrowRef<'_, T> { type Target = T; #[inline] @@ -308,14 +308,14 @@ impl<'a, T> TryFrom<&'a UserDataVariant> for UserDataBorrowRef<'a, T> { pub(crate) struct UserDataBorrowMut<'a, T>(&'a UserDataVariant); -impl<'a, T> Drop for UserDataBorrowMut<'a, T> { +impl Drop for UserDataBorrowMut<'_, T> { #[inline] fn drop(&mut self) { unsafe { self.0.raw_lock().unlock_exclusive() }; } } -impl<'a, T> Deref for UserDataBorrowMut<'a, T> { +impl Deref for UserDataBorrowMut<'_, T> { type Target = T; #[inline] @@ -324,7 +324,7 @@ impl<'a, T> Deref for UserDataBorrowMut<'a, T> { } } -impl<'a, T> DerefMut for UserDataBorrowMut<'a, T> { +impl DerefMut for UserDataBorrowMut<'_, T> { #[inline] fn deref_mut(&mut self) -> &mut T { unsafe { &mut *self.0.as_ptr() } diff --git a/src/value.rs b/src/value.rs index 4c8251b5..f84829d8 100644 --- a/src/value.rs +++ b/src/value.rs @@ -689,7 +689,7 @@ impl<'a> SerializableValue<'a> { } #[cfg(feature = "serialize")] -impl<'a> Serialize for SerializableValue<'a> { +impl Serialize for SerializableValue<'_> { fn serialize(&self, serializer: S) -> StdResult where S: Serializer, From c7020770286ed749d619d47149e8c070bc74360d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 19 Oct 2024 15:08:51 +0100 Subject: [PATCH 246/635] More Lua values conversion tests --- src/conversion.rs | 8 +--- tests/conversion.rs | 96 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 7 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index f9ba62d2..b196ae95 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -243,14 +243,10 @@ impl IntoLua for Error { impl FromLua for Error { #[inline] - fn from_lua(value: Value, lua: &Lua) -> Result { + fn from_lua(value: Value, _: &Lua) -> Result { match value { Value::Error(err) => Ok(*err), - val => Ok(Error::runtime( - lua.coerce_string(val)? - .and_then(|s| Some(s.to_str().ok()?.to_owned())) - .unwrap_or_else(|| "".to_owned()), - )), + val => Ok(Error::runtime(val.to_string()?)), } } } diff --git a/tests/conversion.rs b/tests/conversion.rs index a9671e0e..2cb9b880 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -94,6 +94,21 @@ fn test_function_into_lua() -> Result<()> { Ok(()) } +#[test] +fn test_function_from_lua() -> Result<()> { + let lua = Lua::new(); + + assert!(lua.globals().get::("print").is_ok()); + match lua.globals().get::("math") { + Err(err @ Error::FromLuaConversionError { .. }) => { + assert_eq!(err.to_string(), "error converting Lua table to function"); + } + _ => panic!("expected `Error::FromLuaConversionError`"), + } + + Ok(()) +} + #[test] fn test_thread_into_lua() -> Result<()> { let lua = Lua::new(); @@ -112,6 +127,20 @@ fn test_thread_into_lua() -> Result<()> { Ok(()) } +#[test] +fn test_thread_from_lua() -> Result<()> { + let lua = Lua::new(); + + match lua.globals().get::("print") { + Err(err @ Error::FromLuaConversionError { .. }) => { + assert_eq!(err.to_string(), "error converting Lua function to thread"); + } + _ => panic!("expected `Error::FromLuaConversionError`"), + } + + Ok(()) +} + #[test] fn test_anyuserdata_into_lua() -> Result<()> { let lua = Lua::new(); @@ -130,6 +159,45 @@ fn test_anyuserdata_into_lua() -> Result<()> { Ok(()) } +#[test] +fn test_anyuserdata_from_lua() -> Result<()> { + let lua = Lua::new(); + + match lua.globals().get::("print") { + Err(err @ Error::FromLuaConversionError { .. }) => { + assert_eq!(err.to_string(), "error converting Lua function to userdata"); + } + _ => panic!("expected `Error::FromLuaConversionError`"), + } + + Ok(()) +} + +#[test] +fn test_error_conversion() -> Result<()> { + let lua = Lua::new(); + + // Any Lua value can be converted to `Error` + match lua.convert::(Error::external("external error")) { + Ok(Error::ExternalError(msg)) => assert_eq!(msg.to_string(), "external error"), + res => panic!("expected `Error::ExternalError`, got {res:?}"), + } + match lua.convert::("abc") { + Ok(Error::RuntimeError(msg)) => assert_eq!(msg, "abc"), + res => panic!("expected `Error::RuntimeError`, got {res:?}"), + } + match lua.convert::(true) { + Ok(Error::RuntimeError(msg)) => assert_eq!(msg, "true"), + res => panic!("expected `Error::RuntimeError`, got {res:?}"), + } + match lua.convert::(lua.globals()) { + Ok(Error::RuntimeError(msg)) => assert!(msg.starts_with("table:")), + res => panic!("expected `Error::RuntimeError`, got {res:?}"), + } + + Ok(()) +} + #[test] fn test_registry_value_into_lua() -> Result<()> { let lua = Lua::new(); @@ -140,7 +208,7 @@ fn test_registry_value_into_lua() -> Result<()> { let value1 = lua.pack(&r)?; let value2 = lua.pack(r)?; assert_eq!(value1.as_str().as_deref(), Some("hello, world")); - assert_eq!(value2.to_pointer(), value2.to_pointer()); + assert_eq!(value1.to_pointer(), value2.to_pointer()); // Push into stack let t = lua.create_table()?; @@ -175,6 +243,32 @@ fn test_registry_key_from_lua() -> Result<()> { Ok(()) } +#[test] +fn test_bool_into_lua() -> Result<()> { + let lua = Lua::new(); + + // Direct conversion + assert!(true.into_lua(&lua)?.is_boolean()); + + // Push into stack + let table = lua.create_table()?; + table.set("b", true)?; + assert_eq!(true, table.get::("b")?); + + Ok(()) +} + +#[test] +fn test_bool_from_lua() -> Result<()> { + let lua = Lua::new(); + + assert!(lua.globals().get::("print")?); + assert!(lua.convert::(123)?); + assert!(!lua.convert::(Value::Nil)?); + + Ok(()) +} + #[test] fn test_integer_from_lua() -> Result<()> { let lua = Lua::new(); From 2c756e59585962f534421aa57bd92e2b3efe3e56 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 19 Oct 2024 15:13:42 +0100 Subject: [PATCH 247/635] Add back `Lua::load_from_std_lib` (with deprecated flag) --- src/state.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/state.rs b/src/state.rs index 7c9f66e0..ffd2b3aa 100644 --- a/src/state.rs +++ b/src/state.rs @@ -306,7 +306,11 @@ impl Lua { R::from_stack_multi(nresults, &lua) } - // FIXME: Deprecated load_from_std_lib + #[doc(hidden)] + #[deprecated(since = "0.10.0", note = "please use `load_std_libs` instead")] + pub fn load_from_std_lib(&self, libs: StdLib) -> Result<()> { + self.load_std_libs(libs) + } /// Loads the specified subset of the standard libraries into an existing Lua state. /// From a020b2b5b21753055ae778f5aa2d54e74357f0e0 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 19 Oct 2024 15:14:09 +0100 Subject: [PATCH 248/635] Remove functions deprecated in v0.9 --- src/userdata.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/userdata.rs b/src/userdata.rs index ea7211e8..4a668f6e 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -739,12 +739,6 @@ impl AnyUserData { self.nth_user_value(1) } - #[doc(hidden)] - #[deprecated(since = "0.9.0", note = "please use `user_value` instead")] - pub fn get_user_value(&self) -> Result { - self.nth_user_value(1) - } - /// Sets an associated `n`th value to this `AnyUserData`. /// /// The value may be any Lua value whatsoever, and can be retrieved with [`nth_user_value`]. @@ -818,12 +812,6 @@ impl AnyUserData { } } - #[doc(hidden)] - #[deprecated(since = "0.9.0", note = "please use `nth_user_value` instead")] - pub fn get_nth_user_value(&self, n: usize) -> Result { - self.nth_user_value(n) - } - /// Sets an associated value to this `AnyUserData` by name. /// /// The value can be retrieved with [`named_user_value`]. @@ -880,12 +868,6 @@ impl AnyUserData { } } - #[doc(hidden)] - #[deprecated(since = "0.9.0", note = "please use `named_user_value` instead")] - pub fn get_named_user_value(&self, name: &str) -> Result { - self.named_user_value(name) - } - /// Returns a metatable of this `UserData`. /// /// Returned [`UserDataMetatable`] object wraps the original metatable and From 93a1a55aaaae48c0a1f92f8e6f8c8eb88dc01b28 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 19 Oct 2024 23:10:30 +0100 Subject: [PATCH 249/635] Update docs --- src/chunk.rs | 13 +-- src/error.rs | 6 +- src/function.rs | 8 +- src/hook.rs | 6 +- src/lib.rs | 37 +++---- src/multi.rs | 5 +- src/scope.rs | 7 +- src/serde/de.rs | 4 +- src/serde/mod.rs | 6 -- src/state.rs | 196 ++++++++++++++++++++------------------ src/state/raw.rs | 12 +-- src/string.rs | 10 +- src/table.rs | 50 +++++----- src/thread.rs | 44 +++++---- src/traits.rs | 13 +-- src/types.rs | 2 +- src/types/registry_key.rs | 9 +- src/userdata.rs | 176 ++++++++++++++-------------------- src/userdata/cell.rs | 4 +- src/value.rs | 16 +++- 20 files changed, 298 insertions(+), 326 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index 37176adf..0aa3e4d4 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -14,7 +14,6 @@ use crate::traits::{FromLuaMulti, IntoLuaMulti}; /// Trait for types [loadable by Lua] and convertible to a [`Chunk`] /// /// [loadable by Lua]: https://www.lua.org/manual/5.4/manual.html#3.3.2 -/// [`Chunk`]: crate::Chunk pub trait AsChunk<'a> { /// Returns optional chunk name fn name(&self) -> Option { @@ -95,8 +94,6 @@ impl AsChunk<'static> for PathBuf { } /// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks. -/// -/// [`Lua::load`]: crate::Lua::load #[must_use = "`Chunk`s do nothing unless one of `exec`, `eval`, `call`, or `into_function` are called on them"] pub struct Chunk<'a> { pub(crate) lua: WeakLua, @@ -241,7 +238,7 @@ impl Compiler { /// Compiles the `source` into bytecode. /// - /// Returns `Error::SyntaxError` if the source code is invalid. + /// Returns [`Error::SyntaxError`] if the source code is invalid. pub fn compile(&self, source: impl AsRef<[u8]>) -> Result> { use std::os::raw::c_int; use std::ptr; @@ -361,7 +358,7 @@ impl<'a> Chunk<'a> { /// /// Requires `feature = "async"` /// - /// [`exec`]: #method.exec + /// [`exec`]: Chunk::exec #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] pub async fn exec_async(self) -> Result<()> { @@ -393,7 +390,7 @@ impl<'a> Chunk<'a> { /// /// Requires `feature = "async"` /// - /// [`eval`]: #method.eval + /// [`eval`]: Chunk::eval #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] pub async fn eval_async(self) -> Result @@ -422,7 +419,7 @@ impl<'a> Chunk<'a> { /// /// Requires `feature = "async"` /// - /// [`call`]: #method.call + /// [`call`]: Chunk::call #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] pub async fn call_async(self, args: impl IntoLuaMulti) -> Result @@ -432,7 +429,7 @@ impl<'a> Chunk<'a> { self.into_function()?.call_async(args).await } - /// Load this chunk into a regular `Function`. + /// Load this chunk into a regular [`Function`]. /// /// This simply compiles the chunk without actually executing it. #[cfg_attr(not(feature = "luau"), allow(unused_mut))] diff --git a/src/error.rs b/src/error.rs index db433e66..db4d479d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -61,10 +61,12 @@ pub enum Error { /// /// Due to the way `mlua` works, it should not be directly possible to run out of stack space /// during normal use. The only way that this error can be triggered is if a `Function` is - /// called with a huge number of arguments, or a rust callback returns a huge number of return + /// called with a huge number of arguments, or a Rust callback returns a huge number of return /// values. StackError, - /// Too many arguments to `Function::bind`. + /// Too many arguments to [`Function::bind`]. + /// + /// [`Function::bind`]: crate::Function::bind BindError, /// Bad argument received from Lua (usually when calling a function). /// diff --git a/src/function.rs b/src/function.rs index 19e52c36..57e5951b 100644 --- a/src/function.rs +++ b/src/function.rs @@ -389,9 +389,9 @@ impl Function { /// If `strip` is true, the binary representation may not include all debug information /// about the function, to save space. /// - /// For Luau a [Compiler] can be used to compile Lua chunks to bytecode. + /// For Luau a [`Compiler`] can be used to compile Lua chunks to bytecode. /// - /// [Compiler]: crate::chunk::Compiler + /// [`Compiler`]: crate::chunk::Compiler #[cfg(not(feature = "luau"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub fn dump(&self, strip: bool) -> Vec { @@ -490,10 +490,10 @@ impl Function { /// /// Copies the function prototype and all its upvalues to the /// newly created function. - /// /// This function returns shallow clone (same handle) for Rust/C functions. + /// /// Requires `feature = "luau"` - #[cfg(feature = "luau")] + #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn deep_clone(&self) -> Self { let lua = self.0.lua.lock(); diff --git a/src/hook.rs b/src/hook.rs index 3f38c6d5..d650dd81 100644 --- a/src/hook.rs +++ b/src/hook.rs @@ -16,9 +16,9 @@ use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str}; /// The `Debug` structure is provided as a parameter to the hook function set with /// [`Lua::set_hook`]. You may call the methods on this structure to retrieve information about the /// Lua code executing at the time that the hook function was called. Further information can be -/// found in the Lua [documentation][lua_doc]. +/// found in the Lua [documentation]. /// -/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#lua_Debug +/// [documentation]: https://www.lua.org/manual/5.4/manual.html#lua_Debug /// [`Lua::set_hook`]: crate::Lua::set_hook pub struct Debug<'a> { lua: EitherLua<'a>, @@ -66,7 +66,7 @@ impl<'a> Debug<'a> { /// Returns the specific event that triggered the hook. /// - /// For [Lua 5.1] `DebugEvent::TailCall` is used for return events to indicate a return + /// For [Lua 5.1] [`DebugEvent::TailCall`] is used for return events to indicate a return /// from a function that did a tail call. /// /// [Lua 5.1]: https://www.lua.org/manual/5.1/manual.html#pdf-LUA_HOOKTAILRET diff --git a/src/lib.rs b/src/lib.rs index 3769d181..013e253a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,41 +32,32 @@ //! [`serde::Serialize`] or [`serde::Deserialize`] can be converted. //! For convenience, additional functionality to handle `NULL` values and arrays is provided. //! -//! The [`Value`] enum implements [`serde::Serialize`] trait to support serializing Lua values -//! (including [`UserData`]) into Rust values. +//! The [`Value`] enum and other types implement [`serde::Serialize`] trait to support serializing +//! Lua values into Rust values. //! //! Requires `feature = "serialize"`. //! //! # Async/await support //! -//! The [`create_async_function`] allows creating non-blocking functions that returns [`Future`]. -//! Lua code with async capabilities can be executed by [`call_async`] family of functions or -//! polling [`AsyncThread`] using any runtime (eg. Tokio). +//! The [`Lua::create_async_function`] allows creating non-blocking functions that returns +//! [`Future`]. Lua code with async capabilities can be executed by [`Function::call_async`] family +//! of functions or polling [`AsyncThread`] using any runtime (eg. Tokio). //! //! Requires `feature = "async"`. //! -//! # `Send` requirement +//! # `Send` and `Sync` support +//! //! By default `mlua` is `!Send`. This can be changed by enabling `feature = "send"` that adds -//! `Send` requirement to [`Function`]s and [`UserData`]. +//! `Send` requirement to Rust functions and [`UserData`] types. +//! +//! In this case [`Lua`] object and their types can be send or used from other threads. Internally +//! access to Lua VM is synchronized using a reentrant mutex that can be locked many times within +//! the same thread. //! //! [Lua programming language]: https://www.lua.org/ -//! [`Lua`]: crate::Lua //! [executing]: crate::Chunk::exec //! [evaluating]: crate::Chunk::eval //! [globals]: crate::Lua::globals -//! [`IntoLua`]: crate::IntoLua -//! [`FromLua`]: crate::FromLua -//! [`IntoLuaMulti`]: crate::IntoLuaMulti -//! [`FromLuaMulti`]: crate::FromLuaMulti -//! [`Function`]: crate::Function -//! [`UserData`]: crate::UserData -//! [`UserDataFields`]: crate::UserDataFields -//! [`UserDataMethods`]: crate::UserDataMethods -//! [`LuaSerdeExt`]: crate::LuaSerdeExt -//! [`Value`]: crate::Value -//! [`create_async_function`]: crate::Lua::create_async_function -//! [`call_async`]: crate::Function::call_async -//! [`AsyncThread`]: crate::AsyncThread //! [`Future`]: std::future::Future //! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html //! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html @@ -199,10 +190,6 @@ extern crate mlua_derive; /// - The `//` (floor division) operator is unusable, as its start a comment. /// /// Everything else should work. -/// -/// [`AsChunk`]: crate::AsChunk -/// [`UserData`]: crate::UserData -/// [`IntoLua`]: crate::IntoLua #[cfg(feature = "macros")] #[cfg_attr(docsrs, doc(cfg(feature = "macros")))] pub use mlua_derive::chunk; diff --git a/src/multi.rs b/src/multi.rs index 3619588f..aded0b64 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -11,7 +11,7 @@ use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::util::check_stack; use crate::value::{Nil, Value}; -/// Result is convertible to `MultiValue` following the common Lua idiom of returning the result +/// Result is convertible to [`MultiValue`] following the common Lua idiom of returning the result /// on success, or in the case of an error, returning `nil` and an error message. impl IntoLuaMulti for StdResult { #[inline] @@ -203,9 +203,6 @@ impl FromLuaMulti for MultiValue { /// # Ok(()) /// # } /// ``` -/// -/// [`FromLua`]: crate::FromLua -/// [`MultiValue`]: crate::MultiValue #[derive(Debug, Clone)] pub struct Variadic(Vec); diff --git a/src/scope.rs b/src/scope.rs index 0a0e5beb..0aa3b897 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -148,14 +148,15 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { /// [`Lua::scope`] for more details. /// /// The main limitation that comes from using non-'static userdata is that the produced userdata - /// will no longer have a `TypeId` associated with it, because `TypeId` can only work for + /// will no longer have a [`TypeId`] associated with it, because [`TypeId`] can only work for /// `'static` types. This means that it is impossible, once the userdata is created, to get a - /// reference to it back *out* of an `AnyUserData` handle. This also implies that the + /// reference to it back *out* of an [`AnyUserData`] handle. This also implies that the /// "function" type methods that can be added via [`UserDataMethods`] (the ones that accept - /// `AnyUserData` as a first parameter) are vastly less useful. Also, there is no way to re-use + /// [`AnyUserData`] as a first parameter) are vastly less useful. Also, there is no way to re-use /// a single metatable for multiple non-'static types, so there is a higher cost associated with /// creating the userdata metatable each time a new userdata is created. /// + /// [`TypeId`]: std::any::TypeId /// [`UserDataMethods`]: crate::UserDataMethods pub fn create_userdata(&'scope self, data: T) -> Result where diff --git a/src/serde/de.rs b/src/serde/de.rs index 6c2b2b20..f2a37833 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -94,12 +94,12 @@ impl Options { } impl Deserializer { - /// Creates a new Lua Deserializer for the `Value`. + /// Creates a new Lua Deserializer for the [`Value`]. pub fn new(value: Value) -> Self { Self::new_with_options(value, Options::default()) } - /// Creates a new Lua Deserializer for the `Value` with custom options. + /// Creates a new Lua Deserializer for the [`Value`] with custom options. pub fn new_with_options(value: Value, options: Options) -> Self { Deserializer { value, diff --git a/src/serde/mod.rs b/src/serde/mod.rs index 599960ed..2ba30e56 100644 --- a/src/serde/mod.rs +++ b/src/serde/mod.rs @@ -106,8 +106,6 @@ pub trait LuaSerdeExt: Sealed { /// /// Requires `feature = "serialize"` /// - /// [`Value`]: crate::Value - /// /// # Example /// /// ``` @@ -133,8 +131,6 @@ pub trait LuaSerdeExt: Sealed { /// /// Requires `feature = "serialize"` /// - /// [`Value`]: crate::Value - /// /// # Example /// /// ``` @@ -164,8 +160,6 @@ pub trait LuaSerdeExt: Sealed { /// /// Requires `feature = "serialize"` /// - /// [`Value`]: crate::Value - /// /// # Example /// /// ``` diff --git a/src/state.rs b/src/state.rs index ffd2b3aa..5188bfdf 100644 --- a/src/state.rs +++ b/src/state.rs @@ -171,12 +171,10 @@ impl Lua { /// Creates a new Lua state and loads the **safe** subset of the standard libraries. /// /// # Safety - /// The created Lua state would have _some_ safety guarantees and would not allow to load unsafe + /// The created Lua state will have _some_ safety guarantees and will not allow to load unsafe /// standard libraries or C modules. /// /// See [`StdLib`] documentation for a list of unsafe modules that cannot be loaded. - /// - /// [`StdLib`]: crate::StdLib pub fn new() -> Lua { mlua_expect!( Self::new_with(StdLib::ALL_SAFE, LuaOptions::default()), @@ -187,7 +185,7 @@ impl Lua { /// Creates a new Lua state and loads all the standard libraries. /// /// # Safety - /// The created Lua state would not have safety guarantees and would allow to load C modules. + /// The created Lua state will not have safety guarantees and will allow to load C modules. pub unsafe fn unsafe_new() -> Lua { Self::unsafe_new_with(StdLib::ALL, LuaOptions::default()) } @@ -197,12 +195,10 @@ impl Lua { /// Use the [`StdLib`] flags to specify the libraries you want to load. /// /// # Safety - /// The created Lua state would have _some_ safety guarantees and would not allow to load unsafe + /// The created Lua state will have _some_ safety guarantees and will not allow to load unsafe /// standard libraries or C modules. /// /// See [`StdLib`] documentation for a list of unsafe modules that cannot be loaded. - /// - /// [`StdLib`]: crate::StdLib pub fn new_with(libs: StdLib, options: LuaOptions) -> Result { #[cfg(not(feature = "luau"))] if libs.contains(StdLib::DEBUG) { @@ -222,7 +218,7 @@ impl Lua { if libs.contains(StdLib::PACKAGE) { mlua_expect!(lua.disable_c_modules(), "Error disabling C modules"); } - unsafe { lua.lock().set_safe() }; + lua.lock().mark_safe(); Ok(lua) } @@ -233,8 +229,6 @@ impl Lua { /// /// # Safety /// The created Lua state will not have safety guarantees and allow to load C modules. - /// - /// [`StdLib`]: crate::StdLib pub unsafe fn unsafe_new_with(libs: StdLib, options: LuaOptions) -> Lua { // Workaround to avoid stripping a few unused Lua symbols that could be imported // by C modules in unsafe mode @@ -289,6 +283,28 @@ impl Lua { /// /// This method ensures that the Lua instance is locked while the function is called /// and restores Lua stack after the function returns. + /// + /// # Example + /// ``` + /// # use mlua::{Lua, Result}; + /// # fn main() -> Result<()> { + /// let lua = Lua::new(); + /// let n: i32 = unsafe { + /// let nums = (3, 4, 5); + /// lua.exec_raw(nums, |state| { + /// let n = ffi::lua_gettop(state); + /// let mut sum = 0; + /// for i in 1..=n { + /// sum += ffi::lua_tointeger(state, i); + /// } + /// ffi::lua_pop(state, n); + /// ffi::lua_pushinteger(state, sum); + /// }) + /// }?; + /// assert_eq!(n, 12); + /// # Ok(()) + /// # } + /// ``` #[allow(clippy::missing_safety_doc)] pub unsafe fn exec_raw( &self, @@ -399,7 +415,7 @@ impl Lua { // Make sure that Lua is initialized let mut lua = Self::init_from_ptr(state); lua.collect_garbage = false; - // `Lua` is no longer needed and must be dropped at this point to avoid possible memory leak + // `Lua` is no longer needed and must be dropped at this point to avoid memory leak // in case of possible longjmp (lua_error) below drop(lua); @@ -444,6 +460,7 @@ impl Lua { /// /// ``` /// # use mlua::{Lua, Result}; + /// # #[cfg(feature = "luau")] /// # fn main() -> Result<()> { /// let lua = Lua::new(); /// @@ -456,10 +473,13 @@ impl Lua { /// assert_eq!(lua.globals().get::>("var")?, None); /// # Ok(()) /// # } + /// + /// # #[cfg(not(feature = "luau"))] + /// # fn main() {} /// ``` /// /// Requires `feature = "luau"` - #[cfg(any(feature = "luau", docsrs))] + #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn sandbox(&self, enabled: bool) -> Result<()> { let lua = self.lock(); @@ -484,7 +504,7 @@ impl Lua { } } - /// Sets a 'hook' function that will periodically be called as Lua code executes. + /// Sets a hook function that will periodically be called as Lua code executes. /// /// When exactly the hook function is called depends on the contents of the `triggers` /// parameter, see [`HookTriggers`] for more details. @@ -496,7 +516,7 @@ impl Lua { /// /// This method sets a hook function for the current thread of this Lua instance. /// If you want to set a hook function for another thread (coroutine), use - /// [`Thread::set_hook()`] instead. + /// [`Thread::set_hook`] instead. /// /// Please note you cannot have more than one hook function set at a time for this Lua instance. /// @@ -521,7 +541,6 @@ impl Lua { /// # } /// ``` /// - /// [`HookTriggers`]: crate::HookTriggers /// [`HookTriggers.every_nth_instruction`]: crate::HookTriggers::every_nth_instruction #[cfg(not(feature = "luau"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] @@ -533,7 +552,7 @@ impl Lua { unsafe { lua.set_thread_hook(lua.state(), triggers, callback) }; } - /// Removes any hook previously set by [`Lua::set_hook()`] or [`Thread::set_hook()`]. + /// Removes any hook previously set by [`Lua::set_hook`] or [`Thread::set_hook`]. /// /// This function has no effect if a hook was not previously set. #[cfg(not(feature = "luau"))] @@ -555,7 +574,7 @@ impl Lua { } } - /// Sets an 'interrupt' function that will periodically be called by Luau VM. + /// Sets an interrupt function that will periodically be called by Luau VM. /// /// Any Luau code is guaranteed to call this handler "eventually" /// (in practice this can happen at any function call or at any loop iteration). @@ -574,6 +593,7 @@ impl Lua { /// ``` /// # use std::sync::{Arc, atomic::{AtomicU64, Ordering}}; /// # use mlua::{Lua, Result, ThreadStatus, VmState}; + /// # #[cfg(feature = "luau")] /// # fn main() -> Result<()> { /// let lua = Lua::new(); /// let count = Arc::new(AtomicU64::new(0)); @@ -596,8 +616,11 @@ impl Lua { /// } /// # Ok(()) /// # } + /// + /// # #[cfg(not(feature = "luau"))] + /// # fn main() {} /// ``` - #[cfg(any(feature = "luau", docsrs))] + #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn set_interrupt(&self, callback: F) where @@ -635,10 +658,10 @@ impl Lua { } } - /// Removes any 'interrupt' previously set by `set_interrupt`. + /// Removes any interrupt function previously set by `set_interrupt`. /// /// This function has no effect if an 'interrupt' was not previously set. - #[cfg(any(feature = "luau", docsrs))] + #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn remove_interrupt(&self) { let lua = self.lock(); @@ -759,8 +782,8 @@ impl Lua { /// Sets a memory limit (in bytes) on this Lua state. /// - /// Once an allocation occurs that would pass this memory limit, - /// a `Error::MemoryError` is generated instead. + /// Once an allocation occurs that would pass this memory limit, a `Error::MemoryError` is + /// generated instead. /// Returns previous limit (zero means no limit). /// /// Does not work in module mode where Lua state is managed externally. @@ -774,7 +797,7 @@ impl Lua { } } - /// Returns true if the garbage collector is currently running automatically. + /// Returns `true` if the garbage collector is currently running automatically. /// /// Requires `feature = "lua54/lua53/lua52/luau"` #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] @@ -809,7 +832,7 @@ impl Lua { /// Steps the garbage collector one indivisible step. /// - /// Returns true if this has finished a collection cycle. + /// Returns `true` if this has finished a collection cycle. pub fn gc_step(&self) -> Result { self.gc_step_kbytes(0) } @@ -828,9 +851,9 @@ impl Lua { } } - /// Sets the 'pause' value of the collector. + /// Sets the `pause` value of the collector. /// - /// Returns the previous value of 'pause'. More information can be found in the Lua + /// Returns the previous value of `pause`. More information can be found in the Lua /// [documentation]. /// /// For Luau this parameter sets GC goal @@ -846,9 +869,9 @@ impl Lua { } } - /// Sets the 'step multiplier' value of the collector. + /// Sets the `step multiplier` value of the collector. /// - /// Returns the previous value of the 'step multiplier'. More information can be found in the + /// Returns the previous value of the `step multiplier`. More information can be found in the /// Lua [documentation]. /// /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 @@ -989,9 +1012,10 @@ impl Lua { } } - /// Create and return an interned Lua string. Lua strings can be arbitrary `[u8]` data including - /// embedded nulls, so in addition to `&str` and `&String`, you can also pass plain `&[u8]` - /// here. + /// Create and return an interned Lua string. + /// + /// Lua strings can be arbitrary `[u8]` data including embedded nulls, so in addition to `&str` + /// and `&String`, you can also pass plain `&[u8]` here. #[inline] pub fn create_string(&self, s: impl AsRef<[u8]>) -> Result { unsafe { self.lock().create_string(s) } @@ -1002,8 +1026,8 @@ impl Lua { /// Requires `feature = "luau"` /// /// [buffer]: https://luau-lang.org/library#buffer-library - #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result { let lua = self.lock(); let state = lua.state(); @@ -1014,20 +1038,23 @@ impl Lua { } let _sg = StackGuard::new(state); - check_stack(state, 4)?; + check_stack(state, 3)?; crate::util::push_buffer(state, buf.as_ref(), true)?; Ok(Buffer(lua.pop_ref())) } } /// Creates and returns a new empty table. + #[inline] pub fn create_table(&self) -> Result
{ self.create_table_with_capacity(0, 0) } /// Creates and returns a new empty table, with the specified capacity. - /// `narr` is a hint for how many elements the table will have as a sequence; - /// `nrec` is a hint for how many other elements the table will have. + /// + /// - `narr` is a hint for how many elements the table will have as a sequence. + /// - `nrec` is a hint for how many other elements the table will have. + /// /// Lua may use these hints to preallocate memory for the new table. pub fn create_table_with_capacity(&self, narr: usize, nrec: usize) -> Result
{ unsafe { self.lock().create_table_with_capacity(narr, nrec) } @@ -1113,9 +1140,6 @@ impl Lua { /// # Ok(()) /// # } /// ``` - /// - /// [`IntoLua`]: crate::IntoLua - /// [`IntoLuaMulti`]: crate::IntoLuaMulti pub fn create_function(&self, func: F) -> Result where F: Fn(&Lua, A) -> Result + MaybeSend + 'static, @@ -1130,10 +1154,7 @@ impl Lua { /// Wraps a Rust mutable closure, creating a callable Lua function handle to it. /// - /// This is a version of [`create_function`] that accepts a FnMut argument. Refer to - /// [`create_function`] for more information about the implementation. - /// - /// [`create_function`]: #method.create_function + /// This is a version of [`Lua::create_function`] that accepts a `FnMut` argument. pub fn create_function_mut(&self, func: F) -> Result where F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, @@ -1162,9 +1183,9 @@ impl Lua { /// call `yield()` passing internal representation of a `Poll::Pending` value. /// /// The function must be called inside Lua coroutine ([`Thread`]) to be able to suspend its - /// execution. An executor should be used to poll [`AsyncThread`] and mlua will take a - /// provided Waker in that case. Otherwise noop waker will be used if try to call the - /// function outside of Rust executors. + /// execution. An executor should be used to poll [`AsyncThread`] and mlua will take a provided + /// Waker in that case. Otherwise noop waker will be used if try to call the function outside of + /// Rust executors. /// /// The family of `call_async()` functions takes care about creating [`Thread`]. /// @@ -1193,7 +1214,6 @@ impl Lua { /// } /// ``` /// - /// [`Thread`]: crate::Thread /// [`AsyncThread`]: crate::AsyncThread #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] @@ -1250,7 +1270,7 @@ impl Lua { /// Creates a Lua userdata object from a custom Rust type. /// - /// You can register the type using [`Lua::register_userdata_type()`] to add fields or methods + /// You can register the type using [`Lua::register_userdata_type`] to add fields or methods /// _before_ calling this method. /// Otherwise, the userdata object will have an empty metatable. /// @@ -1265,7 +1285,7 @@ impl Lua { /// Creates a Lua userdata object from a custom serializable Rust type. /// - /// See [`Lua::create_any_userdata()`] for more details. + /// See [`Lua::create_any_userdata`] for more details. /// /// Requires `feature = "serialize"` #[cfg(feature = "serialize")] @@ -1423,9 +1443,10 @@ impl Lua { } } - /// Returns a handle to the active `Thread`. For calls to `Lua` this will be the main Lua - /// thread, for parameters given to a callback, this will be whatever Lua thread called the - /// callback. + /// Returns a handle to the active `Thread`. + /// + /// For calls to `Lua` this will be the main Lua thread, for parameters given to a callback, + /// this will be whatever Lua thread called the callback. pub fn current_thread(&self) -> Thread { let lua = self.lock(); let state = lua.state(); @@ -1437,26 +1458,16 @@ impl Lua { } } - /// Calls the given function with a `Scope` parameter, giving the function the ability to create - /// userdata and callbacks from rust types that are !Send or non-'static. + /// Calls the given function with a [`Scope`] parameter, giving the function the ability to + /// create userdata and callbacks from Rust types that are `!Send`` or non-`'static`. /// - /// The lifetime of any function or userdata created through `Scope` lasts only until the + /// The lifetime of any function or userdata created through [`Scope`] lasts only until the /// completion of this method call, on completion all such created values are automatically /// dropped and Lua references to them are invalidated. If a script accesses a value created - /// through `Scope` outside of this method, a Lua error will result. Since we can ensure the - /// lifetime of values created through `Scope`, and we know that `Lua` cannot be sent to another - /// thread while `Scope` is live, it is safe to allow !Send datatypes and whose lifetimes only - /// outlive the scope lifetime. - /// - /// Inside the scope callback, all handles created through Scope will share the same unique 'lua - /// lifetime of the parent `Lua`. This allows scoped and non-scoped values to be mixed in - /// API calls, which is very useful (e.g. passing a scoped userdata to a non-scoped function). - /// However, this also enables handles to scoped values to be trivially leaked from the given - /// callback. This is not dangerous, though! After the callback returns, all scoped values are - /// invalidated, which means that though references may exist, the Rust types backing them have - /// dropped. `Function` types will error when called, and `AnyUserData` will be typeless. It - /// would be impossible to prevent handles to scoped values from escaping anyway, since you - /// would always be able to smuggle them through Lua state. + /// through [`Scope`] outside of this method, a Lua error will result. Since we can ensure the + /// lifetime of values created through [`Scope`], and we know that [`Lua`] cannot be sent to + /// another thread while [`Scope`] is live, it is safe to allow `!Send` data types and whose + /// lifetimes only outlive the scope lifetime. pub fn scope<'env, R>( &self, f: impl for<'scope> FnOnce(&'scope mut Scope<'scope, 'env>) -> Result, @@ -1548,41 +1559,41 @@ impl Lua { }) } - /// Converts a value that implements `IntoLua` into a `Value` instance. + /// Converts a value that implements [`IntoLua`] into a [`Value`] instance. #[inline] pub fn pack(&self, t: impl IntoLua) -> Result { t.into_lua(self) } - /// Converts a `Value` instance into a value that implements `FromLua`. + /// Converts a [`Value`] instance into a value that implements [`FromLua`]. #[inline] pub fn unpack(&self, value: Value) -> Result { T::from_lua(value, self) } - /// Converts a value that implements `IntoLua` into a `FromLua` variant. + /// Converts a value that implements [`IntoLua`] into a [`FromLua`] variant. #[inline] pub fn convert(&self, value: impl IntoLua) -> Result { U::from_lua(value.into_lua(self)?, self) } - /// Converts a value that implements `IntoLuaMulti` into a `MultiValue` instance. + /// Converts a value that implements [`IntoLuaMulti`] into a [`MultiValue`] instance. #[inline] pub fn pack_multi(&self, t: impl IntoLuaMulti) -> Result { t.into_lua_multi(self) } - /// Converts a `MultiValue` instance into a value that implements `FromLuaMulti`. + /// Converts a [`MultiValue`] instance into a value that implements [`FromLuaMulti`]. #[inline] pub fn unpack_multi(&self, value: MultiValue) -> Result { T::from_lua_multi(value, self) } - /// Set a value in the Lua registry based on a string name. + /// Set a value in the Lua registry based on a string key. /// - /// This value will be available to rust from all `Lua` instances which share the same main + /// This value will be available to Rust from all Lua instances which share the same main /// state. - pub fn set_named_registry_value(&self, name: &str, t: impl IntoLua) -> Result<()> { + pub fn set_named_registry_value(&self, key: &str, t: impl IntoLua) -> Result<()> { let lua = self.lock(); let state = lua.state(); unsafe { @@ -1590,15 +1601,15 @@ impl Lua { check_stack(state, 5)?; lua.push(t)?; - rawset_field(state, ffi::LUA_REGISTRYINDEX, name) + rawset_field(state, ffi::LUA_REGISTRYINDEX, key) } } - /// Get a value from the Lua registry based on a string name. + /// Get a value from the Lua registry based on a string key. /// /// Any Lua instance which shares the underlying main state may call this method to /// get a value previously set by [`Lua::set_named_registry_value`]. - pub fn named_registry_value(&self, name: &str) -> Result + pub fn named_registry_value(&self, key: &str) -> Result where T: FromLua, { @@ -1609,7 +1620,7 @@ impl Lua { check_stack(state, 3)?; let protect = !lua.unlikely_memory_error(); - push_string(state, name.as_bytes(), protect)?; + push_string(state, key.as_bytes(), protect)?; ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX); T::from_stack(-1, &lua) @@ -1618,14 +1629,15 @@ impl Lua { /// Removes a named value in the Lua registry. /// - /// Equivalent to calling [`Lua::set_named_registry_value`] with a value of Nil. - pub fn unset_named_registry_value(&self, name: &str) -> Result<()> { - self.set_named_registry_value(name, Nil) + /// Equivalent to calling [`Lua::set_named_registry_value`] with a value of [`Nil`]. + #[inline] + pub fn unset_named_registry_value(&self, key: &str) -> Result<()> { + self.set_named_registry_value(key, Nil) } /// Place a value in the Lua registry with an auto-generated key. /// - /// This value will be available to Rust from all `Lua` instances which share the same main + /// This value will be available to Rust from all Lua instances which share the same main /// state. /// /// Be warned, garbage collection of values held inside the registry is not automatic, see @@ -1667,7 +1679,7 @@ impl Lua { } } - /// Get a value from the Lua registry by its `RegistryKey` + /// Get a value from the Lua registry by its [`RegistryKey`] /// /// Any Lua instance which shares the underlying main state may call this method to get a value /// previously placed by [`Lua::create_registry_value`]. @@ -1702,9 +1714,7 @@ impl Lua { return Err(Error::MismatchedRegistryKey); } - unsafe { - ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, key.take()); - } + unsafe { ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, key.take()) }; Ok(()) } @@ -1750,8 +1760,8 @@ impl Lua { Ok(()) } - /// Returns true if the given [`RegistryKey`] was created by a [`Lua`] which shares the - /// underlying main state with this [`Lua`] instance. + /// Returns true if the given [`RegistryKey`] was created by a Lua which shares the + /// underlying main state with this Lua instance. /// /// Other than this, methods that accept a [`RegistryKey`] will return /// [`Error::MismatchedRegistryKey`] if passed a [`RegistryKey`] that was not created with a @@ -1823,14 +1833,14 @@ impl Lua { /// - `Err(data)` if the data object of type `T` was not inserted because the container is /// currently borrowed. /// - /// See [`Lua::set_app_data()`] for examples. + /// See [`Lua::set_app_data`] for examples. pub fn try_set_app_data(&self, data: T) -> StdResult, T> { let lua = self.lock(); let extra = unsafe { &*lua.extra.get() }; extra.app_data.try_insert(data) } - /// Gets a reference to an application data object stored by [`Lua::set_app_data()`] of type + /// Gets a reference to an application data object stored by [`Lua::set_app_data`] of type /// `T`. /// /// # Panics @@ -1844,7 +1854,7 @@ impl Lua { extra.app_data.borrow(Some(guard)) } - /// Gets a mutable reference to an application data object stored by [`Lua::set_app_data()`] of + /// Gets a mutable reference to an application data object stored by [`Lua::set_app_data`] of /// type `T`. /// /// # Panics diff --git a/src/state/raw.rs b/src/state/raw.rs index c756356e..3a477642 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -129,7 +129,7 @@ impl RawLua { let extra = rawlua.lock().extra.get(); mlua_expect!( - load_from_std_lib(state, libs), + load_std_libs(state, libs), "Error during loading standard libraries" ); (*extra).libs |= libs; @@ -238,8 +238,8 @@ impl RawLua { /// Marks the Lua state as safe. #[inline(always)] - pub(super) unsafe fn set_safe(&self) { - (*self.extra.get()).safe = true; + pub(super) fn mark_safe(&self) { + unsafe { (*self.extra.get()).safe = true }; } /// Loads the specified subset of the standard libraries into an existing Lua state. @@ -263,7 +263,7 @@ impl RawLua { )); } - let res = load_from_std_lib(self.main_state, libs); + let res = load_std_libs(self.main_state, libs); // If `package` library loaded into a safe lua state then disable C modules let curr_libs = (*self.extra.get()).libs; @@ -1100,7 +1100,7 @@ impl RawLua { #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] unsafe { if !(*self.extra.get()).libs.contains(StdLib::COROUTINE) { - load_from_std_lib(self.main_state, StdLib::COROUTINE)?; + load_std_libs(self.main_state, StdLib::COROUTINE)?; (*self.extra.get()).libs |= StdLib::COROUTINE; } } @@ -1249,7 +1249,7 @@ impl RawLua { } // Uses 3 stack spaces -unsafe fn load_from_std_lib(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> { +unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> { #[inline(always)] pub unsafe fn requiref( state: *mut ffi::lua_State, diff --git a/src/string.rs b/src/string.rs index 364010f9..b8fd7c08 100644 --- a/src/string.rs +++ b/src/string.rs @@ -5,16 +5,16 @@ use std::os::raw::{c_int, c_void}; use std::string::String as StdString; use std::{cmp, fmt, slice, str}; +use crate::error::{Error, Result}; +use crate::state::Lua; +use crate::types::{LuaType, ValueRef}; + #[cfg(feature = "serialize")] use { serde::ser::{Serialize, Serializer}, std::result::Result as StdResult, }; -use crate::error::{Error, Result}; -use crate::state::Lua; -use crate::types::{LuaType, ValueRef}; - /// Handle to an internal Lua string. /// /// Unlike Rust strings, Lua strings may not be valid UTF-8. @@ -148,7 +148,7 @@ impl fmt::Debug for String { } } -// Lua strings are basically &[u8] slices, so implement PartialEq for anything resembling that. +// Lua strings are basically `&[u8]` slices, so implement `PartialEq` for anything resembling that. // // This makes our `String` comparable with `Vec`, `[u8]`, `&str` and `String`. // diff --git a/src/table.rs b/src/table.rs index 2ad512f8..b6325009 100644 --- a/src/table.rs +++ b/src/table.rs @@ -4,13 +4,6 @@ use std::marker::PhantomData; use std::os::raw::{c_int, c_void}; use std::string::String as StdString; -#[cfg(feature = "serialize")] -use { - rustc_hash::FxHashSet, - serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}, - std::{cell::RefCell, rc::Rc, result::Result as StdResult}, -}; - use crate::error::{Error, Result}; use crate::function::Function; use crate::state::{LuaGuard, RawLua}; @@ -22,6 +15,13 @@ use crate::value::{Nil, Value}; #[cfg(feature = "async")] use futures_util::future::{self, Either, Future}; +#[cfg(feature = "serialize")] +use { + rustc_hash::FxHashSet, + serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}, + std::{cell::RefCell, rc::Rc, result::Result as StdResult}, +}; + /// Handle to an internal Lua table. #[derive(Clone, PartialEq)] pub struct Table(pub(crate) ValueRef); @@ -59,7 +59,7 @@ impl Table { /// # } /// ``` /// - /// [`raw_set`]: #method.raw_set + /// [`raw_set`]: Table::raw_set pub fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { // Fast track (skip protected call) if !self.has_metatable() { @@ -106,7 +106,7 @@ impl Table { /// # } /// ``` /// - /// [`raw_get`]: #method.raw_get + /// [`raw_get`]: Table::raw_get pub fn get(&self, key: impl IntoLua) -> Result { // Fast track (skip protected call) if !self.has_metatable() { @@ -282,7 +282,9 @@ impl Table { } /// Inserts element value at position `idx` to the table, shifting up the elements from - /// `table[idx]`. The worst case complexity is O(n), where n is the table length. + /// `table[idx]`. + /// + /// The worst case complexity is O(n), where n is the table length. pub fn raw_insert(&self, idx: Integer, value: impl IntoLua) -> Result<()> { let size = self.raw_len() as Integer; if idx < 1 || idx > size + 1 { @@ -361,8 +363,8 @@ impl Table { /// Removes a key from the table. /// /// If `key` is an integer, mlua shifts down the elements from `table[key+1]`, - /// and erases element `table[key]`. The complexity is O(n) in the worst case, - /// where n is the table length. + /// and erases element `table[key]`. The complexity is `O(n)` in the worst case, + /// where `n` is the table length. /// /// For other key types this is equivalent to setting `table[key] = nil`. pub fn raw_remove(&self, key: impl IntoLua) -> Result<()> { @@ -437,9 +439,8 @@ impl Table { /// Returns the result of the Lua `#` operator. /// - /// This might invoke the `__len` metamethod. Use the [`raw_len`] method if that is not desired. - /// - /// [`raw_len`]: #method.raw_len + /// This might invoke the `__len` metamethod. Use the [`Table::raw_len`] method if that is not + /// desired. pub fn len(&self) -> Result { // Fast track (skip protected call) if !self.has_metatable() { @@ -491,7 +492,9 @@ impl Table { /// Returns a reference to the metatable of this table, or `None` if no metatable is set. /// - /// Unlike the `getmetatable` Lua function, this method ignores the `__metatable` field. + /// Unlike the [`getmetatable`] Lua function, this method ignores the `__metatable` field. + /// + /// [`getmetatable`]: https://www.lua.org/manual/5.4/manual.html#pdf-getmetatable pub fn metatable(&self) -> Option
{ let lua = self.0.lua.lock(); let state = lua.state(); @@ -621,7 +624,6 @@ impl Table { /// # } /// ``` /// - /// [`Result`]: crate::Result /// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next pub fn pairs(&self) -> TablePairs { TablePairs { @@ -688,10 +690,6 @@ impl Table { /// # Ok(()) /// # } /// ``` - /// - /// [`pairs`]: #method.pairs - /// [`Result`]: crate::Result - /// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next pub fn sequence_values(&self) -> TableSequence { TableSequence { guard: self.0.lua.lock(), @@ -701,7 +699,7 @@ impl Table { } } - #[cfg(feature = "serialize")] + /// Iterates over the sequence part of the table, invoking the given closure on each value. pub(crate) fn for_each_value(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()> where V: FromLua, @@ -860,6 +858,10 @@ where } } +impl LuaType for Table { + const TYPE_ID: c_int = ffi::LUA_TTABLE; +} + impl ObjectLike for Table { #[inline] fn get(&self, key: impl IntoLua) -> Result { @@ -954,10 +956,6 @@ impl Serialize for Table { } } -impl LuaType for Table { - const TYPE_ID: c_int = ffi::LUA_TTABLE; -} - #[cfg(feature = "serialize")] impl<'a> SerializableTable<'a> { #[inline] diff --git a/src/thread.rs b/src/thread.rs index 1669a0b4..aaec30a7 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -75,17 +75,17 @@ impl Thread { /// Resumes execution of this thread. /// - /// Equivalent to `coroutine.resume`. + /// Equivalent to [`coroutine.resume`]. /// - /// Passes `args` as arguments to the thread. If the coroutine has called `coroutine.yield`, it - /// will return these arguments. Otherwise, the coroutine wasn't yet started, so the arguments - /// are passed to its main function. + /// Passes `args` as arguments to the thread. If the coroutine has called [`coroutine.yield`], + /// it will return these arguments. Otherwise, the coroutine wasn't yet started, so the + /// arguments are passed to its main function. /// - /// If the thread is no longer in `Active` state (meaning it has finished execution or - /// encountered an error), this will return `Err(CoroutineInactive)`, otherwise will return `Ok` - /// as follows: + /// If the thread is no longer resumable (meaning it has finished execution or encountered an + /// error), this will return [`Error::CoroutineUnresumable`], otherwise will return `Ok` as + /// follows: /// - /// If the thread calls `coroutine.yield`, returns the values passed to `yield`. If the thread + /// If the thread calls [`coroutine.yield`], returns the values passed to `yield`. If the thread /// `return`s values from its main function, returns those. /// /// # Examples @@ -114,6 +114,9 @@ impl Thread { /// # Ok(()) /// # } /// ``` + /// + /// [`coroutine.resume`]: https://www.lua.org/manual/5.4/manual.html#pdf-coroutine.resume + /// [`coroutine.yield`]: https://www.lua.org/manual/5.4/manual.html#pdf-coroutine.yield pub fn resume(&self, args: impl IntoLuaMulti) -> Result where R: FromLuaMulti, @@ -187,10 +190,10 @@ impl Thread { } } - /// Sets a 'hook' function that will periodically be called as Lua code executes. + /// Sets a hook function that will periodically be called as Lua code executes. /// - /// This function is similar or [`Lua::set_hook()`] except that it sets for the thread. - /// To remove a hook call [`Lua::remove_hook()`]. + /// This function is similar or [`Lua::set_hook`] except that it sets for the thread. + /// To remove a hook call [`Lua::remove_hook`]. #[cfg(not(feature = "luau"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub fn set_hook(&self, triggers: HookTriggers, callback: F) @@ -252,21 +255,22 @@ impl Thread { } } - /// Converts Thread to an AsyncThread which implements [`Future`] and [`Stream`] traits. + /// Converts [`Thread`] to an [`AsyncThread`] which implements [`Future`] and [`Stream`] traits. /// /// `args` are passed as arguments to the thread function for first call. - /// The object calls [`resume()`] while polling and also allows to run rust futures + /// The object calls [`resume`] while polling and also allow to run Rust futures /// to completion using an executor. /// - /// Using AsyncThread as a Stream allows to iterate through `coroutine.yield()` - /// values whereas Future version discards that values and poll until the final + /// Using [`AsyncThread`] as a [`Stream`] allow to iterate through [`coroutine.yield`] + /// values whereas [`Future`] version discards that values and poll until the final /// one (returned from the thread function). /// /// Requires `feature = "async"` /// /// [`Future`]: std::future::Future /// [`Stream`]: futures_util::stream::Stream - /// [`resume()`]: https://www.lua.org/manual/5.4/manual.html#lua_resume + /// [`resume`]: https://www.lua.org/manual/5.4/manual.html#lua_resume + /// [`coroutine.yield`]: https://www.lua.org/manual/5.4/manual.html#pdf-coroutine.yield /// /// # Examples /// @@ -316,7 +320,7 @@ impl Thread { /// Under the hood replaces the global environment table with a new table, /// that performs writes locally and proxies reads to caller's global environment. /// - /// This mode ideally should be used together with the global sandbox mode [`Lua::sandbox()`]. + /// This mode ideally should be used together with the global sandbox mode [`Lua::sandbox`]. /// /// Please note that Luau links environment table with chunk when loading it into Lua state. /// Therefore you need to load chunks into a thread to link with the thread environment. @@ -325,6 +329,7 @@ impl Thread { /// /// ``` /// # use mlua::{Lua, Result}; + /// # #[cfg(feature = "luau")] /// # fn main() -> Result<()> { /// let lua = Lua::new(); /// let thread = lua.create_thread(lua.create_function(|lua2, ()| { @@ -339,10 +344,13 @@ impl Thread { /// assert_eq!(lua.globals().get::>("var")?, None); /// # Ok(()) /// # } + /// + /// # #[cfg(not(feature = "luau"))] + /// # fn main() { } /// ``` /// /// Requires `feature = "luau"` - #[cfg(any(feature = "luau", docsrs))] + #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[doc(hidden)] pub fn sandbox(&self) -> Result<()> { diff --git a/src/traits.rs b/src/traits.rs index 46adfbf4..0156c018 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -13,7 +13,7 @@ use crate::value::Value; #[cfg(feature = "async")] use std::future::Future; -/// Trait for types convertible to `Value`. +/// Trait for types convertible to [`Value`]. pub trait IntoLua: Sized { /// Performs the conversion. fn into_lua(self, lua: &Lua) -> Result; @@ -29,7 +29,7 @@ pub trait IntoLua: Sized { } } -/// Trait for types convertible from `Value`. +/// Trait for types convertible from [`Value`]. pub trait FromLua: Sized { /// Performs the conversion. fn from_lua(value: Value, lua: &Lua) -> Result; @@ -71,8 +71,8 @@ pub trait FromLua: Sized { /// Trait for types convertible to any number of Lua values. /// -/// This is a generalization of `IntoLua`, allowing any number of resulting Lua values instead of -/// just one. Any type that implements `IntoLua` will automatically implement this trait. +/// This is a generalization of [`IntoLua`], allowing any number of resulting Lua values instead of +/// just one. Any type that implements [`IntoLua`] will automatically implement this trait. pub trait IntoLuaMulti: Sized { /// Performs the conversion. fn into_lua_multi(self, lua: &Lua) -> Result; @@ -97,8 +97,9 @@ pub trait IntoLuaMulti: Sized { /// Trait for types that can be created from an arbitrary number of Lua values. /// -/// This is a generalization of `FromLua`, allowing an arbitrary number of Lua values to participate -/// in the conversion. Any type that implements `FromLua` will automatically implement this trait. +/// This is a generalization of [`FromLua`], allowing an arbitrary number of Lua values to +/// participate in the conversion. Any type that implements [`FromLua`] will automatically +/// implement this trait. pub trait FromLuaMulti: Sized { /// Performs the conversion. /// diff --git a/src/types.rs b/src/types.rs index 12f40832..a5a74051 100644 --- a/src/types.rs +++ b/src/types.rs @@ -66,7 +66,7 @@ pub(crate) type AsyncCallbackUpvalue = Upvalue; #[cfg(feature = "async")] pub(crate) type AsyncPollUpvalue = Upvalue>>; -/// Type to set next Luau VM action after executing interrupt function. +/// Type to set next Lua VM action after executing interrupt or hook function. pub enum VmState { Continue, /// Yield the current thread. diff --git a/src/types/registry_key.rs b/src/types/registry_key.rs index b92b103b..6df0002e 100644 --- a/src/types/registry_key.rs +++ b/src/types/registry_key.rs @@ -12,17 +12,16 @@ use parking_lot::Mutex; /// and instances not manually removed can be garbage collected with /// [`Lua::expire_registry_values`]. /// -/// Be warned, If you place this into Lua via a [`UserData`] type or a rust callback, it is *very -/// easy* to accidentally cause reference cycles that the Lua garbage collector cannot resolve. -/// Instead of placing a [`RegistryKey`] into a [`UserData`] type, prefer instead to use -/// [`AnyUserData::set_user_value`] / [`AnyUserData::user_value`]. +/// Be warned, If you place this into Lua via a [`UserData`] type or a Rust callback, it is *easy* +/// to accidentally cause reference cycles that the Lua garbage collector cannot resolve. Instead of +/// placing a [`RegistryKey`] into a [`UserData`] type, consider to use +/// [`AnyUserData::set_user_value`]. /// /// [`UserData`]: crate::UserData /// [`RegistryKey`]: crate::RegistryKey /// [`Lua::remove_registry_value`]: crate::Lua::remove_registry_value /// [`Lua::expire_registry_values`]: crate::Lua::expire_registry_values /// [`AnyUserData::set_user_value`]: crate::AnyUserData::set_user_value -/// [`AnyUserData::user_value`]: crate::AnyUserData::user_value pub struct RegistryKey { pub(crate) registry_id: i32, pub(crate) unref_list: Arc>>>, diff --git a/src/userdata.rs b/src/userdata.rs index 4a668f6e..db1f37ae 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -5,15 +5,6 @@ use std::hash::Hash; use std::os::raw::{c_char, c_void}; use std::string::String as StdString; -#[cfg(feature = "async")] -use std::future::Future; - -#[cfg(feature = "serialize")] -use { - serde::ser::{self, Serialize, Serializer}, - std::result::Result as StdResult, -}; - use crate::error::{Error, Result}; use crate::function::Function; use crate::state::Lua; @@ -24,6 +15,15 @@ use crate::types::{MaybeSend, ValueRef}; use crate::util::{check_stack, get_userdata, push_string, take_userdata, StackGuard}; use crate::value::Value; +#[cfg(feature = "async")] +use std::future::Future; + +#[cfg(feature = "serialize")] +use { + serde::ser::{self, Serialize, Serializer}, + std::result::Result as StdResult, +}; + // Re-export for convenience pub(crate) use cell::UserDataStorage; pub use cell::{UserDataRef, UserDataRefMut}; @@ -34,8 +34,6 @@ pub use registry::UserDataRegistry; /// /// Currently, this mechanism does not allow overriding the `__gc` metamethod, since there is /// generally no need to do so: [`UserData`] implementors can instead just implement `Drop`. -/// -/// [`UserData`]: crate::UserData #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum MetaMethod { @@ -243,8 +241,6 @@ impl AsRef for MetaMethod { } /// Method registry for [`UserData`] implementors. -/// -/// [`UserData`]: crate::UserData pub trait UserDataMethods { /// Add a regular method which accepts a `&T` as the first parameter. /// @@ -263,20 +259,20 @@ pub trait UserDataMethods { /// /// Refer to [`add_method`] for more information about the implementation. /// - /// [`add_method`]: #method.add_method + /// [`add_method`]: UserDataMethods::add_method fn add_method_mut(&mut self, name: impl ToString, method: M) where M: FnMut(&Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti; - /// Add an async method which accepts a `&T` as the first parameter and returns Future. + /// Add an async method which accepts a `&T` as the first parameter and returns [`Future`]. /// /// Refer to [`add_method`] for more information about the implementation. /// /// Requires `feature = "async"` /// - /// [`add_method`]: #method.add_method + /// [`add_method`]: UserDataMethods::add_method #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] fn add_async_method(&mut self, name: impl ToString, method: M) @@ -287,13 +283,13 @@ pub trait UserDataMethods { MR: Future> + MaybeSend + 'static, R: IntoLuaMulti; - /// Add an async method which accepts a `&mut T` as the first parameter and returns Future. + /// Add an async method which accepts a `&mut T` as the first parameter and returns [`Future`]. /// /// Refer to [`add_method`] for more information about the implementation. /// /// Requires `feature = "async"` /// - /// [`add_method`]: #method.add_method + /// [`add_method`]: UserDataMethods::add_method #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] fn add_async_method_mut(&mut self, name: impl ToString, method: M) @@ -304,16 +300,11 @@ pub trait UserDataMethods { MR: Future> + MaybeSend + 'static, R: IntoLuaMulti; - /// Add a regular method as a function which accepts generic arguments, the first argument will - /// be a [`AnyUserData`] of type `T` if the method is called with Lua method syntax: - /// `my_userdata:my_method(arg1, arg2)`, or it is passed in as the first argument: - /// `my_userdata.my_method(my_userdata, arg1, arg2)`. + /// Add a regular method as a function which accepts generic arguments. /// - /// Prefer to use [`add_method`] or [`add_method_mut`] as they are easier to use. - /// - /// [`AnyUserData`]: crate::AnyUserData - /// [`add_method`]: #method.add_method - /// [`add_method_mut`]: #method.add_method_mut + /// The first argument will be a [`AnyUserData`] of type `T` if the method is called with Lua + /// method syntax: `my_userdata:my_method(arg1, arg2)`, or it is passed in as the first + /// argument: `my_userdata.my_method(my_userdata, arg1, arg2)`. fn add_function(&mut self, name: impl ToString, function: F) where F: Fn(&Lua, A) -> Result + MaybeSend + 'static, @@ -322,23 +313,23 @@ pub trait UserDataMethods { /// Add a regular method as a mutable function which accepts generic arguments. /// - /// This is a version of [`add_function`] that accepts a FnMut argument. + /// This is a version of [`add_function`] that accepts a `FnMut` argument. /// - /// [`add_function`]: #method.add_function + /// [`add_function`]: UserDataMethods::add_function fn add_function_mut(&mut self, name: impl ToString, function: F) where F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti; - /// Add a regular method as an async function which accepts generic arguments - /// and returns Future. + /// Add a regular method as an async function which accepts generic arguments and returns + /// [`Future`]. /// /// This is an async version of [`add_function`]. /// /// Requires `feature = "async"` /// - /// [`add_function`]: #method.add_function + /// [`add_function`]: UserDataMethods::add_function #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] fn add_async_function(&mut self, name: impl ToString, function: F) @@ -355,7 +346,7 @@ pub trait UserDataMethods { /// This can cause an error with certain binary metamethods that can trigger if only the right /// side has a metatable. To prevent this, use [`add_meta_function`]. /// - /// [`add_meta_function`]: #method.add_meta_function + /// [`add_meta_function`]: UserDataMethods::add_meta_function fn add_meta_method(&mut self, name: impl ToString, method: M) where M: Fn(&Lua, &T, A) -> Result + MaybeSend + 'static, @@ -369,20 +360,20 @@ pub trait UserDataMethods { /// This can cause an error with certain binary metamethods that can trigger if only the right /// side has a metatable. To prevent this, use [`add_meta_function`]. /// - /// [`add_meta_function`]: #method.add_meta_function + /// [`add_meta_function`]: UserDataMethods::add_meta_function fn add_meta_method_mut(&mut self, name: impl ToString, method: M) where M: FnMut(&Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti; - /// Add an async metamethod which accepts a `&T` as the first parameter and returns Future. + /// Add an async metamethod which accepts a `&T` as the first parameter and returns [`Future`]. /// /// This is an async version of [`add_meta_method`]. /// /// Requires `feature = "async"` /// - /// [`add_meta_method`]: #method.add_meta_method + /// [`add_meta_method`]: UserDataMethods::add_meta_method #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] fn add_async_meta_method(&mut self, name: impl ToString, method: M) @@ -393,13 +384,14 @@ pub trait UserDataMethods { MR: Future> + MaybeSend + 'static, R: IntoLuaMulti; - /// Add an async metamethod which accepts a `&mut T` as the first parameter and returns Future. + /// Add an async metamethod which accepts a `&mut T` as the first parameter and returns + /// [`Future`]. /// /// This is an async version of [`add_meta_method_mut`]. /// /// Requires `feature = "async"` /// - /// [`add_meta_method_mut`]: #method.add_meta_method_mut + /// [`add_meta_method_mut`]: UserDataMethods::add_meta_method_mut #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] fn add_async_meta_method_mut(&mut self, name: impl ToString, method: M) @@ -423,22 +415,22 @@ pub trait UserDataMethods { /// Add a metamethod as a mutable function which accepts generic arguments. /// - /// This is a version of [`add_meta_function`] that accepts a FnMut argument. + /// This is a version of [`add_meta_function`] that accepts a `FnMut` argument. /// - /// [`add_meta_function`]: #method.add_meta_function + /// [`add_meta_function`]: UserDataMethods::add_meta_function fn add_meta_function_mut(&mut self, name: impl ToString, function: F) where F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti; - /// Add a metamethod which accepts generic arguments and returns Future. + /// 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 + /// [`add_meta_function`]: UserDataMethods::add_meta_function #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] fn add_async_meta_function(&mut self, name: impl ToString, function: F) @@ -450,15 +442,13 @@ pub trait UserDataMethods { } /// Field registry for [`UserData`] implementors. -/// -/// [`UserData`]: crate::UserData pub trait UserDataFields { - /// Add a static field to the `UserData`. + /// Add a static field to the [`UserData`]. /// /// Static fields are implemented by updating the `__index` metamethod and returning the /// accessed field. This allows them to be used with the expected `userdata.field` syntax. /// - /// Static fields are usually shared between all instances of the `UserData` of the same type. + /// Static fields are usually shared between all instances of the [`UserData`] of the same type. /// /// If `add_meta_method` is used to set the `__index` metamethod, it will /// be used as a fall-back if no regular field or method are found. @@ -493,11 +483,6 @@ pub trait UserDataFields { /// Add a regular field getter as a function which accepts a generic [`AnyUserData`] of type `T` /// argument. - /// - /// Prefer to use [`add_field_method_get`] as it is easier to use. - /// - /// [`AnyUserData`]: crate::AnyUserData - /// [`add_field_method_get`]: #method.add_field_method_get fn add_field_function_get(&mut self, name: impl ToString, function: F) where F: Fn(&Lua, AnyUserData) -> Result + MaybeSend + 'static, @@ -505,11 +490,6 @@ pub trait UserDataFields { /// Add a regular field setter as a function which accepts a generic [`AnyUserData`] of type `T` /// first argument. - /// - /// Prefer to use [`add_field_method_set`] as it is easier to use. - /// - /// [`AnyUserData`]: crate::AnyUserData - /// [`add_field_method_set`]: #method.add_field_method_set fn add_field_function_set(&mut self, name: impl ToString, function: F) where F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static, @@ -517,7 +497,7 @@ pub trait UserDataFields { /// Add a metatable field. /// - /// This will initialize the metatable field with `value` on `UserData` creation. + /// This will initialize the metatable field with `value` on [`UserData`] creation. /// /// # Note /// @@ -529,7 +509,7 @@ pub trait UserDataFields { /// Add a metatable field computed from `f`. /// - /// This will initialize the metatable field from `f` on `UserData` creation. + /// This will initialize the metatable field from `f` on [`UserData`] creation. /// /// # Note /// @@ -544,6 +524,7 @@ pub trait UserDataFields { /// Trait for custom userdata types. /// /// By implementing this trait, a struct becomes eligible for use inside Lua code. +/// /// Implementation of [`IntoLua`] is automatically provided, [`FromLua`] needs to be implemented /// manually. /// @@ -603,11 +584,6 @@ pub trait UserDataFields { /// # Ok(()) /// # } /// ``` -/// -/// [`IntoLua`]: crate::IntoLua -/// [`FromLua`]: crate::FromLua -/// [`UserDataFields`]: crate::UserDataFields -/// [`UserDataMethods`]: crate::UserDataMethods pub trait UserData: Sized { /// Adds custom fields specific to this userdata. #[allow(unused_variables)] @@ -629,18 +605,14 @@ pub trait UserData: Sized { /// Handle to an internal Lua userdata for any type that implements [`UserData`]. /// -/// Similar to `std::any::Any`, this provides an interface for dynamic type checking via the [`is`] -/// and [`borrow`] methods. -/// -/// Internally, instances are stored in a `RefCell`, to best match the mutable semantics of the Lua -/// language. +/// Similar to [`std::any::Any`], this provides an interface for dynamic type checking via the +/// [`is`] and [`borrow`] methods. /// /// # Note /// /// This API should only be used when necessary. Implementing [`UserData`] already allows defining /// methods which check the type and acquire a borrow behind the scenes. /// -/// [`UserData`]: crate::UserData /// [`is`]: crate::AnyUserData::is /// [`borrow`]: crate::AnyUserData::borrow #[derive(Clone, Debug, PartialEq)] @@ -657,8 +629,12 @@ impl AnyUserData { /// /// # Errors /// - /// Returns a `UserDataBorrowError` if the userdata is already mutably borrowed. Returns a - /// `UserDataTypeMismatch` if the userdata is not of type `T` or if it's scoped. + /// Returns a [`UserDataBorrowError`] if the userdata is already mutably borrowed. + /// Returns a [`DataTypeMismatch`] if the userdata is not of type `T` or if it's + /// scoped. + /// + /// [`UserDataBorrowError`]: crate::Error::UserDataBorrowError + /// [`DataTypeMismatch`]: crate::Error::UserDataTypeMismatch #[inline] pub fn borrow(&self) -> Result> { self.inspect(|ud| ud.try_borrow_owned()) @@ -676,8 +652,12 @@ impl AnyUserData { /// /// # Errors /// - /// Returns a `UserDataBorrowMutError` if the userdata cannot be mutably borrowed. - /// Returns a `UserDataTypeMismatch` if the userdata is not of type `T` or if it's scoped. + /// Returns a [`UserDataBorrowMutError`] if the userdata cannot be mutably borrowed. + /// Returns a [`UserDataTypeMismatch`] if the userdata is not of type `T` or if it's + /// scoped. + /// + /// [`UserDataBorrowMutError`]: crate::Error::UserDataBorrowMutError + /// [`UserDataTypeMismatch`]: crate::Error::UserDataTypeMismatch #[inline] pub fn borrow_mut(&self) -> Result> { self.inspect(|ud| ud.try_borrow_owned_mut()) @@ -692,6 +672,7 @@ impl AnyUserData { } /// Takes the value out of this userdata. + /// /// Sets the special "destructed" metatable that prevents any further operations with this /// userdata. /// @@ -715,14 +696,14 @@ impl AnyUserData { } } - /// Sets an associated value to this `AnyUserData`. + /// Sets an associated value to this [`AnyUserData`]. /// /// The value may be any Lua value whatsoever, and can be retrieved with [`user_value`]. /// /// This is the same as calling [`set_nth_user_value`] with `n` set to 1. /// - /// [`user_value`]: #method.user_value - /// [`set_nth_user_value`]: #method.set_nth_user_value + /// [`user_value`]: AnyUserData::user_value + /// [`set_nth_user_value`]: AnyUserData::set_nth_user_value #[inline] pub fn set_user_value(&self, v: impl IntoLua) -> Result<()> { self.set_nth_user_value(1, v) @@ -732,23 +713,21 @@ impl AnyUserData { /// /// This is the same as calling [`nth_user_value`] with `n` set to 1. /// - /// [`set_user_value`]: #method.set_user_value - /// [`nth_user_value`]: #method.nth_user_value + /// [`set_user_value`]: AnyUserData::set_user_value + /// [`nth_user_value`]: AnyUserData::nth_user_value #[inline] pub fn user_value(&self) -> Result { self.nth_user_value(1) } - /// Sets an associated `n`th value to this `AnyUserData`. + /// Sets an associated `n`th value to this [`AnyUserData`]. /// /// The value may be any Lua value whatsoever, and can be retrieved with [`nth_user_value`]. /// `n` starts from 1 and can be up to 65535. /// - /// This is supported for all Lua versions. - /// In Lua 5.4 first 7 elements are stored in a most efficient way. - /// For other Lua versions this functionality is provided using a wrapping table. + /// This is supported for all Lua versions using a wrapping table. /// - /// [`nth_user_value`]: #method.nth_user_value + /// [`nth_user_value`]: AnyUserData::nth_user_value pub fn set_nth_user_value(&self, n: usize, v: impl IntoLua) -> Result<()> { if n < 1 || n > u16::MAX as usize { return Err(Error::runtime("user value index out of bounds")); @@ -784,11 +763,9 @@ impl AnyUserData { /// /// `n` starts from 1 and can be up to 65535. /// - /// This is supported for all Lua versions. - /// In Lua 5.4 first 7 elements are stored in a most efficient way. - /// For other Lua versions this functionality is provided using a wrapping table. + /// This is supported for all Lua versions using a wrapping table. /// - /// [`set_nth_user_value`]: #method.set_nth_user_value + /// [`set_nth_user_value`]: AnyUserData::set_nth_user_value pub fn nth_user_value(&self, n: usize) -> Result { if n < 1 || n > u16::MAX as usize { return Err(Error::runtime("user value index out of bounds")); @@ -812,11 +789,11 @@ impl AnyUserData { } } - /// Sets an associated value to this `AnyUserData` by name. + /// Sets an associated value to this [`AnyUserData`] by name. /// /// The value can be retrieved with [`named_user_value`]. /// - /// [`named_user_value`]: #method.named_user_value + /// [`named_user_value`]: AnyUserData::named_user_value pub fn set_named_user_value(&self, name: &str, v: impl IntoLua) -> Result<()> { let lua = self.0.lua.lock(); let state = lua.state(); @@ -847,7 +824,7 @@ impl AnyUserData { /// Returns an associated value by name set by [`set_named_user_value`]. /// - /// [`set_named_user_value`]: #method.set_named_user_value + /// [`set_named_user_value`]: AnyUserData::set_named_user_value pub fn named_user_value(&self, name: &str) -> Result { let lua = self.0.lua.lock(); let state = lua.state(); @@ -868,14 +845,12 @@ impl AnyUserData { } } - /// Returns a metatable of this `UserData`. + /// Returns a metatable of this [`AnyUserData`]. /// /// Returned [`UserDataMetatable`] object wraps the original metatable and /// provides safe access to its methods. /// /// For `T: 'static` returned metatable is shared among all instances of type `T`. - /// - /// [`UserDataMetatable`]: crate::UserDataMetatable #[inline] pub fn metatable(&self) -> Result { self.raw_metatable().map(UserDataMetatable) @@ -952,8 +927,8 @@ impl AnyUserData { Ok(false) } - /// Returns `true` if this `AnyUserData` is serializable (eg. was created using - /// `create_ser_userdata`). + /// Returns `true` if this [`AnyUserData`] is serializable (e.g. was created using + /// [`Lua::create_ser_userdata`]). #[cfg(feature = "serialize")] pub(crate) fn is_serializable(&self) -> bool { let lua = self.0.lua.lock(); @@ -985,7 +960,7 @@ impl AnyUserData { } } -/// Handle to a `UserData` metatable. +/// Handle to a [`AnyUserData`] metatable. #[derive(Clone, Debug)] pub struct UserDataMetatable(pub(crate) Table); @@ -1028,14 +1003,11 @@ impl UserDataMetatable { } } -/// An iterator over the pairs of a [`UserData`] metatable. +/// An iterator over the pairs of a [`AnyUserData`] metatable. /// /// It skips restricted metamethods, such as `__gc` or `__metatable`. /// /// This struct is created by the [`UserDataMetatable::pairs`] method. -/// -/// [`UserData`]: crate::UserData -/// [`UserDataMetatable::pairs`]: crate::UserDataMetatable::method.pairs pub struct UserDataMetatablePairs<'a, V>(TablePairs<'a, StdString, V>); impl Iterator for UserDataMetatablePairs<'_, V> @@ -1081,7 +1053,7 @@ pub(crate) struct WrappedUserdata Result>(F); impl AnyUserData { /// Wraps any Rust type, returning an opaque type that implements [`IntoLua`] trait. /// - /// This function uses [`Lua::create_any_userdata()`] under the hood. + /// This function uses [`Lua::create_any_userdata`] under the hood. pub fn wrap(data: T) -> impl IntoLua { WrappedUserdata(move |lua| lua.create_any_userdata(data)) } @@ -1089,7 +1061,7 @@ impl AnyUserData { /// Wraps any Rust type that implements [`Serialize`], returning an opaque type that implements /// [`IntoLua`] trait. /// - /// This function uses [`Lua::create_ser_any_userdata()`] under the hood. + /// This function uses [`Lua::create_ser_any_userdata`] under the hood. #[cfg(feature = "serialize")] #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] pub fn wrap_ser(data: T) -> impl IntoLua { diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index c48ae2fa..fe3a0190 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -145,7 +145,7 @@ impl UserDataCell { } } -/// A wrapper type for a [`UserData`] value that provides read access. +/// A wrapper type for a userdata value that provides read access. /// /// It implements [`FromLua`] and can be used to receive a typed userdata from Lua. pub struct UserDataRef(UserDataVariant); @@ -206,7 +206,7 @@ impl FromLua for UserDataRef { } } -/// A wrapper type for a mutably borrowed value from a `AnyUserData`. +/// A wrapper type for a userdata value that provides read and write access. /// /// It implements [`FromLua`] and can be used to receive a typed userdata from Lua. pub struct UserDataRefMut(UserDataVariant); diff --git a/src/value.rs b/src/value.rs index f84829d8..6c81ee55 100644 --- a/src/value.rs +++ b/src/value.rs @@ -104,10 +104,10 @@ impl Value { /// Compares two values for equality. /// /// Equality comparisons do not convert strings to numbers or vice versa. - /// Tables, Functions, Threads, and UserData are compared by reference: + /// Tables, functions, threads, and userdata are compared by reference: /// two objects are considered equal only if they are the same object. /// - /// If Tables or UserData have `__eq` metamethod then mlua will try to invoke it. + /// If table or userdata have `__eq` metamethod then mlua will try to invoke it. /// The first value is checked first. If that value does not define a metamethod /// for `__eq`, then mlua will check the second value. /// Then mlua calls the metamethod with the two values as arguments, if found. @@ -193,6 +193,8 @@ impl Value { } /// Returns `true` if the value is a [`NULL`]. + /// + /// [`NULL`]: Value::NULL #[inline] pub fn is_null(&self) -> bool { self == &Self::NULL @@ -433,9 +435,11 @@ impl Value { } } - /// Cast the value to a `Buffer`. + /// Cast the value to a [`Buffer`]. + /// + /// If the value is [`Buffer`], returns it or `None` otherwise. /// - /// If the value is `Buffer`, returns it or `None` otherwise. + /// [`Buffer`]: crate::Buffer #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[inline] @@ -446,7 +450,9 @@ impl Value { } } - /// Returns `true` if the value is a `Buffer`. + /// Returns `true` if the value is a [`Buffer`]. + /// + /// [`Buffer`]: crate::Buffer #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[inline] From ec227f905648a2e15938e3a9e2ff7ceac3426d6e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 19 Oct 2024 23:58:57 +0100 Subject: [PATCH 250/635] Optimize `Table` readonly check (Luau) Optimize `Table::has_metatable` check. --- src/table.rs | 47 ++++++++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/src/table.rs b/src/table.rs index b6325009..dc0fb565 100644 --- a/src/table.rs +++ b/src/table.rs @@ -9,7 +9,7 @@ use crate::function::Function; use crate::state::{LuaGuard, RawLua}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike}; use crate::types::{Integer, LuaType, ValueRef}; -use crate::util::{assert_stack, check_stack, StackGuard}; +use crate::util::{assert_stack, check_stack, get_metatable_ptr, StackGuard}; use crate::value::{Nil, Value}; #[cfg(feature = "async")] @@ -242,12 +242,12 @@ impl Table { /// Sets a key-value pair without invoking metamethods. pub fn raw_set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { - #[cfg(feature = "luau")] - self.check_readonly_write()?; - let lua = self.0.lua.lock(); let state = lua.state(); unsafe { + #[cfg(feature = "luau")] + self.check_readonly_write(&lua)?; + let _sg = StackGuard::new(state); check_stack(state, 5)?; @@ -312,12 +312,12 @@ impl Table { /// Appends a value to the back of the table without invoking metamethods. pub fn raw_push(&self, value: impl IntoLua) -> Result<()> { - #[cfg(feature = "luau")] - self.check_readonly_write()?; - let lua = self.0.lua.lock(); let state = lua.state(); unsafe { + #[cfg(feature = "luau")] + self.check_readonly_write(&lua)?; + let _sg = StackGuard::new(state); check_stack(state, 4)?; @@ -340,12 +340,12 @@ impl Table { /// Removes the last element from the table and returns it, without invoking metamethods. pub fn raw_pop(&self) -> Result { - #[cfg(feature = "luau")] - self.check_readonly_write()?; - let lua = self.0.lua.lock(); let state = lua.state(); unsafe { + #[cfg(feature = "luau")] + self.check_readonly_write(&lua)?; + let _sg = StackGuard::new(state); check_stack(state, 3)?; @@ -401,13 +401,13 @@ impl Table { /// /// This method is useful to clear the table while keeping its capacity. pub fn clear(&self) -> Result<()> { - #[cfg(feature = "luau")] - self.check_readonly_write()?; - let lua = self.0.lua.lock(); unsafe { #[cfg(feature = "luau")] - ffi::lua_cleartable(lua.ref_thread(), self.0.index); + { + self.check_readonly_write(&lua)?; + ffi::lua_cleartable(lua.ref_thread(), self.0.index); + } #[cfg(not(feature = "luau"))] { @@ -550,14 +550,7 @@ impl Table { #[inline] pub fn has_metatable(&self) -> bool { let lua = self.0.lua.lock(); - let ref_thread = lua.ref_thread(); - unsafe { - if ffi::lua_getmetatable(ref_thread, self.0.index) != 0 { - ffi::lua_pop(ref_thread, 1); - return true; - } - } - false + unsafe { !get_metatable_ptr(lua.ref_thread(), self.0.index).is_null() } } /// Sets `readonly` attribute on the table. @@ -724,12 +717,12 @@ impl Table { /// Sets element value at position `idx` without invoking metamethods. #[doc(hidden)] pub fn raw_seti(&self, idx: usize, value: impl IntoLua) -> Result<()> { - #[cfg(feature = "luau")] - self.check_readonly_write()?; - let lua = self.0.lua.lock(); let state = lua.state(); unsafe { + #[cfg(feature = "luau")] + self.check_readonly_write(&lua)?; + let _sg = StackGuard::new(state); check_stack(state, 5)?; @@ -765,8 +758,8 @@ impl Table { #[cfg(feature = "luau")] #[inline(always)] - pub(crate) fn check_readonly_write(&self) -> Result<()> { - if self.is_readonly() { + fn check_readonly_write(&self, lua: &RawLua) -> Result<()> { + if unsafe { ffi::lua_getreadonly(lua.ref_thread(), self.0.index) != 0 } { return Err(Error::runtime("attempt to modify a readonly table")); } Ok(()) From 5c543612366cff45cf708b6ac18f5716a1ef8e77 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 20 Oct 2024 00:47:20 +0100 Subject: [PATCH 251/635] Keep stack in `FromLuaMulti::from_stack_multi` --- src/multi.rs | 5 +---- src/traits.rs | 4 ---- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/multi.rs b/src/multi.rs index aded0b64..2dab9b94 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -287,10 +287,7 @@ macro_rules! impl_tuple { } #[inline] - unsafe fn from_stack_multi(nvals: c_int, lua: &RawLua) -> Result { - if nvals > 0 { - ffi::lua_pop(lua.state(), nvals); - } + unsafe fn from_stack_multi(_nvals: c_int, _lua: &RawLua) -> Result { Ok(()) } } diff --git a/src/traits.rs b/src/traits.rs index 0156c018..794e9c3b 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -128,10 +128,6 @@ pub trait FromLuaMulti: Sized { for idx in 0..nvals { values.push_back(lua.stack_value(-nvals + idx, None)); } - if nvals > 0 { - // It's safe to clear the stack as all references moved to ref thread - ffi::lua_pop(lua.state(), nvals); - } Self::from_lua_multi(values, lua.lua()) } From f8fe9246bbe7ce4be921885b722cf214815c102a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 20 Oct 2024 10:42:59 +0100 Subject: [PATCH 252/635] Bump TARGET_MLUA_LUAU_ABI_VERSION --- src/luau/package.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/luau/package.rs b/src/luau/package.rs index 97060e7c..fc1aa1ac 100644 --- a/src/luau/package.rs +++ b/src/luau/package.rs @@ -20,7 +20,7 @@ use {libloading::Library, rustc_hash::FxHashMap}; // #[cfg(unix)] -const TARGET_MLUA_LUAU_ABI_VERSION: u32 = 1; +const TARGET_MLUA_LUAU_ABI_VERSION: u32 = 2; #[cfg(all(unix, feature = "module"))] #[no_mangle] From 75475fc9a84047f94e0dc6d7d2013228476c211d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 20 Oct 2024 11:52:13 +0100 Subject: [PATCH 253/635] Add missing `serde::{de, ser}` top level comment --- src/serde/de.rs | 2 ++ src/serde/ser.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/serde/de.rs b/src/serde/de.rs index f2a37833..616d529d 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -1,3 +1,5 @@ +//! Deserialize Lua values to a Rust data structure. + use std::cell::RefCell; use std::os::raw::c_void; use std::rc::Rc; diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 1292e4d6..a6cac26e 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -1,3 +1,5 @@ +//! Serialize a Rust data structure into Lua value. + use serde::{ser, Serialize}; use super::LuaSerdeExt; From d64d9719c6e2f25b898778faea60fb4ba3e2a9f1 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 20 Oct 2024 12:06:08 +0100 Subject: [PATCH 254/635] Replace `Either` enum with implementation from `either` crate --- Cargo.toml | 1 + src/conversion.rs | 58 ++++++++++++++++++- src/types.rs | 1 - src/types/either.rs | 135 -------------------------------------------- 4 files changed, 58 insertions(+), 137 deletions(-) delete mode 100644 src/types/either.rs diff --git a/Cargo.toml b/Cargo.toml index 42365634..43affd2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ anyhow = ["dep:anyhow"] [dependencies] mlua_derive = { version = "=0.10.0-rc.1", optional = true, path = "mlua_derive" } bstr = { version = "1.0", features = ["std"], default-features = false } +either = "1.0" num-traits = { version = "0.2.14" } rustc-hash = "2.0" futures-util = { version = "0.3", optional = true, default-features = false, features = ["std"] } diff --git a/src/conversion.rs b/src/conversion.rs index b196ae95..f93cdfcd 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -17,7 +17,7 @@ use crate::string::String; use crate::table::Table; use crate::thread::Thread; use crate::traits::{FromLua, IntoLua, ShortTypeName as _}; -use crate::types::{LightUserData, MaybeSend, RegistryKey}; +use crate::types::{Either, LightUserData, MaybeSend, RegistryKey}; use crate::userdata::{AnyUserData, UserData}; use crate::value::{Nil, Value}; @@ -1039,3 +1039,59 @@ impl FromLua for Option { } } } + +impl IntoLua for Either { + #[inline] + fn into_lua(self, lua: &Lua) -> Result { + match self { + Either::Left(l) => l.into_lua(lua), + Either::Right(r) => r.into_lua(lua), + } + } + + #[inline] + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + match self { + Either::Left(l) => l.push_into_stack(lua), + Either::Right(r) => r.push_into_stack(lua), + } + } +} + +impl FromLua for Either { + #[inline] + fn from_lua(value: Value, lua: &Lua) -> Result { + let value_type_name = value.type_name(); + // Try the left type first + match L::from_lua(value.clone(), lua) { + Ok(l) => Ok(Either::Left(l)), + // Try the right type + Err(_) => match R::from_lua(value, lua).map(Either::Right) { + Ok(r) => Ok(r), + Err(_) => Err(Error::FromLuaConversionError { + from: value_type_name, + to: Self::type_name(), + message: None, + }), + }, + } + } + + #[inline] + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { + match L::from_stack(idx, lua) { + Ok(l) => Ok(Either::Left(l)), + Err(_) => match R::from_stack(idx, lua).map(Either::Right) { + Ok(r) => Ok(r), + Err(_) => { + let value_type_name = CStr::from_ptr(ffi::luaL_typename(lua.state(), idx)); + Err(Error::FromLuaConversionError { + from: value_type_name.to_str().unwrap(), + to: Self::type_name(), + message: None, + }) + } + }, + } + } +} diff --git a/src/types.rs b/src/types.rs index a5a74051..0fc0f1c0 100644 --- a/src/types.rs +++ b/src/types.rs @@ -123,7 +123,6 @@ impl LuaType for LightUserData { } mod app_data; -mod either; mod registry_key; mod sync; mod value_ref; diff --git a/src/types/either.rs b/src/types/either.rs deleted file mode 100644 index fdec083d..00000000 --- a/src/types/either.rs +++ /dev/null @@ -1,135 +0,0 @@ -use std::ffi::CStr; -use std::fmt; -use std::hash::Hash; -use std::os::raw::c_int; - -use crate::error::{Error, Result}; -use crate::state::{Lua, RawLua}; -use crate::traits::{FromLua, IntoLua, ShortTypeName as _}; -use crate::value::Value; - -/// Combination of two types into a single one. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Either { - Left(L), - Right(R), -} - -impl Either { - /// Return true if the value is the Left variant. - #[inline] - pub fn is_left(&self) -> bool { - matches!(self, Either::Left(_)) - } - - /// Return true if the value is the Right variant. - #[inline] - pub fn is_right(&self) -> bool { - matches!(self, Either::Right(_)) - } - - /// Convert the left side of `Either` to an `Option`. - #[inline] - pub fn left(self) -> Option { - match self { - Either::Left(l) => Some(l), - _ => None, - } - } - - /// Convert the right side of `Either` to an `Option`. - #[inline] - pub fn right(self) -> Option { - match self { - Either::Right(r) => Some(r), - _ => None, - } - } - - /// Convert `&Either` to `Either<&L, &R>`. - #[inline] - pub fn as_ref(&self) -> Either<&L, &R> { - match self { - Either::Left(l) => Either::Left(l), - Either::Right(r) => Either::Right(r), - } - } - - /// Convert `&mut Either` to `Either<&mut L, &mut R>`. - #[inline] - pub fn as_mut(&mut self) -> Either<&mut L, &mut R> { - match self { - Either::Left(l) => Either::Left(l), - Either::Right(r) => Either::Right(r), - } - } -} - -impl fmt::Display for Either -where - L: fmt::Display, - R: fmt::Display, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Either::Left(a) => a.fmt(f), - Either::Right(b) => b.fmt(f), - } - } -} - -impl IntoLua for Either { - #[inline] - fn into_lua(self, lua: &Lua) -> Result { - match self { - Either::Left(l) => l.into_lua(lua), - Either::Right(r) => r.into_lua(lua), - } - } - - #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - match self { - Either::Left(l) => l.push_into_stack(lua), - Either::Right(r) => r.push_into_stack(lua), - } - } -} - -impl FromLua for Either { - #[inline] - fn from_lua(value: Value, lua: &Lua) -> Result { - let value_type_name = value.type_name(); - // Try the left type first - match L::from_lua(value.clone(), lua) { - Ok(l) => Ok(Either::Left(l)), - // Try the right type - Err(_) => match R::from_lua(value, lua).map(Either::Right) { - Ok(r) => Ok(r), - Err(_) => Err(Error::FromLuaConversionError { - from: value_type_name, - to: Self::type_name(), - message: None, - }), - }, - } - } - - #[inline] - unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - match L::from_stack(idx, lua) { - Ok(l) => Ok(Either::Left(l)), - Err(_) => match R::from_stack(idx, lua).map(Either::Right) { - Ok(r) => Ok(r), - Err(_) => { - let value_type_name = CStr::from_ptr(ffi::luaL_typename(lua.state(), idx)); - Err(Error::FromLuaConversionError { - from: value_type_name.to_str().unwrap(), - to: Self::type_name(), - message: None, - }) - } - }, - } - } -} From 5724b5f11289dd1f91649ca208983d02d8bb231b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 20 Oct 2024 13:23:57 +0100 Subject: [PATCH 255/635] cargo fmt --- src/scope.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/scope.rs b/src/scope.rs index 0aa3b897..3f379047 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -152,9 +152,9 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { /// `'static` types. This means that it is impossible, once the userdata is created, to get a /// reference to it back *out* of an [`AnyUserData`] handle. This also implies that the /// "function" type methods that can be added via [`UserDataMethods`] (the ones that accept - /// [`AnyUserData`] as a first parameter) are vastly less useful. Also, there is no way to re-use - /// a single metatable for multiple non-'static types, so there is a higher cost associated with - /// creating the userdata metatable each time a new userdata is created. + /// [`AnyUserData`] as a first parameter) are vastly less useful. Also, there is no way to + /// re-use a single metatable for multiple non-'static types, so there is a higher cost + /// associated with creating the userdata metatable each time a new userdata is created. /// /// [`TypeId`]: std::any::TypeId /// [`UserDataMethods`]: crate::UserDataMethods From 3dc58cdfc9b45232143e39cfe3925822a807a74e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 22 Oct 2024 22:39:54 +0100 Subject: [PATCH 256/635] Move Luau Vector type to top level --- src/conversion.rs | 6 +++--- src/lib.rs | 3 ++- src/serde/de.rs | 4 ++-- src/serde/ser.rs | 6 +++--- src/state/raw.rs | 4 ++-- src/types.rs | 5 ----- src/{types => }/vector.rs | 17 ++++++----------- 7 files changed, 18 insertions(+), 27 deletions(-) rename src/{types => }/vector.rs (86%) diff --git a/src/conversion.rs b/src/conversion.rs index f93cdfcd..ad0a7003 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -350,7 +350,7 @@ impl FromLua for LightUserData { } #[cfg(feature = "luau")] -impl IntoLua for crate::types::Vector { +impl IntoLua for crate::Vector { #[inline] fn into_lua(self, _: &Lua) -> Result { Ok(Value::Vector(self)) @@ -358,7 +358,7 @@ impl IntoLua for crate::types::Vector { } #[cfg(feature = "luau")] -impl FromLua for crate::types::Vector { +impl FromLua for crate::Vector { #[inline] fn from_lua(value: Value, _: &Lua) -> Result { match value { @@ -848,7 +848,7 @@ where match value { #[cfg(feature = "luau")] #[rustfmt::skip] - Value::Vector(v) if N == crate::types::Vector::SIZE => unsafe { + Value::Vector(v) if N == crate::Vector::SIZE => unsafe { use std::{mem, ptr}; let mut arr: [mem::MaybeUninit; N] = mem::MaybeUninit::uninit().assume_init(); ptr::write(arr[0].as_mut_ptr() , T::from_lua(Value::Number(v.x() as _), _lua)?); diff --git a/src/lib.rs b/src/lib.rs index 013e253a..eb5ddfa2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,6 +90,7 @@ mod types; mod userdata; mod util; mod value; +mod vector; pub mod prelude; @@ -124,7 +125,7 @@ pub use crate::hook::HookTriggers; #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] -pub use crate::{buffer::Buffer, chunk::Compiler, function::CoverageInfo, types::Vector}; +pub use crate::{buffer::Buffer, chunk::Compiler, function::CoverageInfo, vector::Vector}; #[cfg(feature = "async")] pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn}; diff --git a/src/serde/de.rs b/src/serde/de.rs index 616d529d..0a941170 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -420,7 +420,7 @@ impl<'de> de::SeqAccess<'de> for SeqDeserializer<'_> { #[cfg(feature = "luau")] struct VecDeserializer { - vec: crate::types::Vector, + vec: crate::Vector, next: usize, options: Options, visited: Rc>>, @@ -446,7 +446,7 @@ impl<'de> de::SeqAccess<'de> for VecDeserializer { } fn size_hint(&self) -> Option { - Some(crate::types::Vector::SIZE) + Some(crate::Vector::SIZE) } } diff --git a/src/serde/ser.rs b/src/serde/ser.rs index a6cac26e..81b24682 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -269,7 +269,7 @@ impl<'a> ser::Serializer for Serializer<'a> { #[inline] fn serialize_tuple_struct(self, name: &'static str, len: usize) -> Result { #[cfg(feature = "luau")] - if name == "Vector" && len == crate::types::Vector::SIZE { + if name == "Vector" && len == crate::Vector::SIZE { return Ok(SerializeSeq::new_vector(self.lua, self.options)); } _ = name; @@ -343,7 +343,7 @@ impl<'a> ser::Serializer for Serializer<'a> { pub struct SerializeSeq<'a> { lua: &'a Lua, #[cfg(feature = "luau")] - vector: Option, + vector: Option, table: Option
, next: usize, options: Options, @@ -365,7 +365,7 @@ impl<'a> SerializeSeq<'a> { const fn new_vector(lua: &'a Lua, options: Options) -> Self { Self { lua, - vector: Some(crate::types::Vector::zero()), + vector: Some(crate::Vector::zero()), table: None, next: 0, options, diff --git a/src/state/raw.rs b/src/state/raw.rs index 3a477642..893e9394 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -613,9 +613,9 @@ impl RawLua { let v = ffi::lua_tovector(state, idx); mlua_debug_assert!(!v.is_null(), "vector is null"); #[cfg(not(feature = "luau-vector4"))] - return Value::Vector(crate::types::Vector([*v, *v.add(1), *v.add(2)])); + return Value::Vector(crate::Vector([*v, *v.add(1), *v.add(2)])); #[cfg(feature = "luau-vector4")] - return Value::Vector(crate::types::Vector([*v, *v.add(1), *v.add(2), *v.add(3)])); + return Value::Vector(crate::Vector([*v, *v.add(1), *v.add(2), *v.add(3)])); } ffi::LUA_TSTRING => { diff --git a/src/types.rs b/src/types.rs index 0fc0f1c0..afeb239d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -20,8 +20,6 @@ pub use app_data::{AppData, AppDataRef, AppDataRefMut}; pub use either::Either; pub use registry_key::RegistryKey; pub(crate) use value_ref::ValueRef; -#[cfg(any(feature = "luau", doc))] -pub use vector::Vector; /// Type of Lua integer numbers. pub type Integer = ffi::lua_Integer; @@ -127,9 +125,6 @@ mod registry_key; mod sync; mod value_ref; -#[cfg(any(feature = "luau", doc))] -mod vector; - #[cfg(test)] mod assertions { use super::*; diff --git a/src/types/vector.rs b/src/vector.rs similarity index 86% rename from src/types/vector.rs rename to src/vector.rs index 3aa8d126..57a5a96d 100644 --- a/src/types/vector.rs +++ b/src/vector.rs @@ -1,10 +1,8 @@ use std::fmt; -#[cfg(all(any(feature = "luau", doc), feature = "serialize"))] +#[cfg(feature = "serialize")] use serde::ser::{Serialize, SerializeTupleStruct, Serializer}; -use super::LuaType; - /// A Luau vector type. /// /// By default vectors are 3-dimensional, but can be 4-dimensional @@ -23,6 +21,7 @@ impl fmt::Display for Vector { } } +#[cfg_attr(not(feature = "luau"), allow(unused))] impl Vector { pub(crate) const SIZE: usize = if cfg!(feature = "luau-vector4") { 4 } else { 3 }; @@ -67,7 +66,7 @@ impl Vector { } } -#[cfg(all(any(feature = "luau", doc), feature = "serialize"))] +#[cfg(feature = "serialize")] impl Serialize for Vector { fn serialize(&self, serializer: S) -> std::result::Result { let mut ts = serializer.serialize_tuple_struct("Vector", Self::SIZE)?; @@ -87,11 +86,7 @@ impl PartialEq<[f32; Self::SIZE]> for Vector { } } -impl LuaType for Vector { - #[cfg(feature = "luau")] - const TYPE_ID: i32 = ffi::LUA_TVECTOR; - - // This is a dummy value, as `Vector` is supported only by Luau - #[cfg(not(feature = "luau"))] - const TYPE_ID: i32 = ffi::LUA_TNONE; +#[cfg(feature = "luau")] +impl crate::types::LuaType for Vector { + const TYPE_ID: std::os::raw::c_int = ffi::LUA_TVECTOR; } From 0d31a1caa65382492c52a1e855f379fee67dbd64 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 22 Oct 2024 22:44:52 +0100 Subject: [PATCH 257/635] Rename `Error::MemoryLimitNotAvailable` to `Error::MemoryControlNotAvailable` --- src/error.rs | 8 ++++---- src/state.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/error.rs b/src/error.rs index db4d479d..fa61edac 100644 --- a/src/error.rs +++ b/src/error.rs @@ -42,11 +42,11 @@ pub enum Error { GarbageCollectorError(StdString), /// Potentially unsafe action in safe mode. SafetyError(StdString), - /// Setting memory limit is not available. + /// Memory control is not available. /// /// This error can only happen when Lua state was not created by us and does not have the /// custom allocator attached. - MemoryLimitNotAvailable, + MemoryControlNotAvailable, /// A mutable callback has triggered Lua code that has called the same mutable callback again. /// /// This is an error because a mutable callback can only be borrowed mutably once. @@ -220,8 +220,8 @@ impl fmt::Display for Error { Error::SafetyError(msg) => { write!(fmt, "safety error: {msg}") }, - Error::MemoryLimitNotAvailable => { - write!(fmt, "setting memory limit is not available") + Error::MemoryControlNotAvailable => { + write!(fmt, "memory control is not available") } Error::RecursiveMutCallback => write!(fmt, "mutable callback called recursively"), Error::CallbackDestructed => write!( diff --git a/src/state.rs b/src/state.rs index 5188bfdf..096798b0 100644 --- a/src/state.rs +++ b/src/state.rs @@ -792,7 +792,7 @@ impl Lua { unsafe { match MemoryState::get(lua.main_state) { mem_state if !mem_state.is_null() => Ok((*mem_state).set_memory_limit(limit)), - _ => Err(Error::MemoryLimitNotAvailable), + _ => Err(Error::MemoryControlNotAvailable), } } } From 8d8d521721eaaf8a15792ef517889dd69bb6889e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 23 Oct 2024 10:36:28 +0100 Subject: [PATCH 258/635] Add `error-send` feature flag --- Cargo.toml | 5 +++-- README.md | 1 + src/error.rs | 22 +++++++++++++++++++--- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 43affd2b..141f11d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,10 +38,11 @@ luau-vector4 = ["luau", "ffi/luau-vector4"] vendored = ["ffi/vendored"] module = ["dep:mlua_derive", "ffi/module"] async = ["dep:futures-util"] -send = ["parking_lot/send_guard"] +send = ["parking_lot/send_guard", "error-send"] +error-send = [] serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value"] macros = ["mlua_derive/macros"] -anyhow = ["dep:anyhow"] +anyhow = ["dep:anyhow", "error-send"] [dependencies] mlua_derive = { version = "=0.10.0-rc.1", optional = true, path = "mlua_derive" } diff --git a/README.md b/README.md index 3a0efe89..5bd01572 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl * `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]) * `send`: make `mlua::Lua: Send + Sync` (adds [`Send`] requirement to `mlua::Function` and `mlua::UserData`) +* `error-send`: make `mlua:Error: Send + Sync` * `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework * `macros`: enable procedural macros (such as `chunk!`) * `anyhow`: enable `anyhow::Error` conversion into Lua diff --git a/src/error.rs b/src/error.rs index fa61edac..1f243967 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,6 +9,12 @@ use std::sync::Arc; use crate::private::Sealed; +#[cfg(feature = "error-send")] +type DynStdError = dyn StdError + Send + Sync; + +#[cfg(not(feature = "error-send"))] +type DynStdError = dyn StdError; + /// Error type returned by `mlua` methods. #[derive(Debug, Clone)] #[non_exhaustive] @@ -191,7 +197,7 @@ pub enum Error { /// Returning `Err(ExternalError(...))` from a Rust callback will raise the error as a Lua /// error. The Rust code that originally invoked the Lua code then receives a `CallbackError`, /// from which the original error (and a stack traceback) can be recovered. - ExternalError(Arc), + ExternalError(Arc), /// An error with additional context. WithContext { /// A string containing additional context. @@ -345,7 +351,7 @@ impl Error { /// Wraps an external error object. #[inline] - pub fn external>>(err: T) -> Self { + pub fn external>>(err: T) -> Self { Error::ExternalError(err.into().into()) } @@ -406,7 +412,7 @@ pub trait ExternalError { fn into_lua_err(self) -> Error; } -impl>> ExternalError for E { +impl>> ExternalError for E { fn into_lua_err(self) -> Error { Error::external(self) } @@ -552,3 +558,13 @@ impl<'a> Iterator for Chain<'a> { } } } + +#[cfg(test)] +mod assertions { + use super::*; + + #[cfg(not(feature = "error-send"))] + static_assertions::assert_not_impl_any!(Error: Send, Sync); + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(Error: Send, Sync); +} From 446d63a77e3a368adea0e10d2634d81e97c5b97d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 23 Oct 2024 15:50:58 +0100 Subject: [PATCH 259/635] More tests --- tests/tests.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/tests.rs b/tests/tests.rs index 65335845..00158c6f 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -872,6 +872,49 @@ fn test_application_data() -> Result<()> { Ok(()) } +#[test] +fn test_rust_function() -> Result<()> { + let lua = Lua::new(); + + let globals = lua.globals(); + lua.load( + r#" + function lua_function() + return rust_function() + end + + -- Test to make sure chunk return is ignored + return 1 + "#, + ) + .exec()?; + + let lua_function = globals.get::("lua_function")?; + let rust_function = lua.create_function(|_, ()| Ok("hello"))?; + + globals.set("rust_function", rust_function)?; + assert_eq!(lua_function.call::(())?, "hello"); + + Ok(()) +} + +#[test] +fn test_c_function() -> Result<()> { + let lua = Lua::new(); + + unsafe extern "C-unwind" fn c_function(state: *mut mlua::lua_State) -> std::os::raw::c_int { + ffi::lua_pushboolean(state, 1); + ffi::lua_setglobal(state, b"c_function\0" as *const _ as *const _); + 0 + } + + let func = unsafe { lua.create_c_function(c_function)? }; + func.call::<()>(())?; + assert_eq!(lua.globals().get::("c_function")?, true); + + Ok(()) +} + #[test] #[cfg(not(target_arch = "wasm32"))] fn test_recursion() -> Result<()> { From 35fa76263efc28cf2b70a730d5c99aedf34fd6c5 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 25 Oct 2024 10:59:36 +0200 Subject: [PATCH 260/635] Update docs --- src/lib.rs | 1 + src/state.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index eb5ddfa2..da41d4a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,6 +128,7 @@ pub use crate::hook::HookTriggers; pub use crate::{buffer::Buffer, chunk::Compiler, function::CoverageInfo, vector::Vector}; #[cfg(feature = "async")] +#[cfg_attr(docsrs, doc(cfg(feature = "async")))] pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn}; #[cfg(feature = "serialize")] diff --git a/src/state.rs b/src/state.rs index 096798b0..a11371dc 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1459,7 +1459,7 @@ impl Lua { } /// Calls the given function with a [`Scope`] parameter, giving the function the ability to - /// create userdata and callbacks from Rust types that are `!Send`` or non-`'static`. + /// create userdata and callbacks from Rust types that are `!Send` or non-`'static`. /// /// The lifetime of any function or userdata created through [`Scope`] lasts only until the /// completion of this method call, on completion all such created values are automatically From 4f56575e056b38d1308b6f47de8a1b517211ad2d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 25 Oct 2024 12:57:21 +0200 Subject: [PATCH 261/635] v0.10.0 --- CHANGELOG.md | 7 +++++++ Cargo.toml | 4 ++-- README.md | 8 +++----- docs/release_notes/v0.10.md | 11 +++++++++++ mlua_derive/Cargo.toml | 2 +- 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 787825d1..4ea64484 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## v0.10.0 (Oct 25th, 2024) + +Changes since v0.10.0-rc.1 + +- Added `error-send` feature flag (disabled by default) to require `Send + Sync` for `Error` +- Some performance improvements + ## v0.10.0-rc.1 - `Lua::scope` is back diff --git a/Cargo.toml b/Cargo.toml index 141f11d4..988cad29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.10.0-rc.1" # remember to update mlua_derive +version = "0.10.0" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.79.0" edition = "2021" @@ -45,7 +45,7 @@ macros = ["mlua_derive/macros"] anyhow = ["dep:anyhow", "error-send"] [dependencies] -mlua_derive = { version = "=0.10.0-rc.1", optional = true, path = "mlua_derive" } +mlua_derive = { version = "=0.10.0", optional = true, path = "mlua_derive" } bstr = { version = "1.0", features = ["std"], default-features = false } either = "1.0" num-traits = { version = "0.2.14" } diff --git a/README.md b/README.md index 5bd01572..c7035f22 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,9 @@ [Benchmarks]: https://github.com/khvzak/script-bench-rs [FAQ]: FAQ.md -# The main branch is the v0.10, development version of `mlua`. Please see the [v0.9](https://github.com/mlua-rs/mlua/tree/v0.9) branch for the stable versions of `mlua`. - > **Note** > -> See (upcoming) v0.10 [release notes](https://github.com/khvzak/mlua/blob/main/docs/release_notes/v0.10.md). +> See v0.10 [release notes](https://github.com/khvzak/mlua/blob/main/docs/release_notes/v0.10.md). `mlua` is bindings to [Lua](https://www.lua.org) programming language for Rust with a goal to provide _safe_ (as far as it's possible), high level, easy to use, practical and flexible API. @@ -134,7 +132,7 @@ Add to `Cargo.toml` : ``` toml [dependencies] -mlua = { version = "0.10.0-rc.1", features = ["lua54", "vendored"] } +mlua = { version = "0.10.0", features = ["lua54", "vendored"] } ``` `main.rs` @@ -169,7 +167,7 @@ Add to `Cargo.toml` : crate-type = ["cdylib"] [dependencies] -mlua = { version = "0.10.0-rc.1", features = ["lua54", "module"] } +mlua = { version = "0.10.0", features = ["lua54", "module"] } ``` `lib.rs` : diff --git a/docs/release_notes/v0.10.md b/docs/release_notes/v0.10.md index 466929d7..62721b5b 100644 --- a/docs/release_notes/v0.10.md +++ b/docs/release_notes/v0.10.md @@ -121,3 +121,14 @@ assert_eq!(sum, 15); ``` The `exec_raw` method is longjmp-safe. It's not recommended to move `Drop` types into the closure to avoid possible memory leaks. + +#### `anyhow` feature flag + +The new `anyhow` feature flag adds `IntoLua` and `Into` implementation for the `anyhow::Error` type. + +```rust +let f = lua.create_function(|_, ()| { + Err(anyhow!("error message"))?; + Ok(()) +})?; +``` diff --git a/mlua_derive/Cargo.toml b/mlua_derive/Cargo.toml index c17ae158..773351e7 100644 --- a/mlua_derive/Cargo.toml +++ b/mlua_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua_derive" -version = "0.10.0-rc.1" +version = "0.10.0" authors = ["Aleksandr Orlenko "] edition = "2021" description = "Procedural macros for the mlua crate." From ddebf56b4149297f9c7d7ed726d80ec796f74db8 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 29 Oct 2024 10:57:37 +0000 Subject: [PATCH 262/635] Defer metatable return on userdata creation until the end Relates to #477 --- src/state/raw.rs | 11 +++-------- src/util/mod.rs | 14 ++++++++++++++ src/util/userdata.rs | 22 ++++++++-------------- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index 893e9394..7ac55587 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -832,7 +832,7 @@ impl RawLua { pub(crate) unsafe fn push_userdata_metatable(&self, mut registry: UserDataRegistry) -> Result<()> { let state = self.state(); - let _sg = StackGuard::with_top(state, ffi::lua_gettop(state) + 1); + let mut stack_guard = StackGuard::new(state); check_stack(state, 13)?; // Prepare metatable, add meta methods first and then meta fields @@ -863,8 +863,6 @@ impl RawLua { } let metatable_index = ffi::lua_absindex(state, -1); - let mut extra_tables_count = 0; - let fields_nrec = registry.fields.len(); if fields_nrec > 0 { // If `__index` is a table then update it in-place @@ -909,7 +907,6 @@ impl RawLua { rawset_field(state, -2, &k)?; } field_getters_index = Some(ffi::lua_absindex(state, -1)); - extra_tables_count += 1; } let mut field_setters_index = None; @@ -921,7 +918,6 @@ impl RawLua { rawset_field(state, -2, &k)?; } field_setters_index = Some(ffi::lua_absindex(state, -1)); - extra_tables_count += 1; } let mut methods_index = None; @@ -958,7 +954,6 @@ impl RawLua { } _ => { methods_index = Some(ffi::lua_absindex(state, -1)); - extra_tables_count += 1; } } } @@ -980,8 +975,8 @@ impl RawLua { extra_init, )?; - // Pop extra tables to get metatable on top of the stack - ffi::lua_pop(state, extra_tables_count); + // Update stack guard to keep metatable after return + stack_guard.keep(1); Ok(()) } diff --git a/src/util/mod.rs b/src/util/mod.rs index f81e9e68..35e7196b 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -66,6 +66,11 @@ impl StackGuard { pub(crate) fn with_top(state: *mut ffi::lua_State, top: c_int) -> StackGuard { StackGuard { state, top } } + + #[inline] + pub(crate) fn keep(&mut self, n: c_int) { + self.top += n; + } } impl Drop for StackGuard { @@ -129,6 +134,15 @@ pub(crate) unsafe fn push_table( } } +// Uses 4 stack spaces, does not call checkstack. +pub(crate) unsafe fn rawget_field(state: *mut ffi::lua_State, table: c_int, field: &str) -> Result { + ffi::lua_pushvalue(state, table); + protect_lua!(state, 1, 1, |state| { + ffi::lua_pushlstring(state, field.as_ptr() as *const c_char, field.len()); + ffi::lua_rawget(state, -2) + }) +} + // Uses 4 stack spaces, does not call checkstack. pub(crate) unsafe fn rawset_field(state: *mut ffi::lua_State, table: c_int, field: &str) -> Result<()> { ffi::lua_pushvalue(state, table); diff --git a/src/util/userdata.rs b/src/util/userdata.rs index 24c4a601..d352f8c1 100644 --- a/src/util/userdata.rs +++ b/src/util/userdata.rs @@ -3,7 +3,7 @@ use std::os::raw::{c_int, c_void}; use std::{ptr, str}; use crate::error::Result; -use crate::util::{check_stack, get_metatable_ptr, push_string, push_table, rawset_field, TypeKey}; +use crate::util::{check_stack, get_metatable_ptr, push_table, rawget_field, rawset_field, TypeKey}; // Pushes the userdata and attaches a metatable with __gc method. // Internally uses 3 stack spaces, does not call checkstack. @@ -154,14 +154,11 @@ pub(crate) unsafe fn init_userdata_metatable( methods: Option, extra_init: Option Result<()>>, ) -> Result<()> { - ffi::lua_pushvalue(state, metatable); - if field_getters.is_some() || methods.is_some() { // Push `__index` generator function init_userdata_metatable_index(state)?; - push_string(state, b"__index", true)?; - let index_type = ffi::lua_rawget(state, -3); + let index_type = rawget_field(state, metatable, "__index")?; match index_type { ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => { for &idx in &[field_getters, methods] { @@ -175,28 +172,27 @@ pub(crate) unsafe fn init_userdata_metatable( // Generate `__index` protect_lua!(state, 4, 1, fn(state) ffi::lua_call(state, 3, 1))?; } - _ => mlua_panic!("improper __index type {}", index_type), + _ => mlua_panic!("improper `__index` type: {}", index_type), } - rawset_field(state, -2, "__index")?; + rawset_field(state, metatable, "__index")?; } if let Some(field_setters) = field_setters { // Push `__newindex` generator function init_userdata_metatable_newindex(state)?; - push_string(state, b"__newindex", true)?; - let newindex_type = ffi::lua_rawget(state, -3); + let newindex_type = rawget_field(state, metatable, "__newindex")?; match newindex_type { ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => { ffi::lua_pushvalue(state, field_setters); // Generate `__newindex` protect_lua!(state, 3, 1, fn(state) ffi::lua_call(state, 2, 1))?; } - _ => mlua_panic!("improper __newindex type {}", newindex_type), + _ => mlua_panic!("improper `__newindex` type: {}", newindex_type), } - rawset_field(state, -2, "__newindex")?; + rawset_field(state, metatable, "__newindex")?; } // Additional initialization @@ -205,9 +201,7 @@ pub(crate) unsafe fn init_userdata_metatable( } ffi::lua_pushboolean(state, 0); - rawset_field(state, -2, "__metatable")?; - - ffi::lua_pop(state, 1); + rawset_field(state, metatable, "__metatable")?; Ok(()) } From 76b896edccad69932cf48bef42bb4e8c6a6d4875 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 29 Oct 2024 22:56:29 +0000 Subject: [PATCH 263/635] Fix attaching __gc metamethod Bug introdused in ddebf56 --- src/state/raw.rs | 4 ++-- src/util/userdata.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index 7ac55587..5ec57481 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -961,9 +961,9 @@ impl RawLua { #[cfg(feature = "luau")] let extra_init = None; #[cfg(not(feature = "luau"))] - let extra_init: Option Result<()>> = Some(|state| { + let extra_init: Option Result<()>> = Some(|state, mt_idx| { ffi::lua_pushcfunction(state, crate::util::userdata_destructor::>); - rawset_field(state, -2, "__gc") + rawset_field(state, mt_idx, "__gc") }); init_userdata_metatable( diff --git a/src/util/userdata.rs b/src/util/userdata.rs index d352f8c1..17a5ee2d 100644 --- a/src/util/userdata.rs +++ b/src/util/userdata.rs @@ -152,7 +152,7 @@ pub(crate) unsafe fn init_userdata_metatable( field_getters: Option, field_setters: Option, methods: Option, - extra_init: Option Result<()>>, + extra_init: Option Result<()>>, ) -> Result<()> { if field_getters.is_some() || methods.is_some() { // Push `__index` generator function @@ -197,7 +197,7 @@ pub(crate) unsafe fn init_userdata_metatable( // Additional initialization if let Some(extra_init) = extra_init { - extra_init(state)?; + extra_init(state, metatable)?; } ffi::lua_pushboolean(state, 0); From d27d1365b5d48a947faaae9850cee6b4aa92df21 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 30 Oct 2024 00:42:31 +0000 Subject: [PATCH 264/635] Update v0.10 release notes (add breaking changes) --- docs/release_notes/v0.10.md | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/docs/release_notes/v0.10.md b/docs/release_notes/v0.10.md index 62721b5b..c2e09008 100644 --- a/docs/release_notes/v0.10.md +++ b/docs/release_notes/v0.10.md @@ -132,3 +132,64 @@ let f = lua.create_function(|_, ()| { Ok(()) })?; ``` + +### Breaking changes + +#### Scope changes + +The following `Scope` methods were changed: +- Removed `Scope::create_any_userdata` +- `Scope::create_nonstatic_userdata` is renamed to `Scope::create_userdata` + +Instead, scope has comprehensive support for borrowed userdata: `create_any_userdata_ref`, `create_any_userdata_ref_mut`, `create_userdata_ref`, `create_userdata_ref_mut`. + +`UserDataRef` and `UserDataRefMut` are no longer acceptable for scoped userdata access as they require owned underlying data. +In mlua v0.9 this can cause read-after-free bug in some edge cases. + +To temporarily borrow underlying data, the `AnyUserData::borrow_scoped` and `AnyUserData::borrow_mut_scoped` methods were introduced: + +```rust +let data = "hello".to_string(); +lua.scope(|scope| { + let ud = scope.create_any_userdata_ref(&data)?; + + // We can only borrow scoped userdata using this method + ud.borrow_scoped::(|s| { + assert_eq!(s, "hello"); + })?; + + Ok(()) +})?; +``` + +Those methods work for scoped and regular userdata objects (but still require `T: 'static`). + +#### String changes + +Since `mlua::String` holds a weak reference to Lua without any guarantees about the lifetime of the underlying data, getting a `&str` or `&[u8]` from it is no longer safe. +Lua instance can be destroyed while reference to the data is still alive: + +```rust +let lua = Lua::new(); +let s: mlua::String = lua.create_string("hello, world")?; // only weak reference to Lua! +let s_ref: &str = s.to_str()?; // this is not safe! +drop(lua); +println!("{s_ref}"); // use after free! +``` + +To solve this issue, return types of `mlua::String::to_str` and `mlua::String::as_bytes` methods changed to `BorrowedStr` and `BorrowedBytes` respectively. + +These new types hold a strong reference to the Lua instance and can be safely converted to `&str` or `&[u8]`: + +```rust +let lua = Lua::new(); +let s: mlua::String = lua.create_string("hello, world")?; +let s_ref: mlua::BorrowedStr = s.to_str()?; // The strong reference to Lua is held here +drop(lua); +println!("{s_ref}"); // ok +``` + +The good news is that `BorrowedStr` implements `Deref`/`AsRef` as well as `Display`, `Debug`, `Eq`, `PartialEq` and other traits for easy usage. +The same applies to `BorrowedBytes`. + +Unfortunately, `mlua::String::to_string_lossy` cannot return `Cow<'a, str>` anymore, because it requires a strong reference to Lua. It now returns Rust `String` instead. From 5ec4e0338a4fe27a58227f52e8d94606f8b2b89d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 30 Oct 2024 12:58:34 +0000 Subject: [PATCH 265/635] Add `From` and `Into` support to `MultiValue` and `Variadic` types --- src/multi.rs | 80 ++++++++++++++++++++++++++++++++++++++------------ tests/multi.rs | 27 ++++++++++++++++- 2 files changed, 88 insertions(+), 19 deletions(-) diff --git a/src/multi.rs b/src/multi.rs index 2dab9b94..b4fb0e2e 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -124,6 +124,23 @@ impl MultiValue { MultiValue(VecDeque::with_capacity(capacity)) } + /// Creates a `MultiValue` container from vector of values. + /// + /// This methods needs *O*(*n*) data movement if the circular buffer doesn't happen to be at the + /// beginning of the allocation. + #[inline] + pub fn from_vec(vec: Vec) -> MultiValue { + vec.into() + } + + /// Consumes the `MultiValue` and returns a vector of values. + /// + /// This methods works in *O*(1) time and does not allocate any additional memory. + #[inline] + pub fn into_vec(self) -> Vec { + self.into() + } + #[inline] pub(crate) fn from_lua_iter(lua: &Lua, iter: impl IntoIterator) -> Result { let iter = iter.into_iter(); @@ -135,6 +152,20 @@ impl MultiValue { } } +impl From> for MultiValue { + #[inline] + fn from(value: Vec) -> Self { + MultiValue(value.into()) + } +} + +impl From for Vec { + #[inline] + fn from(value: MultiValue) -> Self { + value.0.into() + } +} + impl FromIterator for MultiValue { #[inline] fn from_iter>(iter: I) -> Self { @@ -203,7 +234,7 @@ impl FromLuaMulti for MultiValue { /// # Ok(()) /// # } /// ``` -#[derive(Debug, Clone)] +#[derive(Default, Debug, Clone)] pub struct Variadic(Vec); impl Variadic { @@ -211,11 +242,38 @@ impl Variadic { pub const fn new() -> Variadic { Variadic(Vec::new()) } + + /// Creates an empty `Variadic` container with space for at least `capacity` elements. + pub fn with_capacity(capacity: usize) -> Variadic { + Variadic(Vec::with_capacity(capacity)) + } +} + +impl Deref for Variadic { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } } -impl Default for Variadic { - fn default() -> Variadic { - const { Variadic::new() } +impl DerefMut for Variadic { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From> for Variadic { + #[inline] + fn from(vec: Vec) -> Self { + Variadic(vec) + } +} + +impl From> for Vec { + #[inline] + fn from(value: Variadic) -> Self { + value.0 } } @@ -234,20 +292,6 @@ impl IntoIterator for Variadic { } } -impl Deref for Variadic { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Variadic { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - impl IntoLuaMulti for Variadic { #[inline] fn into_lua_multi(self, lua: &Lua) -> Result { diff --git a/tests/multi.rs b/tests/multi.rs index 3ee7bdb2..88dc1e01 100644 --- a/tests/multi.rs +++ b/tests/multi.rs @@ -1,4 +1,4 @@ -use mlua::{Error, ExternalError, IntoLuaMulti, Lua, Result, String, Value}; +use mlua::{Error, ExternalError, IntoLuaMulti, Lua, MultiValue, Result, String, Value, Variadic}; #[test] fn test_result_conversions() -> Result<()> { @@ -58,3 +58,28 @@ fn test_result_conversions() -> Result<()> { Ok(()) } + +#[test] +fn test_multivalue() { + let mut multi = MultiValue::with_capacity(3); + multi.push_back(Value::Integer(1)); + multi.push_back(Value::Integer(2)); + multi.push_front(Value::Integer(3)); + assert_eq!(multi.iter().filter_map(|v| v.as_integer()).sum::(), 6); + + let vec = multi.into_vec(); + assert_eq!(&vec, &[Value::Integer(3), Value::Integer(1), Value::Integer(2)]); + let _multi2 = MultiValue::from_vec(vec); +} + +#[test] +fn test_variadic() { + let mut var = Variadic::with_capacity(3); + var.extend_from_slice(&[1, 2, 3]); + assert_eq!(var.iter().sum::(), 6); + + let vec = Vec::::from(var); + assert_eq!(&vec, &[1, 2, 3]); + let var2 = Variadic::from(vec); + assert_eq!(var2.as_slice(), &[1, 2, 3]); +} From a8d5f23818394ac25577ee6ba9ebe67a52153bc1 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 30 Oct 2024 15:22:55 +0000 Subject: [PATCH 266/635] Add `Lua::try_app_data_ref` and `Lua::try_app_data_mut` --- src/state.rs | 18 ++++++++++- src/types/app_data.rs | 70 ++++++++++++++++++++++++++++++++----------- tests/tests.rs | 2 ++ 3 files changed, 72 insertions(+), 18 deletions(-) diff --git a/src/state.rs b/src/state.rs index a11371dc..1cf95f1c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,5 +1,5 @@ use std::any::TypeId; -use std::cell::RefCell; +use std::cell::{BorrowError, BorrowMutError, RefCell}; use std::marker::PhantomData; use std::ops::Deref; use std::os::raw::c_int; @@ -1854,6 +1854,14 @@ impl Lua { extra.app_data.borrow(Some(guard)) } + /// Tries to get a reference to an application data object stored by [`Lua::set_app_data`] of + /// type `T`. + pub fn try_app_data_ref(&self) -> StdResult>, BorrowError> { + let guard = self.lock_arc(); + let extra = unsafe { &*guard.extra.get() }; + extra.app_data.try_borrow(Some(guard)) + } + /// Gets a mutable reference to an application data object stored by [`Lua::set_app_data`] of /// type `T`. /// @@ -1867,6 +1875,14 @@ impl Lua { extra.app_data.borrow_mut(Some(guard)) } + /// Tries to get a mutable reference to an application data object stored by + /// [`Lua::set_app_data`] of type `T`. + pub fn try_app_data_mut(&self) -> StdResult>, BorrowMutError> { + let guard = self.lock_arc(); + let extra = unsafe { &*guard.extra.get() }; + extra.app_data.try_borrow_mut(Some(guard)) + } + /// Removes an application data of type `T`. /// /// # Panics diff --git a/src/types/app_data.rs b/src/types/app_data.rs index 35cde1a0..0b4c4ba6 100644 --- a/src/types/app_data.rs +++ b/src/types/app_data.rs @@ -1,5 +1,5 @@ use std::any::{Any, TypeId}; -use std::cell::{Cell, Ref, RefCell, RefMut, UnsafeCell}; +use std::cell::{BorrowError, BorrowMutError, Cell, Ref, RefCell, RefMut, UnsafeCell}; use std::fmt; use std::ops::{Deref, DerefMut}; use std::result::Result as StdResult; @@ -41,30 +41,66 @@ impl AppData { .and_then(|data| data.into_inner().downcast::().ok().map(|data| *data))) } + #[inline] #[track_caller] pub(crate) fn borrow(&self, guard: Option) -> Option> { + match self.try_borrow(guard) { + Ok(data) => data, + Err(err) => panic!("already mutably borrowed: {err:?}"), + } + } + + pub(crate) fn try_borrow( + &self, + guard: Option, + ) -> Result>, BorrowError> { let data = unsafe { &*self.container.get() } - .get(&TypeId::of::())? - .borrow(); - self.borrow.set(self.borrow.get() + 1); - Some(AppDataRef { - data: Ref::filter_map(data, |data| data.downcast_ref()).ok()?, - borrow: &self.borrow, - _guard: guard, - }) + .get(&TypeId::of::()) + .map(|c| c.try_borrow()) + .transpose()? + .and_then(|data| Ref::filter_map(data, |data| data.downcast_ref()).ok()); + match data { + Some(data) => { + self.borrow.set(self.borrow.get() + 1); + Ok(Some(AppDataRef { + data, + borrow: &self.borrow, + _guard: guard, + })) + } + None => Ok(None), + } } + #[inline] #[track_caller] pub(crate) fn borrow_mut(&self, guard: Option) -> Option> { + match self.try_borrow_mut(guard) { + Ok(data) => data, + Err(err) => panic!("already borrowed: {err:?}"), + } + } + + pub(crate) fn try_borrow_mut( + &self, + guard: Option, + ) -> Result>, BorrowMutError> { let data = unsafe { &*self.container.get() } - .get(&TypeId::of::())? - .borrow_mut(); - self.borrow.set(self.borrow.get() + 1); - Some(AppDataRefMut { - data: RefMut::filter_map(data, |data| data.downcast_mut()).ok()?, - borrow: &self.borrow, - _guard: guard, - }) + .get(&TypeId::of::()) + .map(|c| c.try_borrow_mut()) + .transpose()? + .and_then(|data| RefMut::filter_map(data, |data| data.downcast_mut()).ok()); + match data { + Some(data) => { + self.borrow.set(self.borrow.get() + 1); + Ok(Some(AppDataRefMut { + data, + borrow: &self.borrow, + _guard: guard, + })) + } + None => Ok(None), + } } #[track_caller] diff --git a/tests/tests.rs b/tests/tests.rs index 00158c6f..318b03bb 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -844,10 +844,12 @@ fn test_application_data() -> Result<()> { assert_eq!(format!("{s:?}"), "\"test1\""); // Borrowing immutably and mutably of the same type is not allowed + assert!(lua.try_app_data_mut::<&str>().is_err()); match catch_unwind(AssertUnwindSafe(|| lua.app_data_mut::<&str>().unwrap())) { Ok(_) => panic!("expected panic"), Err(_) => {} } + assert!(lua.try_app_data_ref::>().is_err()); drop((s, v)); // Test that application data is accessible from anywhere From 6066089cc1726e71c176ef9caa25fa8a0145f3bd Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 30 Oct 2024 23:32:13 +0000 Subject: [PATCH 267/635] Skip setting `Send`/`Sync` in non-send mode for `UserDataCell` --- src/userdata/cell.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index fe3a0190..bd3b8039 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -132,7 +132,9 @@ pub(crate) struct UserDataCell { value: UnsafeCell, } +#[cfg(feature = "send")] unsafe impl Send for UserDataCell {} +#[cfg(feature = "send")] unsafe impl Sync for UserDataCell {} impl UserDataCell { From 4e9a17707b1b8d85c025127c709244b9db28d744 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 31 Oct 2024 09:23:03 +0000 Subject: [PATCH 268/635] Fix tests --- tests/multi.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/multi.rs b/tests/multi.rs index 88dc1e01..13ce6875 100644 --- a/tests/multi.rs +++ b/tests/multi.rs @@ -1,4 +1,4 @@ -use mlua::{Error, ExternalError, IntoLuaMulti, Lua, MultiValue, Result, String, Value, Variadic}; +use mlua::{Error, ExternalError, Integer, IntoLuaMulti, Lua, MultiValue, Result, String, Value, Variadic}; #[test] fn test_result_conversions() -> Result<()> { @@ -65,7 +65,7 @@ fn test_multivalue() { multi.push_back(Value::Integer(1)); multi.push_back(Value::Integer(2)); multi.push_front(Value::Integer(3)); - assert_eq!(multi.iter().filter_map(|v| v.as_integer()).sum::(), 6); + assert_eq!(multi.iter().filter_map(|v| v.as_integer()).sum::(), 6); let vec = multi.into_vec(); assert_eq!(&vec, &[Value::Integer(3), Value::Integer(1), Value::Integer(2)]); From 5b8681dcf2d9ef58bcc97f8b1246aea2cad8f9d0 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 31 Oct 2024 14:35:21 +0000 Subject: [PATCH 269/635] Add `Scope::add_destructor` to attach custom destructors --- src/scope.rs | 39 +++++++++++++++++++++++++++++++++ src/state.rs | 4 ++-- tests/scope.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 2 deletions(-) diff --git a/src/scope.rs b/src/scope.rs index 3f379047..b17d36ef 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -19,7 +19,9 @@ use crate::util::{ /// See [`Lua::scope`] for more details. pub struct Scope<'scope, 'env: 'scope> { lua: LuaGuard, + // Internal destructors run first, then user destructors (based on the declaration order) destructors: Destructors<'env>, + user_destructors: UserDestructors<'env>, _scope_invariant: PhantomData<&'scope mut &'scope ()>, _env_invariant: PhantomData<&'env mut &'env ()>, } @@ -29,11 +31,14 @@ type DestructorCallback<'a> = Box Vec(RefCell)>>); +struct UserDestructors<'a>(RefCell>>); + impl<'scope, 'env: 'scope> Scope<'scope, 'env> { pub(crate) fn new(lua: LuaGuard) -> Self { Scope { lua, destructors: Destructors(RefCell::new(Vec::new())), + user_destructors: UserDestructors(RefCell::new(Vec::new())), _scope_invariant: PhantomData, _env_invariant: PhantomData, } @@ -215,6 +220,31 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { } } + /// Adds a destructor function to be run when the scope ends. + /// + /// This functionality is useful for cleaning up any resources after the scope ends. + /// + /// # Example + /// + /// ```rust + /// # use mlua::{Error, Lua, Result}; + /// # fn main() -> Result<()> { + /// let lua = Lua::new(); + /// let ud = lua.create_any_userdata(String::from("hello"))?; + /// lua.scope(|scope| { + /// scope.add_destructor(|| { + /// _ = ud.take::(); + /// }); + /// // Run the code that uses `ud` here + /// Ok(()) + /// })?; + /// assert!(matches!(ud.borrow::(), Err(Error::UserDataDestructed))); + /// # Ok(()) + /// # } + pub fn add_destructor(&'scope self, destructor: impl FnOnce() + 'env) { + self.user_destructors.0.borrow_mut().push(Box::new(destructor)); + } + unsafe fn create_callback(&'scope self, f: ScopedCallback<'scope>) -> Result { let f = mem::transmute::(f); let f = self.lua.create_callback(f)?; @@ -271,3 +301,12 @@ impl Drop for Destructors<'_> { } } } + +impl Drop for UserDestructors<'_> { + fn drop(&mut self) { + let destructors = mem::take(&mut *self.0.borrow_mut()); + for destructor in destructors { + destructor(); + } + } +} diff --git a/src/state.rs b/src/state.rs index 1cf95f1c..eb674d7e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1470,9 +1470,9 @@ impl Lua { /// lifetimes only outlive the scope lifetime. pub fn scope<'env, R>( &self, - f: impl for<'scope> FnOnce(&'scope mut Scope<'scope, 'env>) -> Result, + f: impl for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> Result, ) -> Result { - f(&mut Scope::new(self.lock_arc())) + f(&Scope::new(self.lock_arc())) } /// Attempts to coerce a Lua value into a String in a manner consistent with Lua's internal diff --git a/tests/scope.rs b/tests/scope.rs index 4ff9a94e..419f0a64 100644 --- a/tests/scope.rs +++ b/tests/scope.rs @@ -1,6 +1,7 @@ use std::cell::Cell; use std::rc::Rc; use std::string::String as StdString; +use std::sync::Arc; use mlua::{ AnyUserData, Error, Function, Lua, MetaMethod, ObjectLike, Result, String, UserData, UserDataFields, @@ -66,6 +67,27 @@ fn test_scope_outer_lua_access() -> Result<()> { Ok(()) } +#[test] +fn test_scope_capture_scope() -> Result<()> { + let lua = Lua::new(); + + let i = Cell::new(0); + lua.scope(|scope| { + let f = scope.create_function(|_, ()| { + scope.create_function(|_, n: u32| { + i.set(i.get() + n); + Ok(()) + }) + })?; + f.call::(())?.call::<()>(10)?; + Ok(()) + })?; + + assert_eq!(i.get(), 10); + + Ok(()) +} + #[test] fn test_scope_userdata_fields() -> Result<()> { struct MyUserData<'a>(&'a Cell); @@ -463,6 +485,42 @@ fn test_scope_any_userdata_ref_mut() -> Result<()> { Ok(()) } +#[test] +fn test_scope_destructors() -> Result<()> { + let lua = Lua::new(); + + lua.register_userdata_type::>(|reg| { + reg.add_meta_method("__tostring", |_, data, ()| Ok(data.to_string())); + })?; + + let arc_str = Arc::new(StdString::from("foo")); + + let ud = lua.create_any_userdata(arc_str.clone())?; + lua.scope(|scope| { + scope.add_destructor(|| { + assert!(ud.take::>().is_ok()); + }); + Ok(()) + })?; + assert_eq!(Arc::strong_count(&arc_str), 1); + + // Try destructing the userdata while it's borrowed + let ud = lua.create_any_userdata(arc_str.clone())?; + ud.borrow_scoped::, _>(|arc_str| { + assert_eq!(arc_str.as_str(), "foo"); + lua.scope(|scope| { + scope.add_destructor(|| { + assert!(ud.take::>().is_err()); + }); + Ok(()) + }) + .unwrap(); + assert_eq!(arc_str.as_str(), "foo"); + })?; + + Ok(()) +} + fn modify_userdata(lua: &Lua, ud: &AnyUserData) -> Result<()> { lua.load( r#" From 928e1d92213fcb1e85cb4a30a5e8182f00059ea2 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 31 Oct 2024 18:41:13 +0000 Subject: [PATCH 270/635] Revert `&Scope` to `&mut Scope` --- src/state.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/state.rs b/src/state.rs index eb674d7e..ce46c64d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1470,9 +1470,10 @@ impl Lua { /// lifetimes only outlive the scope lifetime. pub fn scope<'env, R>( &self, - f: impl for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> Result, + f: impl for<'scope> FnOnce(&'scope mut Scope<'scope, 'env>) -> Result, ) -> Result { - f(&Scope::new(self.lock_arc())) + // TODO: Update to `&Scope` in next major release + f(&mut Scope::new(self.lock_arc())) } /// Attempts to coerce a Lua value into a String in a manner consistent with Lua's internal From bb311349ecb24cb9ad4598f9c997eb9bf658f716 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 2 Nov 2024 10:14:50 +0000 Subject: [PATCH 271/635] Switch between shared and exclusive lock for `UserDataRef` depending if `T: Sync` or not. --- src/userdata.rs | 1 + src/userdata/cell.rs | 25 +++++++++++++--- src/userdata/lock.rs | 14 ++++----- src/userdata/util.rs | 31 ++++++++++++++++++++ tests/send.rs | 69 ++++++++++++++++++++++++++++++++++++++------ 5 files changed, 120 insertions(+), 20 deletions(-) create mode 100644 src/userdata/util.rs diff --git a/src/userdata.rs b/src/userdata.rs index db1f37ae..886119bd 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -1082,6 +1082,7 @@ mod cell; mod lock; mod object; mod registry; +mod util; #[cfg(test)] mod assertions { diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index bd3b8039..a6f4849e 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -16,6 +16,7 @@ use crate::util::get_userdata; use crate::value::Value; use super::lock::{RawLock, UserDataLock}; +use super::util::is_sync; #[cfg(all(feature = "serialize", not(feature = "send")))] type DynSerialize = dyn erased_serde::Serialize; @@ -164,7 +165,11 @@ impl Deref for UserDataRef { impl Drop for UserDataRef { #[inline] fn drop(&mut self) { - unsafe { self.0.raw_lock().unlock_shared() }; + if !cfg!(feature = "send") || is_sync::() { + unsafe { self.0.raw_lock().unlock_shared() }; + } else { + unsafe { self.0.raw_lock().unlock_exclusive() }; + } } } @@ -185,7 +190,11 @@ impl TryFrom> for UserDataRef { #[inline] fn try_from(variant: UserDataVariant) -> Result { - if !variant.raw_lock().try_lock_shared() { + if !cfg!(feature = "send") || is_sync::() { + if !variant.raw_lock().try_lock_shared() { + return Err(Error::UserDataBorrowError); + } + } else if !variant.raw_lock().try_lock_exclusive() { return Err(Error::UserDataBorrowError); } Ok(UserDataRef(variant)) @@ -282,7 +291,11 @@ pub(crate) struct UserDataBorrowRef<'a, T>(&'a UserDataVariant); impl Drop for UserDataBorrowRef<'_, T> { #[inline] fn drop(&mut self) { - unsafe { self.0.raw_lock().unlock_shared() }; + if !cfg!(feature = "send") || is_sync::() { + unsafe { self.0.raw_lock().unlock_shared() }; + } else { + unsafe { self.0.raw_lock().unlock_exclusive() }; + } } } @@ -301,7 +314,11 @@ impl<'a, T> TryFrom<&'a UserDataVariant> for UserDataBorrowRef<'a, T> { #[inline(always)] fn try_from(variant: &'a UserDataVariant) -> Result { - if !variant.raw_lock().try_lock_shared() { + if !cfg!(feature = "send") || is_sync::() { + if !variant.raw_lock().try_lock_shared() { + return Err(Error::UserDataBorrowError); + } + } else if !variant.raw_lock().try_lock_exclusive() { return Err(Error::UserDataBorrowError); } Ok(UserDataBorrowRef(variant)) diff --git a/src/userdata/lock.rs b/src/userdata/lock.rs index 02cf005d..c5690444 100644 --- a/src/userdata/lock.rs +++ b/src/userdata/lock.rs @@ -63,32 +63,32 @@ mod lock_impl { #[cfg(feature = "send")] mod lock_impl { - use parking_lot::lock_api::RawMutex; + use parking_lot::lock_api::RawRwLock; - pub(crate) type RawLock = parking_lot::RawMutex; + pub(crate) type RawLock = parking_lot::RawRwLock; impl super::UserDataLock for RawLock { #[allow(clippy::declare_interior_mutable_const)] - const INIT: Self = ::INIT; + const INIT: Self = ::INIT; #[inline(always)] fn try_lock_shared(&self) -> bool { - RawLock::try_lock(self) + RawRwLock::try_lock_shared(self) } #[inline(always)] fn try_lock_exclusive(&self) -> bool { - RawLock::try_lock(self) + RawRwLock::try_lock_exclusive(self) } #[inline(always)] unsafe fn unlock_shared(&self) { - RawLock::unlock(self) + RawRwLock::unlock_shared(self) } #[inline(always)] unsafe fn unlock_exclusive(&self) { - RawLock::unlock(self) + RawRwLock::unlock_exclusive(self) } } } diff --git a/src/userdata/util.rs b/src/userdata/util.rs new file mode 100644 index 00000000..5d403c5f --- /dev/null +++ b/src/userdata/util.rs @@ -0,0 +1,31 @@ +use std::cell::Cell; +use std::marker::PhantomData; + +// This is a trick to check if a type is `Sync` or not. +// It uses leaked specialization feature from stdlib. +struct IsSync<'a, T> { + is_sync: &'a Cell, + _marker: PhantomData, +} + +impl Clone for IsSync<'_, T> { + fn clone(&self) -> Self { + self.is_sync.set(false); + IsSync { + is_sync: self.is_sync, + _marker: PhantomData, + } + } +} + +impl Copy for IsSync<'_, T> {} + +pub(crate) fn is_sync() -> bool { + let is_sync = Cell::new(true); + let _ = [IsSync:: { + is_sync: &is_sync, + _marker: PhantomData, + }] + .clone(); + is_sync.get() +} diff --git a/tests/send.rs b/tests/send.rs index 25b0602e..c4f27cec 100644 --- a/tests/send.rs +++ b/tests/send.rs @@ -4,25 +4,36 @@ use std::cell::UnsafeCell; use std::marker::PhantomData; use std::string::String as StdString; -use mlua::{AnyUserData, Error, Lua, Result, UserDataRef}; +use mlua::{AnyUserData, Error, Lua, ObjectLike, Result, UserData, UserDataMethods, UserDataRef}; use static_assertions::{assert_impl_all, assert_not_impl_all}; #[test] -fn test_userdata_multithread_access() -> Result<()> { +fn test_userdata_multithread_access_send_only() -> Result<()> { let lua = Lua::new(); // This type is `Send` but not `Sync`. - struct MyUserData(#[allow(unused)] StdString, PhantomData>); - + struct MyUserData(StdString, PhantomData>); assert_impl_all!(MyUserData: Send); assert_not_impl_all!(MyUserData: Sync); - lua.globals().set( - "ud", - AnyUserData::wrap(MyUserData("hello".to_string(), PhantomData)), - )?; + impl UserData for MyUserData { + fn add_methods>(methods: &mut M) { + methods.add_method("method", |lua, this, ()| { + let ud = lua.globals().get::("ud")?; + assert!((ud.call_method::<()>("method2", ()).err().unwrap().to_string()) + .contains("error borrowing userdata")); + Ok(this.0.clone()) + }); + + methods.add_method("method2", |_, _, ()| Ok(())); + } + } + + lua.globals() + .set("ud", MyUserData("hello".to_string(), PhantomData))?; + // We acquired the exclusive reference. - let _ud1 = lua.globals().get::>("ud")?; + let ud = lua.globals().get::>("ud")?; std::thread::scope(|s| { s.spawn(|| { @@ -31,5 +42,45 @@ fn test_userdata_multithread_access() -> Result<()> { }); }); + drop(ud); + lua.load("ud:method()").exec().unwrap(); + + Ok(()) +} + +#[test] +fn test_userdata_multithread_access_sync() -> Result<()> { + let lua = Lua::new(); + + // This type is `Send` and `Sync`. + struct MyUserData(StdString); + assert_impl_all!(MyUserData: Send, Sync); + + impl UserData for MyUserData { + fn add_methods>(methods: &mut M) { + methods.add_method("method", |lua, this, ()| { + let ud = lua.globals().get::("ud")?; + assert!(ud.call_method::<()>("method2", ()).is_ok()); + Ok(this.0.clone()) + }); + + methods.add_method("method2", |_, _, ()| Ok(())); + } + } + + lua.globals().set("ud", MyUserData("hello".to_string()))?; + + // We acquired the shared reference. + let _ud = lua.globals().get::>("ud")?; + + std::thread::scope(|s| { + s.spawn(|| { + // Getting another shared reference for `Sync` type is allowed. + let _ = lua.globals().get::>("ud").unwrap(); + }); + }); + + lua.load("ud:method()").exec().unwrap(); + Ok(()) } From c2eab173c5554cd8a3bd7b4ee42fa3bc9087b1fa Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 3 Nov 2024 11:36:23 +0000 Subject: [PATCH 272/635] Add `userdata-wrappers` feature This feature allow to opt into `impl UserData` for `Rc`/`Arc`/`Rc>`/`Arc>` where `T: UserData` Close #470 --- .github/workflows/main.yml | 24 +-- Cargo.toml | 1 + README.md | 1 + src/scope.rs | 3 +- src/userdata/registry.rs | 314 +++++++++++++++++++++++++++++-------- tests/userdata.rs | 157 +++++++++++++++++++ 6 files changed, 422 insertions(+), 78 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a4795c1b..cd12c620 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,8 +27,8 @@ jobs: - name: Build ${{ matrix.lua }} vendored run: | cargo build --features "${{ matrix.lua }},vendored" - cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow" - cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,send" + cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers" + cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send" shell: bash - name: Build ${{ matrix.lua }} pkg-config if: ${{ matrix.os == 'ubuntu-latest' }} @@ -51,7 +51,7 @@ jobs: toolchain: stable target: aarch64-apple-darwin - name: Cross-compile - run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros" + run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" build_aarch64_cross_ubuntu: name: Cross-compile to aarch64-unknown-linux-gnu @@ -72,7 +72,7 @@ jobs: sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross shell: bash - name: Cross-compile - run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros" + run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" shell: bash build_armv7_cross_ubuntu: @@ -94,7 +94,7 @@ jobs: sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross shell: bash - name: Cross-compile - run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros" + run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" shell: bash test: @@ -123,8 +123,8 @@ jobs: - name: Run ${{ matrix.lua }} tests run: | cargo test --features "${{ matrix.lua }},vendored" - cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow" - cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,send" + cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers" + cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send" shell: bash - name: Run compile tests (macos lua54) if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }} @@ -154,8 +154,8 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Run ${{ matrix.lua }} tests with address sanitizer run: | - cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions - cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,send" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions + cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions + cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions shell: bash env: RUSTFLAGS: -Z sanitizer=address @@ -181,7 +181,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Run ${{ matrix.lua }} tests with forced memory limit run: | - cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros" + cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" shell: bash env: RUSTFLAGS: --cfg=force_memory_limit @@ -254,7 +254,7 @@ jobs: - name: Run ${{ matrix.lua }} tests run: | cargo test --tests --features "${{ matrix.lua }},vendored" - cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros" + cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers" rustfmt: name: Rustfmt @@ -281,4 +281,4 @@ jobs: - uses: giraffate/clippy-action@v1 with: reporter: 'github-pr-review' - clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow" + clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" diff --git a/Cargo.toml b/Cargo.toml index 988cad29..eb958cba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ error-send = [] serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value"] macros = ["mlua_derive/macros"] anyhow = ["dep:anyhow", "error-send"] +userdata-wrappers = [] [dependencies] mlua_derive = { version = "=0.10.0", optional = true, path = "mlua_derive" } diff --git a/README.md b/README.md index c7035f22..ee0661f1 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl * `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework * `macros`: enable procedural macros (such as `chunk!`) * `anyhow`: enable `anyhow::Error` conversion into Lua +* `userdata-wrappers`: opt into `impl UserData` for `Rc`/`Arc`/`Rc>`/`Arc>` where `T: UserData` [5.4]: https://www.lua.org/manual/5.4/manual.html [5.3]: https://www.lua.org/manual/5.3/manual.html diff --git a/src/scope.rs b/src/scope.rs index b17d36ef..99419b6b 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,7 +1,6 @@ use std::cell::RefCell; use std::marker::PhantomData; use std::mem; -use std::os::raw::c_void; use crate::error::{Error, Result}; use crate::function::Function; @@ -183,7 +182,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { let ud_ptr = util::push_uninit_userdata::>(state, protect)?; // Push the metatable and register it with no TypeId - let mut registry = UserDataRegistry::new_unique(ud_ptr as *const c_void); + let mut registry = UserDataRegistry::new_unique(ud_ptr as *mut _); T::register(&mut registry); self.lua.push_userdata_metatable(registry)?; let mt_ptr = ffi::lua_topointer(state, -1); diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index 4c7eee82..d83b4487 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -21,12 +21,32 @@ use { std::future::{self, Future}, }; +#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] +use std::rc::Rc; +#[cfg(feature = "userdata-wrappers")] +use std::sync::{Arc, Mutex, RwLock}; + type StaticFieldCallback = Box Result<()> + 'static>; #[derive(Clone, Copy)] -pub(crate) enum UserDataTypeId { +enum UserDataTypeId { Shared(TypeId), Unique(usize), + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Rc(TypeId), + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + RcRefCell(TypeId), + #[cfg(feature = "userdata-wrappers")] + Arc(TypeId), + #[cfg(feature = "userdata-wrappers")] + ArcMutex(TypeId), + #[cfg(feature = "userdata-wrappers")] + ArcRwLock(TypeId), + #[cfg(feature = "userdata-wrappers")] + ArcParkingLotMutex(TypeId), + #[cfg(feature = "userdata-wrappers")] + ArcParkingLotRwLock(TypeId), } /// Handle to registry for userdata methods and metamethods. @@ -45,31 +65,23 @@ pub struct UserDataRegistry { #[cfg(feature = "async")] pub(crate) async_meta_methods: Vec<(String, AsyncCallback)>, - pub(crate) type_id: UserDataTypeId, + type_id: UserDataTypeId, _type: PhantomData, } impl UserDataRegistry { - #[inline] + #[inline(always)] pub(crate) fn new(type_id: TypeId) -> Self { - UserDataRegistry { - fields: Vec::new(), - field_getters: Vec::new(), - field_setters: Vec::new(), - meta_fields: Vec::new(), - methods: Vec::new(), - #[cfg(feature = "async")] - async_methods: Vec::new(), - meta_methods: Vec::new(), - #[cfg(feature = "async")] - async_meta_methods: Vec::new(), - type_id: UserDataTypeId::Shared(type_id), - _type: PhantomData, - } + Self::with_type_id(UserDataTypeId::Shared(type_id)) } - #[inline] - pub(crate) fn new_unique(ud_ptr: *const c_void) -> Self { + #[inline(always)] + pub(crate) fn new_unique(ud_ptr: *mut c_void) -> Self { + Self::with_type_id(UserDataTypeId::Unique(ud_ptr as usize)) + } + + #[inline(always)] + fn with_type_id(type_id: UserDataTypeId) -> Self { UserDataRegistry { fields: Vec::new(), field_getters: Vec::new(), @@ -81,7 +93,7 @@ impl UserDataRegistry { meta_methods: Vec::new(), #[cfg(feature = "async")] async_meta_methods: Vec::new(), - type_id: UserDataTypeId::Unique(ud_ptr as usize), + type_id, _type: PhantomData, } } @@ -91,6 +103,20 @@ impl UserDataRegistry { match self.type_id { UserDataTypeId::Shared(type_id) => Some(type_id), UserDataTypeId::Unique(_) => None, + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + UserDataTypeId::Rc(type_id) => Some(type_id), + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + UserDataTypeId::RcRefCell(type_id) => Some(type_id), + #[cfg(feature = "userdata-wrappers")] + UserDataTypeId::Arc(type_id) => Some(type_id), + #[cfg(feature = "userdata-wrappers")] + UserDataTypeId::ArcMutex(type_id) => Some(type_id), + #[cfg(feature = "userdata-wrappers")] + UserDataTypeId::ArcRwLock(type_id) => Some(type_id), + #[cfg(feature = "userdata-wrappers")] + UserDataTypeId::ArcParkingLotMutex(type_id) => Some(type_id), + #[cfg(feature = "userdata-wrappers")] + UserDataTypeId::ArcParkingLotRwLock(type_id) => Some(type_id), } } @@ -120,28 +146,102 @@ impl UserDataRegistry { let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua); match target_type_id { - // This branch is for `'static` userdata that share type metatable - UserDataTypeId::Shared(target_type_id) => { - match try_self_arg!(rawlua.get_userdata_type_id::(self_index)) { - Some(self_type_id) if self_type_id == target_type_id => { - let ud = get_userdata::>(state, self_index); - try_self_arg!((*ud).try_borrow_scoped(|ud| { - method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) - })) - } - _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), - } + #[rustfmt::skip] + UserDataTypeId::Shared(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[rustfmt::skip] + UserDataTypeId::Unique(target_ptr) + if get_userdata::>(state, self_index) as usize == target_ptr => + { + let ud = target_ptr as *mut UserDataStorage; + try_self_arg!((*ud).try_borrow_scoped(|ud| { + method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + #[rustfmt::skip] + UserDataTypeId::Rc(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + #[rustfmt::skip] + UserDataTypeId::RcRefCell(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let ud = ud.try_borrow().map_err(|_| Error::UserDataBorrowError)?; + method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::Arc(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::ArcMutex(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let ud = ud.try_lock().map_err(|_| Error::UserDataBorrowError)?; + method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::ArcRwLock(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let ud = ud.try_read().map_err(|_| Error::UserDataBorrowError)?; + method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua) + })) } - UserDataTypeId::Unique(target_ptr) => { - match get_userdata::>(state, self_index) { - ud if ud as usize == target_ptr => { - try_self_arg!((*ud).try_borrow_scoped(|ud| { - method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) - })) - } - _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), - } + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::ArcParkingLotMutex(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) + == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let ud = ud.try_lock().ok_or(Error::UserDataBorrowError)?; + method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua) + })) } + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::ArcParkingLotRwLock(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) + == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let ud = ud.try_read().ok_or(Error::UserDataBorrowError)?; + method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua) + })) + } + _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), } }) } @@ -174,28 +274,96 @@ impl UserDataRegistry { let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua); match target_type_id { - // This branch is for `'static` userdata that share type metatable - UserDataTypeId::Shared(target_type_id) => { - match try_self_arg!(rawlua.get_userdata_type_id::(self_index)) { - Some(self_type_id) if self_type_id == target_type_id => { - let ud = get_userdata::>(state, self_index); - try_self_arg!((*ud).try_borrow_scoped_mut(|ud| { - method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) - })) - } - _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), - } + #[rustfmt::skip] + UserDataTypeId::Shared(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped_mut(|ud| { + method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[rustfmt::skip] + UserDataTypeId::Unique(target_ptr) + if get_userdata::>(state, self_index) as usize == target_ptr => + { + let ud = target_ptr as *mut UserDataStorage; + try_self_arg!((*ud).try_borrow_scoped_mut(|ud| { + method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + #[rustfmt::skip] + UserDataTypeId::Rc(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>(self_index)) == Some(target_type_id) => + { + Err(Error::UserDataBorrowMutError) + }, + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + #[rustfmt::skip] + UserDataTypeId::RcRefCell(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let mut ud = ud.try_borrow_mut().map_err(|_| Error::UserDataBorrowMutError)?; + method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::Arc(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>(self_index)) == Some(target_type_id) => + { + Err(Error::UserDataBorrowMutError) + }, + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::ArcMutex(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let mut ud = ud.try_lock().map_err(|_| Error::UserDataBorrowMutError)?; + method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua) + })) } - UserDataTypeId::Unique(target_ptr) => { - match get_userdata::>(state, self_index) { - ud if ud as usize == target_ptr => { - try_self_arg!((*ud).try_borrow_scoped_mut(|ud| { - method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) - })) - } - _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), - } + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::ArcRwLock(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let mut ud = ud.try_write().map_err(|_| Error::UserDataBorrowMutError)?; + method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua) + })) } + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::ArcParkingLotMutex(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) + == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let mut ud = ud.try_lock().ok_or(Error::UserDataBorrowMutError)?; + method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::ArcParkingLotRwLock(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) + == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let mut ud = ud.try_write().ok_or(Error::UserDataBorrowMutError)?; + method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua) + })) + } + _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), } }) } @@ -607,11 +775,14 @@ impl UserDataMethods for UserDataRegistry { } macro_rules! lua_userdata_impl { - ($type:ty) => { + ($type:ty => $type_variant:tt) => { + lua_userdata_impl!($type, UserDataTypeId::$type_variant(TypeId::of::<$type>())); + }; + + ($type:ty, $type_id:expr) => { impl UserData for $type { fn register(registry: &mut UserDataRegistry) { - let type_id = TypeId::of::(); - let mut orig_registry = UserDataRegistry::new(type_id); + let mut orig_registry = UserDataRegistry::with_type_id($type_id); T::register(&mut orig_registry); // Copy all fields, methods, etc. from the original registry @@ -635,4 +806,19 @@ macro_rules! lua_userdata_impl { // A special proxy object for UserData pub(crate) struct UserDataProxy(pub(crate) PhantomData); -lua_userdata_impl!(UserDataProxy); +lua_userdata_impl!(UserDataProxy, UserDataTypeId::Shared(TypeId::of::())); + +#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] +lua_userdata_impl!(Rc => Rc); +#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] +lua_userdata_impl!(Rc> => RcRefCell); +#[cfg(feature = "userdata-wrappers")] +lua_userdata_impl!(Arc => Arc); +#[cfg(feature = "userdata-wrappers")] +lua_userdata_impl!(Arc> => ArcMutex); +#[cfg(feature = "userdata-wrappers")] +lua_userdata_impl!(Arc> => ArcRwLock); +#[cfg(feature = "userdata-wrappers")] +lua_userdata_impl!(Arc> => ArcParkingLotMutex); +#[cfg(feature = "userdata-wrappers")] +lua_userdata_impl!(Arc> => ArcParkingLotRwLock); diff --git a/tests/userdata.rs b/tests/userdata.rs index d8c06b9e..c05fb108 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -881,3 +881,160 @@ fn test_nested_userdata_gc() -> Result<()> { Ok(()) } + +#[cfg(feature = "userdata-wrappers")] +#[test] +fn test_userdata_wrappers() -> Result<()> { + struct MyUserData(i64); + + impl UserData for MyUserData { + fn add_fields>(fields: &mut F) { + fields.add_field("static", "constant"); + fields.add_field_method_get("data", |_, this| Ok(this.0)); + fields.add_field_method_set("data", |_, this, val| { + this.0 = val; + Ok(()) + }) + } + } + + let lua = Lua::new(); + let globals = lua.globals(); + + // Rc + #[cfg(not(feature = "send"))] + { + let ud = std::rc::Rc::new(MyUserData(1)); + globals.set("rc_ud", ud.clone())?; + lua.load( + r#" + assert(rc_ud.static == "constant") + local ok, err = pcall(function() rc_ud.data = 2 end) + assert( + tostring(err):sub(1, 32) == "error mutably borrowing userdata", + "expected error mutably borrowing userdata, got " .. tostring(err) + ) + assert(rc_ud.data == 1) + "#, + ) + .exec() + .unwrap(); + globals.set("rc_ud", Nil)?; + lua.gc_collect()?; + assert_eq!(std::rc::Rc::strong_count(&ud), 1); + } + + // Rc> + #[cfg(not(feature = "send"))] + { + let ud = std::rc::Rc::new(std::cell::RefCell::new(MyUserData(2))); + globals.set("rc_refcell_ud", ud.clone())?; + lua.load( + r#" + assert(rc_refcell_ud.static == "constant") + rc_refcell_ud.data = rc_refcell_ud.data + 1 + assert(rc_refcell_ud.data == 3) + "#, + ) + .exec()?; + assert_eq!(ud.borrow().0, 3); + globals.set("rc_refcell_ud", Nil)?; + lua.gc_collect()?; + assert_eq!(std::rc::Rc::strong_count(&ud), 1); + } + + // Arc + { + let ud = Arc::new(MyUserData(3)); + globals.set("arc_ud", ud.clone())?; + lua.load( + r#" + assert(arc_ud.static == "constant") + local ok, err = pcall(function() arc_ud.data = 10 end) + assert( + tostring(err):sub(1, 32) == "error mutably borrowing userdata", + "expected error mutably borrowing userdata, got " .. tostring(err) + ) + assert(arc_ud.data == 3) + "#, + ) + .exec()?; + globals.set("arc_ud", Nil)?; + lua.gc_collect()?; + assert_eq!(Arc::strong_count(&ud), 1); + } + + // Arc> + { + let ud = Arc::new(std::sync::Mutex::new(MyUserData(4))); + globals.set("arc_mutex_ud", ud.clone())?; + lua.load( + r#" + assert(arc_mutex_ud.static == "constant") + arc_mutex_ud.data = arc_mutex_ud.data + 1 + assert(arc_mutex_ud.data == 5) + "#, + ) + .exec()?; + assert_eq!(ud.lock().unwrap().0, 5); + globals.set("arc_mutex_ud", Nil)?; + lua.gc_collect()?; + assert_eq!(Arc::strong_count(&ud), 1); + } + + // Arc> + { + let ud = Arc::new(std::sync::RwLock::new(MyUserData(6))); + globals.set("arc_rwlock_ud", ud.clone())?; + lua.load( + r#" + assert(arc_rwlock_ud.static == "constant") + arc_rwlock_ud.data = arc_rwlock_ud.data + 1 + assert(arc_rwlock_ud.data == 7) + "#, + ) + .exec()?; + assert_eq!(ud.read().unwrap().0, 7); + globals.set("arc_rwlock_ud", Nil)?; + lua.gc_collect()?; + assert_eq!(Arc::strong_count(&ud), 1); + } + + // Arc> + { + let ud = Arc::new(parking_lot::Mutex::new(MyUserData(8))); + globals.set("arc_parking_lot_mutex_ud", ud.clone())?; + lua.load( + r#" + assert(arc_parking_lot_mutex_ud.static == "constant") + arc_parking_lot_mutex_ud.data = arc_parking_lot_mutex_ud.data + 1 + assert(arc_parking_lot_mutex_ud.data == 9) + "#, + ) + .exec()?; + assert_eq!(ud.lock().0, 9); + globals.set("arc_parking_lot_mutex_ud", Nil)?; + lua.gc_collect()?; + assert_eq!(Arc::strong_count(&ud), 1); + } + + // Arc> + { + let ud = Arc::new(parking_lot::RwLock::new(MyUserData(10))); + globals.set("arc_parking_lot_rwlock_ud", ud.clone())?; + lua.load( + r#" + assert(arc_parking_lot_rwlock_ud.static == "constant") + arc_parking_lot_rwlock_ud.data = arc_parking_lot_rwlock_ud.data + 1 + assert(arc_parking_lot_rwlock_ud.data == 11) + "#, + ) + .exec()?; + assert_eq!(ud.read().0, 11); + globals.set("arc_parking_lot_rwlock_ud", Nil)?; + lua.gc_collect()?; + assert_eq!(Arc::strong_count(&ud), 1); + } + + Ok(()) +} From 1f32754f05bdce830d26d457d1ac2d297fafbdd7 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 3 Nov 2024 12:18:00 +0000 Subject: [PATCH 273/635] Relax `UserDataBorrowRef` restrictions to allow recursive calls --- src/userdata/cell.rs | 16 ++++++---------- tests/send.rs | 5 ++--- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index a6f4849e..5766f7e0 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -291,11 +291,7 @@ pub(crate) struct UserDataBorrowRef<'a, T>(&'a UserDataVariant); impl Drop for UserDataBorrowRef<'_, T> { #[inline] fn drop(&mut self) { - if !cfg!(feature = "send") || is_sync::() { - unsafe { self.0.raw_lock().unlock_shared() }; - } else { - unsafe { self.0.raw_lock().unlock_exclusive() }; - } + unsafe { self.0.raw_lock().unlock_shared() }; } } @@ -314,11 +310,11 @@ impl<'a, T> TryFrom<&'a UserDataVariant> for UserDataBorrowRef<'a, T> { #[inline(always)] fn try_from(variant: &'a UserDataVariant) -> Result { - if !cfg!(feature = "send") || is_sync::() { - if !variant.raw_lock().try_lock_shared() { - return Err(Error::UserDataBorrowError); - } - } else if !variant.raw_lock().try_lock_exclusive() { + // We don't need to check for `T: Sync` because when this method is used (internally), + // Lua mutex is already locked. + // If non-`Sync` userdata is already borrowed by another thread (via `UserDataRef`), it will be + // exclusively locked. + if !variant.raw_lock().try_lock_shared() { return Err(Error::UserDataBorrowError); } Ok(UserDataBorrowRef(variant)) diff --git a/tests/send.rs b/tests/send.rs index c4f27cec..9def807d 100644 --- a/tests/send.rs +++ b/tests/send.rs @@ -20,12 +20,11 @@ fn test_userdata_multithread_access_send_only() -> Result<()> { fn add_methods>(methods: &mut M) { methods.add_method("method", |lua, this, ()| { let ud = lua.globals().get::("ud")?; - assert!((ud.call_method::<()>("method2", ()).err().unwrap().to_string()) - .contains("error borrowing userdata")); + assert_eq!(ud.call_method::("method2", ())?, "method2"); Ok(this.0.clone()) }); - methods.add_method("method2", |_, _, ()| Ok(())); + methods.add_method("method2", |_, _, ()| Ok("method2")); } } From 15738dda1fe95fdddf3852489e8783682ec2627b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 3 Nov 2024 12:19:22 +0000 Subject: [PATCH 274/635] Update tarpaulin.toml to include `userdata-wrappers` --- tarpaulin.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tarpaulin.toml b/tarpaulin.toml index 26ede626..eb39aec2 100644 --- a/tarpaulin.toml +++ b/tarpaulin.toml @@ -1,5 +1,5 @@ [lua54_coverage] -features = "lua54,vendored,async,send,serialize,macros,anyhow" +features = "lua54,vendored,async,send,serialize,macros,anyhow,userdata-wrappers" [lua54_with_memory_limit_coverage] features = "lua54,vendored,async,send,serialize,macros" From 46ee7ea77271146c353c8db31d7e8de17d2fb186 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 3 Nov 2024 12:54:21 +0000 Subject: [PATCH 275/635] Update tarpaulin.toml --- tarpaulin.toml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tarpaulin.toml b/tarpaulin.toml index eb39aec2..71c73a35 100644 --- a/tarpaulin.toml +++ b/tarpaulin.toml @@ -1,20 +1,23 @@ -[lua54_coverage] +[lua54] features = "lua54,vendored,async,send,serialize,macros,anyhow,userdata-wrappers" -[lua54_with_memory_limit_coverage] -features = "lua54,vendored,async,send,serialize,macros" +[lua54_non_send] +features = "lua54,vendored,async,serialize,macros,anyhow,userdata-wrappers" + +[lua54_with_memory_limit] +features = "lua54,vendored,async,send,serialize,macros,anyhow,userdata-wrappers" rustflags = "--cfg force_memory_limit" -[lua51_coverage] +[lua51] features = "lua51,vendored,async,send,serialize,macros" -[lua51_with_memory_limit_coverage] +[lua51_with_memory_limit] features = "lua51,vendored,async,send,serialize,macros" rustflags = "--cfg force_memory_limit" -[luau_coverage] +[luau] features = "luau,async,send,serialize,macros" -[luau_with_memory_limit_coverage] +[luau_with_memory_limit] features = "luau,async,send,serialize,macros" rustflags = "--cfg force_memory_limit" From b34d67ec412af70617df31f66e4ab1cfc469a48b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 3 Nov 2024 14:49:12 +0000 Subject: [PATCH 276/635] Update Luau to 0.650 (luau0-src 0.11.1) --- mlua-sys/Cargo.toml | 2 +- mlua-sys/src/luau/lua.rs | 1 + mlua-sys/src/luau/lualib.rs | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 32f7c878..7501c974 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -40,7 +40,7 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 547.0.0, < 547.1.0", optional = true } luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } -luau0-src = { version = "0.11.0", optional = true } +luau0-src = { version = "0.11.1", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 53d1c223..c75174a6 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -185,6 +185,7 @@ extern "C-unwind" { pub fn lua_pushlightuserdatatagged(L: *mut lua_State, p: *mut c_void, tag: c_int); pub fn lua_newuserdatatagged(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void; + pub fn lua_newuserdatataggedwithmetatable(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void; pub fn lua_newuserdatadtor(L: *mut lua_State, sz: usize, dtor: lua_Udestructor) -> *mut c_void; pub fn lua_newbuffer(L: *mut lua_State, sz: usize) -> *mut c_void; diff --git a/mlua-sys/src/luau/lualib.rs b/mlua-sys/src/luau/lualib.rs index 96864a07..0469bfd1 100644 --- a/mlua-sys/src/luau/lualib.rs +++ b/mlua-sys/src/luau/lualib.rs @@ -13,6 +13,7 @@ pub const LUA_BUFFERLIBNAME: &str = "buffer"; pub const LUA_UTF8LIBNAME: &str = "utf8"; pub const LUA_MATHLIBNAME: &str = "math"; pub const LUA_DBLIBNAME: &str = "debug"; +pub const LUA_VECLIBNAME: &str = "vector"; extern "C-unwind" { pub fn luaopen_base(L: *mut lua_State) -> c_int; @@ -25,6 +26,7 @@ extern "C-unwind" { pub fn luaopen_utf8(L: *mut lua_State) -> c_int; pub fn luaopen_math(L: *mut lua_State) -> c_int; pub fn luaopen_debug(L: *mut lua_State) -> c_int; + pub fn luaopen_vector(L: *mut lua_State) -> c_int; // open all builtin libraries pub fn luaL_openlibs(L: *mut lua_State); From 05778fbe6f9d91fe7bf2d8e5cb7393aebe7709b9 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 4 Nov 2024 15:48:22 +0000 Subject: [PATCH 277/635] Don't store and use wrong main Lua state in module mode (Lua 5.1/JIT only). When mlua module is loaded from a non-main coroutine we store a reference to it to use later. If the coroutine is destroyed by GC we can pass a wrong pointer to Lua that will trigger a segfault. Instead, set main_state as Option and use current (active) state if needed. Relates to #479 --- src/state.rs | 53 +++++++++++++++++++++++++----------------------- src/state/raw.rs | 22 +++++++++++--------- 2 files changed, 40 insertions(+), 35 deletions(-) diff --git a/src/state.rs b/src/state.rs index ce46c64d..861a59b4 100644 --- a/src/state.rs +++ b/src/state.rs @@ -485,7 +485,7 @@ impl Lua { let lua = self.lock(); unsafe { if (*lua.extra.get()).sandboxed != enabled { - let state = lua.main_state; + let state = lua.main_state(); check_stack(state, 3)?; protect_lua!(state, 0, 0, |state| { if enabled { @@ -562,10 +562,10 @@ impl Lua { unsafe { let state = lua.state(); ffi::lua_sethook(state, None, 0, 0); - match crate::util::get_main_state(lua.main_state) { - Some(main_state) if !ptr::eq(state, main_state) => { + match lua.main_state { + Some(main_state) if state != main_state.as_ptr() => { // If main_state is different from state, remove hook from it too - ffi::lua_sethook(main_state, None, 0, 0); + ffi::lua_sethook(main_state.as_ptr(), None, 0, 0); } _ => {} }; @@ -654,7 +654,7 @@ impl Lua { let lua = self.lock(); unsafe { (*lua.extra.get()).interrupt_callback = Some(Rc::new(callback)); - (*ffi::lua_callbacks(lua.main_state)).interrupt = Some(interrupt_proc); + (*ffi::lua_callbacks(lua.main_state())).interrupt = Some(interrupt_proc); } } @@ -667,7 +667,7 @@ impl Lua { let lua = self.lock(); unsafe { (*lua.extra.get()).interrupt_callback = None; - (*ffi::lua_callbacks(lua.main_state)).interrupt = None; + (*ffi::lua_callbacks(lua.main_state())).interrupt = None; } } @@ -697,10 +697,9 @@ impl Lua { } let lua = self.lock(); - let state = lua.main_state; unsafe { (*lua.extra.get()).warn_callback = Some(Box::new(callback)); - ffi::lua_setwarnf(state, Some(warn_proc), lua.extra.get() as *mut c_void); + ffi::lua_setwarnf(lua.state(), Some(warn_proc), lua.extra.get() as *mut c_void); } } @@ -715,7 +714,7 @@ impl Lua { let lua = self.lock(); unsafe { (*lua.extra.get()).warn_callback = None; - ffi::lua_setwarnf(lua.main_state, None, ptr::null_mut()); + ffi::lua_setwarnf(lua.state(), None, ptr::null_mut()); } } @@ -767,13 +766,14 @@ impl Lua { /// Returns the amount of memory (in bytes) currently used inside this Lua state. pub fn used_memory(&self) -> usize { let lua = self.lock(); + let state = lua.main_state(); unsafe { - match MemoryState::get(lua.main_state) { + match MemoryState::get(state) { mem_state if !mem_state.is_null() => (*mem_state).used_memory(), _ => { // Get data from the Lua GC - let used_kbytes = ffi::lua_gc(lua.main_state, ffi::LUA_GCCOUNT, 0); - let used_kbytes_rem = ffi::lua_gc(lua.main_state, ffi::LUA_GCCOUNTB, 0); + let used_kbytes = ffi::lua_gc(state, ffi::LUA_GCCOUNT, 0); + let used_kbytes_rem = ffi::lua_gc(state, ffi::LUA_GCCOUNTB, 0); (used_kbytes as usize) * 1024 + (used_kbytes_rem as usize) } } @@ -790,7 +790,7 @@ impl Lua { pub fn set_memory_limit(&self, limit: usize) -> Result { let lua = self.lock(); unsafe { - match MemoryState::get(lua.main_state) { + match MemoryState::get(lua.state()) { mem_state if !mem_state.is_null() => Ok((*mem_state).set_memory_limit(limit)), _ => Err(Error::MemoryControlNotAvailable), } @@ -803,19 +803,19 @@ impl Lua { #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] pub fn gc_is_running(&self) -> bool { let lua = self.lock(); - unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCISRUNNING, 0) != 0 } + unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCISRUNNING, 0) != 0 } } /// Stop the Lua GC from running pub fn gc_stop(&self) { let lua = self.lock(); - unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCSTOP, 0) }; + unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCSTOP, 0) }; } /// Restarts the Lua GC if it is not running pub fn gc_restart(&self) { let lua = self.lock(); - unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCRESTART, 0) }; + unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCRESTART, 0) }; } /// Perform a full garbage-collection cycle. @@ -824,9 +824,10 @@ impl Lua { /// objects. Once to finish the current gc cycle, and once to start and finish the next cycle. pub fn gc_collect(&self) -> Result<()> { let lua = self.lock(); + let state = lua.main_state(); unsafe { - check_stack(lua.main_state, 2)?; - protect_lua!(lua.main_state, 0, 0, fn(state) ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0)) + check_stack(state, 2)?; + protect_lua!(state, 0, 0, fn(state) ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0)) } } @@ -843,9 +844,10 @@ impl Lua { /// finished a collection cycle. pub fn gc_step_kbytes(&self, kbytes: c_int) -> Result { let lua = self.lock(); + let state = lua.main_state(); unsafe { - check_stack(lua.main_state, 3)?; - protect_lua!(lua.main_state, 0, 0, |state| { + check_stack(state, 3)?; + protect_lua!(state, 0, 0, |state| { ffi::lua_gc(state, ffi::LUA_GCSTEP, kbytes) != 0 }) } @@ -861,11 +863,12 @@ impl Lua { /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 pub fn gc_set_pause(&self, pause: c_int) -> c_int { let lua = self.lock(); + let state = lua.main_state(); unsafe { #[cfg(not(feature = "luau"))] - return ffi::lua_gc(lua.main_state, ffi::LUA_GCSETPAUSE, pause); + return ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause); #[cfg(feature = "luau")] - return ffi::lua_gc(lua.main_state, ffi::LUA_GCSETGOAL, pause); + return ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause); } } @@ -877,7 +880,7 @@ impl Lua { /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 pub fn gc_set_step_multiplier(&self, step_multiplier: c_int) -> c_int { let lua = self.lock(); - unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCSETSTEPMUL, step_multiplier) } + unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCSETSTEPMUL, step_multiplier) } } /// Changes the collector to incremental mode with the given parameters. @@ -888,7 +891,7 @@ impl Lua { /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5.1 pub fn gc_inc(&self, pause: c_int, step_multiplier: c_int, step_size: c_int) -> GCMode { let lua = self.lock(); - let state = lua.main_state; + let state = lua.main_state(); #[cfg(any( feature = "lua53", @@ -941,7 +944,7 @@ impl Lua { #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] pub fn gc_gen(&self, minor_multiplier: c_int, major_multiplier: c_int) -> GCMode { let lua = self.lock(); - let state = lua.main_state; + let state = lua.main_state(); let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCGEN, minor_multiplier, major_multiplier) }; match prev_mode { ffi::LUA_GCGEN => GCMode::Generational, diff --git a/src/state/raw.rs b/src/state/raw.rs index 5ec57481..ab616100 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -1,11 +1,12 @@ use std::any::TypeId; use std::cell::{Cell, UnsafeCell}; use std::ffi::{CStr, CString}; +use std::mem; use std::os::raw::{c_char, c_int, c_void}; use std::panic::resume_unwind; +use std::ptr::{self, NonNull}; use std::result::Result as StdResult; use std::sync::Arc; -use std::{mem, ptr}; use crate::chunk::ChunkMode; use crate::error::{Error, Result}; @@ -41,7 +42,6 @@ use { crate::multi::MultiValue, crate::traits::FromLuaMulti, crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue}, - std::ptr::NonNull, std::task::{Context, Poll, Waker}, }; @@ -50,7 +50,7 @@ use { pub struct RawLua { // The state is dynamic and depends on context pub(super) state: Cell<*mut ffi::lua_State>, - pub(super) main_state: *mut ffi::lua_State, + pub(super) main_state: Option>, pub(super) extra: XRc>, } @@ -61,9 +61,9 @@ impl Drop for RawLua { return; } - let mem_state = MemoryState::get(self.main_state); + let mem_state = MemoryState::get(self.main_state()); - ffi::lua_close(self.main_state); + ffi::lua_close(self.main_state()); // Deallocate `MemoryState` if !mem_state.is_null() { @@ -95,10 +95,11 @@ impl RawLua { self.state.get() } - #[cfg(feature = "luau")] #[inline(always)] pub(crate) fn main_state(&self) -> *mut ffi::lua_State { self.main_state + .map(|state| state.as_ptr()) + .unwrap_or_else(|| self.state()) } #[inline(always)] @@ -221,7 +222,8 @@ impl RawLua { #[allow(clippy::arc_with_non_send_sync)] let rawlua = XRc::new(ReentrantMutex::new(RawLua { state: Cell::new(state), - main_state, + // Make sure that we don't store current state as main state (if it's not available) + main_state: get_main_state(state).and_then(NonNull::new), extra: XRc::clone(&extra), })); (*extra.get()).set_lua(&rawlua); @@ -263,7 +265,7 @@ impl RawLua { )); } - let res = load_std_libs(self.main_state, libs); + let res = load_std_libs(self.main_state(), libs); // If `package` library loaded into a safe lua state then disable C modules let curr_libs = (*self.extra.get()).libs; @@ -734,7 +736,7 @@ impl RawLua { } // MemoryInfo is empty in module mode so we cannot predict memory limits - match MemoryState::get(self.main_state) { + match MemoryState::get(self.state()) { mem_state if !mem_state.is_null() => (*mem_state).memory_limit() == 0, _ => (*self.extra.get()).skip_memory_check, // Check the special flag (only for module mode) } @@ -1095,7 +1097,7 @@ impl RawLua { #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] unsafe { if !(*self.extra.get()).libs.contains(StdLib::COROUTINE) { - load_std_libs(self.main_state, StdLib::COROUTINE)?; + load_std_libs(self.main_state(), StdLib::COROUTINE)?; (*self.extra.get()).libs |= StdLib::COROUTINE; } } From a7d0691e10aab66a18b288e92c1555fe67f75da5 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 7 Nov 2024 16:12:20 +0000 Subject: [PATCH 278/635] Add `AnyUserData::destroy` method --- src/state/raw.rs | 23 ++++++++++++++--------- src/userdata.rs | 25 +++++++++++++++++++++++++ src/userdata/cell.rs | 33 ++++++++++++++++++++++++++++++--- src/util/mod.rs | 3 --- src/util/userdata.rs | 8 +------- tests/userdata.rs | 30 +++++++++++++++++++++++++++++- 6 files changed, 99 insertions(+), 23 deletions(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index ab616100..f8389907 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -27,7 +27,7 @@ use crate::util::{ assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state, get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, init_userdata_metatable, pop_error, push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, - short_type_name, StackGuard, WrappedFailure, + short_type_name, take_userdata, StackGuard, WrappedFailure, }; use crate::value::{Nil, Value}; @@ -960,13 +960,19 @@ impl RawLua { } } - #[cfg(feature = "luau")] - let extra_init = None; - #[cfg(not(feature = "luau"))] - let extra_init: Option Result<()>> = Some(|state, mt_idx| { - ffi::lua_pushcfunction(state, crate::util::userdata_destructor::>); - rawset_field(state, mt_idx, "__gc") - }); + unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::lua_State) -> c_int { + let ud = get_userdata::>(state, -1); + if !(*ud).is_borrowed() { + take_userdata::>(state); + ffi::lua_pushboolean(state, 1); + } else { + ffi::lua_pushboolean(state, 0); + } + 1 + } + + ffi::lua_pushcfunction(state, userdata_destructor::); + rawset_field(state, metatable_index, "__gc")?; init_userdata_metatable( state, @@ -974,7 +980,6 @@ impl RawLua { field_getters_index, field_setters_index, methods_index, - extra_init, )?; // Update stack guard to keep metatable after return diff --git a/src/userdata.rs b/src/userdata.rs index 886119bd..50f925dd 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -696,6 +696,31 @@ impl AnyUserData { } } + /// Destroys this userdata. + /// + /// This is similar to [`AnyUserData::take`], but it doesn't require a type. + /// + /// This method works for non-scoped userdata only. + pub fn destroy(&self) -> Result<()> { + let lua = self.0.lua.lock(); + let state = lua.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 3)?; + + lua.push_userdata_ref(&self.0)?; + protect_lua!(state, 1, 1, fn(state) { + if ffi::luaL_callmeta(state, -1, cstr!("__gc")) == 0 { + ffi::lua_pushboolean(state, 0); + } + })?; + if ffi::lua_isboolean(state, -1) != 0 && ffi::lua_toboolean(state, -1) != 0 { + return Ok(()); + } + Err(Error::UserDataBorrowMutError) + } + } + /// Sets an associated value to this [`AnyUserData`]. /// /// The value may be any Lua value whatsoever, and can be retrieved with [`user_value`]. diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index 5766f7e0..d5d8e005 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -1,5 +1,5 @@ use std::any::{type_name, TypeId}; -use std::cell::{RefCell, UnsafeCell}; +use std::cell::{Cell, RefCell, UnsafeCell}; use std::fmt; use std::ops::{Deref, DerefMut}; use std::os::raw::c_int; @@ -99,6 +99,15 @@ impl UserDataVariant { } } + #[inline(always)] + fn borrow_count(&self) -> &Cell { + match self { + Self::Default(inner) => &inner.borrow_count, + #[cfg(feature = "serialize")] + Self::Serializable(inner) => &inner.borrow_count, + } + } + #[inline(always)] fn as_ptr(&self) -> *mut T { match self { @@ -130,6 +139,7 @@ impl Serialize for UserDataStorage<()> { /// A type that provides interior mutability for a userdata value (thread-safe). pub(crate) struct UserDataCell { raw_lock: RawLock, + borrow_count: Cell, value: UnsafeCell, } @@ -143,6 +153,7 @@ impl UserDataCell { fn new(value: T) -> Self { UserDataCell { raw_lock: RawLock::INIT, + borrow_count: Cell::new(0), value: UnsafeCell::new(value), } } @@ -291,7 +302,10 @@ pub(crate) struct UserDataBorrowRef<'a, T>(&'a UserDataVariant); impl Drop for UserDataBorrowRef<'_, T> { #[inline] fn drop(&mut self) { - unsafe { self.0.raw_lock().unlock_shared() }; + unsafe { + self.0.borrow_count().set(self.0.borrow_count().get() - 1); + self.0.raw_lock().unlock_shared(); + } } } @@ -317,6 +331,7 @@ impl<'a, T> TryFrom<&'a UserDataVariant> for UserDataBorrowRef<'a, T> { if !variant.raw_lock().try_lock_shared() { return Err(Error::UserDataBorrowError); } + variant.borrow_count().set(variant.borrow_count().get() + 1); Ok(UserDataBorrowRef(variant)) } } @@ -326,7 +341,10 @@ pub(crate) struct UserDataBorrowMut<'a, T>(&'a UserDataVariant); impl Drop for UserDataBorrowMut<'_, T> { #[inline] fn drop(&mut self) { - unsafe { self.0.raw_lock().unlock_exclusive() }; + unsafe { + self.0.borrow_count().set(self.0.borrow_count().get() - 1); + self.0.raw_lock().unlock_exclusive(); + } } } @@ -354,6 +372,7 @@ impl<'a, T> TryFrom<&'a UserDataVariant> for UserDataBorrowMut<'a, T> { if !variant.raw_lock().try_lock_exclusive() { return Err(Error::UserDataBorrowMutError); } + variant.borrow_count().set(variant.borrow_count().get() + 1); Ok(UserDataBorrowMut(variant)) } } @@ -470,6 +489,14 @@ impl UserDataStorage { Self::Scoped(ScopedUserDataVariant::Boxed(RefCell::new(data))) } + #[inline(always)] + pub(crate) fn is_borrowed(&self) -> bool { + match self { + Self::Owned(variant) => variant.borrow_count().get() > 0, + Self::Scoped(_) => true, + } + } + #[inline] pub(crate) fn try_borrow_scoped(&self, f: impl FnOnce(&T) -> R) -> Result { match self { diff --git a/src/util/mod.rs b/src/util/mod.rs index 35e7196b..48e7d8fa 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -21,9 +21,6 @@ pub(crate) use userdata::{ pub(crate) use userdata::push_uninit_userdata; pub(crate) use userdata::push_userdata; -#[cfg(not(feature = "luau"))] -pub(crate) use userdata::userdata_destructor; - // Checks that Lua has enough free stack space for future stack operations. On failure, this will // panic with an internal error message. #[inline] diff --git a/src/util/userdata.rs b/src/util/userdata.rs index 17a5ee2d..685254f4 100644 --- a/src/util/userdata.rs +++ b/src/util/userdata.rs @@ -152,7 +152,6 @@ pub(crate) unsafe fn init_userdata_metatable( field_getters: Option, field_setters: Option, methods: Option, - extra_init: Option Result<()>>, ) -> Result<()> { if field_getters.is_some() || methods.is_some() { // Push `__index` generator function @@ -195,11 +194,6 @@ pub(crate) unsafe fn init_userdata_metatable( rawset_field(state, metatable, "__newindex")?; } - // Additional initialization - if let Some(extra_init) = extra_init { - extra_init(state, metatable)?; - } - ffi::lua_pushboolean(state, 0); rawset_field(state, metatable, "__metatable")?; @@ -345,7 +339,7 @@ unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result } #[cfg(not(feature = "luau"))] -pub(crate) unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::lua_State) -> c_int { +unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::lua_State) -> c_int { // It's probably NOT a good idea to catch Rust panics in finalizer // Lua 5.4 ignores it, other versions generates `LUA_ERRGCMM` without calling message handler take_userdata::(state); diff --git a/tests/userdata.rs b/tests/userdata.rs index c05fb108..59248d0f 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -376,7 +376,18 @@ fn test_userdata_take() -> Result<()> { fn test_userdata_destroy() -> Result<()> { struct MyUserdata(#[allow(unused)] Arc<()>); - impl UserData for MyUserdata {} + impl UserData for MyUserdata { + fn add_methods>(methods: &mut M) { + methods.add_method("try_destroy", |lua, _this, ()| { + let ud = lua.globals().get::("ud")?; + match ud.destroy() { + Err(Error::UserDataBorrowMutError) => {} + r => panic!("expected `UserDataBorrowMutError` error, got {:?}", r), + } + Ok(()) + }); + } + } let rc = Arc::new(()); @@ -394,6 +405,23 @@ fn test_userdata_destroy() -> Result<()> { assert_eq!(Arc::strong_count(&rc), 1); + let ud = lua.create_userdata(MyUserdata(rc.clone()))?; + assert_eq!(Arc::strong_count(&rc), 2); + let ud_ref = ud.borrow::()?; + // With active `UserDataRef` this methods only marks userdata as destructed + // without running destructor + ud.destroy()?; + assert_eq!(Arc::strong_count(&rc), 2); + drop(ud_ref); + assert_eq!(Arc::strong_count(&rc), 1); + + // We cannot destroy (internally) borrowed userdata + let ud = lua.create_userdata(MyUserdata(rc.clone()))?; + lua.globals().set("ud", &ud)?; + lua.load("ud:try_destroy()").exec().unwrap(); + ud.destroy()?; + assert_eq!(Arc::strong_count(&rc), 1); + Ok(()) } From c7094d470ff377775ba267ea78650d095db9b017 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 7 Nov 2024 19:43:21 +0000 Subject: [PATCH 279/635] Add `Scope::create_any_userdata` to create Lua objects from any non-static Rust types. --- src/scope.rs | 86 +++++++++++++++++++++++++++++++++++++++----------- tests/scope.rs | 27 +++++++++++----- 2 files changed, 87 insertions(+), 26 deletions(-) diff --git a/src/scope.rs b/src/scope.rs index 99419b6b..9aefb7f6 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -171,7 +171,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { let _sg = StackGuard::new(state); check_stack(state, 3)?; - // // We don't write the data to the userdata until pushing the metatable + // We don't write the data to the userdata until pushing the metatable let protect = !self.lua.unlikely_memory_error(); #[cfg(feature = "luau")] let ud_ptr = { @@ -194,29 +194,79 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { ffi::lua_setmetatable(state, -2); let ud = AnyUserData(self.lua.pop_ref()); + self.attach_destructor::(&ud); - let destructor: DestructorCallback = Box::new(|rawlua, vref| { - let state = rawlua.state(); - let _sg = StackGuard::new(state); - assert_stack(state, 2); + Ok(ud) + } + } - // Check that userdata is valid (very likely) - if rawlua.push_userdata_ref(&vref).is_err() { - return vec![]; - } + /// Creates a Lua userdata object from a custom Rust type. + /// + /// Since the Rust type is not required to be static and implement [`UserData`] trait, + /// you need to provide a function to register fields or methods for the object. + /// + /// See also [`Scope::create_userdata`] for more details about non-static limitations. + pub fn create_any_userdata( + &'scope self, + data: T, + register: impl FnOnce(&mut UserDataRegistry), + ) -> Result + where + T: 'env, + { + let state = self.lua.state(); + let ud = unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 3)?; - // Deregister metatable - let mt_ptr = get_metatable_ptr(state, -1); - rawlua.deregister_userdata_metatable(mt_ptr); + // We don't write the data to the userdata until pushing the metatable + let protect = !self.lua.unlikely_memory_error(); + #[cfg(feature = "luau")] + let ud_ptr = { + let data = UserDataStorage::new_scoped(data); + util::push_userdata::>(state, data, protect)? + }; + #[cfg(not(feature = "luau"))] + let ud_ptr = util::push_uninit_userdata::>(state, protect)?; - let ud = take_userdata::>(state); + // Push the metatable and register it with no TypeId + let mut registry = UserDataRegistry::new_unique(ud_ptr as *mut _); + register(&mut registry); + self.lua.push_userdata_metatable(registry)?; + let mt_ptr = ffi::lua_topointer(state, -1); + self.lua.register_userdata_metatable(mt_ptr, None); - vec![Box::new(move || drop(ud))] - }); - self.destructors.0.borrow_mut().push((ud.0.clone(), destructor)); + // Write data to the pointer and attach metatable + #[cfg(not(feature = "luau"))] + std::ptr::write(ud_ptr, UserDataStorage::new_scoped(data)); + ffi::lua_setmetatable(state, -2); - Ok(ud) - } + AnyUserData(self.lua.pop_ref()) + }; + self.attach_destructor::(&ud); + Ok(ud) + } + + fn attach_destructor(&'scope self, ud: &AnyUserData) { + let destructor: DestructorCallback = Box::new(|rawlua, vref| unsafe { + let state = rawlua.state(); + let _sg = StackGuard::new(state); + assert_stack(state, 2); + + // Check that userdata is valid (very likely) + if rawlua.push_userdata_ref(&vref).is_err() { + return vec![]; + } + + // Deregister metatable + let mt_ptr = get_metatable_ptr(state, -1); + rawlua.deregister_userdata_metatable(mt_ptr); + + let ud = take_userdata::>(state); + + vec![Box::new(move || drop(ud))] + }); + self.destructors.0.borrow_mut().push((ud.0.clone(), destructor)); } /// Adds a destructor function to be run when the scope ends. diff --git a/tests/scope.rs b/tests/scope.rs index 419f0a64..4113f385 100644 --- a/tests/scope.rs +++ b/tests/scope.rs @@ -410,15 +410,26 @@ fn test_scope_userdata_ref_mut() -> Result<()> { fn test_scope_any_userdata() -> Result<()> { let lua = Lua::new(); - lua.register_userdata_type::(|reg| { - reg.add_meta_method("__tostring", |_, data, ()| Ok(data.clone())); - })?; + fn register(reg: &mut UserDataRegistry<&mut StdString>) { + reg.add_method_mut("push", |_, this, s: String| { + this.push_str(&s.to_str()?); + Ok(()) + }); + reg.add_meta_method("__tostring", |_, data, ()| Ok((*data).clone())); + } - let data = StdString::from("foo"); + let mut data = StdString::from("foo"); lua.scope(|scope| { - let ud = scope.create_any_userdata_ref(&data)?; + let ud = scope.create_any_userdata(&mut data, register)?; lua.globals().set("ud", ud)?; - lua.load("assert(tostring(ud) == 'foo')").exec() + lua.load( + r#" + assert(tostring(ud) == "foo") + ud:push("bar") + assert(tostring(ud) == "foobar") + "#, + ) + .exec() })?; // Check that userdata is destructed @@ -498,7 +509,7 @@ fn test_scope_destructors() -> Result<()> { let ud = lua.create_any_userdata(arc_str.clone())?; lua.scope(|scope| { scope.add_destructor(|| { - assert!(ud.take::>().is_ok()); + assert!(ud.destroy().is_ok()); }); Ok(()) })?; @@ -510,7 +521,7 @@ fn test_scope_destructors() -> Result<()> { assert_eq!(arc_str.as_str(), "foo"); lua.scope(|scope| { scope.add_destructor(|| { - assert!(ud.take::>().is_err()); + assert!(ud.destroy().is_err()); }); Ok(()) }) From 58e0661086126a4118b6faa9092823914893e6fd Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 8 Nov 2024 15:04:46 +0000 Subject: [PATCH 280/635] Merge `Scope::attach_destructor` into `Scope::seal_userdata` --- src/scope.rs | 74 +++++++++++++++++----------------------------------- 1 file changed, 24 insertions(+), 50 deletions(-) diff --git a/src/scope.rs b/src/scope.rs index 9aefb7f6..1c317505 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -88,11 +88,9 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { where T: UserData + 'static, { - unsafe { - let ud = self.lua.make_userdata(UserDataStorage::new_ref(data))?; - self.seal_userdata::(&ud)?; - Ok(ud) - } + let ud = unsafe { self.lua.make_userdata(UserDataStorage::new_ref(data)) }?; + self.seal_userdata::(&ud); + Ok(ud) } /// Creates a Lua userdata object from a mutable reference to custom userdata type. @@ -104,11 +102,9 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { where T: UserData + 'static, { - unsafe { - let ud = self.lua.make_userdata(UserDataStorage::new_ref_mut(data))?; - self.seal_userdata::(&ud)?; - Ok(ud) - } + let ud = unsafe { self.lua.make_userdata(UserDataStorage::new_ref_mut(data)) }?; + self.seal_userdata::(&ud); + Ok(ud) } /// Creates a Lua userdata object from a reference to custom Rust type. @@ -122,11 +118,9 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { where T: 'static, { - unsafe { - let ud = self.lua.make_any_userdata(UserDataStorage::new_ref(data))?; - self.seal_userdata::(&ud)?; - Ok(ud) - } + let ud = unsafe { self.lua.make_any_userdata(UserDataStorage::new_ref(data)) }?; + self.seal_userdata::(&ud); + Ok(ud) } /// Creates a Lua userdata object from a mutable reference to custom Rust type. @@ -138,11 +132,9 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { where T: 'static, { - unsafe { - let ud = self.lua.make_any_userdata(UserDataStorage::new_ref_mut(data))?; - self.seal_userdata::(&ud)?; - Ok(ud) - } + let ud = unsafe { self.lua.make_any_userdata(UserDataStorage::new_ref_mut(data)) }?; + self.seal_userdata::(&ud); + Ok(ud) } /// Creates a Lua userdata object from a custom userdata type. @@ -194,7 +186,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { ffi::lua_setmetatable(state, -2); let ud = AnyUserData(self.lua.pop_ref()); - self.attach_destructor::(&ud); + self.seal_userdata::(&ud); Ok(ud) } @@ -243,32 +235,10 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { AnyUserData(self.lua.pop_ref()) }; - self.attach_destructor::(&ud); + self.seal_userdata::(&ud); Ok(ud) } - fn attach_destructor(&'scope self, ud: &AnyUserData) { - let destructor: DestructorCallback = Box::new(|rawlua, vref| unsafe { - let state = rawlua.state(); - let _sg = StackGuard::new(state); - assert_stack(state, 2); - - // Check that userdata is valid (very likely) - if rawlua.push_userdata_ref(&vref).is_err() { - return vec![]; - } - - // Deregister metatable - let mt_ptr = get_metatable_ptr(state, -1); - rawlua.deregister_userdata_metatable(mt_ptr); - - let ud = take_userdata::>(state); - - vec![Box::new(move || drop(ud))] - }); - self.destructors.0.borrow_mut().push((ud.0.clone(), destructor)); - } - /// Adds a destructor function to be run when the scope ends. /// /// This functionality is useful for cleaning up any resources after the scope ends. @@ -312,23 +282,27 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { } /// Shortens the lifetime of the userdata to the lifetime of the scope. - unsafe fn seal_userdata(&self, ud: &AnyUserData) -> Result<()> { - let destructor: DestructorCallback = Box::new(|rawlua, vref| { + fn seal_userdata(&self, ud: &AnyUserData) { + let destructor: DestructorCallback = Box::new(|rawlua, vref| unsafe { let state = rawlua.state(); let _sg = StackGuard::new(state); assert_stack(state, 2); // Ensure that userdata is not destructed - if rawlua.push_userdata_ref(&vref).is_err() { - return vec![]; + match rawlua.push_userdata_ref(&vref) { + Ok(Some(_)) => {} + Ok(None) => { + // Deregister metatable + let mt_ptr = get_metatable_ptr(state, -1); + rawlua.deregister_userdata_metatable(mt_ptr); + } + Err(_) => return vec![], } let data = take_userdata::>(state); vec![Box::new(move || drop(data))] }); self.destructors.0.borrow_mut().push((ud.0.clone(), destructor)); - - Ok(()) } } From 0fda512938a05bf100f5b4cce249953d2d5d69d9 Mon Sep 17 00:00:00 2001 From: vhyrro <76052559+vhyrro@users.noreply.github.com> Date: Sat, 9 Nov 2024 12:08:49 +0000 Subject: [PATCH 281/635] feat(table): improve pretty-printing for simple tables and lists (#478) --- src/table.rs | 41 +++++++++++++++++++++++++++++++++++------ tests/debug.rs | 2 +- tests/table.rs | 3 ++- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/table.rs b/src/table.rs index dc0fb565..94304159 100644 --- a/src/table.rs +++ b/src/table.rs @@ -777,16 +777,45 @@ impl Table { let mut pairs = self.pairs::().flatten().collect::>(); // Sort keys pairs.sort_by(|(a, _), (b, _)| a.sort_cmp(b)); + let is_sequence = pairs.iter().enumerate().all(|(i, (k, _))| { + if let Value::Integer(n) = k { + *n == (i + 1) as Integer + } else { + false + } + }); if pairs.is_empty() { return write!(fmt, "{{}}"); } writeln!(fmt, "{{")?; - for (key, value) in pairs { - write!(fmt, "{}[", " ".repeat(ident + 2))?; - key.fmt_pretty(fmt, false, ident + 2, visited)?; - write!(fmt, "] = ")?; - value.fmt_pretty(fmt, true, ident + 2, visited)?; - writeln!(fmt, ",")?; + if is_sequence { + // Format as list + for (_, value) in pairs { + write!(fmt, "{}", " ".repeat(ident + 2))?; + value.fmt_pretty(fmt, true, ident + 2, visited)?; + writeln!(fmt, ",")?; + } + } else { + for (key, value) in pairs { + match key { + Value::String(key) + if key + .to_string_lossy() + .chars() + .all(|c| matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_')) => + { + write!(fmt, "{}{}", " ".repeat(ident + 2), key.to_string_lossy())?; + write!(fmt, " = ")?; + } + _ => { + write!(fmt, "{}[", " ".repeat(ident + 2))?; + key.fmt_pretty(fmt, false, ident + 2, visited)?; + write!(fmt, "] = ")?; + } + } + value.fmt_pretty(fmt, true, ident + 2, visited)?; + writeln!(fmt, ",")?; + } } write!(fmt, "{}}}", " ".repeat(ident)) } diff --git a/tests/debug.rs b/tests/debug.rs index 3bb6b8b1..24c8adcf 100644 --- a/tests/debug.rs +++ b/tests/debug.rs @@ -7,7 +7,7 @@ fn test_debug_format() -> Result<()> { // Globals let globals = lua.globals(); let dump = format!("{globals:#?}"); - assert!(dump.starts_with("{\n [\"_G\"] = table:")); + assert!(dump.starts_with("{\n _G = table:")); // TODO: Other cases diff --git a/tests/table.rs b/tests/table.rs index d7a425e3..d6cc90f7 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -397,6 +397,7 @@ fn test_table_fmt() -> Result<()> { .load( r#" local t = {1, 2, 3, a = 5, b = { 6 }} + t["special-"] = 10 t[9.2] = 9.2 t[1.99] = 1.99 t[true] = true @@ -410,7 +411,7 @@ fn test_table_fmt() -> Result<()> { // Pretty print assert_eq!( format!("{table:#?}"), - "{\n [false] = false,\n [true] = true,\n [1] = 1,\n [1.99] = 1.99,\n [2] = 2,\n [3] = 3,\n [9.2] = 9.2,\n [\"a\"] = 5,\n [\"b\"] = {\n [1] = 6,\n },\n}" + "{\n [false] = false,\n [true] = true,\n [1] = 1,\n [1.99] = 1.99,\n [2] = 2,\n [3] = 3,\n [9.2] = 9.2,\n a = 5,\n b = {\n 6,\n },\n [\"special-\"] = 10,\n}" ); Ok(()) From a4bfeb7752430b3ae3ccaedec2507257e6a15d76 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 9 Nov 2024 12:38:05 +0000 Subject: [PATCH 282/635] clippy --- src/chunk.rs | 2 +- src/lib.rs | 1 + src/table.rs | 3 ++- src/types/sync.rs | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index 0aa3e4d4..ff3d9be0 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -301,7 +301,7 @@ impl Compiler { } } -impl<'a> Chunk<'a> { +impl Chunk<'_> { /// Sets the name of this chunk, which results in more informative error traces. pub fn set_name(mut self, name: impl Into) -> Self { self.name = name.into(); diff --git a/src/lib.rs b/src/lib.rs index da41d4a9..a404594c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,6 +65,7 @@ // Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any* // warnings at all. #![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(not(send), allow(clippy::arc_with_non_send_sync))] #[macro_use] mod macros; diff --git a/src/table.rs b/src/table.rs index 94304159..d09b6622 100644 --- a/src/table.rs +++ b/src/table.rs @@ -693,7 +693,8 @@ impl Table { } /// Iterates over the sequence part of the table, invoking the given closure on each value. - pub(crate) fn for_each_value(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()> + #[doc(hidden)] + pub fn for_each_value(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()> where V: FromLua, { diff --git a/src/types/sync.rs b/src/types/sync.rs index 7f9adbd6..b6d61fb1 100644 --- a/src/types/sync.rs +++ b/src/types/sync.rs @@ -53,7 +53,7 @@ mod inner { pub(crate) struct ReentrantMutexGuard<'a, T>(&'a T); - impl<'a, T> Deref for ReentrantMutexGuard<'a, T> { + impl Deref for ReentrantMutexGuard<'_, T> { type Target = T; #[inline(always)] From a3cd25db7ad41fc6862848171c8a774cb728a373 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 9 Nov 2024 12:44:00 +0000 Subject: [PATCH 283/635] Support Luau 0.650 native vector library --- src/luau/mod.rs | 18 +----------------- src/state/raw.rs | 23 ++++++++++++++--------- src/stdlib.rs | 7 ++++++- tests/luau.rs | 24 ++++++++++++++---------- tests/serde.rs | 18 ++++-------------- 5 files changed, 39 insertions(+), 51 deletions(-) diff --git a/src/luau/mod.rs b/src/luau/mod.rs index 75d1a767..b3935d38 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -1,5 +1,5 @@ use std::ffi::CStr; -use std::os::raw::{c_float, c_int}; +use std::os::raw::c_int; use crate::error::Result; use crate::state::Lua; @@ -11,7 +11,6 @@ impl Lua { let globals = self.globals(); globals.raw_set("collectgarbage", self.create_c_function(lua_collectgarbage)?)?; - globals.raw_set("vector", self.create_c_function(lua_vector)?)?; // Set `_VERSION` global to include version number // The environment variable `LUAU_VERSION` set by the build script @@ -65,21 +64,6 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_ } } -// Luau vector datatype constructor -unsafe extern "C-unwind" fn lua_vector(state: *mut ffi::lua_State) -> c_int { - let x = ffi::luaL_checknumber(state, 1) as c_float; - let y = ffi::luaL_checknumber(state, 2) as c_float; - let z = ffi::luaL_checknumber(state, 3) as c_float; - #[cfg(feature = "luau-vector4")] - let w = ffi::luaL_checknumber(state, 4) as c_float; - - #[cfg(not(feature = "luau-vector4"))] - ffi::lua_pushvector(state, x, y, z); - #[cfg(feature = "luau-vector4")] - ffi::lua_pushvector(state, x, y, z, w); - 1 -} - pub(crate) use package::register_package_module; mod package; diff --git a/src/state/raw.rs b/src/state/raw.rs index f8389907..53aca043 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -1347,6 +1347,12 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> ffi::lua_pop(state, 1); } + #[cfg(feature = "luau")] + if libs.contains(StdLib::VECTOR) { + requiref(state, ffi::LUA_VECLIBNAME, ffi::luaopen_vector, 1)?; + ffi::lua_pop(state, 1); + } + if libs.contains(StdLib::MATH) { requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?; ffi::lua_pop(state, 1); @@ -1369,16 +1375,15 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> } #[cfg(feature = "luajit")] - { - if libs.contains(StdLib::JIT) { - requiref(state, ffi::LUA_JITLIBNAME, ffi::luaopen_jit, 1)?; - ffi::lua_pop(state, 1); - } + if libs.contains(StdLib::JIT) { + requiref(state, ffi::LUA_JITLIBNAME, ffi::luaopen_jit, 1)?; + ffi::lua_pop(state, 1); + } - if libs.contains(StdLib::FFI) { - requiref(state, ffi::LUA_FFILIBNAME, ffi::luaopen_ffi, 1)?; - ffi::lua_pop(state, 1); - } + #[cfg(feature = "luajit")] + if libs.contains(StdLib::FFI) { + requiref(state, ffi::LUA_FFILIBNAME, ffi::luaopen_ffi, 1)?; + ffi::lua_pop(state, 1); } Ok(()) diff --git a/src/stdlib.rs b/src/stdlib.rs index c71d1497..b05e0565 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -48,12 +48,17 @@ impl StdLib { #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub const BUFFER: StdLib = StdLib(1 << 9); + /// [`vector`](https://luau-lang.org/library#vector-library) library + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub const VECTOR: StdLib = StdLib(1 << 10); + /// [`jit`](http://luajit.org/ext_jit.html) library /// /// Requires `feature = "luajit"` #[cfg(any(feature = "luajit", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luajit")))] - pub const JIT: StdLib = StdLib(1 << 9); + pub const JIT: StdLib = StdLib(1 << 11); /// (**unsafe**) [`ffi`](http://luajit.org/ext_ffi.html) library /// diff --git a/tests/luau.rs b/tests/luau.rs index 0b37f7d6..b45404c9 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -97,17 +97,19 @@ fn test_require() -> Result<()> { fn test_vectors() -> Result<()> { let lua = Lua::new(); - let v: Vector = lua.load("vector(1, 2, 3) + vector(3, 2, 1)").eval()?; + let v: Vector = lua + .load("vector.create(1, 2, 3) + vector.create(3, 2, 1)") + .eval()?; assert_eq!(v, [4.0, 4.0, 4.0]); // Test conversion into Rust array - let v: [f64; 3] = lua.load("vector(1, 2, 3)").eval()?; + let v: [f64; 3] = lua.load("vector.create(1, 2, 3)").eval()?; assert!(v == [1.0, 2.0, 3.0]); // Test vector methods lua.load( r#" - local v = vector(1, 2, 3) + local v = vector.create(1, 2, 3) assert(v.x == 1) assert(v.y == 2) assert(v.z == 3) @@ -118,7 +120,7 @@ fn test_vectors() -> Result<()> { // Test vector methods (fastcall) lua.load( r#" - local v = vector(1, 2, 3) + local v = vector.create(1, 2, 3) assert(v.x == 1) assert(v.y == 2) assert(v.z == 3) @@ -135,17 +137,19 @@ fn test_vectors() -> Result<()> { fn test_vectors() -> Result<()> { let lua = Lua::new(); - let v: Vector = lua.load("vector(1, 2, 3, 4) + vector(4, 3, 2, 1)").eval()?; + let v: Vector = lua + .load("vector.create(1, 2, 3, 4) + vector.create(4, 3, 2, 1)") + .eval()?; assert_eq!(v, [5.0, 5.0, 5.0, 5.0]); // Test conversion into Rust array - let v: [f64; 4] = lua.load("vector(1, 2, 3, 4)").eval()?; + let v: [f64; 4] = lua.load("vector.create(1, 2, 3, 4)").eval()?; assert!(v == [1.0, 2.0, 3.0, 4.0]); // Test vector methods lua.load( r#" - local v = vector(1, 2, 3, 4) + local v = vector.create(1, 2, 3, 4) assert(v.x == 1) assert(v.y == 2) assert(v.z == 3) @@ -157,7 +161,7 @@ fn test_vectors() -> Result<()> { // Test vector methods (fastcall) lua.load( r#" - local v = vector(1, 2, 3, 4) + local v = vector.create(1, 2, 3, 4) assert(v.x == 1) assert(v.y == 2) assert(v.z == 3) @@ -180,10 +184,10 @@ fn test_vector_metatable() -> Result<()> { r#" { __index = { - new = vector, + new = vector.create, product = function(a, b) - return vector(a.x * b.x, a.y * b.y, a.z * b.z) + return vector.create(a.x * b.x, a.y * b.y, a.z * b.z) end } } diff --git a/tests/serde.rs b/tests/serde.rs index cafbe70b..4efb6537 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -133,13 +133,7 @@ fn test_serialize_failure() -> Result<(), Box> { fn test_serialize_vector() -> Result<(), Box> { let lua = Lua::new(); - let globals = lua.globals(); - globals.set( - "vector", - lua.create_function(|_, (x, y, z)| Ok(mlua::Vector::new(x, y, z)))?, - )?; - - let val = lua.load("{_vector = vector(1, 2, 3)}").eval::()?; + let val = lua.load("{_vector = vector.create(1, 2, 3)}").eval::()?; let json = serde_json::json!({ "_vector": [1.0, 2.0, 3.0], }); @@ -156,13 +150,9 @@ fn test_serialize_vector() -> Result<(), Box> { fn test_serialize_vector() -> Result<(), Box> { let lua = Lua::new(); - let globals = lua.globals(); - globals.set( - "vector", - lua.create_function(|_, (x, y, z, w)| Ok(mlua::Vector::new(x, y, z, w)))?, - )?; - - let val = lua.load("{_vector = vector(1, 2, 3, 4)}").eval::()?; + let val = lua + .load("{_vector = vector.create(1, 2, 3, 4)}") + .eval::()?; let json = serde_json::json!({ "_vector": [1.0, 2.0, 3.0, 4.0], }); From 7aad0adcb4beacc8fb778b84fa6a2f088767acb7 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 9 Nov 2024 12:48:15 +0000 Subject: [PATCH 284/635] Update links to luau.org --- README.md | 4 ++-- src/state.rs | 2 +- src/stdlib.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ee0661f1..fbd847d1 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Started as `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua versions excluding JIT. [GitHub Actions]: https://github.com/khvzak/mlua/actions -[Roblox Luau]: https://luau-lang.org +[Roblox Luau]: https://luau.org ## Usage @@ -291,7 +291,7 @@ Please check the [Luau Sandboxing] page if you are interested in running untrust `mlua` provides `Lua::sandbox` method for enabling sandbox mode (Luau only). -[Luau Sandboxing]: https://luau-lang.org/sandbox +[Luau Sandboxing]: https://luau.org/sandbox ## License diff --git a/src/state.rs b/src/state.rs index 861a59b4..bd2e52a9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1028,7 +1028,7 @@ impl Lua { /// /// Requires `feature = "luau"` /// - /// [buffer]: https://luau-lang.org/library#buffer-library + /// [buffer]: https://luau.org/library#buffer-library #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result { diff --git a/src/stdlib.rs b/src/stdlib.rs index b05e0565..787b2fcb 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -43,12 +43,12 @@ impl StdLib { /// [`package`](https://www.lua.org/manual/5.4/manual.html#6.3) library pub const PACKAGE: StdLib = StdLib(1 << 8); - /// [`buffer`](https://luau-lang.org/library#buffer-library) library + /// [`buffer`](https://luau.org/library#buffer-library) library #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub const BUFFER: StdLib = StdLib(1 << 9); - /// [`vector`](https://luau-lang.org/library#vector-library) library + /// [`vector`](https://luau.org/library#vector-library) library #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub const VECTOR: StdLib = StdLib(1 << 10); From b34b90eca31a305c4a4bd900cbb9cb6bb4eb6da8 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 9 Nov 2024 13:51:55 +0000 Subject: [PATCH 285/635] Fix wrong formatting table with string keys that are numbers --- src/table.rs | 21 ++++++++------------- tests/table.rs | 3 +++ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/table.rs b/src/table.rs index d09b6622..ddf3ff35 100644 --- a/src/table.rs +++ b/src/table.rs @@ -778,13 +778,8 @@ impl Table { let mut pairs = self.pairs::().flatten().collect::>(); // Sort keys pairs.sort_by(|(a, _), (b, _)| a.sort_cmp(b)); - let is_sequence = pairs.iter().enumerate().all(|(i, (k, _))| { - if let Value::Integer(n) = k { - *n == (i + 1) as Integer - } else { - false - } - }); + let is_sequence = (pairs.iter().enumerate()) + .all(|(i, (k, _))| matches!(k, Value::Integer(n) if *n == (i + 1) as Integer)); if pairs.is_empty() { return write!(fmt, "{{}}"); } @@ -797,14 +792,14 @@ impl Table { writeln!(fmt, ",")?; } } else { + fn is_simple_key(key: &[u8]) -> bool { + key.iter().take(1).all(|c| c.is_ascii_alphabetic() || *c == b'_') + && key.iter().all(|c| c.is_ascii_alphanumeric() || *c == b'_') + } + for (key, value) in pairs { match key { - Value::String(key) - if key - .to_string_lossy() - .chars() - .all(|c| matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_')) => - { + Value::String(key) if is_simple_key(&key.as_bytes()) => { write!(fmt, "{}{}", " ".repeat(ident + 2), key.to_string_lossy())?; write!(fmt, " = ")?; } diff --git a/tests/table.rs b/tests/table.rs index d6cc90f7..d2e49363 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -414,6 +414,9 @@ fn test_table_fmt() -> Result<()> { "{\n [false] = false,\n [true] = true,\n [1] = 1,\n [1.99] = 1.99,\n [2] = 2,\n [3] = 3,\n [9.2] = 9.2,\n a = 5,\n b = {\n 6,\n },\n [\"special-\"] = 10,\n}" ); + let table2 = lua.create_table_from([("1", "first"), ("2", "second")])?; + assert_eq!(format!("{table2:#?}"), "{\n [\"1\"] = \"first\",\n [\"2\"] = \"second\",\n}"); + Ok(()) } From 92a8203e1c0ce7a4aa38cb9d68b24b3ec9565620 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 9 Nov 2024 13:58:06 +0000 Subject: [PATCH 286/635] Fix formatting --- tests/table.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/table.rs b/tests/table.rs index d2e49363..d76f3a5f 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -415,7 +415,10 @@ fn test_table_fmt() -> Result<()> { ); let table2 = lua.create_table_from([("1", "first"), ("2", "second")])?; - assert_eq!(format!("{table2:#?}"), "{\n [\"1\"] = \"first\",\n [\"2\"] = \"second\",\n}"); + assert_eq!( + format!("{table2:#?}"), + "{\n [\"1\"] = \"first\",\n [\"2\"] = \"second\",\n}" + ); Ok(()) } From 958abd050e629c5501c7f802bd890a3b0bdc42a6 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 9 Nov 2024 14:10:45 +0000 Subject: [PATCH 287/635] Update `String::to_string_lossy` doc --- src/string.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/string.rs b/src/string.rs index b8fd7c08..04b7ee01 100644 --- a/src/string.rs +++ b/src/string.rs @@ -55,7 +55,11 @@ impl String { /// /// Any non-Unicode sequences are replaced with [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD]. /// + /// This method returns [`StdString`] instead of [`Cow<'_, str>`] because lifetime cannot be + /// bound to a weak Lua object. + /// /// [U+FFFD]: std::char::REPLACEMENT_CHARACTER + /// [`Cow<'_, str>`]: std::borrow::Cow /// /// # Examples /// From 8c889cc353ceb239850e90a0cc592ae4dd557425 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 9 Nov 2024 14:24:43 +0000 Subject: [PATCH 288/635] Add `String::display` method --- src/string.rs | 19 +++++++++++++++++++ src/table.rs | 2 +- tests/string.rs | 14 ++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/string.rs b/src/string.rs index 04b7ee01..4424a8f4 100644 --- a/src/string.rs +++ b/src/string.rs @@ -78,6 +78,16 @@ impl String { StdString::from_utf8_lossy(&self.as_bytes()).into_owned() } + /// Returns an object that implements [`Display`] for safely printing a Lua [`String`] that may + /// contain non-Unicode data. + /// + /// This may perform lossy conversion. + /// + /// [`Display`]: fmt::Display + pub fn display(&self) -> impl fmt::Display + '_ { + Display(self) + } + /// Get the bytes that make up this string. /// /// The returned slice will not contain the terminating nul byte, but will contain any nul @@ -216,6 +226,15 @@ impl Serialize for String { } } +struct Display<'a>(&'a String); + +impl fmt::Display for Display<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let bytes = self.0.as_bytes(); + ::fmt(bstr::BStr::new(&bytes), f) + } +} + /// A borrowed string (`&str`) that holds a strong reference to the Lua state. pub struct BorrowedStr<'a>(&'a str, #[allow(unused)] Lua); diff --git a/src/table.rs b/src/table.rs index ddf3ff35..eb12e964 100644 --- a/src/table.rs +++ b/src/table.rs @@ -800,7 +800,7 @@ impl Table { for (key, value) in pairs { match key { Value::String(key) if is_simple_key(&key.as_bytes()) => { - write!(fmt, "{}{}", " ".repeat(ident + 2), key.to_string_lossy())?; + write!(fmt, "{}{}", " ".repeat(ident + 2), key.display())?; write!(fmt, " = ")?; } _ => { diff --git a/tests/string.rs b/tests/string.rs index 34a32de6..7e2d877f 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -114,3 +114,17 @@ fn test_string_pointer() -> Result<()> { Ok(()) } + +#[test] +fn test_string_display() -> Result<()> { + let lua = Lua::new(); + + let s = lua.create_string("hello")?; + assert_eq!(format!("{}", s.display()), "hello"); + + // With invalid utf8 + let s = lua.create_string(b"hello\0world\xFF")?; + assert_eq!(format!("{}", s.display()), "hello\0world�"); + + Ok(()) +} From 7c099500d03ec511a70b03a7b8617088431beba3 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 9 Nov 2024 14:30:33 +0000 Subject: [PATCH 289/635] mlua-sys: v0.6.5 --- Cargo.toml | 2 +- mlua-sys/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index eb958cba..0da7c258 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ serde-value = { version = "0.7", optional = true } parking_lot = { version = "0.12", features = ["arc_lock"] } anyhow = { version = "1.0", optional = true } -ffi = { package = "mlua-sys", version = "0.6.4", path = "mlua-sys" } +ffi = { package = "mlua-sys", version = "0.6.5", path = "mlua-sys" } [target.'cfg(unix)'.dependencies] libloading = { version = "0.8", optional = true } diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 7501c974..d6d08441 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.6.4" +version = "0.6.5" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" From c926327a6a4be7d31743f7c8474ecf6bd763b216 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 9 Nov 2024 14:43:02 +0000 Subject: [PATCH 290/635] v0.10.1 --- CHANGELOG.md | 15 +++++++++++++++ Cargo.toml | 2 +- README.md | 4 ++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ea64484..78dfbf3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## v0.10.1 (Nov 9th, 2024) + +- Minimal Luau updated to 0.650 +- Added Luau native vector library support (this can change behavior if you use `vector` function!) +- Added Lua `String::display` method +- Improved pretty-printing for Lua tables (#478) +- Added `Scope::create_any_userdata` to create Lua objects from any non-`'static` Rust types +- Added `AnyUserData::destroy` method +- New `userdata-wrappers` feature to `impl UserData` for `Rc`/`Arc`/`Rc>`/`Arc>` (similar to v0.9) +- `UserDataRef` in `send` mode now uses shared lock if `T: Sync` (and exclusive lock otherwise) +- Added `Scope::add_destructor` to attach custom destructors +- Added `Lua::try_app_data_ref` and `Lua::try_app_data_mut` methods +- Added `From` and `Into` support to `MultiValue` and `Variadic` types +- Bug fixes and improvements (#477 #479) + ## v0.10.0 (Oct 25th, 2024) Changes since v0.10.0-rc.1 diff --git a/Cargo.toml b/Cargo.toml index 0da7c258..b0ee449e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.10.0" # remember to update mlua_derive +version = "0.10.1" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.79.0" edition = "2021" diff --git a/README.md b/README.md index fbd847d1..1c68d9a8 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ Add to `Cargo.toml` : ``` toml [dependencies] -mlua = { version = "0.10.0", features = ["lua54", "vendored"] } +mlua = { version = "0.10.1", features = ["lua54", "vendored"] } ``` `main.rs` @@ -168,7 +168,7 @@ Add to `Cargo.toml` : crate-type = ["cdylib"] [dependencies] -mlua = { version = "0.10.0", features = ["lua54", "module"] } +mlua = { version = "0.10.1", features = ["lua54", "module"] } ``` `lib.rs` : From cbf805f492f40c7c86a418da4c0f2f6bbee3d471 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 15 Nov 2024 21:47:19 +0000 Subject: [PATCH 291/635] Avoid ptr->usize->ptr conversion to comply strict provenance --- src/userdata/registry.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index d83b4487..54ec8194 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -31,7 +31,7 @@ type StaticFieldCallback = Box Result<()> + 'static>; #[derive(Clone, Copy)] enum UserDataTypeId { Shared(TypeId), - Unique(usize), + Unique(*mut c_void), #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] Rc(TypeId), @@ -77,7 +77,7 @@ impl UserDataRegistry { #[inline(always)] pub(crate) fn new_unique(ud_ptr: *mut c_void) -> Self { - Self::with_type_id(UserDataTypeId::Unique(ud_ptr as usize)) + Self::with_type_id(UserDataTypeId::Unique(ud_ptr)) } #[inline(always)] @@ -157,7 +157,7 @@ impl UserDataRegistry { } #[rustfmt::skip] UserDataTypeId::Unique(target_ptr) - if get_userdata::>(state, self_index) as usize == target_ptr => + if get_userdata::>(state, self_index) as *mut c_void == target_ptr => { let ud = target_ptr as *mut UserDataStorage; try_self_arg!((*ud).try_borrow_scoped(|ud| { @@ -285,7 +285,7 @@ impl UserDataRegistry { } #[rustfmt::skip] UserDataTypeId::Unique(target_ptr) - if get_userdata::>(state, self_index) as usize == target_ptr => + if get_userdata::>(state, self_index) as *mut c_void == target_ptr => { let ud = target_ptr as *mut UserDataStorage; try_self_arg!((*ud).try_borrow_scoped_mut(|ud| { From 89b68e2a24976815e2e10e5936f3f0acaa6f087e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 16 Nov 2024 01:44:17 +0000 Subject: [PATCH 292/635] Remove generic from `RawLua::push_userdata_metatable`. This should help to reduce amount of generated code. --- src/scope.rs | 4 +- src/state.rs | 2 +- src/state/raw.rs | 34 ++++------ src/userdata.rs | 2 +- src/userdata/registry.rs | 139 ++++++++++++++++++++++----------------- src/userdata/util.rs | 15 +++++ 6 files changed, 109 insertions(+), 87 deletions(-) diff --git a/src/scope.rs b/src/scope.rs index 1c317505..498cf742 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -176,7 +176,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { // Push the metatable and register it with no TypeId let mut registry = UserDataRegistry::new_unique(ud_ptr as *mut _); T::register(&mut registry); - self.lua.push_userdata_metatable(registry)?; + self.lua.push_userdata_metatable(registry.into_raw())?; let mt_ptr = ffi::lua_topointer(state, -1); self.lua.register_userdata_metatable(mt_ptr, None); @@ -224,7 +224,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { // Push the metatable and register it with no TypeId let mut registry = UserDataRegistry::new_unique(ud_ptr as *mut _); register(&mut registry); - self.lua.push_userdata_metatable(registry)?; + self.lua.push_userdata_metatable(registry.into_raw())?; let mt_ptr = ffi::lua_topointer(state, -1); self.lua.register_userdata_metatable(mt_ptr, None); diff --git a/src/state.rs b/src/state.rs index bd2e52a9..99937148 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1317,7 +1317,7 @@ impl Lua { } // Register the type - lua.create_userdata_metatable(registry)?; + lua.create_userdata_metatable(registry.into_raw())?; } Ok(()) } diff --git a/src/state/raw.rs b/src/state/raw.rs index 53aca043..5d56eac0 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -22,12 +22,14 @@ use crate::types::{ AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData, MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc, }; -use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataRegistry, UserDataStorage}; +use crate::userdata::{ + AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, UserDataStorage, +}; use crate::util::{ assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state, get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, init_userdata_metatable, pop_error, push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, - short_type_name, take_userdata, StackGuard, WrappedFailure, + short_type_name, StackGuard, WrappedFailure, }; use crate::value::{Nil, Value}; @@ -757,7 +759,7 @@ impl RawLua { let mut registry = UserDataRegistry::new(type_id); T::register(&mut registry); - self.create_userdata_metatable(registry) + self.create_userdata_metatable(registry.into_raw()) }) } @@ -774,7 +776,7 @@ impl RawLua { // Create an empty metatable let registry = UserDataRegistry::::new(type_id); - self.create_userdata_metatable(registry) + self.create_userdata_metatable(registry.into_raw()) }) } @@ -810,12 +812,9 @@ impl RawLua { Ok(AnyUserData(self.pop_ref())) } - pub(crate) unsafe fn create_userdata_metatable( - &self, - registry: UserDataRegistry, - ) -> Result { + pub(crate) unsafe fn create_userdata_metatable(&self, registry: RawUserDataRegistry) -> Result { let state = self.state(); - let type_id = registry.type_id(); + let type_id = registry.type_id; self.push_userdata_metatable(registry)?; @@ -832,7 +831,7 @@ impl RawLua { Ok(id as Integer) } - pub(crate) unsafe fn push_userdata_metatable(&self, mut registry: UserDataRegistry) -> Result<()> { + pub(crate) unsafe fn push_userdata_metatable(&self, mut registry: RawUserDataRegistry) -> Result<()> { let state = self.state(); let mut stack_guard = StackGuard::new(state); check_stack(state, 13)?; @@ -859,7 +858,7 @@ impl RawLua { } // Set `__name/__type` if not provided if !has_name { - let type_name = short_type_name::(); + let type_name = registry.type_name; push_string(state, type_name.as_bytes(), !self.unlikely_memory_error())?; rawset_field(state, -2, MetaMethod::Type.name())?; } @@ -960,18 +959,7 @@ impl RawLua { } } - unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::lua_State) -> c_int { - let ud = get_userdata::>(state, -1); - if !(*ud).is_borrowed() { - take_userdata::>(state); - ffi::lua_pushboolean(state, 1); - } else { - ffi::lua_pushboolean(state, 0); - } - 1 - } - - ffi::lua_pushcfunction(state, userdata_destructor::); + ffi::lua_pushcfunction(state, registry.destructor); rawset_field(state, metatable_index, "__gc")?; init_userdata_metatable( diff --git a/src/userdata.rs b/src/userdata.rs index 50f925dd..7db8896e 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -27,8 +27,8 @@ use { // Re-export for convenience pub(crate) use cell::UserDataStorage; pub use cell::{UserDataRef, UserDataRefMut}; -pub(crate) use registry::UserDataProxy; pub use registry::UserDataRegistry; +pub(crate) use registry::{RawUserDataRegistry, UserDataProxy}; /// Kinds of metamethods that can be overridden. /// diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index 54ec8194..d7209156 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -51,6 +51,12 @@ enum UserDataTypeId { /// Handle to registry for userdata methods and metamethods. pub struct UserDataRegistry { + raw: RawUserDataRegistry, + ud_type_id: UserDataTypeId, + _type: PhantomData, +} + +pub(crate) struct RawUserDataRegistry { // Fields pub(crate) fields: Vec<(String, StaticFieldCallback)>, pub(crate) field_getters: Vec<(String, Callback)>, @@ -65,8 +71,33 @@ pub struct UserDataRegistry { #[cfg(feature = "async")] pub(crate) async_meta_methods: Vec<(String, AsyncCallback)>, - type_id: UserDataTypeId, - _type: PhantomData, + pub(crate) destructor: ffi::lua_CFunction, + pub(crate) type_id: Option, + pub(crate) type_name: StdString, +} + +impl UserDataTypeId { + #[inline] + pub(crate) fn type_id(self) -> Option { + match self { + UserDataTypeId::Shared(type_id) => Some(type_id), + UserDataTypeId::Unique(_) => None, + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + UserDataTypeId::Rc(type_id) => Some(type_id), + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + UserDataTypeId::RcRefCell(type_id) => Some(type_id), + #[cfg(feature = "userdata-wrappers")] + UserDataTypeId::Arc(type_id) => Some(type_id), + #[cfg(feature = "userdata-wrappers")] + UserDataTypeId::ArcMutex(type_id) => Some(type_id), + #[cfg(feature = "userdata-wrappers")] + UserDataTypeId::ArcRwLock(type_id) => Some(type_id), + #[cfg(feature = "userdata-wrappers")] + UserDataTypeId::ArcParkingLotMutex(type_id) => Some(type_id), + #[cfg(feature = "userdata-wrappers")] + UserDataTypeId::ArcParkingLotRwLock(type_id) => Some(type_id), + } + } } impl UserDataRegistry { @@ -81,8 +112,8 @@ impl UserDataRegistry { } #[inline(always)] - fn with_type_id(type_id: UserDataTypeId) -> Self { - UserDataRegistry { + fn with_type_id(ud_type_id: UserDataTypeId) -> Self { + let raw = RawUserDataRegistry { fields: Vec::new(), field_getters: Vec::new(), field_setters: Vec::new(), @@ -93,30 +124,15 @@ impl UserDataRegistry { meta_methods: Vec::new(), #[cfg(feature = "async")] async_meta_methods: Vec::new(), - type_id, - _type: PhantomData, - } - } + destructor: super::util::userdata_destructor::, + type_id: ud_type_id.type_id(), + type_name: short_type_name::(), + }; - #[inline] - pub(crate) fn type_id(&self) -> Option { - match self.type_id { - UserDataTypeId::Shared(type_id) => Some(type_id), - UserDataTypeId::Unique(_) => None, - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - UserDataTypeId::Rc(type_id) => Some(type_id), - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - UserDataTypeId::RcRefCell(type_id) => Some(type_id), - #[cfg(feature = "userdata-wrappers")] - UserDataTypeId::Arc(type_id) => Some(type_id), - #[cfg(feature = "userdata-wrappers")] - UserDataTypeId::ArcMutex(type_id) => Some(type_id), - #[cfg(feature = "userdata-wrappers")] - UserDataTypeId::ArcRwLock(type_id) => Some(type_id), - #[cfg(feature = "userdata-wrappers")] - UserDataTypeId::ArcParkingLotMutex(type_id) => Some(type_id), - #[cfg(feature = "userdata-wrappers")] - UserDataTypeId::ArcParkingLotRwLock(type_id) => Some(type_id), + UserDataRegistry { + raw, + ud_type_id, + _type: PhantomData, } } @@ -133,7 +149,7 @@ impl UserDataRegistry { }; } - let target_type_id = self.type_id; + let target_type_id = self.ud_type_id; Box::new(move |rawlua, nargs| unsafe { if nargs == 0 { let err = Error::from_lua_conversion("missing argument", "userdata", None); @@ -260,7 +276,7 @@ impl UserDataRegistry { } let method = RefCell::new(method); - let target_type_id = self.type_id; + let target_type_id = self.ud_type_id; Box::new(move |rawlua, nargs| unsafe { let mut method = method.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?; if nargs == 0 { @@ -514,6 +530,11 @@ impl UserDataRegistry { } value.into_lua(lua) } + + #[inline(always)] + pub(crate) fn into_raw(self) -> RawUserDataRegistry { + self.raw + } } // Returns function name for the type `T`, without the module path @@ -527,7 +548,7 @@ impl UserDataFields for UserDataRegistry { V: IntoLua + 'static, { let name = name.to_string(); - self.fields.push(( + self.raw.fields.push(( name, Box::new(move |rawlua| unsafe { value.push_into_stack(rawlua) }), )); @@ -540,7 +561,7 @@ impl UserDataFields for UserDataRegistry { { let name = name.to_string(); let callback = self.box_method(&name, move |lua, data, ()| method(lua, data)); - self.field_getters.push((name, callback)); + self.raw.field_getters.push((name, callback)); } fn add_field_method_set(&mut self, name: impl ToString, method: M) @@ -550,7 +571,7 @@ impl UserDataFields for UserDataRegistry { { let name = name.to_string(); let callback = self.box_method_mut(&name, method); - self.field_setters.push((name, callback)); + self.raw.field_setters.push((name, callback)); } fn add_field_function_get(&mut self, name: impl ToString, function: F) @@ -560,7 +581,7 @@ impl UserDataFields for UserDataRegistry { { let name = name.to_string(); let callback = self.box_function(&name, function); - self.field_getters.push((name, callback)); + self.raw.field_getters.push((name, callback)); } fn add_field_function_set(&mut self, name: impl ToString, mut function: F) @@ -570,7 +591,7 @@ impl UserDataFields for UserDataRegistry { { let name = name.to_string(); let callback = self.box_function_mut(&name, move |lua, (data, val)| function(lua, data, val)); - self.field_setters.push((name, callback)); + self.raw.field_setters.push((name, callback)); } fn add_meta_field(&mut self, name: impl ToString, value: V) @@ -578,7 +599,7 @@ impl UserDataFields for UserDataRegistry { V: IntoLua + 'static, { let name = name.to_string(); - self.meta_fields.push(( + self.raw.meta_fields.push(( name.clone(), Box::new(move |rawlua| unsafe { Self::check_meta_field(rawlua.lua(), &name, value)?.push_into_stack(rawlua) @@ -592,7 +613,7 @@ impl UserDataFields for UserDataRegistry { R: IntoLua, { let name = name.to_string(); - self.meta_fields.push(( + self.raw.meta_fields.push(( name.clone(), Box::new(move |rawlua| unsafe { let lua = rawlua.lua(); @@ -611,7 +632,7 @@ impl UserDataMethods for UserDataRegistry { { let name = name.to_string(); let callback = self.box_method(&name, method); - self.methods.push((name, callback)); + self.raw.methods.push((name, callback)); } fn add_method_mut(&mut self, name: impl ToString, method: M) @@ -622,7 +643,7 @@ impl UserDataMethods for UserDataRegistry { { let name = name.to_string(); let callback = self.box_method_mut(&name, method); - self.methods.push((name, callback)); + self.raw.methods.push((name, callback)); } #[cfg(feature = "async")] @@ -636,7 +657,7 @@ impl UserDataMethods for UserDataRegistry { { let name = name.to_string(); let callback = self.box_async_method(&name, method); - self.async_methods.push((name, callback)); + self.raw.async_methods.push((name, callback)); } #[cfg(feature = "async")] @@ -650,7 +671,7 @@ impl UserDataMethods for UserDataRegistry { { let name = name.to_string(); let callback = self.box_async_method_mut(&name, method); - self.async_methods.push((name, callback)); + self.raw.async_methods.push((name, callback)); } fn add_function(&mut self, name: impl ToString, function: F) @@ -661,7 +682,7 @@ impl UserDataMethods for UserDataRegistry { { let name = name.to_string(); let callback = self.box_function(&name, function); - self.methods.push((name, callback)); + self.raw.methods.push((name, callback)); } fn add_function_mut(&mut self, name: impl ToString, function: F) @@ -672,7 +693,7 @@ impl UserDataMethods for UserDataRegistry { { let name = name.to_string(); let callback = self.box_function_mut(&name, function); - self.methods.push((name, callback)); + self.raw.methods.push((name, callback)); } #[cfg(feature = "async")] @@ -685,7 +706,7 @@ impl UserDataMethods for UserDataRegistry { { let name = name.to_string(); let callback = self.box_async_function(&name, function); - self.async_methods.push((name, callback)); + self.raw.async_methods.push((name, callback)); } fn add_meta_method(&mut self, name: impl ToString, method: M) @@ -696,7 +717,7 @@ impl UserDataMethods for UserDataRegistry { { let name = name.to_string(); let callback = self.box_method(&name, method); - self.meta_methods.push((name, callback)); + self.raw.meta_methods.push((name, callback)); } fn add_meta_method_mut(&mut self, name: impl ToString, method: M) @@ -707,7 +728,7 @@ impl UserDataMethods for UserDataRegistry { { let name = name.to_string(); let callback = self.box_method_mut(&name, method); - self.meta_methods.push((name, callback)); + self.raw.meta_methods.push((name, callback)); } #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] @@ -721,7 +742,7 @@ impl UserDataMethods for UserDataRegistry { { let name = name.to_string(); let callback = self.box_async_method(&name, method); - self.async_meta_methods.push((name, callback)); + self.raw.async_meta_methods.push((name, callback)); } #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] @@ -735,7 +756,7 @@ impl UserDataMethods for UserDataRegistry { { let name = name.to_string(); let callback = self.box_async_method_mut(&name, method); - self.async_meta_methods.push((name, callback)); + self.raw.async_meta_methods.push((name, callback)); } fn add_meta_function(&mut self, name: impl ToString, function: F) @@ -746,7 +767,7 @@ impl UserDataMethods for UserDataRegistry { { let name = name.to_string(); let callback = self.box_function(&name, function); - self.meta_methods.push((name, callback)); + self.raw.meta_methods.push((name, callback)); } fn add_meta_function_mut(&mut self, name: impl ToString, function: F) @@ -757,7 +778,7 @@ impl UserDataMethods for UserDataRegistry { { let name = name.to_string(); let callback = self.box_function_mut(&name, function); - self.meta_methods.push((name, callback)); + self.raw.meta_methods.push((name, callback)); } #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] @@ -770,7 +791,7 @@ impl UserDataMethods for UserDataRegistry { { let name = name.to_string(); let callback = self.box_async_function(&name, function); - self.async_meta_methods.push((name, callback)); + self.raw.async_meta_methods.push((name, callback)); } } @@ -786,18 +807,16 @@ macro_rules! lua_userdata_impl { T::register(&mut orig_registry); // Copy all fields, methods, etc. from the original registry - registry.fields.extend(orig_registry.fields); - registry.field_getters.extend(orig_registry.field_getters); - registry.field_setters.extend(orig_registry.field_setters); - registry.meta_fields.extend(orig_registry.meta_fields); - registry.methods.extend(orig_registry.methods); + (registry.raw.fields).extend(orig_registry.raw.fields); + (registry.raw.field_getters).extend(orig_registry.raw.field_getters); + (registry.raw.field_setters).extend(orig_registry.raw.field_setters); + (registry.raw.meta_fields).extend(orig_registry.raw.meta_fields); + (registry.raw.methods).extend(orig_registry.raw.methods); #[cfg(feature = "async")] - registry.async_methods.extend(orig_registry.async_methods); - registry.meta_methods.extend(orig_registry.meta_methods); + (registry.raw.async_methods).extend(orig_registry.raw.async_methods); + (registry.raw.meta_methods).extend(orig_registry.raw.meta_methods); #[cfg(feature = "async")] - registry - .async_meta_methods - .extend(orig_registry.async_meta_methods); + (registry.raw.async_meta_methods).extend(orig_registry.raw.async_meta_methods); } } }; diff --git a/src/userdata/util.rs b/src/userdata/util.rs index 5d403c5f..02c5ea4e 100644 --- a/src/userdata/util.rs +++ b/src/userdata/util.rs @@ -1,5 +1,9 @@ use std::cell::Cell; use std::marker::PhantomData; +use std::os::raw::c_int; + +use super::UserDataStorage; +use crate::util::{get_userdata, take_userdata}; // This is a trick to check if a type is `Sync` or not. // It uses leaked specialization feature from stdlib. @@ -29,3 +33,14 @@ pub(crate) fn is_sync() -> bool { .clone(); is_sync.get() } + +pub(super) unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::lua_State) -> c_int { + let ud = get_userdata::>(state, -1); + if !(*ud).is_borrowed() { + take_userdata::>(state); + ffi::lua_pushboolean(state, 1); + } else { + ffi::lua_pushboolean(state, 0); + } + 1 +} From 3bfaee4ecccefa2bccc657e61639adb17a251dc7 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 16 Nov 2024 01:47:04 +0000 Subject: [PATCH 293/635] Delay "any" userdata metatable creation until first instance --- src/scope.rs | 4 ++-- src/state.rs | 6 ++--- src/state/extra.rs | 3 +++ src/state/raw.rs | 23 ++++++++++-------- src/userdata/registry.rs | 52 +++++++++++++++++++--------------------- 5 files changed, 45 insertions(+), 43 deletions(-) diff --git a/src/scope.rs b/src/scope.rs index 498cf742..bbe6dd79 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -174,7 +174,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { let ud_ptr = util::push_uninit_userdata::>(state, protect)?; // Push the metatable and register it with no TypeId - let mut registry = UserDataRegistry::new_unique(ud_ptr as *mut _); + let mut registry = UserDataRegistry::new_unique(self.lua.lua(), ud_ptr as *mut _); T::register(&mut registry); self.lua.push_userdata_metatable(registry.into_raw())?; let mt_ptr = ffi::lua_topointer(state, -1); @@ -222,7 +222,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { let ud_ptr = util::push_uninit_userdata::>(state, protect)?; // Push the metatable and register it with no TypeId - let mut registry = UserDataRegistry::new_unique(ud_ptr as *mut _); + let mut registry = UserDataRegistry::new_unique(self.lua.lua(), ud_ptr as *mut _); register(&mut registry); self.lua.push_userdata_metatable(registry.into_raw())?; let mt_ptr = ffi::lua_topointer(state, -1); diff --git a/src/state.rs b/src/state.rs index 99937148..390ee095 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1306,7 +1306,7 @@ impl Lua { /// This methods provides a way to add fields or methods to userdata objects of a type `T`. pub fn register_userdata_type(&self, f: impl FnOnce(&mut UserDataRegistry)) -> Result<()> { let type_id = TypeId::of::(); - let mut registry = UserDataRegistry::new(type_id); + let mut registry = UserDataRegistry::new(self, type_id); f(&mut registry); let lua = self.lock(); @@ -1316,8 +1316,8 @@ impl Lua { ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, table_id); } - // Register the type - lua.create_userdata_metatable(registry.into_raw())?; + // Add to "pending" registration map + ((*lua.extra.get()).pending_userdata_reg).insert(type_id, registry.into_raw()); } Ok(()) } diff --git a/src/state/extra.rs b/src/state/extra.rs index 468f35f1..19ff747d 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -13,6 +13,7 @@ use crate::error::Result; use crate::state::RawLua; use crate::stdlib::StdLib; use crate::types::{AppData, ReentrantMutex, XRc}; +use crate::userdata::RawUserDataRegistry; use crate::util::{get_internal_metatable, push_internal_userdata, TypeKey, WrappedFailure}; #[cfg(any(feature = "luau", doc))] @@ -35,6 +36,7 @@ pub(crate) struct ExtraData { pub(super) weak: MaybeUninit, pub(super) owned: bool, + pub(super) pending_userdata_reg: FxHashMap, pub(super) registered_userdata_t: FxHashMap, pub(super) registered_userdata_mt: FxHashMap<*const c_void, Option>, pub(super) last_checked_userdata_mt: (*const c_void, Option), @@ -144,6 +146,7 @@ impl ExtraData { lua: MaybeUninit::uninit(), weak: MaybeUninit::uninit(), owned, + pending_userdata_reg: FxHashMap::default(), registered_userdata_t: FxHashMap::default(), registered_userdata_mt: FxHashMap::default(), last_checked_userdata_mt: (ptr::null(), None), diff --git a/src/state/raw.rs b/src/state/raw.rs index 5d56eac0..a6913676 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -756,7 +756,7 @@ impl RawLua { } // Create a new metatable from `UserData` definition - let mut registry = UserDataRegistry::new(type_id); + let mut registry = UserDataRegistry::new(self.lua(), type_id); T::register(&mut registry); self.create_userdata_metatable(registry.into_raw()) @@ -774,9 +774,12 @@ impl RawLua { return Ok(table_id as Integer); } - // Create an empty metatable - let registry = UserDataRegistry::::new(type_id); - self.create_userdata_metatable(registry.into_raw()) + // Check if metatable creation is pending or create an empty metatable otherwise + let registry = match (*self.extra.get()).pending_userdata_reg.remove(&type_id) { + Some(registry) => registry, + None => UserDataRegistry::::new(self.lua(), type_id).into_raw(), + }; + self.create_userdata_metatable(registry) }) } @@ -851,9 +854,9 @@ impl RawLua { rawset_field(state, -2, MetaMethod::validate(&k)?)?; } let mut has_name = false; - for (k, push_field) in registry.meta_fields { + for (k, v) in registry.meta_fields { has_name = has_name || k == MetaMethod::Type; - push_field(self)?; + v?.push_into_stack(self)?; rawset_field(state, -2, MetaMethod::validate(&k)?)?; } // Set `__name/__type` if not provided @@ -875,8 +878,8 @@ impl RawLua { ffi::lua_pop(state, 1); push_table(state, 0, fields_nrec, true)?; } - for (k, push_field) in mem::take(&mut registry.fields) { - push_field(self)?; + for (k, v) in mem::take(&mut registry.fields) { + v?.push_into_stack(self)?; rawset_field(state, -2, &k)?; } rawset_field(state, metatable_index, "__index")?; @@ -896,12 +899,12 @@ impl RawLua { self.push(self.create_callback(m)?)?; rawset_field(state, -2, &k)?; } - for (k, push_field) in registry.fields { + for (k, v) in registry.fields { unsafe extern "C-unwind" fn return_field(state: *mut ffi::lua_State) -> c_int { ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1)); 1 } - push_field(self)?; + v?.push_into_stack(self)?; protect_lua!(state, 1, 1, fn(state) { ffi::lua_pushcclosure(state, return_field, 1); })?; diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index d7209156..4f6a0f26 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -7,7 +7,7 @@ use std::os::raw::c_void; use std::string::String as StdString; use crate::error::{Error, Result}; -use crate::state::{Lua, RawLua}; +use crate::state::{Lua, LuaGuard}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::types::{Callback, MaybeSend}; use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataStorage}; @@ -26,8 +26,6 @@ use std::rc::Rc; #[cfg(feature = "userdata-wrappers")] use std::sync::{Arc, Mutex, RwLock}; -type StaticFieldCallback = Box Result<()> + 'static>; - #[derive(Clone, Copy)] enum UserDataTypeId { Shared(TypeId), @@ -51,6 +49,7 @@ enum UserDataTypeId { /// Handle to registry for userdata methods and metamethods. pub struct UserDataRegistry { + lua: LuaGuard, raw: RawUserDataRegistry, ud_type_id: UserDataTypeId, _type: PhantomData, @@ -58,10 +57,10 @@ pub struct UserDataRegistry { pub(crate) struct RawUserDataRegistry { // Fields - pub(crate) fields: Vec<(String, StaticFieldCallback)>, + pub(crate) fields: Vec<(String, Result)>, pub(crate) field_getters: Vec<(String, Callback)>, pub(crate) field_setters: Vec<(String, Callback)>, - pub(crate) meta_fields: Vec<(String, StaticFieldCallback)>, + pub(crate) meta_fields: Vec<(String, Result)>, // Methods pub(crate) methods: Vec<(String, Callback)>, @@ -102,17 +101,17 @@ impl UserDataTypeId { impl UserDataRegistry { #[inline(always)] - pub(crate) fn new(type_id: TypeId) -> Self { - Self::with_type_id(UserDataTypeId::Shared(type_id)) + pub(crate) fn new(lua: &Lua, type_id: TypeId) -> Self { + Self::with_type_id(lua, UserDataTypeId::Shared(type_id)) } #[inline(always)] - pub(crate) fn new_unique(ud_ptr: *mut c_void) -> Self { - Self::with_type_id(UserDataTypeId::Unique(ud_ptr)) + pub(crate) fn new_unique(lua: &Lua, ud_ptr: *mut c_void) -> Self { + Self::with_type_id(lua, UserDataTypeId::Unique(ud_ptr)) } #[inline(always)] - fn with_type_id(ud_type_id: UserDataTypeId) -> Self { + fn with_type_id(lua: &Lua, ud_type_id: UserDataTypeId) -> Self { let raw = RawUserDataRegistry { fields: Vec::new(), field_getters: Vec::new(), @@ -130,6 +129,7 @@ impl UserDataRegistry { }; UserDataRegistry { + lua: lua.lock_arc(), raw, ud_type_id, _type: PhantomData, @@ -548,10 +548,7 @@ impl UserDataFields for UserDataRegistry { V: IntoLua + 'static, { let name = name.to_string(); - self.raw.fields.push(( - name, - Box::new(move |rawlua| unsafe { value.push_into_stack(rawlua) }), - )); + self.raw.fields.push((name, value.into_lua(self.lua.lua()))); } fn add_field_method_get(&mut self, name: impl ToString, method: M) @@ -598,13 +595,10 @@ impl UserDataFields for UserDataRegistry { where V: IntoLua + 'static, { + let lua = self.lua.lua(); let name = name.to_string(); - self.raw.meta_fields.push(( - name.clone(), - Box::new(move |rawlua| unsafe { - Self::check_meta_field(rawlua.lua(), &name, value)?.push_into_stack(rawlua) - }), - )); + let field = Self::check_meta_field(lua, &name, value).and_then(|v| v.into_lua(lua)); + self.raw.meta_fields.push((name, field)); } fn add_meta_field_with(&mut self, name: impl ToString, f: F) @@ -612,14 +606,10 @@ impl UserDataFields for UserDataRegistry { F: FnOnce(&Lua) -> Result + 'static, R: IntoLua, { + let lua = self.lua.lua(); let name = name.to_string(); - self.raw.meta_fields.push(( - name.clone(), - Box::new(move |rawlua| unsafe { - let lua = rawlua.lua(); - Self::check_meta_field(lua, &name, f(lua)?)?.push_into_stack(rawlua) - }), - )); + let field = f(lua).and_then(|v| Self::check_meta_field(lua, &name, v).and_then(|v| v.into_lua(lua))); + self.raw.meta_fields.push((name, field)); } } @@ -803,7 +793,7 @@ macro_rules! lua_userdata_impl { ($type:ty, $type_id:expr) => { impl UserData for $type { fn register(registry: &mut UserDataRegistry) { - let mut orig_registry = UserDataRegistry::with_type_id($type_id); + let mut orig_registry = UserDataRegistry::with_type_id(registry.lua.lua(), $type_id); T::register(&mut orig_registry); // Copy all fields, methods, etc. from the original registry @@ -841,3 +831,9 @@ lua_userdata_impl!(Arc> => ArcRwLock); lua_userdata_impl!(Arc> => ArcParkingLotMutex); #[cfg(feature = "userdata-wrappers")] lua_userdata_impl!(Arc> => ArcParkingLotRwLock); + +#[cfg(test)] +mod assertions { + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(super::RawUserDataRegistry: Send); +} From c31c72076f6953ad09e6ed5e47276463654a6732 Mon Sep 17 00:00:00 2001 From: cos Date: Wed, 19 Jun 2024 15:32:58 +0200 Subject: [PATCH 294/635] Make build script work with FreeBSD On FreeBSD the pkg-config names takes the format with a dash, rather than without (e.g. lua-5.4, not lua5.4). Thus adapt build script to iterate over an array of alt_probes until finding a match. Signed-off-by: Alex Orlenko --- mlua-sys/build/find_normal.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/mlua-sys/build/find_normal.rs b/mlua-sys/build/find_normal.rs index 26b1200e..4fa874aa 100644 --- a/mlua-sys/build/find_normal.rs +++ b/mlua-sys/build/find_normal.rs @@ -32,15 +32,15 @@ pub fn probe_lua() { // Find using `pkg-config` #[cfg(feature = "lua54")] - let (incl_bound, excl_bound, alt_probe, ver) = ("5.4", "5.5", Some("lua5.4"), "5.4"); + let (incl_bound, excl_bound, alt_probe, ver) = ("5.4", "5.5", ["lua5.4", "lua-5.4"], "5.4"); #[cfg(feature = "lua53")] - let (incl_bound, excl_bound, alt_probe, ver) = ("5.3", "5.4", Some("lua5.3"), "5.3"); + let (incl_bound, excl_bound, alt_probe, ver) = ("5.3", "5.4", ["lua5.3", "lua-5.3"], "5.3"); #[cfg(feature = "lua52")] - let (incl_bound, excl_bound, alt_probe, ver) = ("5.2", "5.3", Some("lua5.2"), "5.2"); + let (incl_bound, excl_bound, alt_probe, ver) = ("5.2", "5.3", ["lua5.2", "lua-5.2"], "5.2"); #[cfg(feature = "lua51")] - let (incl_bound, excl_bound, alt_probe, ver) = ("5.1", "5.2", Some("lua5.1"), "5.1"); + let (incl_bound, excl_bound, alt_probe, ver) = ("5.1", "5.2", ["lua5.1", "lua-5.1"], "5.1"); #[cfg(feature = "luajit")] - let (incl_bound, excl_bound, alt_probe, ver) = ("2.0.4", "2.2", None, "JIT"); + let (incl_bound, excl_bound, alt_probe, ver) = ("2.0.4", "2.2", [], "JIT"); #[rustfmt::skip] let mut lua = pkg_config::Config::new() @@ -48,10 +48,16 @@ pub fn probe_lua() { .cargo_metadata(true) .probe(if cfg!(feature = "luajit") { "luajit" } else { "lua" }); - if lua.is_err() && alt_probe.is_some() { - lua = pkg_config::Config::new() - .cargo_metadata(true) - .probe(alt_probe.unwrap()); + if lua.is_err() { + for pkg in alt_probe { + lua = pkg_config::Config::new() + .cargo_metadata(true) + .probe(pkg); + + if lua.is_ok() { + break; + } + } } lua.unwrap_or_else(|err| panic!("cannot find Lua{ver} using `pkg-config`: {err}")); From 30b0122f5d1dba4f0c17553c1b6ce24d1cd3d02f Mon Sep 17 00:00:00 2001 From: cos Date: Tue, 12 Nov 2024 19:00:38 +0100 Subject: [PATCH 295/635] Make build script work with OpenBSD On OpenBSD the pkg-config names takes a minimalist format. No dash, and no separator between the major and minor version number. Build script is thus adapted to have also these in their set of alt_probes. Signed-off-by: Alex Orlenko --- mlua-sys/build/find_normal.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/mlua-sys/build/find_normal.rs b/mlua-sys/build/find_normal.rs index 4fa874aa..dbbc18c4 100644 --- a/mlua-sys/build/find_normal.rs +++ b/mlua-sys/build/find_normal.rs @@ -32,13 +32,17 @@ pub fn probe_lua() { // Find using `pkg-config` #[cfg(feature = "lua54")] - let (incl_bound, excl_bound, alt_probe, ver) = ("5.4", "5.5", ["lua5.4", "lua-5.4"], "5.4"); + let (incl_bound, excl_bound, alt_probe, ver) = + ("5.4", "5.5", ["lua5.4", "lua-5.4", "lua54"], "5.4"); #[cfg(feature = "lua53")] - let (incl_bound, excl_bound, alt_probe, ver) = ("5.3", "5.4", ["lua5.3", "lua-5.3"], "5.3"); + let (incl_bound, excl_bound, alt_probe, ver) = + ("5.3", "5.4", ["lua5.3", "lua-5.3", "lua53"], "5.3"); #[cfg(feature = "lua52")] - let (incl_bound, excl_bound, alt_probe, ver) = ("5.2", "5.3", ["lua5.2", "lua-5.2"], "5.2"); + let (incl_bound, excl_bound, alt_probe, ver) = + ("5.2", "5.3", ["lua5.2", "lua-5.2", "lua52"], "5.2"); #[cfg(feature = "lua51")] - let (incl_bound, excl_bound, alt_probe, ver) = ("5.1", "5.2", ["lua5.1", "lua-5.1"], "5.1"); + let (incl_bound, excl_bound, alt_probe, ver) = + ("5.1", "5.2", ["lua5.1", "lua-5.1", "lua51"], "5.1"); #[cfg(feature = "luajit")] let (incl_bound, excl_bound, alt_probe, ver) = ("2.0.4", "2.2", [], "JIT"); From 4ef0d583fcec087e2ebb27a3e0bb6304f9073387 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 16 Nov 2024 01:56:10 +0000 Subject: [PATCH 296/635] impl Send for UserDataTypeId (in `send` mode) --- src/userdata/registry.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index 4f6a0f26..ec5a989b 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -99,6 +99,9 @@ impl UserDataTypeId { } } +#[cfg(feature = "send")] +unsafe impl Send for UserDataTypeId {} + impl UserDataRegistry { #[inline(always)] pub(crate) fn new(lua: &Lua, type_id: TypeId) -> Self { From 4891a6ac10e152625073335ad0703a6e68aa36fc Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 17 Nov 2024 18:01:05 +0000 Subject: [PATCH 297/635] Fix utf-8 test when bstr v1.11 switchted to LowerHex --- tests/string.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/string.rs b/tests/string.rs index 7e2d877f..5192869d 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -96,8 +96,8 @@ fn test_string_fmt_debug() -> Result<()> { assert_eq!(format!("{:?}", s.as_bytes()), "[104, 101, 108, 108, 111]"); // Invalid utf8 - let s = lua.create_string(b"hello\0world\r\n\t\xF0\x90\x80")?; - assert_eq!(format!("{s:?}"), r#"b"hello\0world\r\n\t\xF0\x90\x80""#); + let s = lua.create_string(b"hello\0world\r\n\t\xf0\x90\x80")?; + assert_eq!(format!("{s:?}"), r#"b"hello\0world\r\n\t\xf0\x90\x80""#); Ok(()) } From 7ce6b97da99aa833cf70add59bae278fa145955b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 22 Nov 2024 11:48:20 +0000 Subject: [PATCH 298/635] Add `String::wrap` method to wrap arbitrary `AsRef<[u8]>` --- src/string.rs | 22 ++++++++++++++++++++++ tests/string.rs | 15 +++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/string.rs b/src/string.rs index 4424a8f4..f4730a90 100644 --- a/src/string.rs +++ b/src/string.rs @@ -7,7 +7,9 @@ use std::{cmp, fmt, slice, str}; use crate::error::{Error, Result}; use crate::state::Lua; +use crate::traits::IntoLua; use crate::types::{LuaType, ValueRef}; +use crate::value::Value; #[cfg(feature = "serialize")] use { @@ -366,6 +368,26 @@ impl<'a> IntoIterator for BorrowedBytes<'a> { } } +pub(crate) struct WrappedString Result>(F); + +impl String { + /// Wraps bytes, returning an opaque type that implements [`IntoLua`] trait. + /// + /// This function uses [`Lua::create_string`] under the hood. + pub fn wrap(data: impl AsRef<[u8]>) -> impl IntoLua { + WrappedString(move |lua| lua.create_string(data)) + } +} + +impl IntoLua for WrappedString +where + F: FnOnce(&Lua) -> Result, +{ + fn into_lua(self, lua: &Lua) -> Result { + (self.0)(lua).map(Value::String) + } +} + impl LuaType for String { const TYPE_ID: c_int = ffi::LUA_TSTRING; } diff --git a/tests/string.rs b/tests/string.rs index 5192869d..1f849df9 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -128,3 +128,18 @@ fn test_string_display() -> Result<()> { Ok(()) } + +#[test] +fn test_string_wrap() -> Result<()> { + let lua = Lua::new(); + + let s = String::wrap("hello, world"); + lua.globals().set("s", s)?; + assert_eq!(lua.globals().get::("s")?, "hello, world"); + + let s2 = String::wrap("hello, world (owned)".to_string()); + lua.globals().set("s2", s2)?; + assert_eq!(lua.globals().get::("s2")?, "hello, world (owned)"); + + Ok(()) +} From bf9fcc5aca35f889717a90a309129dbad8da3049 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 22 Nov 2024 13:06:50 +0000 Subject: [PATCH 299/635] Simplify `WrappedString` --- src/string.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/string.rs b/src/string.rs index f4730a90..47af717a 100644 --- a/src/string.rs +++ b/src/string.rs @@ -368,23 +368,20 @@ impl<'a> IntoIterator for BorrowedBytes<'a> { } } -pub(crate) struct WrappedString Result>(F); +pub(crate) struct WrappedString>(T); impl String { /// Wraps bytes, returning an opaque type that implements [`IntoLua`] trait. /// /// This function uses [`Lua::create_string`] under the hood. pub fn wrap(data: impl AsRef<[u8]>) -> impl IntoLua { - WrappedString(move |lua| lua.create_string(data)) + WrappedString(data) } } -impl IntoLua for WrappedString -where - F: FnOnce(&Lua) -> Result, -{ +impl> IntoLua for WrappedString { fn into_lua(self, lua: &Lua) -> Result { - (self.0)(lua).map(Value::String) + lua.create_string(self.0).map(Value::String) } } From d8307d0e4cfbad43de2674d6f672f2cb5028802b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 22 Nov 2024 13:08:47 +0000 Subject: [PATCH 300/635] Reduce visibility of Wrapped* structs --- src/function.rs | 4 ++-- src/string.rs | 2 +- src/userdata.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/function.rs b/src/function.rs index 57e5951b..37b33fa9 100644 --- a/src/function.rs +++ b/src/function.rs @@ -509,10 +509,10 @@ impl Function { } } -pub(crate) struct WrappedFunction(pub(crate) Callback); +struct WrappedFunction(pub(crate) Callback); #[cfg(feature = "async")] -pub(crate) struct WrappedAsyncFunction(pub(crate) AsyncCallback); +struct WrappedAsyncFunction(pub(crate) AsyncCallback); impl Function { /// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`] diff --git a/src/string.rs b/src/string.rs index 47af717a..38fb8850 100644 --- a/src/string.rs +++ b/src/string.rs @@ -368,7 +368,7 @@ impl<'a> IntoIterator for BorrowedBytes<'a> { } } -pub(crate) struct WrappedString>(T); +struct WrappedString>(T); impl String { /// Wraps bytes, returning an opaque type that implements [`IntoLua`] trait. diff --git a/src/userdata.rs b/src/userdata.rs index 7db8896e..83c5efcd 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -1073,7 +1073,7 @@ impl Serialize for AnyUserData { } } -pub(crate) struct WrappedUserdata Result>(F); +struct WrappedUserdata Result>(F); impl AnyUserData { /// Wraps any Rust type, returning an opaque type that implements [`IntoLua`] trait. From ee7ced63347e4bb37f78df178b6a9602a996b911 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 22 Nov 2024 14:40:52 +0000 Subject: [PATCH 301/635] Add `Chunk::wrap` method --- src/chunk.rs | 34 +++++++++++++++++++++++++++++++++- src/state.rs | 11 +++++++++-- tests/chunk.rs | 19 ++++++++++++++++++- 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index ff3d9be0..d83e16c0 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -2,6 +2,8 @@ use std::borrow::Cow; use std::collections::HashMap; use std::ffi::CString; use std::io::Result as IoResult; +use std::marker::PhantomData; +use std::panic::Location; use std::path::{Path, PathBuf}; use std::string::String as StdString; @@ -9,7 +11,8 @@ use crate::error::{Error, Result}; use crate::function::Function; use crate::state::{Lua, WeakLua}; use crate::table::Table; -use crate::traits::{FromLuaMulti, IntoLuaMulti}; +use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti}; +use crate::value::Value; /// Trait for types [loadable by Lua] and convertible to a [`Chunk`] /// @@ -561,3 +564,32 @@ impl Chunk<'_> { buf } } + +struct WrappedChunk<'a, T: AsChunk<'a>> { + chunk: T, + caller: &'static Location<'static>, + _marker: PhantomData<&'a T>, +} + +impl<'a> Chunk<'a> { + /// Wraps a chunk of Lua code, returning an opaque type that implements [`IntoLua`] trait. + /// + /// The resulted `IntoLua` implementation will convert the chunk into a Lua function without + /// executing it. + #[track_caller] + pub fn wrap(chunk: impl AsChunk<'a> + 'a) -> impl IntoLua + 'a { + WrappedChunk { + chunk, + caller: Location::caller(), + _marker: PhantomData, + } + } +} + +impl<'a, T: AsChunk<'a>> IntoLua for WrappedChunk<'a, T> { + fn into_lua(self, lua: &Lua) -> Result { + lua.load_with_location(self.chunk, self.caller) + .into_function() + .map(Value::Function) + } +} diff --git a/src/state.rs b/src/state.rs index 390ee095..71114ba0 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1003,10 +1003,17 @@ impl Lua { /// [`Chunk::exec`]: crate::Chunk::exec #[track_caller] pub fn load<'a>(&self, chunk: impl AsChunk<'a>) -> Chunk<'a> { - let caller = Location::caller(); + self.load_with_location(chunk, Location::caller()) + } + + pub(crate) fn load_with_location<'a>( + &self, + chunk: impl AsChunk<'a>, + location: &'static Location<'static>, + ) -> Chunk<'a> { Chunk { lua: self.weak(), - name: chunk.name().unwrap_or_else(|| caller.to_string()), + name: chunk.name().unwrap_or_else(|| location.to_string()), env: chunk.environment(self), mode: chunk.mode(), source: chunk.source(), diff --git a/tests/chunk.rs b/tests/chunk.rs index 1a5270ec..16df553b 100644 --- a/tests/chunk.rs +++ b/tests/chunk.rs @@ -1,6 +1,6 @@ use std::{fs, io}; -use mlua::{Lua, Result}; +use mlua::{Chunk, Lua, Result}; #[test] fn test_chunk_path() -> Result<()> { @@ -121,3 +121,20 @@ fn test_compiler() -> Result<()> { Ok(()) } + +#[test] +fn test_chunk_wrap() -> Result<()> { + let lua = Lua::new(); + + let f = Chunk::wrap("return 123"); + lua.globals().set("f", f)?; + lua.load("assert(f() == 123)").exec().unwrap(); + + lua.globals().set("f2", Chunk::wrap("c()"))?; + assert!( + (lua.load("f2()").exec().err().unwrap().to_string()).contains(file!()), + "wrong chunk location" + ); + + Ok(()) +} From fc1c80c142da3c5c920bf078794bcfd53821e6ee Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 22 Nov 2024 20:03:17 +0000 Subject: [PATCH 302/635] Mark `Chunk::wrap` as non-public --- src/chunk.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/chunk.rs b/src/chunk.rs index d83e16c0..8e958eb9 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -576,6 +576,7 @@ impl<'a> Chunk<'a> { /// /// The resulted `IntoLua` implementation will convert the chunk into a Lua function without /// executing it. + #[doc(hidden)] #[track_caller] pub fn wrap(chunk: impl AsChunk<'a> + 'a) -> impl IntoLua + 'a { WrappedChunk { From 9ae3cb0a7c88327e4b53245d1394c082b4a0b791 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 24 Nov 2024 12:00:01 +0000 Subject: [PATCH 303/635] Add doc about possible chunk name prefixes --- src/chunk.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/chunk.rs b/src/chunk.rs index 8e958eb9..089f4b82 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -19,6 +19,8 @@ use crate::value::Value; /// [loadable by Lua]: https://www.lua.org/manual/5.4/manual.html#3.3.2 pub trait AsChunk<'a> { /// Returns optional chunk name + /// + /// See [`Chunk::set_name`] for possible name prefixes. fn name(&self) -> Option { None } @@ -306,6 +308,11 @@ impl Compiler { impl Chunk<'_> { /// Sets the name of this chunk, which results in more informative error traces. + /// + /// Possible name prefixes: + /// - `@` - file path (when truncation is needed, the end of the file path is kept, as this is + /// more useful for identifying the file) + /// - `=` - custom chunk name (when truncation is needed, the beginning of the name is kept) pub fn set_name(mut self, name: impl Into) -> Self { self.name = name.into(); self From af31dbd180c3982c69bd13f9fbf3fcb0c5e9c140 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 25 Nov 2024 22:53:59 +0000 Subject: [PATCH 304/635] Use c string literal in few places --- src/util/userdata.rs | 107 ++++++++++++++++++++----------------------- 1 file changed, 50 insertions(+), 57 deletions(-) diff --git a/src/util/userdata.rs b/src/util/userdata.rs index 685254f4..119b8c8d 100644 --- a/src/util/userdata.rs +++ b/src/util/userdata.rs @@ -1,4 +1,3 @@ -use std::ffi::CStr; use std::os::raw::{c_int, c_void}; use std::{ptr, str}; @@ -222,50 +221,47 @@ unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<() ffi::lua_pop(state, 1); // Create and cache `__index` generator - let code = cstr!( - r#" - local error, isfunction, istable = ... - return function (__index, field_getters, methods) - -- Common case: has field getters and index is a table - if field_getters ~= nil and methods == nil and istable(__index) then - return function (self, key) - local field_getter = field_getters[key] - if field_getter ~= nil then - return field_getter(self) - end - return __index[key] + let code = cr#" + local error, isfunction, istable = ... + return function (__index, field_getters, methods) + -- Common case: has field getters and index is a table + if field_getters ~= nil and methods == nil and istable(__index) then + return function (self, key) + local field_getter = field_getters[key] + if field_getter ~= nil then + return field_getter(self) end + return __index[key] end + end - return function (self, key) - if field_getters ~= nil then - local field_getter = field_getters[key] - if field_getter ~= nil then - return field_getter(self) - end + return function (self, key) + if field_getters ~= nil then + local field_getter = field_getters[key] + if field_getter ~= nil then + return field_getter(self) end + end - if methods ~= nil then - local method = methods[key] - if method ~= nil then - return method - end + if methods ~= nil then + local method = methods[key] + if method ~= nil then + return method end + end - if isfunction(__index) then - return __index(self, key) - elseif __index == nil then - error("attempt to get an unknown field '"..key.."'") - else - return __index[key] - end + if isfunction(__index) then + return __index(self, key) + elseif __index == nil then + error("attempt to get an unknown field '"..key.."'") + else + return __index[key] end end - "# - ); - let code_len = CStr::from_ptr(code).to_bytes().len(); + end + "#; protect_lua!(state, 0, 1, |state| { - let ret = ffi::luaL_loadbuffer(state, code, code_len, cstr!("__mlua_index")); + let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_index")); if ret != ffi::LUA_OK { ffi::lua_error(state); } @@ -293,33 +289,30 @@ unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result ffi::lua_pop(state, 1); // Create and cache `__newindex` generator - let code = cstr!( - r#" - local error, isfunction = ... - return function (__newindex, field_setters) - return function (self, key, value) - if field_setters ~= nil then - local field_setter = field_setters[key] - if field_setter ~= nil then - field_setter(self, value) - return - end + let code = cr#" + local error, isfunction = ... + return function (__newindex, field_setters) + return function (self, key, value) + if field_setters ~= nil then + local field_setter = field_setters[key] + if field_setter ~= nil then + field_setter(self, value) + return end + end - if isfunction(__newindex) then - __newindex(self, key, value) - elseif __newindex == nil then - error("attempt to set an unknown field '"..key.."'") - else - __newindex[key] = value - end + if isfunction(__newindex) then + __newindex(self, key, value) + elseif __newindex == nil then + error("attempt to set an unknown field '"..key.."'") + else + __newindex[key] = value end end - "# - ); - let code_len = CStr::from_ptr(code).to_bytes().len(); + end + "#; protect_lua!(state, 0, 1, |state| { - let ret = ffi::luaL_loadbuffer(state, code, code_len, cstr!("__mlua_newindex")); + let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_newindex")); if ret != ffi::LUA_OK { ffi::lua_error(state); } From 55a5d7ef10c08076cb32b8458a19372ca6ba34df Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 27 Nov 2024 00:32:37 +0000 Subject: [PATCH 305/635] Protect Lua(u) during chunk loading if memory limit is enforced Relates to #488 --- src/state/raw.rs | 67 +++++++++++++++++++++++++++++++----------------- tests/memory.rs | 10 ++++++++ 2 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index a6913676..0af877f5 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -319,39 +319,58 @@ impl RawLua { let state = self.state(); unsafe { let _sg = StackGuard::new(state); - check_stack(state, 2)?; + check_stack(state, 3)?; - let mode_str = match mode { + let name = name.map(CStr::as_ptr).unwrap_or(ptr::null()); + let mode = match mode { Some(ChunkMode::Binary) => cstr!("b"), Some(ChunkMode::Text) => cstr!("t"), None => cstr!("bt"), }; + let status = if cfg!(not(feature = "luau")) || self.unlikely_memory_error() { + self.load_chunk_inner(state, name, env, mode, source) + } else { + // Only Luau can trigger an exception during chunk loading + protect_lua!(state, 0, 1, |state| { + self.load_chunk_inner(state, name, env, mode, source) + })? + }; + match status { + ffi::LUA_OK => Ok(Function(self.pop_ref())), + err => Err(pop_error(state, err)), + } + } + } - match ffi::luaL_loadbufferenv( - state, - source.as_ptr() as *const c_char, - source.len(), - name.map(|n| n.as_ptr()).unwrap_or_else(ptr::null), - mode_str, - match env { - Some(env) => { - self.push_ref(&env.0); - -1 - } - _ => 0, - }, - ) { - ffi::LUA_OK => { - #[cfg(feature = "luau-jit")] - if (*self.extra.get()).enable_jit && ffi::luau_codegen_supported() != 0 { - ffi::luau_codegen_compile(state, -1); - } - - Ok(Function(self.pop_ref())) + pub(crate) unsafe fn load_chunk_inner( + &self, + state: *mut ffi::lua_State, + name: *const c_char, + env: Option<&Table>, + mode: *const c_char, + source: &[u8], + ) -> c_int { + let status = ffi::luaL_loadbufferenv( + state, + source.as_ptr() as *const c_char, + source.len(), + name, + mode, + match env { + Some(env) => { + self.push_ref(&env.0); + -1 } - err => Err(pop_error(state, err)), + _ => 0, + }, + ); + #[cfg(feature = "luau-jit")] + if status == ffi::LUA_OK { + if (*self.extra.get()).enable_jit && ffi::luau_codegen_supported() != 0 { + ffi::luau_codegen_compile(state, -1); } } + status } /// Sets a 'hook' function for a thread (coroutine). diff --git a/tests/memory.rs b/tests/memory.rs index e765e06c..ba30761b 100644 --- a/tests/memory.rs +++ b/tests/memory.rs @@ -31,6 +31,16 @@ fn test_memory_limit() -> Result<()> { lua.set_memory_limit(0)?; f.call::<()>(()).expect("should trigger no memory limit"); + // Test memory limit during chunk loading + lua.set_memory_limit(1024)?; + match lua + .load("local t = {}; for i = 1,10000 do t[i] = i end") + .into_function() + { + Err(Error::MemoryError(_)) => {} + _ => panic!("did not trigger memory error"), + }; + Ok(()) } From 7a3f19b857b9720f6b282f05039af7ee4144e0ad Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 29 Nov 2024 13:36:51 +0000 Subject: [PATCH 306/635] Ensure that buffer with Luau compiled code is always freed --- mlua-sys/src/luau/compat.rs | 15 +++++++++++++-- src/state/raw.rs | 4 ++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index a738dae3..3d189cec 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -326,12 +326,16 @@ pub unsafe fn luaL_loadbufferenv( mut size: usize, name: *const c_char, mode: *const c_char, - env: c_int, + mut env: c_int, ) -> c_int { extern "C" { fn free(p: *mut c_void); } + unsafe extern "C-unwind" fn data_dtor(data: *mut c_void) { + free(*(data as *mut *mut c_char) as *mut c_void); + } + let chunk_is_text = size == 0 || (*data as u8) >= b'\t'; if !mode.is_null() { let modeb = CStr::from_ptr(mode).to_bytes(); @@ -345,9 +349,16 @@ pub unsafe fn luaL_loadbufferenv( } if chunk_is_text { + if env < 0 { + env -= 1; + } + let data_ud = lua_newuserdatadtor(L, mem::size_of::<*mut c_char>(), data_dtor) as *mut *mut c_char; let data = luau_compile_(data, size, ptr::null_mut(), &mut size); + ptr::write(data_ud, data); + // By deferring the `free(data)` to the userdata destructor, we ensure that + // even if `luau_load` throws an error, the `data` is still released. let ok = luau_load(L, name, data, size, env) == 0; - free(data as *mut c_void); + lua_replace(L, -2); // replace data with the result if !ok { return LUA_ERRSYNTAX; } diff --git a/src/state/raw.rs b/src/state/raw.rs index 0af877f5..0add603d 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -327,10 +327,10 @@ impl RawLua { Some(ChunkMode::Text) => cstr!("t"), None => cstr!("bt"), }; - let status = if cfg!(not(feature = "luau")) || self.unlikely_memory_error() { + let status = if self.unlikely_memory_error() { self.load_chunk_inner(state, name, env, mode, source) } else { - // Only Luau can trigger an exception during chunk loading + // Luau and Lua 5.2 can trigger an exception during chunk loading protect_lua!(state, 0, 1, |state| { self.load_chunk_inner(state, name, env, mode, source) })? From 5fd96c7908ee574d72fd015365266d9d360c56cf Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 30 Nov 2024 00:57:17 +0000 Subject: [PATCH 307/635] Don't run GC finalizers on ref thread. If this happen then we don't have full access to the ref thread stack. Fixes #491 --- src/state.rs | 4 ++-- src/state/raw.rs | 14 +++++++++----- tests/tests.rs | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/state.rs b/src/state.rs index 71114ba0..35d9e4ec 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1043,8 +1043,8 @@ impl Lua { let state = lua.state(); unsafe { if lua.unlikely_memory_error() { - crate::util::push_buffer(lua.ref_thread(), buf.as_ref(), false)?; - return Ok(Buffer(lua.pop_ref_thread())); + crate::util::push_buffer(state, buf.as_ref(), false)?; + return Ok(Buffer(lua.pop_ref())); } let _sg = StackGuard::new(state); diff --git a/src/state/raw.rs b/src/state/raw.rs index 0add603d..0731f846 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -432,8 +432,8 @@ impl RawLua { pub(crate) unsafe fn create_string(&self, s: impl AsRef<[u8]>) -> Result { let state = self.state(); if self.unlikely_memory_error() { - push_string(self.ref_thread(), s.as_ref(), false)?; - return Ok(String(self.pop_ref_thread())); + push_string(state, s.as_ref(), false)?; + return Ok(String(self.pop_ref())); } let _sg = StackGuard::new(state); @@ -444,12 +444,12 @@ impl RawLua { /// See [`Lua::create_table_with_capacity`] pub(crate) unsafe fn create_table_with_capacity(&self, narr: usize, nrec: usize) -> Result
{ + let state = self.state(); if self.unlikely_memory_error() { - push_table(self.ref_thread(), narr, nrec, false)?; - return Ok(Table(self.pop_ref_thread())); + push_table(state, narr, nrec, false)?; + return Ok(Table(self.pop_ref())); } - let state = self.state(); let _sg = StackGuard::new(state); check_stack(state, 3)?; push_table(state, narr, nrec, true)?; @@ -734,6 +734,10 @@ impl RawLua { pub(crate) unsafe fn drop_ref(&self, vref: &ValueRef) { let ref_thread = self.ref_thread(); + mlua_debug_assert!( + ffi::lua_gettop(ref_thread) >= vref.index, + "GC finalizer is not allowed in ref_thread" + ); ffi::lua_pushnil(ref_thread); ffi::lua_replace(ref_thread, vref.index); (*self.extra.get()).ref_free.push(vref.index); diff --git a/tests/tests.rs b/tests/tests.rs index 318b03bb..89bece8a 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1377,3 +1377,21 @@ fn test_exec_raw() -> Result<()> { Ok(()) } + +#[test] +fn test_gc_drop_ref_thread() -> Result<()> { + let lua = Lua::new(); + + let t = lua.create_table()?; + lua.create_function(move |_, ()| { + _ = &t; + Ok(()) + })?; + + for _ in 0..10000 { + // GC will run eventually to collect the function and the table above + lua.create_table()?; + } + + Ok(()) +} From 031854fa2a4ffcb0a9bb24f7c9537310f03d1933 Mon Sep 17 00:00:00 2001 From: Evie <14899090+evie-calico@users.noreply.github.com> Date: Sat, 30 Nov 2024 17:37:40 -0500 Subject: [PATCH 308/635] Switch proc-macro-error to proc-macro-error2 (#493) --- mlua_derive/Cargo.toml | 4 ++-- mlua_derive/src/lib.rs | 2 +- mlua_derive/src/token.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mlua_derive/Cargo.toml b/mlua_derive/Cargo.toml index 773351e7..28a10490 100644 --- a/mlua_derive/Cargo.toml +++ b/mlua_derive/Cargo.toml @@ -12,12 +12,12 @@ license = "MIT" proc-macro = true [features] -macros = ["proc-macro-error", "itertools", "regex", "once_cell"] +macros = ["proc-macro-error2", "itertools", "regex", "once_cell"] [dependencies] quote = "1.0" proc-macro2 = { version = "1.0", features = ["span-locations"] } -proc-macro-error = { version = "1.0", optional = true } +proc-macro-error2 = { version = "2.0.1", optional = true } syn = { version = "2.0", features = ["full"] } itertools = { version = "0.13", optional = true } regex = { version = "1.4", optional = true } diff --git a/mlua_derive/src/lib.rs b/mlua_derive/src/lib.rs index 4d3bfb29..af54bedf 100644 --- a/mlua_derive/src/lib.rs +++ b/mlua_derive/src/lib.rs @@ -7,7 +7,7 @@ use syn::{parse_macro_input, ItemFn, LitStr, Result}; #[cfg(feature = "macros")] use { crate::chunk::Chunk, proc_macro::TokenTree, proc_macro2::TokenStream as TokenStream2, - proc_macro_error::proc_macro_error, + proc_macro_error2::proc_macro_error, }; #[derive(Default)] diff --git a/mlua_derive/src/token.rs b/mlua_derive/src/token.rs index 19f3f05f..c6ce7c97 100644 --- a/mlua_derive/src/token.rs +++ b/mlua_derive/src/token.rs @@ -74,7 +74,7 @@ fn parse_pos(span: &Span) -> Option<(usize, usize)> { fn fallback_span_pos(span: &Span) -> (Pos, Pos) { let (start, end) = match parse_pos(span) { Some(v) => v, - None => proc_macro_error::abort_call_site!("Cannot retrieve span information; please use nightly"), + None => proc_macro_error2::abort_call_site!("Cannot retrieve span information; please use nightly"), }; (Pos::new(1, start), Pos::new(1, end)) } From 1c6b6ad80121e40845812c73128f9bcb23f620ef Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 1 Dec 2024 02:11:12 +0000 Subject: [PATCH 309/635] Fix tests --- tests/hooks.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/hooks.rs b/tests/hooks.rs index 7231bdfd..ddbfc37f 100644 --- a/tests/hooks.rs +++ b/tests/hooks.rs @@ -75,9 +75,21 @@ fn test_function_calls() -> Result<()> { let output = output.lock().unwrap(); if cfg!(feature = "luajit") && lua.load("jit.version_num").eval::()? >= 20100 { + #[cfg(not(force_memory_limit))] assert_eq!(*output, vec![(None, "main"), (Some("len".to_string()), "Lua")]); + #[cfg(force_memory_limit)] + assert_eq!( + *output, + vec![(None, "C"), (None, "main"), (Some("len".to_string()), "Lua")] + ); } else { + #[cfg(not(force_memory_limit))] assert_eq!(*output, vec![(None, "main"), (Some("len".to_string()), "C")]); + #[cfg(force_memory_limit)] + assert_eq!( + *output, + vec![(None, "C"), (None, "main"), (Some("len".to_string()), "C")] + ); } Ok(()) From aa061bce6f2b5a45baebd2308aef6c41c9676e1f Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 1 Dec 2024 12:55:50 +0000 Subject: [PATCH 310/635] mlua_derive: v0.10.1 --- Cargo.toml | 2 +- mlua_derive/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b0ee449e..01aaaeb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ anyhow = ["dep:anyhow", "error-send"] userdata-wrappers = [] [dependencies] -mlua_derive = { version = "=0.10.0", optional = true, path = "mlua_derive" } +mlua_derive = { version = "=0.10.1", optional = true, path = "mlua_derive" } bstr = { version = "1.0", features = ["std"], default-features = false } either = "1.0" num-traits = { version = "0.2.14" } diff --git a/mlua_derive/Cargo.toml b/mlua_derive/Cargo.toml index 28a10490..1e121341 100644 --- a/mlua_derive/Cargo.toml +++ b/mlua_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua_derive" -version = "0.10.0" +version = "0.10.1" authors = ["Aleksandr Orlenko "] edition = "2021" description = "Procedural macros for the mlua crate." From d51ce861420eb903fe90836ebec66114d5950587 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 1 Dec 2024 12:56:39 +0000 Subject: [PATCH 311/635] mlua-sys: v0.6.6 --- Cargo.toml | 2 +- mlua-sys/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 01aaaeb0..78bd565d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ serde-value = { version = "0.7", optional = true } parking_lot = { version = "0.12", features = ["arc_lock"] } anyhow = { version = "1.0", optional = true } -ffi = { package = "mlua-sys", version = "0.6.5", path = "mlua-sys" } +ffi = { package = "mlua-sys", version = "0.6.6", path = "mlua-sys" } [target.'cfg(unix)'.dependencies] libloading = { version = "0.8", optional = true } diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index d6d08441..dc3168c4 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.6.5" +version = "0.6.6" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" From 6f6cda00990ab2b4db32c271424fda06a3be549e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 1 Dec 2024 13:04:06 +0000 Subject: [PATCH 312/635] v0.10.2 --- CHANGELOG.md | 10 ++++++++++ Cargo.toml | 2 +- README.md | 4 ++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78dfbf3c..8ad002e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## v0.10.2 (Dec 1st, 2024) + +- Switch proc-macro-error to proc-macro-error2 (#493) +- Do not allow Lua to run GC finalizers on ref thread (#491) +- Fix chunks loading in Luau when memory limit is enforced (#488) +- Added `String::wrap` method to wrap arbitrary `AsRef<[u8]>` into `impl IntoLua` +- Better FreeBSD/OpenBSD support (thanks to cos) +- Delay "any" userdata metatable creation until first instance is created (#482) +- Reduce amount of generated code for `UserData` (less generics) + ## v0.10.1 (Nov 9th, 2024) - Minimal Luau updated to 0.650 diff --git a/Cargo.toml b/Cargo.toml index 78bd565d..ad49f56f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.10.1" # remember to update mlua_derive +version = "0.10.2" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.79.0" edition = "2021" diff --git a/README.md b/README.md index 1c68d9a8..c05426e3 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ Add to `Cargo.toml` : ``` toml [dependencies] -mlua = { version = "0.10.1", features = ["lua54", "vendored"] } +mlua = { version = "0.10.2", features = ["lua54", "vendored"] } ``` `main.rs` @@ -168,7 +168,7 @@ Add to `Cargo.toml` : crate-type = ["cdylib"] [dependencies] -mlua = { version = "0.10.1", features = ["lua54", "module"] } +mlua = { version = "0.10.2", features = ["lua54", "module"] } ``` `lib.rs` : From cacd3dc70fbed8e789596a85226e8aa31155a479 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 4 Dec 2024 10:40:49 +0000 Subject: [PATCH 313/635] Add `Table::set_safeenv` method (Luau) --- src/table.rs | 18 ++++++++++++++++++ tests/luau.rs | 14 ++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/table.rs b/src/table.rs index eb12e964..3f100cad 100644 --- a/src/table.rs +++ b/src/table.rs @@ -581,6 +581,24 @@ impl Table { unsafe { ffi::lua_getreadonly(ref_thread, self.0.index) != 0 } } + /// Controls `safeenv` attribute on the table. + /// + /// This a special flag that activates some performance optimizations for environment tables. + /// In particular, it controls: + /// - Optimization of import resolution (cache values of constant keys). + /// - Fast-path for built-in iteration with pairs/ipairs. + /// - Fast-path for some built-in functions (fastcall). + /// + /// For `safeenv` environments, monkey patching or modifying values may not work as expected. + /// + /// Requires `feature = "luau"` + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn set_safeenv(&self, enabled: bool) { + let lua = self.0.lua.lock(); + unsafe { ffi::lua_setsafeenv(lua.ref_thread(), self.0.index, enabled as _) }; + } + /// Converts this table to a generic C pointer. /// /// Different tables will give different pointers. diff --git a/tests/luau.rs b/tests/luau.rs index b45404c9..c3bc8a63 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -282,6 +282,20 @@ fn test_sandbox() -> Result<()> { Ok(()) } +#[test] +fn test_sandbox_safeenv() -> Result<()> { + let lua = Lua::new(); + + lua.sandbox(true)?; + lua.globals().set("state", lua.create_table()?)?; + lua.globals().set_safeenv(false); + lua.load("state.a = 123").exec()?; + let a: i32 = lua.load("state.a = 321; return state.a").eval()?; + assert_eq!(a, 321); + + Ok(()) +} + #[test] fn test_sandbox_nolibs() -> Result<()> { let lua = Lua::new_with(StdLib::NONE, LuaOptions::default()).unwrap(); From 91e069a77ea13e095ee97cd06d57d669dc0b876e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 7 Dec 2024 00:04:12 +0000 Subject: [PATCH 314/635] Optimize (and simplify) protected mode for Rust function calls --- src/state/extra.rs | 6 +++-- src/state/util.rs | 58 ++++++++++++++++++---------------------------- 2 files changed, 27 insertions(+), 37 deletions(-) diff --git a/src/state/extra.rs b/src/state/extra.rs index 19ff747d..d1823b5c 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -27,7 +27,7 @@ use super::{Lua, WeakLua}; // Unique key to store `ExtraData` in the registry static EXTRA_REGISTRY_KEY: u8 = 0; -const WRAPPED_FAILURE_POOL_SIZE: usize = 64; +const WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY: usize = 64; const REF_STACK_RESERVE: c_int = 1; /// Data associated with the Lua state. @@ -60,6 +60,7 @@ pub(crate) struct ExtraData { // Pool of `WrappedFailure` enums in the ref thread (as userdata) pub(super) wrapped_failure_pool: Vec, + pub(super) wrapped_failure_top: usize, // Pool of `Thread`s (coroutines) for async execution #[cfg(feature = "async")] pub(super) thread_pool: Vec, @@ -160,7 +161,8 @@ impl ExtraData { ref_stack_size: ffi::LUA_MINSTACK - REF_STACK_RESERVE, ref_stack_top: ffi::lua_gettop(ref_thread), ref_free: Vec::new(), - wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_SIZE), + wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY), + wrapped_failure_top: 0, #[cfg(feature = "async")] thread_pool: Vec::new(), wrapped_failure_mt_ptr, diff --git a/src/state/util.rs b/src/state/util.rs index ba6339b1..ec701eaf 100644 --- a/src/state/util.rs +++ b/src/state/util.rs @@ -7,8 +7,6 @@ use crate::error::{Error, Result}; use crate::state::{ExtraData, RawLua}; use crate::util::{self, get_internal_metatable, WrappedFailure}; -const WRAPPED_FAILURE_POOL_SIZE: usize = 64; - pub(super) struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State); impl<'a> StateGuard<'a> { @@ -42,26 +40,27 @@ where enum PreallocatedFailure { New(*mut WrappedFailure), - Existing(i32), + Reserved, } impl PreallocatedFailure { unsafe fn reserve(state: *mut ffi::lua_State, extra: *mut ExtraData) -> Self { - match (*extra).wrapped_failure_pool.pop() { - Some(index) => PreallocatedFailure::Existing(index), - None => { - // We need to check stack for Luau in case when callback is called from interrupt - // See https://github.com/Roblox/luau/issues/446 and mlua #142 and #153 - #[cfg(feature = "luau")] - ffi::lua_rawcheckstack(state, 2); - // Place it to the beginning of the stack - let ud = WrappedFailure::new_userdata(state); - ffi::lua_insert(state, 1); - PreallocatedFailure::New(ud) - } + if (*extra).wrapped_failure_top > 0 { + (*extra).wrapped_failure_top -= 1; + return PreallocatedFailure::Reserved; } + + // We need to check stack for Luau in case when callback is called from interrupt + // See https://github.com/Roblox/luau/issues/446 and mlua #142 and #153 + #[cfg(feature = "luau")] + ffi::lua_rawcheckstack(state, 2); + // Place it to the beginning of the stack + let ud = WrappedFailure::new_userdata(state); + ffi::lua_insert(state, 1); + PreallocatedFailure::New(ud) } + #[cold] unsafe fn r#use(&self, state: *mut ffi::lua_State, extra: *mut ExtraData) -> *mut WrappedFailure { let ref_thread = (*extra).ref_thread; match *self { @@ -69,12 +68,12 @@ where ffi::lua_settop(state, 1); ud } - PreallocatedFailure::Existing(index) => { + PreallocatedFailure::Reserved => { + let index = (*extra).wrapped_failure_pool.pop().unwrap(); ffi::lua_settop(state, 0); #[cfg(feature = "luau")] ffi::lua_rawcheckstack(state, 2); - ffi::lua_pushvalue(ref_thread, index); - ffi::lua_xmove(ref_thread, state, 1); + ffi::lua_xpush(ref_thread, state, index); ffi::lua_pushnil(ref_thread); ffi::lua_replace(ref_thread, index); (*extra).ref_free.push(index); @@ -87,24 +86,13 @@ where let ref_thread = (*extra).ref_thread; match self { PreallocatedFailure::New(_) => { - if (*extra).wrapped_failure_pool.len() < WRAPPED_FAILURE_POOL_SIZE { - ffi::lua_rotate(state, 1, -1); - ffi::lua_xmove(state, ref_thread, 1); - let index = ref_stack_pop(extra); - (*extra).wrapped_failure_pool.push(index); - } else { - ffi::lua_remove(state, 1); - } - } - PreallocatedFailure::Existing(index) => { - if (*extra).wrapped_failure_pool.len() < WRAPPED_FAILURE_POOL_SIZE { - (*extra).wrapped_failure_pool.push(index); - } else { - ffi::lua_pushnil(ref_thread); - ffi::lua_replace(ref_thread, index); - (*extra).ref_free.push(index); - } + ffi::lua_rotate(state, 1, -1); + ffi::lua_xmove(state, ref_thread, 1); + let index = ref_stack_pop(extra); + (*extra).wrapped_failure_pool.push(index); + (*extra).wrapped_failure_top += 1; } + PreallocatedFailure::Reserved => (*extra).wrapped_failure_top += 1, } } } From cd4091f64dc003e1236610d34b06cf300b4eaff5 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 10 Dec 2024 23:35:39 +0000 Subject: [PATCH 315/635] Allow exhaustive match on `Value`. It was not possible because `ValueRef` variant in `Value::Other` was private. Closes #502 and #503 --- src/types/value_ref.rs | 2 +- src/value.rs | 3 +-- tests/value.rs | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/types/value_ref.rs b/src/types/value_ref.rs index 89a60ec4..89bac543 100644 --- a/src/types/value_ref.rs +++ b/src/types/value_ref.rs @@ -4,7 +4,7 @@ use std::os::raw::{c_int, c_void}; use crate::state::{RawLua, WeakLua}; /// A reference to a Lua (complex) value stored in the Lua auxiliary thread. -pub(crate) struct ValueRef { +pub struct ValueRef { pub(crate) lua: WeakLua, pub(crate) index: c_int, pub(crate) drop: bool, diff --git a/src/value.rs b/src/value.rs index 6c81ee55..9292261b 100644 --- a/src/value.rs +++ b/src/value.rs @@ -67,8 +67,7 @@ pub enum Value { /// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned. Error(Box), /// Any other value not known to mlua (eg. LuaJIT CData). - #[allow(private_interfaces)] - Other(ValueRef), + Other(#[doc(hidden)] ValueRef), } pub use self::Value::Nil; diff --git a/tests/value.rs b/tests/value.rs index 98fc222f..4185442e 100644 --- a/tests/value.rs +++ b/tests/value.rs @@ -296,3 +296,25 @@ fn test_value_conversions() -> Result<()> { Ok(()) } + +#[test] +fn test_value_exhaustive_match() { + match Value::Nil { + Value::Nil => {} + Value::Boolean(_) => {} + Value::LightUserData(_) => {} + Value::Integer(_) => {} + Value::Number(_) => {} + #[cfg(feature = "luau")] + Value::Vector(_) => {} + Value::String(_) => {} + Value::Table(_) => {} + Value::Function(_) => {} + Value::Thread(_) => {} + Value::UserData(_) => {} + #[cfg(feature = "luau")] + Value::Buffer(_) => {} + Value::Error(_) => {} + Value::Other(_) => {} + } +} From b5d38ab2e3092f89d7c05cea360fb2cc7b563aee Mon Sep 17 00:00:00 2001 From: Radiant <69520693+RadiantUwU@users.noreply.github.com> Date: Mon, 20 Jan 2025 01:52:58 +0200 Subject: [PATCH 316/635] Set Default for LuaValue to be nil. (#512) --- src/value.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/value.rs b/src/value.rs index 9292261b..bd088887 100644 --- a/src/value.rs +++ b/src/value.rs @@ -569,6 +569,12 @@ impl Value { } } +impl Default for Value { + fn default() -> Self { + Self::Nil + } +} + impl fmt::Debug for Value { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { if fmt.alternate() { From cc57bed4c84e970188057de782dd91885cdc9d33 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 27 Jan 2025 14:19:40 +0000 Subject: [PATCH 317/635] Update Luau to 0.657 --- mlua-sys/Cargo.toml | 2 +- mlua-sys/src/luau/luacode.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index dc3168c4..9461a44f 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -40,7 +40,7 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 547.0.0, < 547.1.0", optional = true } luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } -luau0-src = { version = "0.11.1", optional = true } +luau0-src = { version = "0.12.0", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/mlua-sys/src/luau/luacode.rs b/mlua-sys/src/luau/luacode.rs index 81ac22ed..366cb8cf 100644 --- a/mlua-sys/src/luau/luacode.rs +++ b/mlua-sys/src/luau/luacode.rs @@ -1,5 +1,6 @@ //! Contains definitions from `luacode.h`. +use std::marker::{PhantomData, PhantomPinned}; use std::os::raw::{c_char, c_int, c_void}; use std::{ptr, slice}; @@ -15,6 +16,10 @@ pub struct lua_CompileOptions { pub vectorType: *const c_char, pub mutableGlobals: *const *const c_char, pub userdataTypes: *const *const c_char, + pub librariesWithKnownMembers: *const *const c_char, + pub libraryMemberTypeCallback: Option, + pub libraryMemberConstantCallback: Option, + pub disabledBuiltins: *const *const c_char, } impl Default for lua_CompileOptions { @@ -29,10 +34,34 @@ impl Default for lua_CompileOptions { vectorType: ptr::null(), mutableGlobals: ptr::null(), userdataTypes: ptr::null(), + librariesWithKnownMembers: ptr::null(), + libraryMemberTypeCallback: None, + libraryMemberConstantCallback: None, + disabledBuiltins: ptr::null(), } } } +#[repr(C)] +pub struct lua_CompileConstant { + _data: [u8; 0], + _marker: PhantomData<(*mut u8, PhantomPinned)>, +} + +pub type lua_LibraryMemberTypeCallback = + extern "C" fn(library: *const c_char, member: *const c_char) -> c_int; + +pub type lua_LibraryMemberConstantCallback = + extern "C" fn(library: *const c_char, member: *const c_char, constant: *mut lua_CompileConstant); + +extern "C" { + fn luau_set_compile_constant_nil(constant: *mut lua_CompileConstant); + fn luau_set_compile_constant_boolean(constant: *mut lua_CompileConstant, b: c_int); + fn luau_set_compile_constant_number(constant: *mut lua_CompileConstant, n: f64); + fn luau_set_compile_constant_vector(constant: *mut lua_CompileConstant, x: f32, y: f32, z: f32, w: f32); + fn luau_set_compile_constant_string(constant: *mut lua_CompileConstant, s: *const c_char, l: usize); +} + extern "C-unwind" { #[link_name = "luau_compile"] pub fn luau_compile_( From aa3f6ba46ccfa3d818bfacd7c44efd92d48165b6 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 27 Jan 2025 21:19:31 +0000 Subject: [PATCH 318/635] Fix prototype of new Luau compiler options and methods --- mlua-sys/src/luau/luacode.rs | 38 ++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/mlua-sys/src/luau/luacode.rs b/mlua-sys/src/luau/luacode.rs index 366cb8cf..425f2d2d 100644 --- a/mlua-sys/src/luau/luacode.rs +++ b/mlua-sys/src/luau/luacode.rs @@ -48,18 +48,40 @@ pub struct lua_CompileConstant { _marker: PhantomData<(*mut u8, PhantomPinned)>, } +/// Type table tags +#[doc(hidden)] +#[repr(i32)] +#[non_exhaustive] +pub enum luau_BytecodeType { + Nil = 0, + Boolean, + Number, + String, + Table, + Function, + Thread, + UserData, + Vector, + Buffer, + + Any = 15, +} + pub type lua_LibraryMemberTypeCallback = - extern "C" fn(library: *const c_char, member: *const c_char) -> c_int; + unsafe extern "C-unwind" fn(library: *const c_char, member: *const c_char) -> c_int; -pub type lua_LibraryMemberConstantCallback = - extern "C" fn(library: *const c_char, member: *const c_char, constant: *mut lua_CompileConstant); +pub type lua_LibraryMemberConstantCallback = unsafe extern "C-unwind" fn( + library: *const c_char, + member: *const c_char, + constant: *mut lua_CompileConstant, +); extern "C" { - fn luau_set_compile_constant_nil(constant: *mut lua_CompileConstant); - fn luau_set_compile_constant_boolean(constant: *mut lua_CompileConstant, b: c_int); - fn luau_set_compile_constant_number(constant: *mut lua_CompileConstant, n: f64); - fn luau_set_compile_constant_vector(constant: *mut lua_CompileConstant, x: f32, y: f32, z: f32, w: f32); - fn luau_set_compile_constant_string(constant: *mut lua_CompileConstant, s: *const c_char, l: usize); + pub fn luau_set_compile_constant_nil(cons: *mut lua_CompileConstant); + pub fn luau_set_compile_constant_boolean(cons: *mut lua_CompileConstant, b: c_int); + pub fn luau_set_compile_constant_number(cons: *mut lua_CompileConstant, n: f64); + pub fn luau_set_compile_constant_vector(cons: *mut lua_CompileConstant, x: f32, y: f32, z: f32, w: f32); + pub fn luau_set_compile_constant_string(cons: *mut lua_CompileConstant, s: *const c_char, l: usize); } extern "C-unwind" { From d1cb2a9a96cc6bab20a1bf76a873d468e9846b73 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 27 Jan 2025 21:20:47 +0000 Subject: [PATCH 319/635] mlua-sys: v0.6.7 --- mlua-sys/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 9461a44f..f4171a15 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.6.6" +version = "0.6.7" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" From cb45db05fa9d23f6692670a06a6a41ca9fb1610e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 27 Jan 2025 21:31:36 +0000 Subject: [PATCH 320/635] Update README/CHANGELOG --- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ad002e2..a8230bda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v0.10.2 (Jan 27th, 2025) + +- Set `Default` for `Value` to be `Nil` +- Allow exhaustive match on `Value` (#502) +- Add `Table::set_safeenv` method (Luau) + ## v0.10.2 (Dec 1st, 2024) - Switch proc-macro-error to proc-macro-error2 (#493) diff --git a/README.md b/README.md index c05426e3..5f14a829 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ Add to `Cargo.toml` : ``` toml [dependencies] -mlua = { version = "0.10.2", features = ["lua54", "vendored"] } +mlua = { version = "0.10", features = ["lua54", "vendored"] } ``` `main.rs` @@ -168,7 +168,7 @@ Add to `Cargo.toml` : crate-type = ["cdylib"] [dependencies] -mlua = { version = "0.10.2", features = ["lua54", "module"] } +mlua = { version = "0.10", features = ["lua54", "module"] } ``` `lib.rs` : From 9caf3542d9b618ede7a4fb6ba7079cb5e9a3aee8 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 27 Jan 2025 21:33:51 +0000 Subject: [PATCH 321/635] v0.10.3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ad49f56f..efbb8d5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.10.2" # remember to update mlua_derive +version = "0.10.3" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.79.0" edition = "2021" From cf71edc4925d0c7586487ea0580ef51b9c2c2652 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 28 Jan 2025 10:07:41 +0000 Subject: [PATCH 322/635] Update README (set main branch for development) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5f14a829..21818286 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ [Benchmarks]: https://github.com/khvzak/script-bench-rs [FAQ]: FAQ.md +# The main branch is the development version of `mlua`. Please see the [v0.10](https://github.com/mlua-rs/mlua/tree/v0.10) branch for the stable versions of `mlua`. + > **Note** > > See v0.10 [release notes](https://github.com/khvzak/mlua/blob/main/docs/release_notes/v0.10.md). From ea5ecccf02c573a979ddb5d67e5fe0994aa9f993 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 11 Jan 2025 22:03:32 +0000 Subject: [PATCH 323/635] Change `AsyncThread` to `AsyncThread`. Push arguments in `Thread::into_async()` to the thread during the call instead of first poll. The pushed arguments will be automatically used on resume. Fixes #508 and relates to #500. --- src/function.rs | 6 +- src/thread.rs | 197 +++++++++++++++++++++++++++++------------------- tests/async.rs | 36 ++++++++- 3 files changed, 156 insertions(+), 83 deletions(-) diff --git a/src/function.rs b/src/function.rs index 37b33fa9..6ef9afae 100644 --- a/src/function.rs +++ b/src/function.rs @@ -161,10 +161,10 @@ impl Function { { let lua = self.0.lua.lock(); let thread_res = unsafe { - lua.create_recycled_thread(self).map(|th| { - let mut th = th.into_async(args); + lua.create_recycled_thread(self).and_then(|th| { + let mut th = th.into_async(args)?; th.set_recyclable(true); - th + Ok(th) }) }; async move { thread_res?.await } diff --git a/src/thread.rs b/src/thread.rs index aaec30a7..5c3b42a2 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -42,6 +42,33 @@ pub enum ThreadStatus { Error, } +/// Internal representation of a Lua thread status. +/// +/// The number in `New` and `Yielded` variants is the number of arguments pushed +/// to the thread stack. +#[derive(Clone, Copy)] +enum ThreadStatusInner { + New(c_int), + Running, + Yielded(c_int), + Finished, + Error, +} + +impl ThreadStatusInner { + #[cfg(feature = "async")] + #[inline(always)] + fn is_resumable(self) -> bool { + matches!(self, ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_)) + } + + #[cfg(feature = "async")] + #[inline(always)] + fn is_yielded(self) -> bool { + matches!(self, ThreadStatusInner::Yielded(_)) + } +} + /// Handle to an internal Lua thread (coroutine). #[derive(Clone)] pub struct Thread(pub(crate) ValueRef, pub(crate) *mut ffi::lua_State); @@ -60,9 +87,8 @@ unsafe impl Sync for Thread {} #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct AsyncThread { +pub struct AsyncThread { thread: Thread, - init_args: Option, ret: PhantomData, recycle: bool, } @@ -122,9 +148,10 @@ impl Thread { R: FromLuaMulti, { let lua = self.0.lua.lock(); - if self.status_inner(&lua) != ThreadStatus::Resumable { - return Err(Error::CoroutineUnresumable); - } + let mut pushed_nargs = match self.status_inner(&lua) { + ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs, + _ => return Err(Error::CoroutineUnresumable), + }; let state = lua.state(); let thread_state = self.state(); @@ -132,7 +159,14 @@ impl Thread { let _sg = StackGuard::new(state); let _thread_sg = StackGuard::with_top(thread_state, 0); - let nresults = self.resume_inner(&lua, args)?; + let nargs = args.push_into_stack_multi(&lua)?; + if nargs > 0 { + check_stack(thread_state, nargs)?; + ffi::lua_xmove(state, thread_state, nargs); + pushed_nargs += nargs; + } + + let (_, nresults) = self.resume_inner(&lua, pushed_nargs)?; check_stack(state, nresults + 1)?; ffi::lua_xmove(thread_state, state, nresults); @@ -143,50 +177,50 @@ impl Thread { /// Resumes execution of this thread. /// /// It's similar to `resume()` but leaves `nresults` values on the thread stack. - unsafe fn resume_inner(&self, lua: &RawLua, args: impl IntoLuaMulti) -> Result { + unsafe fn resume_inner(&self, lua: &RawLua, nargs: c_int) -> Result<(ThreadStatusInner, c_int)> { let state = lua.state(); let thread_state = self.state(); - - let nargs = args.push_into_stack_multi(lua)?; - if nargs > 0 { - check_stack(thread_state, nargs)?; - ffi::lua_xmove(state, thread_state, nargs); - } - let mut nresults = 0; let ret = ffi::lua_resume(thread_state, state, nargs, &mut nresults as *mut c_int); - if ret != ffi::LUA_OK && ret != ffi::LUA_YIELD { - if ret == ffi::LUA_ERRMEM { + match ret { + ffi::LUA_OK => Ok((ThreadStatusInner::Finished, nresults)), + ffi::LUA_YIELD => Ok((ThreadStatusInner::Yielded(0), nresults)), + ffi::LUA_ERRMEM => { // Don't call error handler for memory errors - return Err(pop_error(thread_state, ret)); + Err(pop_error(thread_state, ret)) + } + _ => { + check_stack(state, 3)?; + protect_lua!(state, 0, 1, |state| error_traceback_thread(state, thread_state))?; + Err(pop_error(state, ret)) } - check_stack(state, 3)?; - protect_lua!(state, 0, 1, |state| error_traceback_thread(state, thread_state))?; - return Err(pop_error(state, ret)); } - - Ok(nresults) } /// Gets the status of the thread. pub fn status(&self) -> ThreadStatus { - self.status_inner(&self.0.lua.lock()) + match self.status_inner(&self.0.lua.lock()) { + ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_) => ThreadStatus::Resumable, + ThreadStatusInner::Running => ThreadStatus::Running, + ThreadStatusInner::Finished => ThreadStatus::Finished, + ThreadStatusInner::Error => ThreadStatus::Error, + } } /// Gets the status of the thread (internal implementation). - pub(crate) fn status_inner(&self, lua: &RawLua) -> ThreadStatus { + fn status_inner(&self, lua: &RawLua) -> ThreadStatusInner { let thread_state = self.state(); if thread_state == lua.state() { // The thread is currently running - return ThreadStatus::Running; + return ThreadStatusInner::Running; } let status = unsafe { ffi::lua_status(thread_state) }; - if status != ffi::LUA_OK && status != ffi::LUA_YIELD { - ThreadStatus::Error - } else if status == ffi::LUA_YIELD || unsafe { ffi::lua_gettop(thread_state) > 0 } { - ThreadStatus::Resumable - } else { - ThreadStatus::Finished + let top = unsafe { ffi::lua_gettop(thread_state) }; + match status { + ffi::LUA_YIELD => ThreadStatusInner::Yielded(top), + ffi::LUA_OK if top > 0 => ThreadStatusInner::New(top - 1), + ffi::LUA_OK => ThreadStatusInner::Finished, + _ => ThreadStatusInner::Error, } } @@ -224,7 +258,7 @@ impl Thread { #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "luau"))))] pub fn reset(&self, func: crate::function::Function) -> Result<()> { let lua = self.0.lua.lock(); - if self.status_inner(&lua) == ThreadStatus::Running { + if matches!(self.status_inner(&lua), ThreadStatusInner::Running) { return Err(Error::runtime("cannot reset a running thread")); } @@ -257,7 +291,9 @@ impl Thread { /// Converts [`Thread`] to an [`AsyncThread`] which implements [`Future`] and [`Stream`] traits. /// - /// `args` are passed as arguments to the thread function for first call. + /// Only resumable threads can be converted to [`AsyncThread`]. + /// + /// `args` are pushed to the thread stack and will be used when the thread is resumed. /// The object calls [`resume`] while polling and also allow to run Rust futures /// to completion using an executor. /// @@ -290,7 +326,7 @@ impl Thread { /// end) /// "#).eval()?; /// - /// let mut stream = thread.into_async::(1); + /// let mut stream = thread.into_async::(1)?; /// let mut sum = 0; /// while let Some(n) = stream.try_next().await? { /// sum += n; @@ -303,15 +339,31 @@ impl Thread { /// ``` #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub fn into_async(self, args: impl IntoLuaMulti) -> AsyncThread + pub fn into_async(self, args: impl IntoLuaMulti) -> Result> where R: FromLuaMulti, { - AsyncThread { - thread: self, - init_args: Some(args), - ret: PhantomData, - recycle: false, + let lua = self.0.lua.lock(); + if !self.status_inner(&lua).is_resumable() { + return Err(Error::CoroutineUnresumable); + } + + let state = lua.state(); + let thread_state = self.state(); + unsafe { + let _sg = StackGuard::new(state); + + let nargs = args.push_into_stack_multi(&lua)?; + if nargs > 0 { + check_stack(thread_state, nargs)?; + ffi::lua_xmove(state, thread_state, nargs); + } + + Ok(AsyncThread { + thread: self, + ret: PhantomData, + recycle: false, + }) } } @@ -392,7 +444,7 @@ impl LuaType for Thread { } #[cfg(feature = "async")] -impl AsyncThread { +impl AsyncThread { #[inline] pub(crate) fn set_recyclable(&mut self, recyclable: bool) { self.recycle = recyclable; @@ -401,7 +453,7 @@ impl AsyncThread { #[cfg(feature = "async")] #[cfg(any(feature = "lua54", feature = "luau"))] -impl Drop for AsyncThread { +impl Drop for AsyncThread { fn drop(&mut self) { if self.recycle { if let Some(lua) = self.thread.0.lua.try_lock() { @@ -409,7 +461,7 @@ impl Drop for AsyncThread { // For Lua 5.4 this also closes all pending to-be-closed variables if !lua.recycle_thread(&mut self.thread) { #[cfg(feature = "lua54")] - if self.thread.status_inner(&lua) == ThreadStatus::Error { + if matches!(self.thread.status_inner(&lua), ThreadStatusInner::Error) { #[cfg(not(feature = "vendored"))] ffi::lua_resetthread(self.thread.state()); #[cfg(feature = "vendored")] @@ -423,14 +475,15 @@ impl Drop for AsyncThread { } #[cfg(feature = "async")] -impl Stream for AsyncThread { +impl Stream for AsyncThread { type Item = Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let lua = self.thread.0.lua.lock(); - if self.thread.status_inner(&lua) != ThreadStatus::Resumable { - return Poll::Ready(None); - } + let nargs = match self.thread.status_inner(&lua) { + ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs, + _ => return Poll::Ready(None), + }; let state = lua.state(); let thread_state = self.thread.state(); @@ -439,36 +492,34 @@ impl Stream for AsyncThread { let _thread_sg = StackGuard::with_top(thread_state, 0); let _wg = WakerGuard::new(&lua, cx.waker()); - // This is safe as we are not moving the whole struct - let this = self.get_unchecked_mut(); - let nresults = if let Some(args) = this.init_args.take() { - this.thread.resume_inner(&lua, args)? - } else { - this.thread.resume_inner(&lua, ())? - }; + let (status, nresults) = (self.thread).resume_inner(&lua, nargs)?; - if nresults == 1 && is_poll_pending(thread_state) { - return Poll::Pending; + if status.is_yielded() { + if nresults == 1 && is_poll_pending(thread_state) { + return Poll::Pending; + } + // Continue polling + cx.waker().wake_by_ref(); } check_stack(state, nresults + 1)?; ffi::lua_xmove(thread_state, state, nresults); - cx.waker().wake_by_ref(); Poll::Ready(Some(R::from_stack_multi(nresults, &lua))) } } } #[cfg(feature = "async")] -impl Future for AsyncThread { +impl Future for AsyncThread { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let lua = self.thread.0.lua.lock(); - if self.thread.status_inner(&lua) != ThreadStatus::Resumable { - return Poll::Ready(Err(Error::CoroutineUnresumable)); - } + let nargs = match self.thread.status_inner(&lua) { + ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs, + _ => return Poll::Ready(Err(Error::CoroutineUnresumable)), + }; let state = lua.state(); let thread_state = self.thread.state(); @@ -477,21 +528,13 @@ impl Future for AsyncThread { let _thread_sg = StackGuard::with_top(thread_state, 0); let _wg = WakerGuard::new(&lua, cx.waker()); - // This is safe as we are not moving the whole struct - let this = self.get_unchecked_mut(); - let nresults = if let Some(args) = this.init_args.take() { - this.thread.resume_inner(&lua, args)? - } else { - this.thread.resume_inner(&lua, ())? - }; - - if nresults == 1 && is_poll_pending(thread_state) { - return Poll::Pending; - } + let (status, nresults) = self.thread.resume_inner(&lua, nargs)?; - if ffi::lua_status(thread_state) == ffi::LUA_YIELD { - // Ignore value returned via yield() - cx.waker().wake_by_ref(); + if status.is_yielded() { + if !(nresults == 1 && is_poll_pending(thread_state)) { + // Ignore value returned via yield() + cx.waker().wake_by_ref(); + } return Poll::Pending; } @@ -545,7 +588,7 @@ mod assertions { #[cfg(feature = "send")] static_assertions::assert_impl_all!(Thread: Send, Sync); #[cfg(all(feature = "async", not(feature = "send")))] - static_assertions::assert_not_impl_any!(AsyncThread<(), ()>: Send); + static_assertions::assert_not_impl_any!(AsyncThread<()>: Send); #[cfg(all(feature = "async", feature = "send"))] - static_assertions::assert_impl_all!(AsyncThread<(), ()>: Send, Sync); + static_assertions::assert_impl_all!(AsyncThread<()>: Send, Sync); } diff --git a/tests/async.rs b/tests/async.rs index 4ce1bdd6..2b538724 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -273,7 +273,7 @@ async fn test_async_lua54_to_be_closed() -> Result<()> { // Don't close by default when awaiting async threads let co = lua.create_thread(f.clone())?; - let _ = co.clone().into_async::<()>(()).await; + let _ = co.clone().into_async::<()>(())?.await; assert_eq!(globals.get::("close_count")?, 1); let _ = co.reset(f); assert_eq!(globals.get::("close_count")?, 2); @@ -300,7 +300,7 @@ async fn test_async_thread_stream() -> Result<()> { .eval()?, )?; - let mut stream = thread.into_async::(1); + let mut stream = thread.into_async::(1)?; let mut sum = 0; while let Some(n) = stream.try_next().await? { sum += n; @@ -325,7 +325,7 @@ async fn test_async_thread() -> Result<()> { } })?; - let res: String = lua.create_thread(f)?.into_async(()).await?; + let res: String = lua.create_thread(f)?.into_async(())?.await?; assert_eq!(res, "done"); @@ -567,3 +567,33 @@ async fn test_async_terminate() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn test_async_task() -> Result<()> { + let lua = Lua::new(); + + let delay = lua.create_function(|lua, (secs, f, args): (f32, Function, MultiValue)| { + let thread = lua.create_thread(f)?; + let thread2 = thread.clone().into_async::<()>(args)?; + tokio::task::spawn_local(async move { + tokio::time::sleep(Duration::from_secs_f32(secs)).await; + _ = thread2.await; + }); + Ok(thread) + })?; + + lua.globals().set("delay", delay)?; + let local = tokio::task::LocalSet::new(); + local + .run_until(async { + _ = lua + .load("delay(0.1, function(msg) global_msg = msg end, 'done')") + .exec_async() + .await; + }) + .await; + local.await; + assert_eq!(lua.globals().get::("global_msg")?, "done"); + + Ok(()) +} From d376cb94030a62c8d74be76b6f19255394fca499 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 12 Jan 2025 00:04:56 +0000 Subject: [PATCH 324/635] Enable `Thread::reset()` for all Lua versions --- src/state.rs | 3 --- src/state/raw.rs | 47 +++++++++++++++++++++++---------- src/thread.rs | 67 ++++++++++++++++++++++-------------------------- tests/thread.rs | 10 +++----- 4 files changed, 66 insertions(+), 61 deletions(-) diff --git a/src/state.rs b/src/state.rs index 35d9e4ec..c2f7005e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -98,9 +98,6 @@ pub struct LuaOptions { /// Max size of thread (coroutine) object pool used to execute asynchronous functions. /// - /// It works on Lua 5.4 and Luau, where [`lua_resetthread`] function - /// is available and allows to reuse old coroutines after resetting their state. - /// /// Default: **0** (disabled) /// /// [`lua_resetthread`]: https://www.lua.org/manual/5.4/manual.html#lua_resetthread diff --git a/src/state/raw.rs b/src/state/raw.rs index 0731f846..3f4fa006 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -505,7 +505,6 @@ impl RawLua { /// Wraps a Lua function into a new or recycled thread (coroutine). #[cfg(feature = "async")] pub(crate) unsafe fn create_recycled_thread(&self, func: &Function) -> Result { - #[cfg(any(feature = "lua54", feature = "luau"))] if let Some(index) = (*self.extra.get()).thread_pool.pop() { let thread_state = ffi::lua_tothread(self.ref_thread(), index); ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index); @@ -525,27 +524,47 @@ impl RawLua { /// Resets thread (coroutine) and returns it to the pool for later use. #[cfg(feature = "async")] - #[cfg(any(feature = "lua54", feature = "luau"))] - pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) -> bool { + pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) { + let thread_state = thread.1; let extra = &mut *self.extra.get(); - if extra.thread_pool.len() < extra.thread_pool.capacity() { - let thread_state = ffi::lua_tothread(extra.ref_thread, thread.0.index); - #[cfg(all(feature = "lua54", not(feature = "vendored")))] - let status = ffi::lua_resetthread(thread_state); - #[cfg(all(feature = "lua54", feature = "vendored"))] - let status = ffi::lua_closethread(thread_state, self.state()); + if extra.thread_pool.len() == extra.thread_pool.capacity() { #[cfg(feature = "lua54")] - if status != ffi::LUA_OK { - // Error object is on top, drop it + if ffi::lua_status(thread_state) != ffi::LUA_OK { + // Close all to-be-closed variables without returning thread to the pool + #[cfg(not(feature = "vendored"))] + ffi::lua_resetthread(thread_state); + #[cfg(feature = "vendored")] + ffi::lua_closethread(thread_state, self.state()); + } + return; + } + + let mut reset_ok = false; + if ffi::lua_status(thread_state) == ffi::LUA_OK { + if ffi::lua_gettop(thread_state) > 0 { ffi::lua_settop(thread_state, 0); } - #[cfg(feature = "luau")] + reset_ok = true; + } + + #[cfg(feature = "lua54")] + if !reset_ok { + #[cfg(not(feature = "vendored"))] + let status = ffi::lua_resetthread(thread_state); + #[cfg(feature = "vendored")] + let status = ffi::lua_closethread(thread_state, self.state()); + reset_ok = status == ffi::LUA_OK; + } + #[cfg(feature = "luau")] + if !reset_ok { ffi::lua_resetthread(thread_state); + reset_ok = true; + } + + if reset_ok { extra.thread_pool.push(thread.0.index); thread.0.drop = false; // Prevent thread from being garbage collected - return true; } - false } /// Pushes a value that implements `IntoLua` onto the Lua stack. diff --git a/src/thread.rs b/src/thread.rs index 5c3b42a2..14e93bb4 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -2,8 +2,7 @@ use std::fmt; use std::os::raw::{c_int, c_void}; use crate::error::{Error, Result}; -#[allow(unused)] -use crate::state::Lua; +use crate::function::Function; use crate::state::RawLua; use crate::traits::{FromLuaMulti, IntoLuaMulti}; use crate::types::{LuaType, ValueRef}; @@ -232,7 +231,7 @@ impl Thread { #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub fn set_hook(&self, triggers: HookTriggers, callback: F) where - F: Fn(&Lua, Debug) -> Result + MaybeSend + 'static, + F: Fn(&crate::Lua, Debug) -> Result + MaybeSend + 'static, { let lua = self.0.lua.lock(); unsafe { @@ -249,32 +248,37 @@ impl Thread { /// In Luau: resets to the initial state of a newly created Lua thread. /// Lua threads in arbitrary states (like yielded or errored) can be reset properly. /// - /// Sets a Lua function for the thread afterwards. + /// Other Lua versions can reset only new or finished threads. /// - /// Requires `feature = "lua54"` OR `feature = "luau"`. + /// Sets a Lua function for the thread afterwards. /// /// [Lua 5.4]: https://www.lua.org/manual/5.4/manual.html#lua_closethread - #[cfg(any(feature = "lua54", feature = "luau"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "luau"))))] - pub fn reset(&self, func: crate::function::Function) -> Result<()> { + pub fn reset(&self, func: Function) -> Result<()> { let lua = self.0.lua.lock(); - if matches!(self.status_inner(&lua), ThreadStatusInner::Running) { - return Err(Error::runtime("cannot reset a running thread")); + let thread_state = self.state(); + match self.status_inner(&lua) { + ThreadStatusInner::Running => return Err(Error::runtime("cannot reset a running thread")), + // Any Lua can reuse new or finished thread + ThreadStatusInner::New(_) => unsafe { ffi::lua_settop(thread_state, 0) }, + ThreadStatusInner::Finished => {} + #[cfg(not(any(feature = "lua54", feature = "luau")))] + _ => return Err(Error::runtime("cannot reset non-finished thread")), + #[cfg(any(feature = "lua54", feature = "luau"))] + _ => unsafe { + #[cfg(all(feature = "lua54", not(feature = "vendored")))] + let status = ffi::lua_resetthread(thread_state); + #[cfg(all(feature = "lua54", feature = "vendored"))] + let status = ffi::lua_closethread(thread_state, lua.state()); + #[cfg(feature = "lua54")] + if status != ffi::LUA_OK { + return Err(pop_error(thread_state, status)); + } + #[cfg(feature = "luau")] + ffi::lua_resetthread(thread_state); + }, } - let thread_state = self.state(); unsafe { - #[cfg(all(feature = "lua54", not(feature = "vendored")))] - let status = ffi::lua_resetthread(thread_state); - #[cfg(all(feature = "lua54", feature = "vendored"))] - let status = ffi::lua_closethread(thread_state, lua.state()); - #[cfg(feature = "lua54")] - if status != ffi::LUA_OK { - return Err(pop_error(thread_state, status)); - } - #[cfg(feature = "luau")] - ffi::lua_resetthread(thread_state); - // Push function to the top of the thread stack ffi::lua_xpush(lua.ref_thread(), thread_state, func.0.index); @@ -445,30 +449,19 @@ impl LuaType for Thread { #[cfg(feature = "async")] impl AsyncThread { - #[inline] + #[inline(always)] pub(crate) fn set_recyclable(&mut self, recyclable: bool) { self.recycle = recyclable; } } #[cfg(feature = "async")] -#[cfg(any(feature = "lua54", feature = "luau"))] impl Drop for AsyncThread { fn drop(&mut self) { if self.recycle { if let Some(lua) = self.thread.0.lua.try_lock() { - unsafe { - // For Lua 5.4 this also closes all pending to-be-closed variables - if !lua.recycle_thread(&mut self.thread) { - #[cfg(feature = "lua54")] - if matches!(self.thread.status_inner(&lua), ThreadStatusInner::Error) { - #[cfg(not(feature = "vendored"))] - ffi::lua_resetthread(self.thread.state()); - #[cfg(feature = "vendored")] - ffi::lua_closethread(self.thread.state(), lua.state()); - } - } - } + // For Lua 5.4 this also closes all pending to-be-closed variables + unsafe { lua.recycle_thread(&mut self.thread) }; } } } @@ -549,7 +542,7 @@ impl Future for AsyncThread { #[cfg(feature = "async")] #[inline(always)] unsafe fn is_poll_pending(state: *mut ffi::lua_State) -> bool { - ffi::lua_tolightuserdata(state, -1) == Lua::poll_pending().0 + ffi::lua_tolightuserdata(state, -1) == crate::Lua::poll_pending().0 } #[cfg(feature = "async")] diff --git a/tests/thread.rs b/tests/thread.rs index 7ece2b56..74f75614 100644 --- a/tests/thread.rs +++ b/tests/thread.rs @@ -107,7 +107,6 @@ fn test_thread() -> Result<()> { } #[test] -#[cfg(any(feature = "lua54", feature = "luau"))] fn test_thread_reset() -> Result<()> { use mlua::{AnyUserData, UserData}; use std::sync::Arc; @@ -120,7 +119,8 @@ fn test_thread_reset() -> Result<()> { let arc = Arc::new(()); let func: Function = lua.load(r#"function(ud) coroutine.yield(ud) end"#).eval()?; - let thread = lua.create_thread(func.clone())?; + let thread = lua.create_thread(lua.load("return 0").into_function()?)?; // Dummy function first + assert!(thread.reset(func.clone()).is_ok()); for _ in 0..2 { assert_eq!(thread.status(), ThreadStatus::Resumable); @@ -145,11 +145,7 @@ fn test_thread_reset() -> Result<()> { assert!(thread.reset(func.clone()).is_err()); // Reset behavior has changed in Lua v5.4.4 // It's became possible to force reset thread by popping error object - assert!(matches!( - thread.status(), - ThreadStatus::Finished | ThreadStatus::Error - )); - // Would pass in 5.4.4 + assert!(matches!(thread.status(), ThreadStatus::Finished)); assert!(thread.reset(func.clone()).is_ok()); assert_eq!(thread.status(), ThreadStatus::Resumable); } From 47bc372096f942349ff2cf781d6f7c377b275e06 Mon Sep 17 00:00:00 2001 From: tk <49250442+tkr-sh@users.noreply.github.com> Date: Tue, 28 Jan 2025 10:10:28 +0000 Subject: [PATCH 325/635] `impl FromLua/IntoLua` for `char` (#516) --- src/conversion.rs | 65 +++++++++++++++++++++++++++++++++++++++++++++ tests/conversion.rs | 26 ++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/src/conversion.rs b/src/conversion.rs index ad0a7003..e24d6c86 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -655,6 +655,71 @@ impl IntoLua for &Path { } } +impl IntoLua for char { + #[inline] + fn into_lua(self, lua: &Lua) -> Result { + let mut char_bytes = [0; 4]; + self.encode_utf8(&mut char_bytes); + Ok(Value::String(lua.create_string(char_bytes)?)) + } +} + +impl FromLua for char { + #[inline] + fn from_lua(value: Value, _lua: &Lua) -> Result { + let ty = value.type_name(); + match value { + // When integer: reduce it to u8 and try to convert to char + Value::Integer(i) => { + if i <= u8::MAX.into() && i >= 0 { + Ok(char::from(i as u8)) + } else { + Err(Error::FromLuaConversionError { + from: ty, + to: Self::type_name(), + message: Some( + "expected int to be a u8 when converting to char. You can also use strings." + .to_string(), + ), + }) + } + } + // When String: first char, and only if there is one char + Value::String(s) => { + let str = s.to_str()?; + let mut str_iter = str.chars(); + let Some(char) = str_iter.next() else { + return Err(Error::FromLuaConversionError { + from: ty, + to: Self::type_name(), + message: Some( + "string must have one char when converting to char. (empty string)".to_string(), + ), + }); + }; + + if let Some(_extra_char) = str_iter.next() { + Err(Error::FromLuaConversionError { + from: ty, + to: Self::type_name(), + message: Some( + "expected lua string to have exactly one char when converting to char" + .to_string(), + ), + }) + } else { + Ok(char) + } + } + _ => Err(Error::FromLuaConversionError { + from: ty, + to: Self::type_name(), + message: Some("expected string or integer".to_string()), + }), + } + } +} + #[inline] unsafe fn push_bytes_into_stack(this: T, lua: &RawLua) -> Result<()> where diff --git a/tests/conversion.rs b/tests/conversion.rs index 2cb9b880..e479c6bd 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -657,3 +657,29 @@ fn test_either_from_lua() -> Result<()> { Ok(()) } + +#[test] +fn test_char_into_lua() -> Result<()> { + let lua = Lua::new(); + + let v = '🦀'; + let v2 = v.into_lua(&lua)?; + assert_eq!(Some(v.to_string()), v2.as_string_lossy()); + + Ok(()) +} + +#[test] +fn test_char_from_lua() -> Result<()> { + let lua = Lua::new(); + + let f = lua.create_function(|_, s: mlua::String| Ok(s))?; + let s = f.call::("A")?; + assert_eq!(s, 'A'); + + let f = lua.create_function(|_, s: mlua::Integer| Ok(s))?; + let s = f.call::(65)?; + assert_eq!(s, 'A'); + + Ok(()) +} From 8574682ffc7337cfb8f1e59c877dc197a25e1949 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 28 Jan 2025 10:41:10 +0000 Subject: [PATCH 326/635] Improve From/Into Lua char conversion --- src/conversion.rs | 44 ++++++++++++-------------------------------- tests/conversion.rs | 19 ++++++++++++------- 2 files changed, 24 insertions(+), 39 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index e24d6c86..c63fd369 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -660,55 +660,35 @@ impl IntoLua for char { fn into_lua(self, lua: &Lua) -> Result { let mut char_bytes = [0; 4]; self.encode_utf8(&mut char_bytes); - Ok(Value::String(lua.create_string(char_bytes)?)) + Ok(Value::String(lua.create_string(&char_bytes[..self.len_utf8()])?)) } } impl FromLua for char { - #[inline] fn from_lua(value: Value, _lua: &Lua) -> Result { let ty = value.type_name(); match value { - // When integer: reduce it to u8 and try to convert to char Value::Integer(i) => { - if i <= u8::MAX.into() && i >= 0 { - Ok(char::from(i as u8)) - } else { - Err(Error::FromLuaConversionError { + cast(i) + .and_then(char::from_u32) + .ok_or_else(|| Error::FromLuaConversionError { from: ty, - to: Self::type_name(), - message: Some( - "expected int to be a u8 when converting to char. You can also use strings." - .to_string(), - ), + to: "char".to_string(), + message: Some("integer out of range when converting to char".to_string()), }) - } } - // When String: first char, and only if there is one char Value::String(s) => { let str = s.to_str()?; let mut str_iter = str.chars(); - let Some(char) = str_iter.next() else { - return Err(Error::FromLuaConversionError { + match (str_iter.next(), str_iter.next()) { + (Some(char), None) => Ok(char), + _ => Err(Error::FromLuaConversionError { from: ty, - to: Self::type_name(), + to: "char".to_string(), message: Some( - "string must have one char when converting to char. (empty string)".to_string(), + "expected string to have exactly one char when converting to char".to_string(), ), - }); - }; - - if let Some(_extra_char) = str_iter.next() { - Err(Error::FromLuaConversionError { - from: ty, - to: Self::type_name(), - message: Some( - "expected lua string to have exactly one char when converting to char" - .to_string(), - ), - }) - } else { - Ok(char) + }), } } _ => Err(Error::FromLuaConversionError { diff --git a/tests/conversion.rs b/tests/conversion.rs index e479c6bd..3987ba42 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -673,13 +673,18 @@ fn test_char_into_lua() -> Result<()> { fn test_char_from_lua() -> Result<()> { let lua = Lua::new(); - let f = lua.create_function(|_, s: mlua::String| Ok(s))?; - let s = f.call::("A")?; - assert_eq!(s, 'A'); - - let f = lua.create_function(|_, s: mlua::Integer| Ok(s))?; - let s = f.call::(65)?; - assert_eq!(s, 'A'); + assert_eq!(lua.convert::("A")?, 'A'); + assert_eq!(lua.convert::(65)?, 'A'); + assert_eq!(lua.convert::(128175)?, '💯'); + assert!(lua + .convert::(5456324) + .is_err_and(|e| e.to_string().contains("integer out of range"))); + assert!(lua + .convert::("hello") + .is_err_and(|e| e.to_string().contains("expected string to have exactly one char"))); + assert!(lua + .convert::(HashMap::::new()) + .is_err_and(|e| e.to_string().contains("expected string or integer"))); Ok(()) } From 58965c6255fb95a770e6295fa496bca05e23e5e4 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 28 Jan 2025 18:17:29 +0000 Subject: [PATCH 327/635] Move lifetime from `AsChunk<'a>` to `AsChunk::source` --- mlua_derive/src/lib.rs | 4 +- src/chunk.rs | 92 +++++++++++++++++++++++------------------- src/state.rs | 4 +- 3 files changed, 55 insertions(+), 45 deletions(-) diff --git a/mlua_derive/src/lib.rs b/mlua_derive/src/lib.rs index af54bedf..54815cfa 100644 --- a/mlua_derive/src/lib.rs +++ b/mlua_derive/src/lib.rs @@ -103,7 +103,7 @@ pub fn chunk(input: TokenStream) -> TokenStream { struct InnerChunk Result
>(Cell>); - impl AsChunk<'static> for InnerChunk + impl AsChunk for InnerChunk where F: FnOnce(&Lua) -> Result
, { @@ -120,7 +120,7 @@ pub fn chunk(input: TokenStream) -> TokenStream { Some(ChunkMode::Text) } - fn source(self) -> IoResult> { + fn source<'a>(self) -> IoResult> { Ok(Cow::Borrowed((#source).as_bytes())) } } diff --git a/src/chunk.rs b/src/chunk.rs index 089f4b82..41a096ef 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -2,7 +2,6 @@ use std::borrow::Cow; use std::collections::HashMap; use std::ffi::CString; use std::io::Result as IoResult; -use std::marker::PhantomData; use std::panic::Location; use std::path::{Path, PathBuf}; use std::string::String as StdString; @@ -17,7 +16,7 @@ use crate::value::Value; /// Trait for types [loadable by Lua] and convertible to a [`Chunk`] /// /// [loadable by Lua]: https://www.lua.org/manual/5.4/manual.html#3.3.2 -pub trait AsChunk<'a> { +pub trait AsChunk { /// Returns optional chunk name /// /// See [`Chunk::set_name`] for possible name prefixes. @@ -39,61 +38,75 @@ pub trait AsChunk<'a> { } /// Returns chunk data (can be text or binary) - fn source(self) -> IoResult>; + fn source<'a>(self) -> IoResult> + where + Self: 'a; } -impl<'a> AsChunk<'a> for &'a str { - fn source(self) -> IoResult> { +impl AsChunk for &str { + fn source<'a>(self) -> IoResult> + where + Self: 'a, + { Ok(Cow::Borrowed(self.as_ref())) } } -impl AsChunk<'static> for StdString { - fn source(self) -> IoResult> { +impl AsChunk for StdString { + fn source<'a>(self) -> IoResult> { Ok(Cow::Owned(self.into_bytes())) } } -impl<'a> AsChunk<'a> for &'a StdString { - fn source(self) -> IoResult> { +impl AsChunk for &StdString { + fn source<'a>(self) -> IoResult> + where + Self: 'a, + { Ok(Cow::Borrowed(self.as_bytes())) } } -impl<'a> AsChunk<'a> for &'a [u8] { - fn source(self) -> IoResult> { +impl AsChunk for &[u8] { + fn source<'a>(self) -> IoResult> + where + Self: 'a, + { Ok(Cow::Borrowed(self)) } } -impl AsChunk<'static> for Vec { - fn source(self) -> IoResult> { +impl AsChunk for Vec { + fn source<'a>(self) -> IoResult> { Ok(Cow::Owned(self)) } } -impl<'a> AsChunk<'a> for &'a Vec { - fn source(self) -> IoResult> { - Ok(Cow::Borrowed(self.as_ref())) +impl AsChunk for &Vec { + fn source<'a>(self) -> IoResult> + where + Self: 'a, + { + Ok(Cow::Borrowed(self)) } } -impl AsChunk<'static> for &Path { +impl AsChunk for &Path { fn name(&self) -> Option { Some(format!("@{}", self.display())) } - fn source(self) -> IoResult> { + fn source<'a>(self) -> IoResult> { std::fs::read(self).map(Cow::Owned) } } -impl AsChunk<'static> for PathBuf { +impl AsChunk for PathBuf { fn name(&self) -> Option { Some(format!("@{}", self.display())) } - fn source(self) -> IoResult> { + fn source<'a>(self) -> IoResult> { std::fs::read(self).map(Cow::Owned) } } @@ -506,10 +519,10 @@ impl Chunk<'_> { if self.detect_mode() == ChunkMode::Binary { let lua = self.lua.lock(); if let Some(mut cache) = lua.app_data_mut_unguarded::() { - cache.0.insert(text_source, binary_source.as_ref().to_vec()); + cache.0.insert(text_source, binary_source.to_vec()); } else { let mut cache = ChunksCache(HashMap::new()); - cache.0.insert(text_source, binary_source.as_ref().to_vec()); + cache.0.insert(text_source, binary_source.to_vec()); let _ = lua.try_set_app_data(cache); }; } @@ -543,21 +556,20 @@ impl Chunk<'_> { } fn detect_mode(&self) -> ChunkMode { - match (self.mode, &self.source) { - (Some(mode), _) => mode, - (None, Ok(source)) => { - #[cfg(not(feature = "luau"))] - if source.starts_with(ffi::LUA_SIGNATURE) { - return ChunkMode::Binary; - } - #[cfg(feature = "luau")] - if *source.first().unwrap_or(&u8::MAX) < b'\n' { - return ChunkMode::Binary; - } - ChunkMode::Text + if let Some(mode) = self.mode { + return mode; + } + if let Ok(source) = &self.source { + #[cfg(not(feature = "luau"))] + if source.starts_with(ffi::LUA_SIGNATURE) { + return ChunkMode::Binary; + } + #[cfg(feature = "luau")] + if *source.first().unwrap_or(&u8::MAX) < b'\n' { + return ChunkMode::Binary; } - (None, Err(_)) => ChunkMode::Text, // any value is fine } + ChunkMode::Text } fn convert_name(name: String) -> Result { @@ -572,29 +584,27 @@ impl Chunk<'_> { } } -struct WrappedChunk<'a, T: AsChunk<'a>> { +struct WrappedChunk { chunk: T, caller: &'static Location<'static>, - _marker: PhantomData<&'a T>, } -impl<'a> Chunk<'a> { +impl Chunk<'_> { /// Wraps a chunk of Lua code, returning an opaque type that implements [`IntoLua`] trait. /// /// The resulted `IntoLua` implementation will convert the chunk into a Lua function without /// executing it. #[doc(hidden)] #[track_caller] - pub fn wrap(chunk: impl AsChunk<'a> + 'a) -> impl IntoLua + 'a { + pub fn wrap(chunk: impl AsChunk) -> impl IntoLua { WrappedChunk { chunk, caller: Location::caller(), - _marker: PhantomData, } } } -impl<'a, T: AsChunk<'a>> IntoLua for WrappedChunk<'a, T> { +impl IntoLua for WrappedChunk { fn into_lua(self, lua: &Lua) -> Result { lua.load_with_location(self.chunk, self.caller) .into_function() diff --git a/src/state.rs b/src/state.rs index c2f7005e..45f2ce76 100644 --- a/src/state.rs +++ b/src/state.rs @@ -999,13 +999,13 @@ impl Lua { /// /// [`Chunk::exec`]: crate::Chunk::exec #[track_caller] - pub fn load<'a>(&self, chunk: impl AsChunk<'a>) -> Chunk<'a> { + pub fn load<'a>(&self, chunk: impl AsChunk + 'a) -> Chunk<'a> { self.load_with_location(chunk, Location::caller()) } pub(crate) fn load_with_location<'a>( &self, - chunk: impl AsChunk<'a>, + chunk: impl AsChunk + 'a, location: &'static Location<'static>, ) -> Chunk<'a> { Chunk { From 21d39a069d56bf30145a50429c39f320fd8ed599 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 28 Jan 2025 18:23:18 +0000 Subject: [PATCH 328/635] Pass `&Scope` instead of `&mut Scope` to `Lua::scope` closure. It was a leftover from an experimental implementation that has been removed. --- src/state.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/state.rs b/src/state.rs index 45f2ce76..a0d6560e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1477,10 +1477,10 @@ impl Lua { /// lifetimes only outlive the scope lifetime. pub fn scope<'env, R>( &self, - f: impl for<'scope> FnOnce(&'scope mut Scope<'scope, 'env>) -> Result, + f: impl for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> Result, ) -> Result { // TODO: Update to `&Scope` in next major release - f(&mut Scope::new(self.lock_arc())) + f(&Scope::new(self.lock_arc())) } /// Attempts to coerce a Lua value into a String in a manner consistent with Lua's internal From cdd6a99136b90d028bf842865136060bd46ce335 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 28 Jan 2025 22:55:45 +0000 Subject: [PATCH 329/635] Support `Thread::resume_error` call (Luau) This method uses Luau-specific C API extension `lua_resumerror` that allow to throw an error immediately when resuming a thead. Closes #500 #513 --- mlua-sys/src/luau/compat.rs | 15 +++++++++++++++ src/thread.rs | 36 ++++++++++++++++++++++++++++++++++++ tests/thread.rs | 25 +++++++++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index 3d189cec..b2f33c70 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -10,6 +10,8 @@ use super::lauxlib::*; use super::lua::*; use super::luacode::*; +pub const LUA_RESUMEERROR: c_int = -1; + unsafe fn compat53_reverse(L: *mut lua_State, mut a: c_int, mut b: c_int) { while a < b { lua_pushvalue(L, a); @@ -284,6 +286,19 @@ pub unsafe fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int, n ret } +#[inline(always)] +pub unsafe fn lua_resumex(L: *mut lua_State, from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int { + let ret = if narg == LUA_RESUMEERROR { + lua_resumeerror(L, from) + } else { + lua_resume_(L, from, narg) + }; + if (ret == LUA_OK || ret == LUA_YIELD) && !(nres.is_null()) { + *nres = lua_gettop(L); + } + ret +} + // // lauxlib ported functions // diff --git a/src/thread.rs b/src/thread.rs index 14e93bb4..34077a8f 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -173,6 +173,39 @@ impl Thread { } } + /// Resumes execution of this thread, immediately raising an error. + /// + /// This is a Luau specific extension. + #[cfg(feature = "luau")] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn resume_error(&self, error: impl crate::IntoLua) -> Result + where + R: FromLuaMulti, + { + let lua = self.0.lua.lock(); + match self.status_inner(&lua) { + ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_) => {} + _ => return Err(Error::CoroutineUnresumable), + }; + + let state = lua.state(); + let thread_state = self.state(); + unsafe { + let _sg = StackGuard::new(state); + let _thread_sg = StackGuard::with_top(thread_state, 0); + + check_stack(state, 1)?; + error.push_into_stack(&lua)?; + ffi::lua_xmove(state, thread_state, 1); + + let (_, nresults) = self.resume_inner(&lua, ffi::LUA_RESUMEERROR)?; + check_stack(state, nresults + 1)?; + ffi::lua_xmove(thread_state, state, nresults); + + R::from_stack_multi(nresults, &lua) + } + } + /// Resumes execution of this thread. /// /// It's similar to `resume()` but leaves `nresults` values on the thread stack. @@ -180,7 +213,10 @@ impl Thread { let state = lua.state(); let thread_state = self.state(); let mut nresults = 0; + #[cfg(not(feature = "luau"))] let ret = ffi::lua_resume(thread_state, state, nargs, &mut nresults as *mut c_int); + #[cfg(feature = "luau")] + let ret = ffi::lua_resumex(thread_state, state, nargs, &mut nresults as *mut c_int); match ret { ffi::LUA_OK => Ok((ThreadStatusInner::Finished, nresults)), ffi::LUA_YIELD => Ok((ThreadStatusInner::Yielded(0), nresults)), diff --git a/tests/thread.rs b/tests/thread.rs index 74f75614..560dcd3f 100644 --- a/tests/thread.rs +++ b/tests/thread.rs @@ -227,3 +227,28 @@ fn test_thread_pointer() -> Result<()> { Ok(()) } + +#[test] +#[cfg(feature = "luau")] +fn test_thread_resume_error() -> Result<()> { + let lua = Lua::new(); + + let thread = lua + .load( + r#" + coroutine.create(function() + local ok, err = pcall(coroutine.yield, 123) + assert(not ok, "yield should fail") + assert(err == "myerror", "unexpected error: " .. tostring(err)) + return "success" + end) + "#, + ) + .eval::()?; + + assert_eq!(thread.resume::(())?, 123); + let status = thread.resume_error::("myerror").unwrap(); + assert_eq!(status, "success"); + + Ok(()) +} From fca38a6637bf17ce4037d495d25249c7a86c0b1f Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 30 Jan 2025 23:07:02 +0000 Subject: [PATCH 330/635] Imporove `BorrowedStr`/`BorrowedBytes` ergonomic. Implement `FromLua` and `IntoLua` for these types to allow working with them directly. --- src/conversion.rs | 92 ++++++++++++++++++++++++++++++++++++++++++- src/string.rs | 96 +++++++++++++++++++++++++++++---------------- tests/conversion.rs | 64 +++++++++++++++++++++++++++++- tests/string.rs | 14 +++++++ 4 files changed, 229 insertions(+), 37 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index c63fd369..2f647199 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -5,7 +5,7 @@ use std::hash::{BuildHasher, Hash}; use std::os::raw::c_int; use std::path::{Path, PathBuf}; use std::string::String as StdString; -use std::{slice, str}; +use std::{mem, slice, str}; use bstr::{BStr, BString, ByteSlice, ByteVec}; use num_traits::cast; @@ -13,7 +13,7 @@ use num_traits::cast; use crate::error::{Error, Result}; use crate::function::Function; use crate::state::{Lua, RawLua}; -use crate::string::String; +use crate::string::{BorrowedBytes, BorrowedStr, String}; use crate::table::Table; use crate::thread::Thread; use crate::traits::{FromLua, IntoLua, ShortTypeName as _}; @@ -91,6 +91,94 @@ impl FromLua for String { } } +impl IntoLua for BorrowedStr<'_> { + #[inline] + fn into_lua(self, _: &Lua) -> Result { + Ok(Value::String(self.borrow.into_owned())) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + lua.push_ref(&self.borrow.0); + Ok(()) + } +} + +impl IntoLua for &BorrowedStr<'_> { + #[inline] + fn into_lua(self, _: &Lua) -> Result { + Ok(Value::String(self.borrow.clone().into_owned())) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + lua.push_ref(&self.borrow.0); + Ok(()) + } +} + +impl FromLua for BorrowedStr<'_> { + fn from_lua(value: Value, lua: &Lua) -> Result { + let s = String::from_lua(value, lua)?; + let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?; + let buf = unsafe { mem::transmute::<&str, &'static str>(buf) }; + let borrow = Cow::Owned(s); + Ok(Self { buf, borrow, _lua }) + } + + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { + let s = String::from_stack(idx, lua)?; + let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?; + let buf = unsafe { mem::transmute::<&str, &'static str>(buf) }; + let borrow = Cow::Owned(s); + Ok(Self { buf, borrow, _lua }) + } +} + +impl IntoLua for BorrowedBytes<'_> { + #[inline] + fn into_lua(self, _: &Lua) -> Result { + Ok(Value::String(self.borrow.into_owned())) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + lua.push_ref(&self.borrow.0); + Ok(()) + } +} + +impl IntoLua for &BorrowedBytes<'_> { + #[inline] + fn into_lua(self, _: &Lua) -> Result { + Ok(Value::String(self.borrow.clone().into_owned())) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + lua.push_ref(&self.borrow.0); + Ok(()) + } +} + +impl FromLua for BorrowedBytes<'_> { + fn from_lua(value: Value, lua: &Lua) -> Result { + let s = String::from_lua(value, lua)?; + let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s); + let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) }; + let borrow = Cow::Owned(s); + Ok(Self { buf, borrow, _lua }) + } + + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { + let s = String::from_stack(idx, lua)?; + let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s); + let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) }; + let borrow = Cow::Owned(s); + Ok(Self { buf, borrow, _lua }) + } +} + impl IntoLua for Table { #[inline] fn into_lua(self, _: &Lua) -> Result { diff --git a/src/string.rs b/src/string.rs index 38fb8850..9c86102b 100644 --- a/src/string.rs +++ b/src/string.rs @@ -1,4 +1,4 @@ -use std::borrow::Borrow; +use std::borrow::{Borrow, Cow}; use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::os::raw::{c_int, c_void}; @@ -44,13 +44,7 @@ impl String { /// ``` #[inline] pub fn to_str(&self) -> Result { - let BorrowedBytes(bytes, guard) = self.as_bytes(); - let s = str::from_utf8(bytes).map_err(|e| Error::FromLuaConversionError { - from: "string", - to: "&str".to_string(), - message: Some(e.to_string()), - })?; - Ok(BorrowedStr(s, guard)) + BorrowedStr::try_from(self) } /// Converts this string to a [`StdString`]. @@ -109,19 +103,21 @@ impl String { /// ``` #[inline] pub fn as_bytes(&self) -> BorrowedBytes { - let (bytes, guard) = unsafe { self.to_slice() }; - BorrowedBytes(&bytes[..bytes.len() - 1], guard) + BorrowedBytes::from(self) } /// Get the bytes that make up this string, including the trailing nul byte. pub fn as_bytes_with_nul(&self) -> BorrowedBytes { - let (bytes, guard) = unsafe { self.to_slice() }; - BorrowedBytes(bytes, guard) + let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self); + // Include the trailing nul byte (it's always present but excluded by default) + let buf = unsafe { slice::from_raw_parts((*buf).as_ptr(), (*buf).len() + 1) }; + BorrowedBytes { buf, borrow, _lua } } + // Does not return the terminating nul byte unsafe fn to_slice(&self) -> (&[u8], Lua) { let lua = self.0.lua.upgrade(); - let slice = unsafe { + let slice = { let rawlua = lua.lock(); let ref_thread = rawlua.ref_thread(); @@ -134,7 +130,7 @@ impl String { // string type let mut size = 0; let data = ffi::lua_tolstring(ref_thread, self.0.index, &mut size); - slice::from_raw_parts(data as *const u8, size + 1) + slice::from_raw_parts(data as *const u8, size) }; (slice, lua) } @@ -238,40 +234,45 @@ impl fmt::Display for Display<'_> { } /// A borrowed string (`&str`) that holds a strong reference to the Lua state. -pub struct BorrowedStr<'a>(&'a str, #[allow(unused)] Lua); +pub struct BorrowedStr<'a> { + // `buf` points to a readonly memory managed by Lua + pub(crate) buf: &'a str, + pub(crate) borrow: Cow<'a, String>, + pub(crate) _lua: Lua, +} impl Deref for BorrowedStr<'_> { type Target = str; #[inline(always)] fn deref(&self) -> &str { - self.0 + self.buf } } impl Borrow for BorrowedStr<'_> { #[inline(always)] fn borrow(&self) -> &str { - self.0 + self.buf } } impl AsRef for BorrowedStr<'_> { #[inline(always)] fn as_ref(&self) -> &str { - self.0 + self.buf } } impl fmt::Display for BorrowedStr<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) + self.buf.fmt(f) } } impl fmt::Debug for BorrowedStr<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) + self.buf.fmt(f) } } @@ -280,7 +281,7 @@ where T: AsRef, { fn eq(&self, other: &T) -> bool { - self.0 == other.as_ref() + self.buf == other.as_ref() } } @@ -291,45 +292,65 @@ where T: AsRef, { fn partial_cmp(&self, other: &T) -> Option { - self.0.partial_cmp(other.as_ref()) + self.buf.partial_cmp(other.as_ref()) } } impl Ord for BorrowedStr<'_> { fn cmp(&self, other: &Self) -> cmp::Ordering { - self.0.cmp(other.0) + self.buf.cmp(other.buf) + } +} + +impl<'a> TryFrom<&'a String> for BorrowedStr<'a> { + type Error = Error; + + #[inline] + fn try_from(value: &'a String) -> Result { + let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(value); + let buf = str::from_utf8(buf).map_err(|e| Error::FromLuaConversionError { + from: "string", + to: "&str".to_string(), + message: Some(e.to_string()), + })?; + Ok(Self { buf, borrow, _lua }) } } /// A borrowed byte slice (`&[u8]`) that holds a strong reference to the Lua state. -pub struct BorrowedBytes<'a>(&'a [u8], #[allow(unused)] Lua); +pub struct BorrowedBytes<'a> { + // `buf` points to a readonly memory managed by Lua + pub(crate) buf: &'a [u8], + pub(crate) borrow: Cow<'a, String>, + pub(crate) _lua: Lua, +} impl Deref for BorrowedBytes<'_> { type Target = [u8]; #[inline(always)] fn deref(&self) -> &[u8] { - self.0 + self.buf } } impl Borrow<[u8]> for BorrowedBytes<'_> { #[inline(always)] fn borrow(&self) -> &[u8] { - self.0 + self.buf } } impl AsRef<[u8]> for BorrowedBytes<'_> { #[inline(always)] fn as_ref(&self) -> &[u8] { - self.0 + self.buf } } impl fmt::Debug for BorrowedBytes<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) + self.buf.fmt(f) } } @@ -338,7 +359,7 @@ where T: AsRef<[u8]>, { fn eq(&self, other: &T) -> bool { - self.0 == other.as_ref() + self.buf == other.as_ref() } } @@ -349,22 +370,31 @@ where T: AsRef<[u8]>, { fn partial_cmp(&self, other: &T) -> Option { - self.0.partial_cmp(other.as_ref()) + self.buf.partial_cmp(other.as_ref()) } } impl Ord for BorrowedBytes<'_> { fn cmp(&self, other: &Self) -> cmp::Ordering { - self.0.cmp(other.0) + self.buf.cmp(other.buf) } } -impl<'a> IntoIterator for BorrowedBytes<'a> { +impl<'a> IntoIterator for &'a BorrowedBytes<'_> { type Item = &'a u8; type IntoIter = slice::Iter<'a, u8>; fn into_iter(self) -> Self::IntoIter { - self.0.iter() + self.iter() + } +} + +impl<'a> From<&'a String> for BorrowedBytes<'a> { + #[inline] + fn from(value: &'a String) -> Self { + let (buf, _lua) = unsafe { value.to_slice() }; + let borrow = Cow::Borrowed(value); + Self { buf, borrow, _lua } } } diff --git a/tests/conversion.rs b/tests/conversion.rs index 3987ba42..e75a3a06 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -6,8 +6,8 @@ use std::path::PathBuf; use bstr::BString; use maplit::{btreemap, btreeset, hashmap, hashset}; use mlua::{ - AnyUserData, Either, Error, Function, IntoLua, Lua, RegistryKey, Result, Table, Thread, UserDataRef, - Value, + AnyUserData, BorrowedBytes, BorrowedStr, Either, Error, Function, IntoLua, Lua, RegistryKey, Result, + Table, Thread, UserDataRef, Value, }; #[test] @@ -60,6 +60,66 @@ fn test_string_from_lua() -> Result<()> { Ok(()) } +#[test] +fn test_borrowedstr_into_lua() -> Result<()> { + let lua = Lua::new(); + + // Direct conversion + let s = lua.create_string("hello, world!")?; + let bs = s.to_str()?; + let bs2 = (&bs).into_lua(&lua)?; + assert_eq!(bs2.as_string().unwrap(), "hello, world!"); + + // Push into stack + let table = lua.create_table()?; + table.set("bs", &bs)?; + assert_eq!(bs, table.get::("bs")?); + + Ok(()) +} + +#[test] +fn test_borrowedstr_from_lua() -> Result<()> { + let lua = Lua::new(); + + // From stack + let f = lua.create_function(|_, s: BorrowedStr| Ok(s))?; + let s = f.call::("hello, world!")?; + assert_eq!(s, "hello, world!"); + + Ok(()) +} + +#[test] +fn test_borrowedbytes_into_lua() -> Result<()> { + let lua = Lua::new(); + + // Direct conversion + let s = lua.create_string("hello, world!")?; + let bb = s.as_bytes(); + let bb2 = (&bb).into_lua(&lua)?; + assert_eq!(bb2.as_string().unwrap(), "hello, world!"); + + // Push into stack + let table = lua.create_table()?; + table.set("bb", &bb)?; + assert_eq!(bb, table.get::("bb")?.as_bytes()); + + Ok(()) +} + +#[test] +fn test_borrowedbytes_from_lua() -> Result<()> { + let lua = Lua::new(); + + // From stack + let f = lua.create_function(|_, s: BorrowedBytes| Ok(s))?; + let s = f.call::("hello, world!")?; + assert_eq!(s, "hello, world!"); + + Ok(()) +} + #[test] fn test_table_into_lua() -> Result<()> { let lua = Lua::new(); diff --git a/tests/string.rs b/tests/string.rs index 1f849df9..f6bdd995 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -143,3 +143,17 @@ fn test_string_wrap() -> Result<()> { Ok(()) } + +#[test] +fn test_bytes_into_iter() -> Result<()> { + let lua = Lua::new(); + + let s = lua.create_string("hello")?; + let bytes = s.as_bytes(); + + for (i, &b) in bytes.into_iter().enumerate() { + assert_eq!(b, s.as_bytes()[i]); + } + + Ok(()) +} From 6882e0434e1008b1420d4a209e1b589c99440407 Mon Sep 17 00:00:00 2001 From: Andrew Farkas <6060305+HactarCE@users.noreply.github.com> Date: Tue, 4 Feb 2025 02:33:33 -0500 Subject: [PATCH 331/635] Fix version number in changelog (#521) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8230bda..dbfa550a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## v0.10.2 (Jan 27th, 2025) +## v0.10.3 (Jan 27th, 2025) - Set `Default` for `Value` to be `Nil` - Allow exhaustive match on `Value` (#502) From 20f7ce097de995d253997141e34282276f13c826 Mon Sep 17 00:00:00 2001 From: Joel Natividad <1980690+jqnatividad@users.noreply.github.com> Date: Wed, 5 Feb 2025 11:16:20 -0500 Subject: [PATCH 332/635] Fix typos (#522) * fix various typos in the codebase * fix typos in CHANGELOG.md --- CHANGELOG.md | 4 ++-- src/state/util.rs | 2 +- tests/function.rs | 2 +- tests/scope.rs | 2 +- tests/tests.rs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbfa550a..df129889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -467,7 +467,7 @@ Breaking changes: - [**Breaking**] Removed `AnyUserData::has_metamethod()` - Added `Thread::reset()` for luajit/lua54 to recycle threads. It's possible to attach a new function to a thread (coroutine). -- Added `chunk!` macro support to load chunks of Lua code using the Rust tokenizer and optinally capturing Rust variables. +- Added `chunk!` macro support to load chunks of Lua code using the Rust tokenizer and optionally capturing Rust variables. - Improved error reporting (`Error`'s `__tostring` method formats full stacktraces). This is useful in the module mode. ## v0.6.0-beta.1 @@ -523,7 +523,7 @@ Breaking changes: - Lua 5.4 support with `MetaMethod::Close`. - `lua53` feature is disabled by default. Now preferred Lua version have to be chosen explicitly. -- Provide safety guaraness for Lua state, which means that potenially unsafe operations, like loading C modules (using `require` or `package.loadlib`) are disabled. Equalient for the previous `Lua::new()` function is `Lua::unsafe_new()`. +- Provide safety guarantees for Lua state, which means that potentially unsafe operations, like loading C modules (using `require` or `package.loadlib`) are disabled. Equivalent to the previous `Lua::new()` function is `Lua::unsafe_new()`. - New `send` feature to require `Send`. - New `module` feature, that disables linking to Lua Core Libraries. Required for modules. - Don't allow `'callback` outlive `'lua` in `Lua::create_function()` to fix [the unsoundness](tests/compile/static_callback_args.rs). diff --git a/src/state/util.rs b/src/state/util.rs index ec701eaf..9dbf3e85 100644 --- a/src/state/util.rs +++ b/src/state/util.rs @@ -23,7 +23,7 @@ impl Drop for StateGuard<'_> { } // An optimized version of `callback_error` that does not allocate `WrappedFailure` userdata -// and instead reuses unsed values from previous calls (or allocates new). +// and instead reuses unused values from previous calls (or allocates new). pub(super) unsafe fn callback_error_ext( state: *mut ffi::lua_State, mut extra: *mut ExtraData, diff --git a/tests/function.rs b/tests/function.rs index b8c10703..683eca9c 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -214,7 +214,7 @@ fn test_function_dump() -> Result<()> { #[cfg(feature = "luau")] #[test] -fn test_finction_coverage() -> Result<()> { +fn test_function_coverage() -> Result<()> { let lua = Lua::new(); lua.set_compiler(mlua::Compiler::default().set_coverage_level(1)); diff --git a/tests/scope.rs b/tests/scope.rs index 4113f385..696b65f4 100644 --- a/tests/scope.rs +++ b/tests/scope.rs @@ -318,7 +318,7 @@ fn test_scope_userdata_drop() -> Result<()> { let ud = lua.globals().get::("ud")?; match ud.borrow_scoped::(|_| Ok::<_, Error>(())) { - Ok(_) => panic!("succesfull borrow for destructed userdata"), + Ok(_) => panic!("successful borrow for destructed userdata"), Err(Error::UserDataDestructed) => {} Err(err) => panic!("improper borrow error for destructed userdata: {err:?}"), } diff --git a/tests/tests.rs b/tests/tests.rs index 89bece8a..ae413117 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -322,7 +322,7 @@ fn test_error() -> Result<()> { let return_string_error = globals.get::("return_string_error")?; assert!(return_string_error.call::(()).is_ok()); - match lua.load("if youre happy and you know it syntax error").exec() { + match lua.load("if you're happy and you know it syntax error").exec() { Err(Error::SyntaxError { incomplete_input: false, .. From 2b6172ef38eb9d84ff3a89f9871eabf84e7d11d9 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 5 Feb 2025 19:01:07 +0000 Subject: [PATCH 333/635] Fix tests --- tests/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.rs b/tests/tests.rs index ae413117..af236e0c 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -322,7 +322,7 @@ fn test_error() -> Result<()> { let return_string_error = globals.get::("return_string_error")?; assert!(return_string_error.call::(()).is_ok()); - match lua.load("if you're happy and you know it syntax error").exec() { + match lua.load("if you are happy and you know it syntax error").exec() { Err(Error::SyntaxError { incomplete_input: false, .. From a89800b94926d3da29408bb45e6dcf90b72472dd Mon Sep 17 00:00:00 2001 From: Joel Natividad <1980690+jqnatividad@users.noreply.github.com> Date: Wed, 5 Feb 2025 14:02:48 -0500 Subject: [PATCH 334/635] Typos config and ci (#523) * typos configuration file * typos CI --- .github/workflows/typos.yml | 17 +++++++++++++++++ _typos.toml | 6 ++++++ 2 files changed, 23 insertions(+) create mode 100644 .github/workflows/typos.yml create mode 100644 _typos.toml diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml new file mode 100644 index 00000000..1056ec10 --- /dev/null +++ b/.github/workflows/typos.yml @@ -0,0 +1,17 @@ +name: Typos Check +on: + pull_request: + workflow_dispatch: + +jobs: + run: + name: Spell Check with Typos + runs-on: ubuntu-latest + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v4 + + - name: Check spelling + uses: crate-ci/typos@master + with: + config: ./_typos.toml diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 00000000..8692cfc1 --- /dev/null +++ b/_typos.toml @@ -0,0 +1,6 @@ +[default] +extend-ignore-identifiers-re = ["catched", "2nd", "ser"] + +[default.extend-words] +thr = "thr" +aas = "aas" From 74b4601b4881ee848a3b8a2b1f8cefb15ed67121 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 7 Feb 2025 22:39:32 +0000 Subject: [PATCH 335/635] Rework Lua hooks: - Support global hooks inherited by new threads - Support thread hooks, where each thread can have its own hook This should also allow to enable hooks for async calls. Related to #489 #347 --- src/state.rs | 57 +++++++++++++------ src/state/extra.rs | 4 +- src/state/raw.rs | 135 +++++++++++++++++++++++++++++++++------------ src/thread.rs | 20 +++++-- src/types.rs | 17 ++++-- src/util/types.rs | 32 +++++------ tests/async.rs | 28 ++++++++++ tests/hooks.rs | 51 ++++++++++++++--- 8 files changed, 253 insertions(+), 91 deletions(-) diff --git a/src/state.rs b/src/state.rs index a0d6560e..021fffca 100644 --- a/src/state.rs +++ b/src/state.rs @@ -30,7 +30,7 @@ use crate::util::{ use crate::value::{Nil, Value}; #[cfg(not(feature = "luau"))] -use crate::hook::HookTriggers; +use crate::{hook::HookTriggers, types::HookKind}; #[cfg(any(feature = "luau", doc))] use crate::{buffer::Buffer, chunk::Compiler}; @@ -501,6 +501,26 @@ impl Lua { } } + /// Sets or replaces a global hook function that will periodically be called as Lua code + /// executes. + /// + /// All new threads created (by mlua) after this call will use the global hook function. + /// + /// For more information see [`Lua::set_hook`]. + #[cfg(not(feature = "luau"))] + #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] + pub fn set_global_hook(&self, triggers: HookTriggers, callback: F) -> Result<()> + where + F: Fn(&Lua, Debug) -> Result + MaybeSend + 'static, + { + let lua = self.lock(); + unsafe { + (*lua.extra.get()).hook_triggers = triggers; + (*lua.extra.get()).hook_callback = Some(Box::new(callback)); + lua.set_thread_hook(lua.state(), HookKind::Global) + } + } + /// Sets a hook function that will periodically be called as Lua code executes. /// /// When exactly the hook function is called depends on the contents of the `triggers` @@ -511,12 +531,10 @@ impl Lua { /// limited form of execution limits by setting [`HookTriggers.every_nth_instruction`] and /// erroring once an instruction limit has been reached. /// - /// This method sets a hook function for the current thread of this Lua instance. + /// This method sets a hook function for the *current* thread of this Lua instance. /// If you want to set a hook function for another thread (coroutine), use /// [`Thread::set_hook`] instead. /// - /// Please note you cannot have more than one hook function set at a time for this Lua instance. - /// /// # Example /// /// Shows each line number of code being executed by the Lua interpreter. @@ -541,33 +559,36 @@ impl Lua { /// [`HookTriggers.every_nth_instruction`]: crate::HookTriggers::every_nth_instruction #[cfg(not(feature = "luau"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] - pub fn set_hook(&self, triggers: HookTriggers, callback: F) + pub fn set_hook(&self, triggers: HookTriggers, callback: F) -> Result<()> where F: Fn(&Lua, Debug) -> Result + MaybeSend + 'static, { let lua = self.lock(); - unsafe { lua.set_thread_hook(lua.state(), triggers, callback) }; + unsafe { lua.set_thread_hook(lua.state(), HookKind::Thread(triggers, Box::new(callback))) } } - /// Removes any hook previously set by [`Lua::set_hook`] or [`Thread::set_hook`]. + /// Removes a global hook previously set by [`Lua::set_global_hook`]. /// /// This function has no effect if a hook was not previously set. #[cfg(not(feature = "luau"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] - pub fn remove_hook(&self) { + pub fn remove_global_hook(&self) { let lua = self.lock(); unsafe { - let state = lua.state(); - ffi::lua_sethook(state, None, 0, 0); - match lua.main_state { - Some(main_state) if state != main_state.as_ptr() => { - // If main_state is different from state, remove hook from it too - ffi::lua_sethook(main_state.as_ptr(), None, 0, 0); - } - _ => {} - }; (*lua.extra.get()).hook_callback = None; - (*lua.extra.get()).hook_thread = ptr::null_mut(); + (*lua.extra.get()).hook_triggers = HookTriggers::default(); + } + } + + /// Removes any hook from the current thread. + /// + /// This function has no effect if a hook was not previously set. + #[cfg(not(feature = "luau"))] + #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] + pub fn remove_hook(&self) { + let lua = self.lock(); + unsafe { + ffi::lua_sethook(lua.state(), None, 0, 0); } } diff --git a/src/state/extra.rs b/src/state/extra.rs index d1823b5c..697ef311 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -75,7 +75,7 @@ pub(crate) struct ExtraData { #[cfg(not(feature = "luau"))] pub(super) hook_callback: Option, #[cfg(not(feature = "luau"))] - pub(super) hook_thread: *mut ffi::lua_State, + pub(super) hook_triggers: crate::hook::HookTriggers, #[cfg(feature = "lua54")] pub(super) warn_callback: Option, #[cfg(feature = "luau")] @@ -171,7 +171,7 @@ impl ExtraData { #[cfg(not(feature = "luau"))] hook_callback: None, #[cfg(not(feature = "luau"))] - hook_thread: ptr::null_mut(), + hook_triggers: Default::default(), #[cfg(feature = "lua54")] warn_callback: None, #[cfg(feature = "luau")] diff --git a/src/state/raw.rs b/src/state/raw.rs index 3f4fa006..ff0e4a7e 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -37,7 +37,10 @@ use super::extra::ExtraData; use super::{Lua, LuaOptions, WeakLua}; #[cfg(not(feature = "luau"))] -use crate::hook::{Debug, HookTriggers}; +use crate::{ + hook::Debug, + types::{HookCallback, HookKind, VmState}, +}; #[cfg(feature = "async")] use { @@ -186,6 +189,8 @@ impl RawLua { init_internal_metatable::>>(state, None)?; init_internal_metatable::(state, None)?; init_internal_metatable::(state, None)?; + #[cfg(not(feature = "luau"))] + init_internal_metatable::(state, None)?; #[cfg(feature = "async")] { init_internal_metatable::(state, None)?; @@ -373,42 +378,22 @@ impl RawLua { status } - /// Sets a 'hook' function for a thread (coroutine). + /// Sets a hook for a thread (coroutine). #[cfg(not(feature = "luau"))] - pub(crate) unsafe fn set_thread_hook( + pub(crate) unsafe fn set_thread_hook( &self, - state: *mut ffi::lua_State, - triggers: HookTriggers, - callback: F, - ) where - F: Fn(&Lua, Debug) -> Result + MaybeSend + 'static, - { - use crate::types::VmState; - use std::rc::Rc; + thread_state: *mut ffi::lua_State, + hook: HookKind, + ) -> Result<()> { + // Key to store hooks in the registry + const HOOKS_KEY: *const c_char = cstr!("__mlua_hooks"); - unsafe extern "C-unwind" fn hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) { - let extra = ExtraData::get(state); - if (*extra).hook_thread != state { - // Hook was destined for a different thread, ignore - ffi::lua_sethook(state, None, 0, 0); - return; - } - let result = callback_error_ext(state, extra, move |extra, _| { - let hook_cb = (*extra).hook_callback.clone(); - let hook_cb = mlua_expect!(hook_cb, "no hook callback set in hook_proc"); - if Rc::strong_count(&hook_cb) > 2 { - return Ok(VmState::Continue); // Don't allow recursion - } - let rawlua = (*extra).raw_lua(); - let _guard = StateGuard::new(rawlua, state); - let debug = Debug::new(rawlua, ar); - hook_cb((*extra).lua(), debug) - }); - match result { + unsafe fn process_status(state: *mut ffi::lua_State, event: c_int, status: VmState) { + match status { VmState::Continue => {} VmState::Yield => { // Only count and line events can yield - if (*ar).event == ffi::LUA_HOOKCOUNT || (*ar).event == ffi::LUA_HOOKLINE { + if event == ffi::LUA_HOOKCOUNT || event == ffi::LUA_HOOKLINE { #[cfg(any(feature = "lua54", feature = "lua53"))] if ffi::lua_isyieldable(state) != 0 { ffi::lua_yield(state, 0); @@ -423,9 +408,86 @@ impl RawLua { } } - (*self.extra.get()).hook_callback = Some(Rc::new(callback)); - (*self.extra.get()).hook_thread = state; // Mark for what thread the hook is set - ffi::lua_sethook(state, Some(hook_proc), triggers.mask(), triggers.count()); + unsafe extern "C-unwind" fn global_hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) { + let status = callback_error_ext(state, ptr::null_mut(), move |extra, _| { + let rawlua = (*extra).raw_lua(); + let debug = Debug::new(rawlua, ar); + match (*extra).hook_callback.take() { + Some(hook_cb) => { + // Temporary obtain ownership of the hook callback + let result = hook_cb((*extra).lua(), debug); + (*extra).hook_callback = Some(hook_cb); + result + } + None => { + ffi::lua_sethook(state, None, 0, 0); + Ok(VmState::Continue) + } + } + }); + process_status(state, (*ar).event, status); + } + + unsafe extern "C-unwind" fn hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) { + ffi::luaL_checkstack(state, 3, ptr::null()); + ffi::lua_getfield(state, ffi::LUA_REGISTRYINDEX, HOOKS_KEY); + ffi::lua_pushthread(state); + if ffi::lua_rawget(state, -2) != ffi::LUA_TUSERDATA { + ffi::lua_pop(state, 2); + ffi::lua_sethook(state, None, 0, 0); + return; + } + + let status = callback_error_ext(state, ptr::null_mut(), |extra, _| { + let rawlua = (*extra).raw_lua(); + let debug = Debug::new(rawlua, ar); + match get_internal_userdata::(state, -1, ptr::null()).as_ref() { + Some(hook_cb) => hook_cb((*extra).lua(), debug), + None => { + ffi::lua_sethook(state, None, 0, 0); + Ok(VmState::Continue) + } + } + }); + process_status(state, (*ar).event, status) + } + + let (triggers, callback) = match hook { + HookKind::Global if (*self.extra.get()).hook_callback.is_none() => { + return Ok(()); + } + HookKind::Global => { + let triggers = (*self.extra.get()).hook_triggers; + let (mask, count) = (triggers.mask(), triggers.count()); + ffi::lua_sethook(thread_state, Some(global_hook_proc), mask, count); + return Ok(()); + } + HookKind::Thread(triggers, callback) => (triggers, callback), + }; + + // Hooks for threads stored in the registry (in a weak table) + let state = self.state(); + let _sg = StackGuard::new(state); + check_stack(state, 3)?; + protect_lua!(state, 0, 0, |state| { + if ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, HOOKS_KEY) == 0 { + // Table just created, initialize it + ffi::lua_pushliteral(state, "k"); + ffi::lua_setfield(state, -2, cstr!("__mode")); // hooktable.__mode = "k" + ffi::lua_pushvalue(state, -1); + ffi::lua_setmetatable(state, -2); // metatable(hooktable) = hooktable + } + + ffi::lua_pushthread(thread_state); + ffi::lua_xmove(thread_state, state, 1); // key (thread) + let callback: HookCallback = Box::new(callback); + let _ = push_internal_userdata(state, callback, false); // value (hook callback) + ffi::lua_rawset(state, -3); // hooktable[thread] = hook callback + })?; + + ffi::lua_sethook(thread_state, Some(hook_proc), triggers.mask(), triggers.count()); + + Ok(()) } /// See [`Lua::create_string`] @@ -497,6 +559,11 @@ impl RawLua { } else { protect_lua!(state, 0, 1, |state| ffi::lua_newthread(state))? }; + + // Inherit global hook if set + #[cfg(not(feature = "luau"))] + self.set_thread_hook(thread_state, HookKind::Global)?; + let thread = Thread(self.pop_ref(), thread_state); ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index); Ok(thread) diff --git a/src/thread.rs b/src/thread.rs index 34077a8f..2e0e3c20 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -11,7 +11,7 @@ use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard}; #[cfg(not(feature = "luau"))] use crate::{ hook::{Debug, HookTriggers}, - types::MaybeSend, + types::HookKind, }; #[cfg(feature = "async")] @@ -262,16 +262,26 @@ impl Thread { /// Sets a hook function that will periodically be called as Lua code executes. /// /// This function is similar or [`Lua::set_hook`] except that it sets for the thread. - /// To remove a hook call [`Lua::remove_hook`]. + /// You can have multiple hooks for different threads. + /// + /// To remove a hook call [`Thread::remove_hook`]. #[cfg(not(feature = "luau"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] - pub fn set_hook(&self, triggers: HookTriggers, callback: F) + pub fn set_hook(&self, triggers: HookTriggers, callback: F) -> Result<()> where - F: Fn(&crate::Lua, Debug) -> Result + MaybeSend + 'static, + F: Fn(&crate::Lua, Debug) -> Result + crate::MaybeSend + 'static, { let lua = self.0.lua.lock(); + unsafe { lua.set_thread_hook(self.state(), HookKind::Thread(triggers, Box::new(callback))) } + } + + /// Removes any hook function from this thread. + #[cfg(not(feature = "luau"))] + #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] + pub fn remove_hook(&self) { + let _lua = self.0.lua.lock(); unsafe { - lua.set_thread_hook(self.state(), triggers, callback); + ffi::lua_sethook(self.state(), None, 0, 0); } } diff --git a/src/types.rs b/src/types.rs index afeb239d..df868586 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,10 +1,9 @@ use std::cell::UnsafeCell; use std::os::raw::{c_int, c_void}; -use std::rc::Rc; use crate::error::Result; #[cfg(not(feature = "luau"))] -use crate::hook::Debug; +use crate::hook::{Debug, HookTriggers}; use crate::state::{ExtraData, Lua, RawLua}; // Re-export mutex wrappers @@ -73,17 +72,23 @@ pub enum VmState { Yield, } +#[cfg(not(feature = "luau"))] +pub(crate) enum HookKind { + Global, + Thread(HookTriggers, HookCallback), +} + #[cfg(all(feature = "send", not(feature = "luau")))] -pub(crate) type HookCallback = Rc Result + Send>; +pub(crate) type HookCallback = Box Result + Send>; #[cfg(all(not(feature = "send"), not(feature = "luau")))] -pub(crate) type HookCallback = Rc Result>; +pub(crate) type HookCallback = Box Result>; #[cfg(all(feature = "send", feature = "luau"))] -pub(crate) type InterruptCallback = Rc Result + Send>; +pub(crate) type InterruptCallback = std::rc::Rc Result + Send>; #[cfg(all(not(feature = "send"), feature = "luau"))] -pub(crate) type InterruptCallback = Rc Result>; +pub(crate) type InterruptCallback = std::rc::Rc Result>; #[cfg(all(feature = "send", feature = "lua54"))] pub(crate) type WarnCallback = Box Result<()> + Send>; diff --git a/src/util/types.rs b/src/util/types.rs index 829b7e9d..8bc9d8b2 100644 --- a/src/util/types.rs +++ b/src/util/types.rs @@ -10,73 +10,71 @@ pub(crate) trait TypeKey: Any { fn type_key() -> *const c_void; } -static STRING_TYPE_KEY: u8 = 0; - impl TypeKey for String { #[inline(always)] fn type_key() -> *const c_void { + static STRING_TYPE_KEY: u8 = 0; &STRING_TYPE_KEY as *const u8 as *const c_void } } -static CALLBACK_TYPE_KEY: u8 = 0; - impl TypeKey for Callback { #[inline(always)] fn type_key() -> *const c_void { + static CALLBACK_TYPE_KEY: u8 = 0; &CALLBACK_TYPE_KEY as *const u8 as *const c_void } } -static CALLBACK_UPVALUE_TYPE_KEY: u8 = 0; - impl TypeKey for CallbackUpvalue { #[inline(always)] fn type_key() -> *const c_void { + static CALLBACK_UPVALUE_TYPE_KEY: u8 = 0; &CALLBACK_UPVALUE_TYPE_KEY as *const u8 as *const c_void } } -#[cfg(feature = "async")] -static ASYNC_CALLBACK_TYPE_KEY: u8 = 0; +#[cfg(not(feature = "luau"))] +impl TypeKey for crate::types::HookCallback { + #[inline(always)] + fn type_key() -> *const c_void { + static HOOK_CALLBACK_TYPE_KEY: u8 = 0; + &HOOK_CALLBACK_TYPE_KEY as *const u8 as *const c_void + } +} #[cfg(feature = "async")] impl TypeKey for AsyncCallback { #[inline(always)] fn type_key() -> *const c_void { + static ASYNC_CALLBACK_TYPE_KEY: u8 = 0; &ASYNC_CALLBACK_TYPE_KEY as *const u8 as *const c_void } } -#[cfg(feature = "async")] -static ASYNC_CALLBACK_UPVALUE_TYPE_KEY: u8 = 0; - #[cfg(feature = "async")] impl TypeKey for AsyncCallbackUpvalue { #[inline(always)] fn type_key() -> *const c_void { + static ASYNC_CALLBACK_UPVALUE_TYPE_KEY: u8 = 0; &ASYNC_CALLBACK_UPVALUE_TYPE_KEY as *const u8 as *const c_void } } -#[cfg(feature = "async")] -static ASYNC_POLL_UPVALUE_TYPE_KEY: u8 = 0; - #[cfg(feature = "async")] impl TypeKey for AsyncPollUpvalue { #[inline(always)] fn type_key() -> *const c_void { + static ASYNC_POLL_UPVALUE_TYPE_KEY: u8 = 0; &ASYNC_POLL_UPVALUE_TYPE_KEY as *const u8 as *const c_void } } -#[cfg(feature = "async")] -static WAKER_TYPE_KEY: u8 = 0; - #[cfg(feature = "async")] impl TypeKey for Option { #[inline(always)] fn type_key() -> *const c_void { + static WAKER_TYPE_KEY: u8 = 0; &WAKER_TYPE_KEY as *const u8 as *const c_void } } diff --git a/tests/async.rs b/tests/async.rs index 2b538724..ce2bae6b 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -597,3 +597,31 @@ async fn test_async_task() -> Result<()> { Ok(()) } + +#[tokio::test] +#[cfg(not(feature = "luau"))] +async fn test_async_hook() -> Result<()> { + use std::sync::atomic::{AtomicBool, Ordering}; + + let lua = Lua::new(); + + static HOOK_CALLED: AtomicBool = AtomicBool::new(false); + lua.set_global_hook(mlua::HookTriggers::new().every_line(), move |_, _| { + if !HOOK_CALLED.swap(true, Ordering::Relaxed) { + Ok(mlua::VmState::Yield) + } else { + Ok(mlua::VmState::Continue) + } + })?; + + let sleep = lua.create_async_function(move |_lua, n: u64| async move { + sleep_ms(n).await; + Ok(()) + })?; + lua.globals().set("sleep", sleep)?; + + lua.load(r"sleep(100)").exec_async().await?; + assert!(HOOK_CALLED.load(Ordering::Relaxed)); + + Ok(()) +} diff --git a/tests/hooks.rs b/tests/hooks.rs index ddbfc37f..ab9a89ac 100644 --- a/tests/hooks.rs +++ b/tests/hooks.rs @@ -27,7 +27,7 @@ fn test_line_counts() -> Result<()> { assert_eq!(debug.event(), DebugEvent::Line); hook_output.lock().unwrap().push(debug.curr_line()); Ok(VmState::Continue) - }); + })?; lua.load( r#" local x = 2 + 3 @@ -62,7 +62,7 @@ fn test_function_calls() -> Result<()> { let name = names.name.map(|s| s.into_owned()); hook_output.lock().unwrap().push((name, source.what)); Ok(VmState::Continue) - }); + })?; lua.load( r#" @@ -101,7 +101,7 @@ fn test_error_within_hook() -> Result<()> { lua.set_hook(HookTriggers::EVERY_LINE, |_lua, _debug| { Err(Error::runtime("Something happened in there!")) - }); + })?; let err = lua.load("x = 1").exec().expect_err("panic didn't propagate"); @@ -135,7 +135,7 @@ fn test_limit_execution_instructions() -> Result<()> { Ok(VmState::Continue) } }, - ); + )?; lua.globals().set("x", Value::Integer(0))?; let _ = lua @@ -158,7 +158,7 @@ fn test_hook_removal() -> Result<()> { lua.set_hook(HookTriggers::new().every_nth_instruction(1), |_lua, _debug| { Err(Error::runtime("this hook should've been removed by this time")) - }); + })?; assert!(lua.load("local x = 1").exec().is_err()); lua.remove_hook(); @@ -205,10 +205,10 @@ fn test_hook_swap_within_hook() -> Result<()> { }); Ok(VmState::Continue) }) - }); + })?; Ok(VmState::Continue) }) - }); + })?; TL_LUA.with(|tl| { let tl = tl.borrow(); @@ -247,7 +247,7 @@ fn test_hook_threads() -> Result<()> { assert_eq!(debug.event(), DebugEvent::Line); hook_output.lock().unwrap().push(debug.curr_line()); Ok(VmState::Continue) - }); + })?; co.resume::<()>(())?; lua.remove_hook(); @@ -277,7 +277,7 @@ fn test_hook_yield() -> Result<()> { .into_function()?; let co = lua.create_thread(func)?; - co.set_hook(HookTriggers::EVERY_LINE, move |_lua, _debug| Ok(VmState::Yield)); + co.set_hook(HookTriggers::EVERY_LINE, move |_lua, _debug| Ok(VmState::Yield))?; #[cfg(any(feature = "lua54", feature = "lua53"))] { @@ -297,3 +297,36 @@ fn test_hook_yield() -> Result<()> { Ok(()) } + +#[test] +fn test_global_hook() -> Result<()> { + let lua = Lua::new(); + + let counter = Arc::new(AtomicI64::new(0)); + let hook_counter = counter.clone(); + lua.set_global_hook(HookTriggers::EVERY_LINE, move |_lua, debug| { + assert_eq!(debug.event(), DebugEvent::Line); + hook_counter.fetch_add(1, Ordering::Relaxed); + Ok(VmState::Continue) + })?; + + let thread = lua.create_thread( + lua.load( + r#" + local x = 2 + 3 + local y = x * 63 + coroutine.yield() + local z = string.len(x..", "..y) + "#, + ) + .into_function()?, + )?; + + thread.resume::<()>(()).unwrap(); + lua.remove_global_hook(); + thread.resume::<()>(()).unwrap(); + assert_eq!(thread.status(), ThreadStatus::Finished); + assert_eq!(counter.load(Ordering::Relaxed), 3); + + Ok(()) +} From 5bbd23ed1a9e23f5d74c303d6c7d7498e81467cd Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 8 Feb 2025 11:51:38 +0000 Subject: [PATCH 336/635] Fix hook tests --- src/state/raw.rs | 2 ++ tests/async.rs | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index ff0e4a7e..f5bf7280 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -411,6 +411,7 @@ impl RawLua { unsafe extern "C-unwind" fn global_hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) { let status = callback_error_ext(state, ptr::null_mut(), move |extra, _| { let rawlua = (*extra).raw_lua(); + let _guard = StateGuard::new(rawlua, state); let debug = Debug::new(rawlua, ar); match (*extra).hook_callback.take() { Some(hook_cb) => { @@ -440,6 +441,7 @@ impl RawLua { let status = callback_error_ext(state, ptr::null_mut(), |extra, _| { let rawlua = (*extra).raw_lua(); + let _guard = StateGuard::new(rawlua, state); let debug = Debug::new(rawlua, ar); match get_internal_userdata::(state, -1, ptr::null()).as_ref() { Some(hook_cb) => hook_cb((*extra).lua(), debug), diff --git a/tests/async.rs b/tests/async.rs index ce2bae6b..6ae3b3de 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -608,10 +608,10 @@ async fn test_async_hook() -> Result<()> { static HOOK_CALLED: AtomicBool = AtomicBool::new(false); lua.set_global_hook(mlua::HookTriggers::new().every_line(), move |_, _| { if !HOOK_CALLED.swap(true, Ordering::Relaxed) { - Ok(mlua::VmState::Yield) - } else { - Ok(mlua::VmState::Continue) + #[cfg(any(feature = "lu53", feature = "lua54"))] + return Ok(mlua::VmState::Yield); } + Ok(mlua::VmState::Continue) })?; let sleep = lua.create_async_function(move |_lua, n: u64| async move { From d1a587f49ac46477a58d647ee365debb3b81d300 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 8 Feb 2025 22:09:59 +0000 Subject: [PATCH 337/635] Wrap hooks in refcounter instead of box, same as previously. This allows to obtain independent clone of closure. Ensure that stack is always clean when running thread hook. --- src/state.rs | 10 ++++------ src/state/raw.rs | 39 ++++++++++++++++++--------------------- src/thread.rs | 7 ++++++- src/types.rs | 8 ++++---- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/state.rs b/src/state.rs index 021fffca..a39f22cc 100644 --- a/src/state.rs +++ b/src/state.rs @@ -516,7 +516,7 @@ impl Lua { let lua = self.lock(); unsafe { (*lua.extra.get()).hook_triggers = triggers; - (*lua.extra.get()).hook_callback = Some(Box::new(callback)); + (*lua.extra.get()).hook_callback = Some(XRc::new(callback)); lua.set_thread_hook(lua.state(), HookKind::Global) } } @@ -564,7 +564,7 @@ impl Lua { F: Fn(&Lua, Debug) -> Result + MaybeSend + 'static, { let lua = self.lock(); - unsafe { lua.set_thread_hook(lua.state(), HookKind::Thread(triggers, Box::new(callback))) } + unsafe { lua.set_thread_hook(lua.state(), HookKind::Thread(triggers, XRc::new(callback))) } } /// Removes a global hook previously set by [`Lua::set_global_hook`]. @@ -644,8 +644,6 @@ impl Lua { where F: Fn(&Lua) -> Result + MaybeSend + 'static, { - use std::rc::Rc; - unsafe extern "C-unwind" fn interrupt_proc(state: *mut ffi::lua_State, gc: c_int) { if gc >= 0 { // We don't support GC interrupts since they cannot survive Lua exceptions @@ -654,7 +652,7 @@ impl Lua { let result = callback_error_ext(state, ptr::null_mut(), move |extra, _| { let interrupt_cb = (*extra).interrupt_callback.clone(); let interrupt_cb = mlua_expect!(interrupt_cb, "no interrupt callback set in interrupt_proc"); - if Rc::strong_count(&interrupt_cb) > 2 { + if XRc::strong_count(&interrupt_cb) > 2 { return Ok(VmState::Continue); // Don't allow recursion } let _guard = StateGuard::new((*extra).raw_lua(), state); @@ -671,7 +669,7 @@ impl Lua { // Set interrupt callback let lua = self.lock(); unsafe { - (*lua.extra.get()).interrupt_callback = Some(Rc::new(callback)); + (*lua.extra.get()).interrupt_callback = Some(XRc::new(callback)); (*ffi::lua_callbacks(lua.main_state())).interrupt = Some(interrupt_proc); } } diff --git a/src/state/raw.rs b/src/state/raw.rs index f5bf7280..d91d8bd6 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -410,15 +410,12 @@ impl RawLua { unsafe extern "C-unwind" fn global_hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) { let status = callback_error_ext(state, ptr::null_mut(), move |extra, _| { - let rawlua = (*extra).raw_lua(); - let _guard = StateGuard::new(rawlua, state); - let debug = Debug::new(rawlua, ar); - match (*extra).hook_callback.take() { - Some(hook_cb) => { - // Temporary obtain ownership of the hook callback - let result = hook_cb((*extra).lua(), debug); - (*extra).hook_callback = Some(hook_cb); - result + match (*extra).hook_callback.clone() { + Some(hook_callback) => { + let rawlua = (*extra).raw_lua(); + let _guard = StateGuard::new(rawlua, state); + let debug = Debug::new(rawlua, ar); + hook_callback((*extra).lua(), debug) } None => { ffi::lua_sethook(state, None, 0, 0); @@ -430,11 +427,17 @@ impl RawLua { } unsafe extern "C-unwind" fn hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) { + let top = ffi::lua_gettop(state); + let mut hook_callback_ptr = ptr::null(); ffi::luaL_checkstack(state, 3, ptr::null()); - ffi::lua_getfield(state, ffi::LUA_REGISTRYINDEX, HOOKS_KEY); - ffi::lua_pushthread(state); - if ffi::lua_rawget(state, -2) != ffi::LUA_TUSERDATA { - ffi::lua_pop(state, 2); + if ffi::lua_getfield(state, ffi::LUA_REGISTRYINDEX, HOOKS_KEY) == ffi::LUA_TTABLE { + ffi::lua_pushthread(state); + if ffi::lua_rawget(state, -2) == ffi::LUA_TUSERDATA { + hook_callback_ptr = get_internal_userdata::(state, -1, ptr::null()); + } + } + ffi::lua_settop(state, top); + if hook_callback_ptr.is_null() { ffi::lua_sethook(state, None, 0, 0); return; } @@ -443,13 +446,8 @@ impl RawLua { let rawlua = (*extra).raw_lua(); let _guard = StateGuard::new(rawlua, state); let debug = Debug::new(rawlua, ar); - match get_internal_userdata::(state, -1, ptr::null()).as_ref() { - Some(hook_cb) => hook_cb((*extra).lua(), debug), - None => { - ffi::lua_sethook(state, None, 0, 0); - Ok(VmState::Continue) - } - } + let hook_callback = (*hook_callback_ptr).clone(); + hook_callback((*extra).lua(), debug) }); process_status(state, (*ar).event, status) } @@ -482,7 +480,6 @@ impl RawLua { ffi::lua_pushthread(thread_state); ffi::lua_xmove(thread_state, state, 1); // key (thread) - let callback: HookCallback = Box::new(callback); let _ = push_internal_userdata(state, callback, false); // value (hook callback) ffi::lua_rawset(state, -3); // hooktable[thread] = hook callback })?; diff --git a/src/thread.rs b/src/thread.rs index 2e0e3c20..72ddac31 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -272,7 +272,12 @@ impl Thread { F: Fn(&crate::Lua, Debug) -> Result + crate::MaybeSend + 'static, { let lua = self.0.lua.lock(); - unsafe { lua.set_thread_hook(self.state(), HookKind::Thread(triggers, Box::new(callback))) } + unsafe { + lua.set_thread_hook( + self.state(), + HookKind::Thread(triggers, crate::types::XRc::new(callback)), + ) + } } /// Removes any hook function from this thread. diff --git a/src/types.rs b/src/types.rs index df868586..35257847 100644 --- a/src/types.rs +++ b/src/types.rs @@ -79,16 +79,16 @@ pub(crate) enum HookKind { } #[cfg(all(feature = "send", not(feature = "luau")))] -pub(crate) type HookCallback = Box Result + Send>; +pub(crate) type HookCallback = XRc Result + Send>; #[cfg(all(not(feature = "send"), not(feature = "luau")))] -pub(crate) type HookCallback = Box Result>; +pub(crate) type HookCallback = XRc Result>; #[cfg(all(feature = "send", feature = "luau"))] -pub(crate) type InterruptCallback = std::rc::Rc Result + Send>; +pub(crate) type InterruptCallback = XRc Result + Send>; #[cfg(all(not(feature = "send"), feature = "luau"))] -pub(crate) type InterruptCallback = std::rc::Rc Result>; +pub(crate) type InterruptCallback = XRc Result>; #[cfg(all(feature = "send", feature = "lua54"))] pub(crate) type WarnCallback = Box Result<()> + Send>; From c1f8abba9e2c40c04ca5a862a5210d1dbebd4793 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 9 Feb 2025 15:08:52 +0000 Subject: [PATCH 338/635] Do not allow recursive warnings (Lua 5.4) --- src/state.rs | 13 +++++++------ src/types.rs | 4 ++-- tests/tests.rs | 7 +++++++ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/state.rs b/src/state.rs index a39f22cc..1054fbfc 100644 --- a/src/state.rs +++ b/src/state.rs @@ -703,18 +703,19 @@ impl Lua { unsafe extern "C-unwind" fn warn_proc(ud: *mut c_void, msg: *const c_char, tocont: c_int) { let extra = ud as *mut ExtraData; callback_error_ext((*extra).raw_lua().state(), extra, |extra, _| { - let cb = mlua_expect!( - (*extra).warn_callback.as_ref(), - "no warning callback set in warn_proc" - ); + let warn_callback = (*extra).warn_callback.clone(); + let warn_callback = mlua_expect!(warn_callback, "no warning callback set in warn_proc"); + if XRc::strong_count(&warn_callback) > 2 { + return Ok(()); + } let msg = StdString::from_utf8_lossy(CStr::from_ptr(msg).to_bytes()); - cb((*extra).lua(), &msg, tocont != 0) + warn_callback((*extra).lua(), &msg, tocont != 0) }); } let lua = self.lock(); unsafe { - (*lua.extra.get()).warn_callback = Some(Box::new(callback)); + (*lua.extra.get()).warn_callback = Some(XRc::new(callback)); ffi::lua_setwarnf(lua.state(), Some(warn_proc), lua.extra.get() as *mut c_void); } } diff --git a/src/types.rs b/src/types.rs index 35257847..537e1feb 100644 --- a/src/types.rs +++ b/src/types.rs @@ -91,10 +91,10 @@ pub(crate) type InterruptCallback = XRc Result + Send>; pub(crate) type InterruptCallback = XRc Result>; #[cfg(all(feature = "send", feature = "lua54"))] -pub(crate) type WarnCallback = Box Result<()> + Send>; +pub(crate) type WarnCallback = XRc Result<()> + Send>; #[cfg(all(not(feature = "send"), feature = "lua54"))] -pub(crate) type WarnCallback = Box Result<()>>; +pub(crate) type WarnCallback = XRc Result<()>>; /// A trait that adds `Send` requirement if `send` feature is enabled. #[cfg(feature = "send")] diff --git a/tests/tests.rs b/tests/tests.rs index af236e0c..01f706d7 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1289,6 +1289,13 @@ fn test_warnings() -> Result<()> { if matches!(*cause, Error::RuntimeError(ref err) if err == "warning error") )); + // Recursive warning + lua.set_warning_function(|lua, _, _| { + lua.warning("inner", false); + Ok(()) + }); + lua.warning("hello", false); + Ok(()) } From 28e8f569897e0edb0d01aefa6d902a984c5b32e3 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 9 Feb 2025 22:17:25 +0000 Subject: [PATCH 339/635] Fix tests --- tests/memory.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/memory.rs b/tests/memory.rs index ba30761b..ba1d7e48 100644 --- a/tests/memory.rs +++ b/tests/memory.rs @@ -57,8 +57,8 @@ fn test_memory_limit_thread() -> Result<()> { return Ok(()); } - lua.set_memory_limit(lua.used_memory() + 10000)?; let thread = lua.create_thread(f)?; + lua.set_memory_limit(lua.used_memory() + 10000)?; match thread.resume::<()>(()) { Err(Error::MemoryError(_)) => {} something_else => panic!("did not trigger memory error: {:?}", something_else), From 18497f15289221a514e650982286e38ba5704e02 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 11 Feb 2025 00:06:03 +0000 Subject: [PATCH 340/635] Add library constants support for Luau compiler. Add ability to specify compile-time constants for known library members. --- src/chunk.rs | 118 ++++++++++++++++++++++++++++++++++++++++++++++--- src/lib.rs | 7 ++- src/prelude.rs | 4 +- tests/chunk.rs | 35 ++++++++++++--- 4 files changed, 152 insertions(+), 12 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index 41a096ef..02dd35a9 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -130,6 +130,28 @@ pub enum ChunkMode { Binary, } +/// Represents a constant value that can be used by Luau compiler. +#[cfg(any(feature = "luau", doc))] +#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] +#[derive(Clone, Debug)] +pub enum CompileConstant { + Nil, + Boolean(bool), + Number(crate::Number), + Vector(crate::Vector), + String(String), +} + +#[cfg(feature = "luau")] +impl From<&'static str> for CompileConstant { + fn from(s: &'static str) -> Self { + CompileConstant::String(s.to_string()) + } +} + +#[cfg(any(feature = "luau", doc))] +type LibraryMemberConstantMap = std::sync::Arc>; + /// Luau compiler #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] @@ -144,6 +166,9 @@ pub struct Compiler { vector_type: Option, mutable_globals: Vec, userdata_types: Vec, + libraries_with_known_members: Vec, + library_constants: Option, + disabled_builtins: Vec, } #[cfg(any(feature = "luau", doc))] @@ -168,6 +193,9 @@ impl Compiler { vector_type: None, mutable_globals: Vec::new(), userdata_types: Vec::new(), + libraries_with_known_members: Vec::new(), + library_constants: None, + disabled_builtins: Vec::new(), } } @@ -200,6 +228,7 @@ impl Compiler { /// Possible values: /// * 0 - generate for native modules (default) /// * 1 - generate for all modules + #[must_use] pub const fn set_type_info_level(mut self, level: u8) -> Self { self.type_info_level = level; self @@ -242,15 +271,46 @@ impl Compiler { /// /// It disables the import optimization for fields accessed through these. #[must_use] - pub fn set_mutable_globals(mut self, globals: Vec) -> Self { - self.mutable_globals = globals; + pub fn set_mutable_globals>(mut self, globals: Vec) -> Self { + self.mutable_globals = globals.into_iter().map(|s| s.into()).collect(); self } /// Sets a list of userdata types that will be included in the type information. #[must_use] - pub fn set_userdata_types(mut self, types: Vec) -> Self { - self.userdata_types = types; + pub fn set_userdata_types>(mut self, types: Vec) -> Self { + self.userdata_types = types.into_iter().map(|s| s.into()).collect(); + self + } + + /// Sets constants for known library members. + /// + /// The constants are used by the compiler to optimize the generated bytecode. + /// Optimization level must be at least 2 for this to have any effect. + /// + /// The first element of the tuple is the library name,the second is the member name, and the + /// third is the constant value. + #[must_use] + pub fn set_library_constants(mut self, constants: Vec<(L, M, CompileConstant)>) -> Self + where + L: Into, + M: Into, + { + let map = constants + .into_iter() + .map(|(lib, member, cons)| ((lib.into(), member.into()), cons)) + .collect::>(); + self.library_constants = Some(std::sync::Arc::new(map)); + self.libraries_with_known_members = (self.library_constants.clone()) + .map(|map| map.keys().map(|(lib, _)| lib.clone()).collect()) + .unwrap_or_default(); + self + } + + /// Sets a list of builtins that should be disabled. + #[must_use] + pub fn set_disabled_builtins>(mut self, builtins: Vec) -> Self { + self.disabled_builtins = builtins.into_iter().map(|s| s.into()).collect(); self } @@ -258,7 +318,9 @@ impl Compiler { /// /// Returns [`Error::SyntaxError`] if the source code is invalid. pub fn compile(&self, source: impl AsRef<[u8]>) -> Result> { - use std::os::raw::c_int; + use std::cell::RefCell; + use std::ffi::CStr; + use std::os::raw::{c_char, c_int}; use std::ptr; let vector_lib = self.vector_lib.clone(); @@ -290,6 +352,44 @@ impl Compiler { vec2cstring_ptr!(mutable_globals, mutable_globals_ptr); vec2cstring_ptr!(userdata_types, userdata_types_ptr); + vec2cstring_ptr!(libraries_with_known_members, libraries_with_known_members_ptr); + vec2cstring_ptr!(disabled_builtins, disabled_builtins_ptr); + + thread_local! { + static LIBRARY_MEMBER_CONSTANT_MAP: RefCell = Default::default(); + } + + #[cfg(feature = "luau")] + unsafe extern "C-unwind" fn library_member_constant_callback( + library: *const c_char, + member: *const c_char, + constant: *mut ffi::lua_CompileConstant, + ) { + let library = CStr::from_ptr(library).to_string_lossy(); + let member = CStr::from_ptr(member).to_string_lossy(); + LIBRARY_MEMBER_CONSTANT_MAP.with_borrow(|map| { + if let Some(cons) = map.get(&(library.to_string(), member.to_string())) { + match cons { + CompileConstant::Nil => ffi::luau_set_compile_constant_nil(constant), + CompileConstant::Boolean(b) => { + ffi::luau_set_compile_constant_boolean(constant, *b as c_int) + } + CompileConstant::Number(n) => ffi::luau_set_compile_constant_number(constant, *n), + CompileConstant::Vector(v) => { + #[cfg(not(feature = "luau-vector4"))] + ffi::luau_set_compile_constant_vector(constant, v.x(), v.y(), v.z(), 0.0); + #[cfg(feature = "luau-vector4")] + ffi::luau_set_compile_constant_vector(constant, v.x(), v.y(), v.z(), v.w()); + } + CompileConstant::String(s) => ffi::luau_set_compile_constant_string( + constant, + s.as_ptr() as *const c_char, + s.len(), + ), + } + } + }) + } let bytecode = unsafe { let mut options = ffi::lua_CompileOptions::default(); @@ -302,6 +402,14 @@ impl Compiler { options.vectorType = vector_type.map_or(ptr::null(), |s| s.as_ptr()); options.mutableGlobals = mutable_globals_ptr; options.userdataTypes = userdata_types_ptr; + options.librariesWithKnownMembers = libraries_with_known_members_ptr; + if let Some(map) = self.library_constants.as_ref() { + if !self.libraries_with_known_members.is_empty() { + LIBRARY_MEMBER_CONSTANT_MAP.with_borrow_mut(|gmap| *gmap = map.clone()); + options.libraryMemberConstantCallback = Some(library_member_constant_callback); + } + } + options.disabledBuiltins = disabled_builtins_ptr; ffi::luau_compile(source.as_ref(), options) }; diff --git a/src/lib.rs b/src/lib.rs index a404594c..913bf589 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,7 +126,12 @@ pub use crate::hook::HookTriggers; #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] -pub use crate::{buffer::Buffer, chunk::Compiler, function::CoverageInfo, vector::Vector}; +pub use crate::{ + buffer::Buffer, + chunk::{CompileConstant, Compiler}, + function::CoverageInfo, + vector::Vector, +}; #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] diff --git a/src/prelude.rs b/src/prelude.rs index 68ba8f2f..b84adf49 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -22,7 +22,9 @@ pub use crate::HookTriggers as LuaHookTriggers; #[cfg(feature = "luau")] #[doc(no_inline)] -pub use crate::{CoverageInfo as LuaCoverageInfo, Vector as LuaVector}; +pub use crate::{ + CompileConstant as LuaCompileConstant, CoverageInfo as LuaCoverageInfo, Vector as LuaVector, +}; #[cfg(feature = "async")] #[doc(no_inline)] diff --git a/tests/chunk.rs b/tests/chunk.rs index 16df553b..17becbe2 100644 --- a/tests/chunk.rs +++ b/tests/chunk.rs @@ -96,8 +96,6 @@ fn test_chunk_macro() -> Result<()> { #[cfg(feature = "luau")] #[test] fn test_compiler() -> Result<()> { - use std::vec; - let compiler = mlua::Compiler::new() .set_optimization_level(2) .set_debug_level(2) @@ -106,10 +104,11 @@ fn test_compiler() -> Result<()> { .set_vector_lib("vector") .set_vector_ctor("new") .set_vector_type("vector") - .set_mutable_globals(vec!["mutable_global".into()]) - .set_userdata_types(vec!["MyUserdata".into()]); + .set_mutable_globals(vec!["mutable_global"]) + .set_userdata_types(vec!["MyUserdata"]) + .set_disabled_builtins(vec!["tostring"]); - assert!(compiler.compile("return vector.new(1, 2, 3)").is_ok()); + assert!(compiler.compile("return tostring(vector.new(1, 2, 3))").is_ok()); // Error match compiler.compile("%") { @@ -122,6 +121,32 @@ fn test_compiler() -> Result<()> { Ok(()) } +#[cfg(feature = "luau")] +#[test] +fn test_compiler_library_constants() { + use mlua::{CompileConstant, Compiler, Vector}; + + let compiler = Compiler::new() + .set_optimization_level(2) + .set_library_constants(vec![ + ("mylib", "const_bool", CompileConstant::Boolean(true)), + ("mylib", "const_num", CompileConstant::Number(123.0)), + ("mylib", "const_vec", CompileConstant::Vector(Vector::zero())), + ("mylib", "const_str", "value1".into()), + ]); + + let lua = Lua::new(); + lua.set_compiler(compiler); + let const_bool = lua.load("return mylib.const_bool").eval::().unwrap(); + assert_eq!(const_bool, true); + let const_num = lua.load("return mylib.const_num").eval::().unwrap(); + assert_eq!(const_num, 123.0); + let const_vec = lua.load("return mylib.const_vec").eval::().unwrap(); + assert_eq!(const_vec, Vector::zero()); + let const_str = lua.load("return mylib.const_str").eval::(); + assert_eq!(const_str.unwrap(), "value1"); +} + #[test] fn test_chunk_wrap() -> Result<()> { let lua = Lua::new(); From a01b032c3583010deb320f1ed43722b978cbb985 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 11 Feb 2025 13:36:03 +0000 Subject: [PATCH 341/635] Add `bstr/serde` dependency if `serialize` feature flag is enabled --- Cargo.toml | 2 +- tests/serde.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index efbb8d5a..fb432cf8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ module = ["dep:mlua_derive", "ffi/module"] async = ["dep:futures-util"] send = ["parking_lot/send_guard", "error-send"] error-send = [] -serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value"] +serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value", "bstr/serde"] macros = ["mlua_derive/macros"] anyhow = ["dep:anyhow", "error-send"] userdata-wrappers = [] diff --git a/tests/serde.rs b/tests/serde.rs index 4efb6537..167095fd 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use std::error::Error as StdError; +use bstr::BString; use mlua::{ AnyUserData, DeserializeOptions, Error, ExternalResult, IntoLua, Lua, LuaSerdeExt, Result as LuaResult, SerializeOptions, UserData, Value, @@ -420,6 +421,7 @@ fn test_from_value_struct() -> Result<(), Box> { map: HashMap, empty: Vec<()>, tuple: (u8, u8, u8), + bytes: BString, } let value = lua @@ -431,6 +433,7 @@ fn test_from_value_struct() -> Result<(), Box> { map = {2, [4] = 1}, empty = {}, tuple = {10, 20, 30}, + bytes = "\240\040\140\040", } "#, ) @@ -443,6 +446,7 @@ fn test_from_value_struct() -> Result<(), Box> { map: vec![(1, 2), (4, 1)].into_iter().collect(), empty: vec![], tuple: (10, 20, 30), + bytes: BString::from([240, 40, 140, 40]), }, got ); From dacddfa967f30709d4097e1913c83903bc03db15 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 14 Feb 2025 12:06:25 +0000 Subject: [PATCH 342/635] Add Variadic to prelude --- src/prelude.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prelude.rs b/src/prelude.rs index b84adf49..1f66e769 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -13,7 +13,7 @@ pub use crate::{ UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, - VmState as LuaVmState, + Variadic as LuaVariadic, VmState as LuaVmState, }; #[cfg(not(feature = "luau"))] From ae7cdcb934dc5248b83e69ebf8ca227480057890 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 14 Feb 2025 14:41:37 +0000 Subject: [PATCH 343/635] Remove (internal) borrow counter and use instead "locked" flag and strong reference counter --- src/userdata/cell.rs | 30 ++++++++++++++---------------- src/userdata/lock.rs | 11 +++++++++++ src/userdata/util.rs | 2 +- tests/userdata.rs | 4 ++-- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index d5d8e005..70b6dd34 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -1,5 +1,5 @@ use std::any::{type_name, TypeId}; -use std::cell::{Cell, RefCell, UnsafeCell}; +use std::cell::{RefCell, UnsafeCell}; use std::fmt; use std::ops::{Deref, DerefMut}; use std::os::raw::c_int; @@ -91,20 +91,20 @@ impl UserDataVariant { } #[inline(always)] - fn raw_lock(&self) -> &RawLock { + fn strong_count(&self) -> usize { match self { - Self::Default(inner) => &inner.raw_lock, + Self::Default(inner) => XRc::strong_count(inner), #[cfg(feature = "serialize")] - Self::Serializable(inner) => &inner.raw_lock, + Self::Serializable(inner) => XRc::strong_count(inner), } } #[inline(always)] - fn borrow_count(&self) -> &Cell { + fn raw_lock(&self) -> &RawLock { match self { - Self::Default(inner) => &inner.borrow_count, + Self::Default(inner) => &inner.raw_lock, #[cfg(feature = "serialize")] - Self::Serializable(inner) => &inner.borrow_count, + Self::Serializable(inner) => &inner.raw_lock, } } @@ -139,7 +139,6 @@ impl Serialize for UserDataStorage<()> { /// A type that provides interior mutability for a userdata value (thread-safe). pub(crate) struct UserDataCell { raw_lock: RawLock, - borrow_count: Cell, value: UnsafeCell, } @@ -153,7 +152,6 @@ impl UserDataCell { fn new(value: T) -> Self { UserDataCell { raw_lock: RawLock::INIT, - borrow_count: Cell::new(0), value: UnsafeCell::new(value), } } @@ -303,7 +301,6 @@ impl Drop for UserDataBorrowRef<'_, T> { #[inline] fn drop(&mut self) { unsafe { - self.0.borrow_count().set(self.0.borrow_count().get() - 1); self.0.raw_lock().unlock_shared(); } } @@ -331,7 +328,6 @@ impl<'a, T> TryFrom<&'a UserDataVariant> for UserDataBorrowRef<'a, T> { if !variant.raw_lock().try_lock_shared() { return Err(Error::UserDataBorrowError); } - variant.borrow_count().set(variant.borrow_count().get() + 1); Ok(UserDataBorrowRef(variant)) } } @@ -342,7 +338,6 @@ impl Drop for UserDataBorrowMut<'_, T> { #[inline] fn drop(&mut self) { unsafe { - self.0.borrow_count().set(self.0.borrow_count().get() - 1); self.0.raw_lock().unlock_exclusive(); } } @@ -372,7 +367,6 @@ impl<'a, T> TryFrom<&'a UserDataVariant> for UserDataBorrowMut<'a, T> { if !variant.raw_lock().try_lock_exclusive() { return Err(Error::UserDataBorrowMutError); } - variant.borrow_count().set(variant.borrow_count().get() + 1); Ok(UserDataBorrowMut(variant)) } } @@ -489,11 +483,15 @@ impl UserDataStorage { Self::Scoped(ScopedUserDataVariant::Boxed(RefCell::new(data))) } + /// Returns `true` if it's safe to destroy the container. + /// + /// It's safe to destroy the container if the reference count is greater than 1 or the lock is + /// not acquired. #[inline(always)] - pub(crate) fn is_borrowed(&self) -> bool { + pub(crate) fn is_safe_to_destroy(&self) -> bool { match self { - Self::Owned(variant) => variant.borrow_count().get() > 0, - Self::Scoped(_) => true, + Self::Owned(variant) => variant.strong_count() > 1 || !variant.raw_lock().is_locked(), + Self::Scoped(_) => false, } } diff --git a/src/userdata/lock.rs b/src/userdata/lock.rs index c5690444..4843ff4c 100644 --- a/src/userdata/lock.rs +++ b/src/userdata/lock.rs @@ -1,6 +1,7 @@ pub(crate) trait UserDataLock { const INIT: Self; + fn is_locked(&self) -> bool; fn try_lock_shared(&self) -> bool; fn try_lock_exclusive(&self) -> bool; @@ -25,6 +26,11 @@ mod lock_impl { #[allow(clippy::declare_interior_mutable_const)] const INIT: Self = Cell::new(UNUSED); + #[inline(always)] + fn is_locked(&self) -> bool { + self.get() != UNUSED + } + #[inline(always)] fn try_lock_shared(&self) -> bool { let flag = self.get().wrapping_add(1); @@ -71,6 +77,11 @@ mod lock_impl { #[allow(clippy::declare_interior_mutable_const)] const INIT: Self = ::INIT; + #[inline(always)] + fn is_locked(&self) -> bool { + RawRwLock::is_locked(self) + } + #[inline(always)] fn try_lock_shared(&self) -> bool { RawRwLock::try_lock_shared(self) diff --git a/src/userdata/util.rs b/src/userdata/util.rs index 02c5ea4e..8cff934c 100644 --- a/src/userdata/util.rs +++ b/src/userdata/util.rs @@ -36,7 +36,7 @@ pub(crate) fn is_sync() -> bool { pub(super) unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::lua_State) -> c_int { let ud = get_userdata::>(state, -1); - if !(*ud).is_borrowed() { + if (*ud).is_safe_to_destroy() { take_userdata::>(state); ffi::lua_pushboolean(state, 1); } else { diff --git a/tests/userdata.rs b/tests/userdata.rs index 59248d0f..77dbfcf5 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -410,7 +410,7 @@ fn test_userdata_destroy() -> Result<()> { let ud_ref = ud.borrow::()?; // With active `UserDataRef` this methods only marks userdata as destructed // without running destructor - ud.destroy()?; + ud.destroy().unwrap(); assert_eq!(Arc::strong_count(&rc), 2); drop(ud_ref); assert_eq!(Arc::strong_count(&rc), 1); @@ -419,7 +419,7 @@ fn test_userdata_destroy() -> Result<()> { let ud = lua.create_userdata(MyUserdata(rc.clone()))?; lua.globals().set("ud", &ud)?; lua.load("ud:try_destroy()").exec().unwrap(); - ud.destroy()?; + ud.destroy().unwrap(); assert_eq!(Arc::strong_count(&rc), 1); Ok(()) From e706ae4fcb66a1a64cb41ae49ad144be68129191 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 24 Feb 2025 19:03:33 +0000 Subject: [PATCH 344/635] Remove Roblox from references to Luau Closes #537 --- CHANGELOG.md | 4 ++-- Cargo.toml | 2 +- README.md | 6 +++--- mlua-sys/Cargo.toml | 2 +- mlua-sys/README.md | 4 ++-- mlua-sys/src/lib.rs | 2 +- mlua-sys/src/luau/compat.rs | 2 +- src/state/util.rs | 2 +- src/util/error.rs | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df129889..d0366875 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -304,7 +304,7 @@ Other: ## v0.8.0 Changes since 0.7.4 -- Roblox Luau support +- Luau support - Removed C glue - Added async support to `__index` and `__newindex` metamethods - Added `Function::info()` to get information about functions (#149). @@ -354,7 +354,7 @@ Breaking changes: ## v0.8.0-beta.1 -- Roblox Luau support +- Luau support - Refactored ffi module. C glue is no longer required - Added async support to `__index` and `__newindex` metamethods diff --git a/Cargo.toml b/Cargo.toml index fb432cf8..04818714 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["lua", "luajit", "luau", "async", "scripting"] categories = ["api-bindings", "asynchronous"] license = "MIT" description = """ -High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox Luau +High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau with async/await features and support of writing native Lua modules in Rust. """ diff --git a/README.md b/README.md index 21818286..747f4821 100644 --- a/README.md +++ b/README.md @@ -26,14 +26,14 @@ `mlua` is bindings to [Lua](https://www.lua.org) programming language for Rust with a goal to provide _safe_ (as far as it's possible), high level, easy to use, practical and flexible API. -Started as `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Roblox Luau] and allows to write native Lua modules in Rust as well as use Lua in a standalone mode. +Started as `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Luau] and allows to write native Lua modules in Rust as well as use Lua in a standalone mode. `mlua` tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platform and cross-compilation to `aarch64` (other targets are also supported). WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua versions excluding JIT. [GitHub Actions]: https://github.com/khvzak/mlua/actions -[Roblox Luau]: https://luau.org +[Luau]: https://luau.org ## Usage @@ -66,7 +66,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl [5.2]: https://www.lua.org/manual/5.2/manual.html [5.1]: https://www.lua.org/manual/5.1/manual.html [LuaJIT]: https://luajit.org/ -[Luau]: https://github.com/Roblox/luau +[Luau]: https://github.com/luau-lang/luau [lua-src]: https://github.com/khvzak/lua-src-rs [luajit-src]: https://github.com/khvzak/luajit-src-rs [tokio]: https://github.com/tokio-rs/tokio diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index f4171a15..26736289 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT" links = "lua" build = "build/main.rs" description = """ -Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox Luau +Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau """ [package.metadata.docs.rs] diff --git a/mlua-sys/README.md b/mlua-sys/README.md index d0de6252..927ebbd6 100644 --- a/mlua-sys/README.md +++ b/mlua-sys/README.md @@ -1,8 +1,8 @@ # mlua-sys -Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox [Luau]. +Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and [Luau]. Intended to be consumed by the [mlua] crate. -[Luau]: https://github.com/Roblox/luau +[Luau]: https://github.com/luau-lang/luau [mlua]: https://crates.io/crates/mlua diff --git a/mlua-sys/src/lib.rs b/mlua-sys/src/lib.rs index 629dfd88..6bbb595c 100644 --- a/mlua-sys/src/lib.rs +++ b/mlua-sys/src/lib.rs @@ -1,4 +1,4 @@ -//! Low level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox Luau. +//! Low level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau. #![allow(non_camel_case_types, non_snake_case, dead_code)] #![allow(clippy::missing_safety_doc)] diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index b2f33c70..885c1d07 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -1,4 +1,4 @@ -//! MLua compatibility layer for Roblox Luau. +//! MLua compatibility layer for Luau. //! //! Based on github.com/keplerproject/lua-compat-5.3 diff --git a/src/state/util.rs b/src/state/util.rs index 9dbf3e85..c0dc015b 100644 --- a/src/state/util.rs +++ b/src/state/util.rs @@ -51,7 +51,7 @@ where } // We need to check stack for Luau in case when callback is called from interrupt - // See https://github.com/Roblox/luau/issues/446 and mlua #142 and #153 + // See https://github.com/luau-lang/luau/issues/446 and mlua #142 and #153 #[cfg(feature = "luau")] ffi::lua_rawcheckstack(state, 2); // Place it to the beginning of the stack diff --git a/src/util/error.rs b/src/util/error.rs index eb9ee1f5..3bf08c6c 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -262,7 +262,7 @@ where pub(crate) unsafe extern "C-unwind" fn error_traceback(state: *mut ffi::lua_State) -> c_int { // Luau calls error handler for memory allocation errors, skip it - // See https://github.com/Roblox/luau/issues/880 + // See https://github.com/luau-lang/luau/issues/880 #[cfg(feature = "luau")] if MemoryState::limit_reached(state) { return 0; From 5ab97666d726b67d97b1b89bf925e63e917ca167 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 7 Mar 2025 21:21:14 +0000 Subject: [PATCH 345/635] Fix clippy warnings --- Cargo.toml | 1 + src/memory.rs | 26 +++++++++++++++++++++----- src/state.rs | 4 ++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 04818714..91f373b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ erased-serde = { version = "0.4", optional = true } serde-value = { version = "0.7", optional = true } parking_lot = { version = "0.12", features = ["arc_lock"] } anyhow = { version = "1.0", optional = true } +rustversion = "1.0" ffi = { package = "mlua-sys", version = "0.6.6", path = "mlua-sys" } diff --git a/src/memory.rs b/src/memory.rs index 672a8647..92c60326 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -18,15 +18,31 @@ pub(crate) struct MemoryState { } impl MemoryState { + #[cfg(feature = "luau")] #[inline] pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { let mut mem_state = ptr::null_mut(); - #[cfg(feature = "luau")] - { - ffi::lua_getallocf(state, &mut mem_state); - mlua_assert!(!mem_state.is_null(), "Luau state has no allocator userdata"); + ffi::lua_getallocf(state, &mut mem_state); + mlua_assert!(!mem_state.is_null(), "Luau state has no allocator userdata"); + mem_state as *mut MemoryState + } + + #[cfg(not(feature = "luau"))] + #[rustversion::since(1.85)] + #[inline] + pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { + let mut mem_state = ptr::null_mut(); + if !ptr::fn_addr_eq(ffi::lua_getallocf(state, &mut mem_state), ALLOCATOR) { + mem_state = ptr::null_mut(); } - #[cfg(not(feature = "luau"))] + mem_state as *mut MemoryState + } + + #[cfg(not(feature = "luau"))] + #[rustversion::before(1.85)] + #[inline] + pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { + let mut mem_state = ptr::null_mut(); if ffi::lua_getallocf(state, &mut mem_state) != ALLOCATOR { mem_state = ptr::null_mut(); } diff --git a/src/state.rs b/src/state.rs index 1054fbfc..fb0bda47 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1809,8 +1809,8 @@ impl Lua { let state = lua.state(); unsafe { let mut unref_list = (*lua.extra.get()).registry_unref_list.lock(); - let unref_list = mem::replace(&mut *unref_list, Some(Vec::new())); - for id in mlua_expect!(unref_list, "unref list not set") { + let unref_list = unref_list.replace(Vec::new()); + for id in mlua_expect!(unref_list, "unref list is not set") { ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, id); } } From 1ebb4b468a74d13bea62de97c4601bf3b4b3b3ee Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 13 Mar 2025 16:18:44 +0000 Subject: [PATCH 346/635] 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 885c1d07..af5512a3 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -127,6 +127,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()) @@ -178,6 +183,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) } @@ -213,6 +219,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) } @@ -315,6 +322,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 284cd3f2..0b75cbe2 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 c75174a6..a916f80c 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 2f647199..b7dfa305 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 b3935d38..29427ed7 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 bd088887..428ce201 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 01f706d7..138a0ad7 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(); From 0ce599aff254009d61bfca3ca1da6f126fa55260 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 13 Mar 2025 17:41:13 +0000 Subject: [PATCH 347/635] Update test case --- tests/tests.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/tests.rs b/tests/tests.rs index 138a0ad7..262b1ab4 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -500,9 +500,7 @@ fn test_safe_integers() -> Result<()> { { 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); + assert_eq!(f.call::(i64::MAX)?, i64::MAX as f64); } Ok(()) From 0ed11e4134d91449ca35edf5ebb97a302657c902 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 14 Mar 2025 11:38:07 +0000 Subject: [PATCH 348/635] Add initial pluto support Pluto is a superset of Lua 5.4 with a focus on general-purpose programming. `pluto` feature also enables `lua54` feature since they are compatible and share the same API. --- .github/workflows/main.yml | 10 ++-- Cargo.toml | 1 + README.md | 2 + mlua-sys/Cargo.toml | 6 ++- mlua-sys/build/find_vendored.rs | 5 +- mlua-sys/build/main.rs | 2 +- mlua-sys/build/main_inner.rs | 2 +- mlua-sys/src/lua51/lualib.rs | 24 ++++----- mlua-sys/src/lua52/lualib.rs | 20 +++---- mlua-sys/src/lua53/lauxlib.rs | 4 +- mlua-sys/src/lua53/lualib.rs | 22 ++++---- mlua-sys/src/lua54/lauxlib.rs | 4 +- mlua-sys/src/lua54/lualib.rs | 40 ++++++++++---- mlua-sys/src/luau/lualib.rs | 22 ++++---- src/state/raw.rs | 92 +++++++++++++++++++++++++-------- src/stdlib.rs | 59 +++++++++++++++++++-- src/util/error.rs | 16 +++--- tests/pluto.rs | 22 ++++++++ 18 files changed, 251 insertions(+), 102 deletions(-) create mode 100644 tests/pluto.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cd12c620..f6646fd4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] rust: [stable] - lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] + lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4, pluto] include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu @@ -105,7 +105,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] rust: [stable, nightly] - lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit, luau-vector4] + lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit, luau-vector4, pluto] include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu @@ -141,7 +141,7 @@ jobs: matrix: os: [ubuntu-latest] rust: [nightly] - lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] + lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4, pluto] include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu @@ -168,7 +168,7 @@ jobs: matrix: os: [ubuntu-latest] rust: [nightly] - lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] + lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4, pluto] include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu @@ -271,7 +271,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] + lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4, pluto] steps: - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@stable diff --git a/Cargo.toml b/Cargo.toml index 91f373b6..0dcbee44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ luajit52 = ["luajit", "ffi/luajit52"] luau = ["ffi/luau", "dep:libloading"] luau-jit = ["luau", "ffi/luau-codegen"] luau-vector4 = ["luau", "ffi/luau-vector4"] +pluto = ["lua54", "ffi/pluto"] vendored = ["ffi/vendored"] module = ["dep:mlua_derive", "ffi/module"] async = ["dep:futures-util"] diff --git a/README.md b/README.md index 747f4821..f00176aa 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl * `luau`: enable [Luau] support (auto vendored mode) * `luau-jit`: enable [Luau] support with JIT backend. * `luau-vector4`: enable [Luau] support with 4-dimensional vector. +* `pluto`: enable [Pluto] support (Lua 5.4 dialect, auto vendored mode) * `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]) @@ -67,6 +68,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl [5.1]: https://www.lua.org/manual/5.1/manual.html [LuaJIT]: https://luajit.org/ [Luau]: https://github.com/luau-lang/luau +[Pluto]: https://github.com/PlutoLang/Pluto [lua-src]: https://github.com/khvzak/lua-src-rs [luajit-src]: https://github.com/khvzak/luajit-src-rs [tokio]: https://github.com/tokio-rs/tokio diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 26736289..825fa26c 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -26,10 +26,11 @@ lua52 = [] lua51 = [] luajit = [] luajit52 = ["luajit"] -luau = ["luau0-src"] +luau = ["dep:luau0-src"] luau-codegen = ["luau"] luau-vector4 = ["luau"] -vendored = ["lua-src", "luajit-src"] +pluto = ["lua54", "dep:pluto-src"] +vendored = ["dep:lua-src", "dep:luajit-src"] module = [] [dependencies] @@ -41,6 +42,7 @@ pkg-config = "0.3.17" lua-src = { version = ">= 547.0.0, < 547.1.0", optional = true } luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } luau0-src = { version = "0.12.0", optional = true } +pluto-src = { version = "0.1.1", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/mlua-sys/build/find_vendored.rs b/mlua-sys/build/find_vendored.rs index e3ddbecf..7c09b46b 100644 --- a/mlua-sys/build/find_vendored.rs +++ b/mlua-sys/build/find_vendored.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] pub fn probe_lua() { - #[cfg(feature = "lua54")] + #[cfg(all(feature = "lua54", not(feature = "pluto")))] let artifacts = lua_src::Build::new().build(lua_src::Lua54); #[cfg(feature = "lua53")] @@ -25,5 +25,8 @@ pub fn probe_lua() { .set_vector_size(if cfg!(feature = "luau-vector4") { 4 } else { 3 }) .build(); + #[cfg(feature = "pluto")] + let artifacts = pluto_src::Build::new().use_longjmp(true).build(); + artifacts.print_cargo_metadata(); } diff --git a/mlua-sys/build/main.rs b/mlua-sys/build/main.rs index 53074f8e..4eff2d90 100644 --- a/mlua-sys/build/main.rs +++ b/mlua-sys/build/main.rs @@ -13,7 +13,7 @@ cfg_if::cfg_if! { include!("main_inner.rs"); } else { fn main() { - compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau"); + compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau, pluto"); } } } diff --git a/mlua-sys/build/main_inner.rs b/mlua-sys/build/main_inner.rs index 05ac53b5..19375820 100644 --- a/mlua-sys/build/main_inner.rs +++ b/mlua-sys/build/main_inner.rs @@ -1,7 +1,7 @@ use std::env; cfg_if::cfg_if! { - if #[cfg(any(feature = "luau", feature = "vendored"))] { + if #[cfg(any(feature = "luau", feature = "pluto", feature = "vendored"))] { #[path = "find_vendored.rs"] mod find; } else { diff --git a/mlua-sys/src/lua51/lualib.rs b/mlua-sys/src/lua51/lualib.rs index 9ef0b214..de994ce1 100644 --- a/mlua-sys/src/lua51/lualib.rs +++ b/mlua-sys/src/lua51/lualib.rs @@ -1,24 +1,24 @@ //! Contains definitions from `lualib.h`. -use std::os::raw::c_int; +use std::os::raw::{c_char, c_int}; use super::lua::lua_State; -pub const LUA_COLIBNAME: &str = "coroutine"; -pub const LUA_TABLIBNAME: &str = "table"; -pub const LUA_IOLIBNAME: &str = "io"; -pub const LUA_OSLIBNAME: &str = "os"; -pub const LUA_STRLIBNAME: &str = "string"; -pub const LUA_MATHLIBNAME: &str = "math"; -pub const LUA_DBLIBNAME: &str = "debug"; -pub const LUA_LOADLIBNAME: &str = "package"; +pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); +pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); +pub const LUA_IOLIBNAME: *const c_char = cstr!("io"); +pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); +pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); +pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); +pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); +pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); #[cfg(feature = "luajit")] -pub const LUA_BITLIBNAME: &str = "bit"; +pub const LUA_BITLIBNAME: *const c_char = cstr!("bit"); #[cfg(feature = "luajit")] -pub const LUA_JITLIBNAME: &str = "jit"; +pub const LUA_JITLIBNAME: *const c_char = cstr!("jit"); #[cfg(feature = "luajit")] -pub const LUA_FFILIBNAME: &str = "ffi"; +pub const LUA_FFILIBNAME: *const c_char = cstr!("ffi"); #[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))] extern "C-unwind" { diff --git a/mlua-sys/src/lua52/lualib.rs b/mlua-sys/src/lua52/lualib.rs index daf1e2a6..a9ed21f7 100644 --- a/mlua-sys/src/lua52/lualib.rs +++ b/mlua-sys/src/lua52/lualib.rs @@ -1,18 +1,18 @@ //! Contains definitions from `lualib.h`. -use std::os::raw::c_int; +use std::os::raw::{c_char, c_int}; use super::lua::lua_State; -pub const LUA_COLIBNAME: &str = "coroutine"; -pub const LUA_TABLIBNAME: &str = "table"; -pub const LUA_IOLIBNAME: &str = "io"; -pub const LUA_OSLIBNAME: &str = "os"; -pub const LUA_STRLIBNAME: &str = "string"; -pub const LUA_BITLIBNAME: &str = "bit32"; -pub const LUA_MATHLIBNAME: &str = "math"; -pub const LUA_DBLIBNAME: &str = "debug"; -pub const LUA_LOADLIBNAME: &str = "package"; +pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); +pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); +pub const LUA_IOLIBNAME: *const c_char = cstr!("io"); +pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); +pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); +pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32"); +pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); +pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); +pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); #[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))] extern "C-unwind" { diff --git a/mlua-sys/src/lua53/lauxlib.rs b/mlua-sys/src/lua53/lauxlib.rs index 7c851ac1..53c45bd0 100644 --- a/mlua-sys/src/lua53/lauxlib.rs +++ b/mlua-sys/src/lua53/lauxlib.rs @@ -9,10 +9,10 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State}; pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1; // Key, in the registry, for table of loaded modules -pub const LUA_LOADED_TABLE: &str = "_LOADED"; +pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED"); // Key, in the registry, for table of preloaded loaders -pub const LUA_PRELOAD_TABLE: &str = "_PRELOAD"; +pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD"); #[repr(C)] pub struct luaL_Reg { diff --git a/mlua-sys/src/lua53/lualib.rs b/mlua-sys/src/lua53/lualib.rs index 5d7509e2..0b556d41 100644 --- a/mlua-sys/src/lua53/lualib.rs +++ b/mlua-sys/src/lua53/lualib.rs @@ -1,19 +1,19 @@ //! Contains definitions from `lualib.h`. -use std::os::raw::c_int; +use std::os::raw::{c_char, c_int}; use super::lua::lua_State; -pub const LUA_COLIBNAME: &str = "coroutine"; -pub const LUA_TABLIBNAME: &str = "table"; -pub const LUA_IOLIBNAME: &str = "io"; -pub const LUA_OSLIBNAME: &str = "os"; -pub const LUA_STRLIBNAME: &str = "string"; -pub const LUA_UTF8LIBNAME: &str = "utf8"; -pub const LUA_BITLIBNAME: &str = "bit32"; -pub const LUA_MATHLIBNAME: &str = "math"; -pub const LUA_DBLIBNAME: &str = "debug"; -pub const LUA_LOADLIBNAME: &str = "package"; +pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); +pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); +pub const LUA_IOLIBNAME: *const c_char = cstr!("io"); +pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); +pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); +pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8"); +pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32"); +pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); +pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); +pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] extern "C-unwind" { diff --git a/mlua-sys/src/lua54/lauxlib.rs b/mlua-sys/src/lua54/lauxlib.rs index 78b0881e..8a1fd12d 100644 --- a/mlua-sys/src/lua54/lauxlib.rs +++ b/mlua-sys/src/lua54/lauxlib.rs @@ -9,10 +9,10 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State}; pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1; // Key, in the registry, for table of loaded modules -pub const LUA_LOADED_TABLE: &str = "_LOADED"; +pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED"); // Key, in the registry, for table of preloaded loaders -pub const LUA_PRELOAD_TABLE: &str = "_PRELOAD"; +pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD"); #[repr(C)] pub struct luaL_Reg { diff --git a/mlua-sys/src/lua54/lualib.rs b/mlua-sys/src/lua54/lualib.rs index c104375f..4cf30027 100644 --- a/mlua-sys/src/lua54/lualib.rs +++ b/mlua-sys/src/lua54/lualib.rs @@ -1,18 +1,18 @@ //! Contains definitions from `lualib.h`. -use std::os::raw::c_int; +use std::os::raw::{c_char, c_int}; use super::lua::lua_State; -pub const LUA_COLIBNAME: &str = "coroutine"; -pub const LUA_TABLIBNAME: &str = "table"; -pub const LUA_IOLIBNAME: &str = "io"; -pub const LUA_OSLIBNAME: &str = "os"; -pub const LUA_STRLIBNAME: &str = "string"; -pub const LUA_UTF8LIBNAME: &str = "utf8"; -pub const LUA_MATHLIBNAME: &str = "math"; -pub const LUA_DBLIBNAME: &str = "debug"; -pub const LUA_LOADLIBNAME: &str = "package"; +pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); +pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); +pub const LUA_IOLIBNAME: *const c_char = cstr!("io"); +pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); +pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); +pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8"); +pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); +pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); +pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); #[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] extern "C-unwind" { @@ -30,3 +30,23 @@ extern "C-unwind" { // open all builtin libraries pub fn luaL_openlibs(L: *mut lua_State); } + +#[cfg(feature = "pluto")] +extern "C-unwind" { + pub fn luaopen_assert(L: *mut lua_State) -> c_int; + pub fn luaopen_base32(L: *mut lua_State) -> c_int; + pub fn luaopen_base64(L: *mut lua_State) -> c_int; + pub fn luaopen_bigint(L: *mut lua_State) -> c_int; + pub fn luaopen_cat(L: *mut lua_State) -> c_int; + pub fn luaopen_canvas(L: *mut lua_State) -> c_int; + pub fn luaopen_crypto(L: *mut lua_State) -> c_int; + pub fn luaopen_ffi(L: *mut lua_State) -> c_int; + pub fn luaopen_http(L: *mut lua_State) -> c_int; + pub fn luaopen_json(L: *mut lua_State) -> c_int; + pub fn luaopen_regex(L: *mut lua_State) -> c_int; + pub fn luaopen_scheduler(L: *mut lua_State) -> c_int; + pub fn luaopen_socket(L: *mut lua_State) -> c_int; + pub fn luaopen_url(L: *mut lua_State) -> c_int; + pub fn luaopen_vector3(L: *mut lua_State) -> c_int; + pub fn luaopen_xml(L: *mut lua_State) -> c_int; +} diff --git a/mlua-sys/src/luau/lualib.rs b/mlua-sys/src/luau/lualib.rs index 0469bfd1..834f09e6 100644 --- a/mlua-sys/src/luau/lualib.rs +++ b/mlua-sys/src/luau/lualib.rs @@ -1,19 +1,19 @@ //! Contains definitions from `lualib.h`. -use std::os::raw::c_int; +use std::os::raw::{c_char, c_int}; use super::lua::lua_State; -pub const LUA_COLIBNAME: &str = "coroutine"; -pub const LUA_TABLIBNAME: &str = "table"; -pub const LUA_OSLIBNAME: &str = "os"; -pub const LUA_STRLIBNAME: &str = "string"; -pub const LUA_BITLIBNAME: &str = "bit32"; -pub const LUA_BUFFERLIBNAME: &str = "buffer"; -pub const LUA_UTF8LIBNAME: &str = "utf8"; -pub const LUA_MATHLIBNAME: &str = "math"; -pub const LUA_DBLIBNAME: &str = "debug"; -pub const LUA_VECLIBNAME: &str = "vector"; +pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); +pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); +pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); +pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); +pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32"); +pub const LUA_BUFFERLIBNAME: *const c_char = cstr!("buffer"); +pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8"); +pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); +pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); +pub const LUA_VECLIBNAME: *const c_char = cstr!("vector"); extern "C-unwind" { pub fn luaopen_base(L: *mut lua_State) -> c_int; diff --git a/src/state/raw.rs b/src/state/raw.rs index d91d8bd6..299ea296 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -1,6 +1,6 @@ use std::any::TypeId; use std::cell::{Cell, UnsafeCell}; -use std::ffi::{CStr, CString}; +use std::ffi::CStr; use std::mem; use std::os::raw::{c_char, c_int, c_void}; use std::panic::resume_unwind; @@ -1351,16 +1351,14 @@ impl RawLua { // Uses 3 stack spaces unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> { - #[inline(always)] - pub unsafe fn requiref( + unsafe fn requiref( state: *mut ffi::lua_State, - modname: &str, + modname: *const c_char, openf: ffi::lua_CFunction, glb: c_int, ) -> Result<()> { - let modname = mlua_expect!(CString::new(modname), "modname contains nil byte"); - protect_lua!(state, 0, 1, |state| { - ffi::luaL_requiref(state, modname.as_ptr() as *const c_char, openf, glb) + protect_lua!(state, 0, 0, |state| { + ffi::luaL_requiref(state, modname, openf, glb) }) } @@ -1391,36 +1389,30 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> { if libs.contains(StdLib::COROUTINE) { requiref(state, ffi::LUA_COLIBNAME, ffi::luaopen_coroutine, 1)?; - ffi::lua_pop(state, 1); } } if libs.contains(StdLib::TABLE) { requiref(state, ffi::LUA_TABLIBNAME, ffi::luaopen_table, 1)?; - ffi::lua_pop(state, 1); } #[cfg(not(feature = "luau"))] if libs.contains(StdLib::IO) { requiref(state, ffi::LUA_IOLIBNAME, ffi::luaopen_io, 1)?; - ffi::lua_pop(state, 1); } if libs.contains(StdLib::OS) { requiref(state, ffi::LUA_OSLIBNAME, ffi::luaopen_os, 1)?; - ffi::lua_pop(state, 1); } if libs.contains(StdLib::STRING) { requiref(state, ffi::LUA_STRLIBNAME, ffi::luaopen_string, 1)?; - ffi::lua_pop(state, 1); } #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] { if libs.contains(StdLib::UTF8) { requiref(state, ffi::LUA_UTF8LIBNAME, ffi::luaopen_utf8, 1)?; - ffi::lua_pop(state, 1); } } @@ -1428,7 +1420,6 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> { if libs.contains(StdLib::BIT) { requiref(state, ffi::LUA_BITLIBNAME, ffi::luaopen_bit32, 1)?; - ffi::lua_pop(state, 1); } } @@ -1436,36 +1427,30 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> { if libs.contains(StdLib::BIT) { requiref(state, ffi::LUA_BITLIBNAME, ffi::luaopen_bit, 1)?; - ffi::lua_pop(state, 1); } } #[cfg(feature = "luau")] if libs.contains(StdLib::BUFFER) { requiref(state, ffi::LUA_BUFFERLIBNAME, ffi::luaopen_buffer, 1)?; - ffi::lua_pop(state, 1); } #[cfg(feature = "luau")] if libs.contains(StdLib::VECTOR) { requiref(state, ffi::LUA_VECLIBNAME, ffi::luaopen_vector, 1)?; - ffi::lua_pop(state, 1); } if libs.contains(StdLib::MATH) { requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?; - ffi::lua_pop(state, 1); } if libs.contains(StdLib::DEBUG) { requiref(state, ffi::LUA_DBLIBNAME, ffi::luaopen_debug, 1)?; - ffi::lua_pop(state, 1); } #[cfg(not(feature = "luau"))] if libs.contains(StdLib::PACKAGE) { requiref(state, ffi::LUA_LOADLIBNAME, ffi::luaopen_package, 1)?; - ffi::lua_pop(state, 1); } #[cfg(feature = "luau")] if libs.contains(StdLib::PACKAGE) { @@ -1476,13 +1461,76 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> #[cfg(feature = "luajit")] if libs.contains(StdLib::JIT) { requiref(state, ffi::LUA_JITLIBNAME, ffi::luaopen_jit, 1)?; - ffi::lua_pop(state, 1); } #[cfg(feature = "luajit")] if libs.contains(StdLib::FFI) { requiref(state, ffi::LUA_FFILIBNAME, ffi::luaopen_ffi, 1)?; - ffi::lua_pop(state, 1); + } + + // Preloaded pluto libraries + #[cfg(feature = "pluto")] + { + unsafe fn preload( + state: *mut ffi::lua_State, + modname: *const c_char, + openf: ffi::lua_CFunction, + ) -> Result<()> { + protect_lua!(state, 0, 0, |state| { + ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_PRELOAD_TABLE); + ffi::lua_pushcfunction(state, openf); + ffi::lua_setfield(state, -2, modname); + }) + } + + if libs.contains(StdLib::ASSERT) { + preload(state, cstr!("assert"), ffi::luaopen_assert)?; + } + if libs.contains(StdLib::BASE32) { + preload(state, cstr!("base32"), ffi::luaopen_base32)?; + } + if libs.contains(StdLib::BASE64) { + preload(state, cstr!("base64"), ffi::luaopen_base64)?; + } + if libs.contains(StdLib::BIGINT) { + preload(state, cstr!("bigint"), ffi::luaopen_bigint)?; + } + if libs.contains(StdLib::CANVAS) { + preload(state, cstr!("canvas"), ffi::luaopen_canvas)?; + } + if libs.contains(StdLib::CAT) { + preload(state, cstr!("cat"), ffi::luaopen_cat)?; + } + if libs.contains(StdLib::CRYPTO) { + preload(state, cstr!("crypto"), ffi::luaopen_crypto)?; + } + if libs.contains(StdLib::FFI) { + preload(state, cstr!("ffi"), ffi::luaopen_ffi)?; + } + if libs.contains(StdLib::HTTP) { + preload(state, cstr!("http"), ffi::luaopen_http)?; + } + if libs.contains(StdLib::JSON) { + preload(state, cstr!("json"), ffi::luaopen_json)?; + } + if libs.contains(StdLib::REGEX) { + preload(state, cstr!("regex"), ffi::luaopen_regex)?; + } + if libs.contains(StdLib::SCHEDULER) { + preload(state, cstr!("scheduler"), ffi::luaopen_scheduler)?; + } + if libs.contains(StdLib::SOCKET) { + preload(state, cstr!("socket"), ffi::luaopen_socket)?; + } + if libs.contains(StdLib::URL) { + preload(state, cstr!("url"), ffi::luaopen_url)?; + } + if libs.contains(StdLib::VECTOR3) { + preload(state, cstr!("vector3"), ffi::luaopen_vector3)?; + } + if libs.contains(StdLib::XML) { + preload(state, cstr!("xml"), ffi::luaopen_xml)?; + } } Ok(()) diff --git a/src/stdlib.rs b/src/stdlib.rs index 787b2fcb..e9531ce6 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -60,11 +60,9 @@ impl StdLib { #[cfg_attr(docsrs, doc(cfg(feature = "luajit")))] pub const JIT: StdLib = StdLib(1 << 11); - /// (**unsafe**) [`ffi`](http://luajit.org/ext_ffi.html) library - /// - /// Requires `feature = "luajit"` - #[cfg(any(feature = "luajit", doc))] - #[cfg_attr(docsrs, doc(cfg(feature = "luajit")))] + /// (**unsafe**) FFI library + #[cfg(any(feature = "luajit", feature = "pluto", doc))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "luajit", feature = "pluto"))))] pub const FFI: StdLib = StdLib(1 << 30); /// (**unsafe**) [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10) library @@ -85,6 +83,57 @@ impl StdLib { } } +#[cfg(feature = "pluto")] +#[cfg_attr(docsrs, doc(cfg(feature = "pluto")))] +impl StdLib { + /// Extended assertion utilities library + pub const ASSERT: StdLib = StdLib(1 << 12); + + /// Base32 encoding/decoding library + pub const BASE32: StdLib = StdLib(1 << 13); + + /// Base64 encoding/decoding library + pub const BASE64: StdLib = StdLib(1 << 14); + + /// Arbitrary-precision integer arithmetic library + pub const BIGINT: StdLib = StdLib(1 << 15); + + /// 2D graphics library + pub const CANVAS: StdLib = StdLib(1 << 16); + + /// Encoding and decoding library for the [Colons and Tabs] format. + /// + /// [Colons and Tabs]: https://github.com/calamity-inc/Soup/blob/senpai/docs/user/cat.md + pub const CAT: StdLib = StdLib(1 << 17); + + /// Cryptographic library + pub const CRYPTO: StdLib = StdLib(1 << 18); + + /// HTTP client library + pub const HTTP: StdLib = StdLib(1 << 19); + + /// JSON encoding/decoding library + pub const JSON: StdLib = StdLib(1 << 20); + + /// Regular expression library + pub const REGEX: StdLib = StdLib(1 << 21); + + /// Task scheduling library + pub const SCHEDULER: StdLib = StdLib(1 << 22); + + /// Network socket library + pub const SOCKET: StdLib = StdLib(1 << 23); + + /// URL parsing library + pub const URL: StdLib = StdLib(1 << 24); + + /// 3D vector library + pub const VECTOR3: StdLib = StdLib(1 << 25); + + /// XML encoding/decoding library + pub const XML: StdLib = StdLib(1 << 26); +} + impl BitAnd for StdLib { type Output = Self; fn bitand(self, rhs: Self) -> Self::Output { diff --git a/src/util/error.rs b/src/util/error.rs index 3bf08c6c..7f0c6ddf 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -130,17 +130,19 @@ pub(crate) unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> E } } _ => { - let err_string = to_string(state, -1); + let message = to_string(state, -1); ffi::lua_pop(state, 1); match err_code { - ffi::LUA_ERRRUN => Error::RuntimeError(err_string), + ffi::LUA_ERRRUN => Error::RuntimeError(message), ffi::LUA_ERRSYNTAX => { Error::SyntaxError { // This seems terrible, but as far as I can tell, this is exactly what the // stock Lua REPL does. - incomplete_input: err_string.ends_with("") || err_string.ends_with("''"), - message: err_string, + incomplete_input: message.ends_with("") + || message.ends_with("''") + || message.contains("near ''"), + message, } } ffi::LUA_ERRERR => { @@ -148,11 +150,11 @@ pub(crate) unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> E // recursively, and continuing to trigger the error handler would cause a stack // overflow. It is not very useful to differentiate between this and "ordinary" // runtime errors, so we handle them the same way. - Error::RuntimeError(err_string) + Error::RuntimeError(message) } - ffi::LUA_ERRMEM => Error::MemoryError(err_string), + ffi::LUA_ERRMEM => Error::MemoryError(message), #[cfg(any(feature = "lua53", feature = "lua52"))] - ffi::LUA_ERRGCMM => Error::GarbageCollectorError(err_string), + ffi::LUA_ERRGCMM => Error::GarbageCollectorError(message), _ => mlua_panic!("unrecognized lua error code"), } } diff --git a/tests/pluto.rs b/tests/pluto.rs new file mode 100644 index 00000000..d837c37d --- /dev/null +++ b/tests/pluto.rs @@ -0,0 +1,22 @@ +#![cfg(feature = "pluto")] + +use mlua::{Lua, Result}; + +#[test] +fn test_pluto_libs() -> Result<()> { + let lua = Lua::new(); + + lua.load( + r#" + local json = require("pluto:json") + local assert = require("pluto:assert") + local data = { foo = "bar" } + local str = json.encode(data) + assert.equal(str, '{"foo":"bar"}') + "#, + ) + .exec() + .unwrap(); + + Ok(()) +} From 69011a89d28dff88723f6f454b4d9d2289ad03b9 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 14 Mar 2025 22:59:06 +0000 Subject: [PATCH 349/635] Revert "Add initial pluto support" This reverts commit 0ed11e4134d91449ca35edf5ebb97a302657c902. --- .github/workflows/main.yml | 10 ++-- Cargo.toml | 1 - README.md | 2 - mlua-sys/Cargo.toml | 6 +-- mlua-sys/build/find_vendored.rs | 5 +- mlua-sys/build/main.rs | 2 +- mlua-sys/build/main_inner.rs | 2 +- mlua-sys/src/lua51/lualib.rs | 24 ++++----- mlua-sys/src/lua52/lualib.rs | 20 +++---- mlua-sys/src/lua53/lauxlib.rs | 4 +- mlua-sys/src/lua53/lualib.rs | 22 ++++---- mlua-sys/src/lua54/lauxlib.rs | 4 +- mlua-sys/src/lua54/lualib.rs | 40 ++++---------- mlua-sys/src/luau/lualib.rs | 22 ++++---- src/state/raw.rs | 92 ++++++++------------------------- src/stdlib.rs | 59 ++------------------- src/util/error.rs | 16 +++--- tests/pluto.rs | 22 -------- 18 files changed, 102 insertions(+), 251 deletions(-) delete mode 100644 tests/pluto.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f6646fd4..cd12c620 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] rust: [stable] - lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4, pluto] + lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu @@ -105,7 +105,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] rust: [stable, nightly] - lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit, luau-vector4, pluto] + lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit, luau-vector4] include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu @@ -141,7 +141,7 @@ jobs: matrix: os: [ubuntu-latest] rust: [nightly] - lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4, pluto] + lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu @@ -168,7 +168,7 @@ jobs: matrix: os: [ubuntu-latest] rust: [nightly] - lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4, pluto] + lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu @@ -271,7 +271,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4, pluto] + lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] steps: - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@stable diff --git a/Cargo.toml b/Cargo.toml index 0dcbee44..91f373b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,6 @@ luajit52 = ["luajit", "ffi/luajit52"] luau = ["ffi/luau", "dep:libloading"] luau-jit = ["luau", "ffi/luau-codegen"] luau-vector4 = ["luau", "ffi/luau-vector4"] -pluto = ["lua54", "ffi/pluto"] vendored = ["ffi/vendored"] module = ["dep:mlua_derive", "ffi/module"] async = ["dep:futures-util"] diff --git a/README.md b/README.md index f00176aa..747f4821 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,6 @@ Below is a list of the available feature flags. By default `mlua` does not enabl * `luau`: enable [Luau] support (auto vendored mode) * `luau-jit`: enable [Luau] support with JIT backend. * `luau-vector4`: enable [Luau] support with 4-dimensional vector. -* `pluto`: enable [Pluto] support (Lua 5.4 dialect, auto vendored mode) * `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]) @@ -68,7 +67,6 @@ Below is a list of the available feature flags. By default `mlua` does not enabl [5.1]: https://www.lua.org/manual/5.1/manual.html [LuaJIT]: https://luajit.org/ [Luau]: https://github.com/luau-lang/luau -[Pluto]: https://github.com/PlutoLang/Pluto [lua-src]: https://github.com/khvzak/lua-src-rs [luajit-src]: https://github.com/khvzak/luajit-src-rs [tokio]: https://github.com/tokio-rs/tokio diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 825fa26c..26736289 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -26,11 +26,10 @@ lua52 = [] lua51 = [] luajit = [] luajit52 = ["luajit"] -luau = ["dep:luau0-src"] +luau = ["luau0-src"] luau-codegen = ["luau"] luau-vector4 = ["luau"] -pluto = ["lua54", "dep:pluto-src"] -vendored = ["dep:lua-src", "dep:luajit-src"] +vendored = ["lua-src", "luajit-src"] module = [] [dependencies] @@ -42,7 +41,6 @@ pkg-config = "0.3.17" lua-src = { version = ">= 547.0.0, < 547.1.0", optional = true } luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } luau0-src = { version = "0.12.0", optional = true } -pluto-src = { version = "0.1.1", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/mlua-sys/build/find_vendored.rs b/mlua-sys/build/find_vendored.rs index 7c09b46b..e3ddbecf 100644 --- a/mlua-sys/build/find_vendored.rs +++ b/mlua-sys/build/find_vendored.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] pub fn probe_lua() { - #[cfg(all(feature = "lua54", not(feature = "pluto")))] + #[cfg(feature = "lua54")] let artifacts = lua_src::Build::new().build(lua_src::Lua54); #[cfg(feature = "lua53")] @@ -25,8 +25,5 @@ pub fn probe_lua() { .set_vector_size(if cfg!(feature = "luau-vector4") { 4 } else { 3 }) .build(); - #[cfg(feature = "pluto")] - let artifacts = pluto_src::Build::new().use_longjmp(true).build(); - artifacts.print_cargo_metadata(); } diff --git a/mlua-sys/build/main.rs b/mlua-sys/build/main.rs index 4eff2d90..53074f8e 100644 --- a/mlua-sys/build/main.rs +++ b/mlua-sys/build/main.rs @@ -13,7 +13,7 @@ cfg_if::cfg_if! { include!("main_inner.rs"); } else { fn main() { - compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau, pluto"); + compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau"); } } } diff --git a/mlua-sys/build/main_inner.rs b/mlua-sys/build/main_inner.rs index 19375820..05ac53b5 100644 --- a/mlua-sys/build/main_inner.rs +++ b/mlua-sys/build/main_inner.rs @@ -1,7 +1,7 @@ use std::env; cfg_if::cfg_if! { - if #[cfg(any(feature = "luau", feature = "pluto", feature = "vendored"))] { + if #[cfg(any(feature = "luau", feature = "vendored"))] { #[path = "find_vendored.rs"] mod find; } else { diff --git a/mlua-sys/src/lua51/lualib.rs b/mlua-sys/src/lua51/lualib.rs index de994ce1..9ef0b214 100644 --- a/mlua-sys/src/lua51/lualib.rs +++ b/mlua-sys/src/lua51/lualib.rs @@ -1,24 +1,24 @@ //! Contains definitions from `lualib.h`. -use std::os::raw::{c_char, c_int}; +use std::os::raw::c_int; use super::lua::lua_State; -pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); -pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); -pub const LUA_IOLIBNAME: *const c_char = cstr!("io"); -pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); -pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); -pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); -pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); -pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); +pub const LUA_COLIBNAME: &str = "coroutine"; +pub const LUA_TABLIBNAME: &str = "table"; +pub const LUA_IOLIBNAME: &str = "io"; +pub const LUA_OSLIBNAME: &str = "os"; +pub const LUA_STRLIBNAME: &str = "string"; +pub const LUA_MATHLIBNAME: &str = "math"; +pub const LUA_DBLIBNAME: &str = "debug"; +pub const LUA_LOADLIBNAME: &str = "package"; #[cfg(feature = "luajit")] -pub const LUA_BITLIBNAME: *const c_char = cstr!("bit"); +pub const LUA_BITLIBNAME: &str = "bit"; #[cfg(feature = "luajit")] -pub const LUA_JITLIBNAME: *const c_char = cstr!("jit"); +pub const LUA_JITLIBNAME: &str = "jit"; #[cfg(feature = "luajit")] -pub const LUA_FFILIBNAME: *const c_char = cstr!("ffi"); +pub const LUA_FFILIBNAME: &str = "ffi"; #[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))] extern "C-unwind" { diff --git a/mlua-sys/src/lua52/lualib.rs b/mlua-sys/src/lua52/lualib.rs index a9ed21f7..daf1e2a6 100644 --- a/mlua-sys/src/lua52/lualib.rs +++ b/mlua-sys/src/lua52/lualib.rs @@ -1,18 +1,18 @@ //! Contains definitions from `lualib.h`. -use std::os::raw::{c_char, c_int}; +use std::os::raw::c_int; use super::lua::lua_State; -pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); -pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); -pub const LUA_IOLIBNAME: *const c_char = cstr!("io"); -pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); -pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); -pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32"); -pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); -pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); -pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); +pub const LUA_COLIBNAME: &str = "coroutine"; +pub const LUA_TABLIBNAME: &str = "table"; +pub const LUA_IOLIBNAME: &str = "io"; +pub const LUA_OSLIBNAME: &str = "os"; +pub const LUA_STRLIBNAME: &str = "string"; +pub const LUA_BITLIBNAME: &str = "bit32"; +pub const LUA_MATHLIBNAME: &str = "math"; +pub const LUA_DBLIBNAME: &str = "debug"; +pub const LUA_LOADLIBNAME: &str = "package"; #[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))] extern "C-unwind" { diff --git a/mlua-sys/src/lua53/lauxlib.rs b/mlua-sys/src/lua53/lauxlib.rs index 53c45bd0..7c851ac1 100644 --- a/mlua-sys/src/lua53/lauxlib.rs +++ b/mlua-sys/src/lua53/lauxlib.rs @@ -9,10 +9,10 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State}; pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1; // Key, in the registry, for table of loaded modules -pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED"); +pub const LUA_LOADED_TABLE: &str = "_LOADED"; // Key, in the registry, for table of preloaded loaders -pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD"); +pub const LUA_PRELOAD_TABLE: &str = "_PRELOAD"; #[repr(C)] pub struct luaL_Reg { diff --git a/mlua-sys/src/lua53/lualib.rs b/mlua-sys/src/lua53/lualib.rs index 0b556d41..5d7509e2 100644 --- a/mlua-sys/src/lua53/lualib.rs +++ b/mlua-sys/src/lua53/lualib.rs @@ -1,19 +1,19 @@ //! Contains definitions from `lualib.h`. -use std::os::raw::{c_char, c_int}; +use std::os::raw::c_int; use super::lua::lua_State; -pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); -pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); -pub const LUA_IOLIBNAME: *const c_char = cstr!("io"); -pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); -pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); -pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8"); -pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32"); -pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); -pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); -pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); +pub const LUA_COLIBNAME: &str = "coroutine"; +pub const LUA_TABLIBNAME: &str = "table"; +pub const LUA_IOLIBNAME: &str = "io"; +pub const LUA_OSLIBNAME: &str = "os"; +pub const LUA_STRLIBNAME: &str = "string"; +pub const LUA_UTF8LIBNAME: &str = "utf8"; +pub const LUA_BITLIBNAME: &str = "bit32"; +pub const LUA_MATHLIBNAME: &str = "math"; +pub const LUA_DBLIBNAME: &str = "debug"; +pub const LUA_LOADLIBNAME: &str = "package"; #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] extern "C-unwind" { diff --git a/mlua-sys/src/lua54/lauxlib.rs b/mlua-sys/src/lua54/lauxlib.rs index 8a1fd12d..78b0881e 100644 --- a/mlua-sys/src/lua54/lauxlib.rs +++ b/mlua-sys/src/lua54/lauxlib.rs @@ -9,10 +9,10 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State}; pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1; // Key, in the registry, for table of loaded modules -pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED"); +pub const LUA_LOADED_TABLE: &str = "_LOADED"; // Key, in the registry, for table of preloaded loaders -pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD"); +pub const LUA_PRELOAD_TABLE: &str = "_PRELOAD"; #[repr(C)] pub struct luaL_Reg { diff --git a/mlua-sys/src/lua54/lualib.rs b/mlua-sys/src/lua54/lualib.rs index 4cf30027..c104375f 100644 --- a/mlua-sys/src/lua54/lualib.rs +++ b/mlua-sys/src/lua54/lualib.rs @@ -1,18 +1,18 @@ //! Contains definitions from `lualib.h`. -use std::os::raw::{c_char, c_int}; +use std::os::raw::c_int; use super::lua::lua_State; -pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); -pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); -pub const LUA_IOLIBNAME: *const c_char = cstr!("io"); -pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); -pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); -pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8"); -pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); -pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); -pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); +pub const LUA_COLIBNAME: &str = "coroutine"; +pub const LUA_TABLIBNAME: &str = "table"; +pub const LUA_IOLIBNAME: &str = "io"; +pub const LUA_OSLIBNAME: &str = "os"; +pub const LUA_STRLIBNAME: &str = "string"; +pub const LUA_UTF8LIBNAME: &str = "utf8"; +pub const LUA_MATHLIBNAME: &str = "math"; +pub const LUA_DBLIBNAME: &str = "debug"; +pub const LUA_LOADLIBNAME: &str = "package"; #[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] extern "C-unwind" { @@ -30,23 +30,3 @@ extern "C-unwind" { // open all builtin libraries pub fn luaL_openlibs(L: *mut lua_State); } - -#[cfg(feature = "pluto")] -extern "C-unwind" { - pub fn luaopen_assert(L: *mut lua_State) -> c_int; - pub fn luaopen_base32(L: *mut lua_State) -> c_int; - pub fn luaopen_base64(L: *mut lua_State) -> c_int; - pub fn luaopen_bigint(L: *mut lua_State) -> c_int; - pub fn luaopen_cat(L: *mut lua_State) -> c_int; - pub fn luaopen_canvas(L: *mut lua_State) -> c_int; - pub fn luaopen_crypto(L: *mut lua_State) -> c_int; - pub fn luaopen_ffi(L: *mut lua_State) -> c_int; - pub fn luaopen_http(L: *mut lua_State) -> c_int; - pub fn luaopen_json(L: *mut lua_State) -> c_int; - pub fn luaopen_regex(L: *mut lua_State) -> c_int; - pub fn luaopen_scheduler(L: *mut lua_State) -> c_int; - pub fn luaopen_socket(L: *mut lua_State) -> c_int; - pub fn luaopen_url(L: *mut lua_State) -> c_int; - pub fn luaopen_vector3(L: *mut lua_State) -> c_int; - pub fn luaopen_xml(L: *mut lua_State) -> c_int; -} diff --git a/mlua-sys/src/luau/lualib.rs b/mlua-sys/src/luau/lualib.rs index 834f09e6..0469bfd1 100644 --- a/mlua-sys/src/luau/lualib.rs +++ b/mlua-sys/src/luau/lualib.rs @@ -1,19 +1,19 @@ //! Contains definitions from `lualib.h`. -use std::os::raw::{c_char, c_int}; +use std::os::raw::c_int; use super::lua::lua_State; -pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); -pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); -pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); -pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); -pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32"); -pub const LUA_BUFFERLIBNAME: *const c_char = cstr!("buffer"); -pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8"); -pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); -pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); -pub const LUA_VECLIBNAME: *const c_char = cstr!("vector"); +pub const LUA_COLIBNAME: &str = "coroutine"; +pub const LUA_TABLIBNAME: &str = "table"; +pub const LUA_OSLIBNAME: &str = "os"; +pub const LUA_STRLIBNAME: &str = "string"; +pub const LUA_BITLIBNAME: &str = "bit32"; +pub const LUA_BUFFERLIBNAME: &str = "buffer"; +pub const LUA_UTF8LIBNAME: &str = "utf8"; +pub const LUA_MATHLIBNAME: &str = "math"; +pub const LUA_DBLIBNAME: &str = "debug"; +pub const LUA_VECLIBNAME: &str = "vector"; extern "C-unwind" { pub fn luaopen_base(L: *mut lua_State) -> c_int; diff --git a/src/state/raw.rs b/src/state/raw.rs index 299ea296..d91d8bd6 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -1,6 +1,6 @@ use std::any::TypeId; use std::cell::{Cell, UnsafeCell}; -use std::ffi::CStr; +use std::ffi::{CStr, CString}; use std::mem; use std::os::raw::{c_char, c_int, c_void}; use std::panic::resume_unwind; @@ -1351,14 +1351,16 @@ impl RawLua { // Uses 3 stack spaces unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> { - unsafe fn requiref( + #[inline(always)] + pub unsafe fn requiref( state: *mut ffi::lua_State, - modname: *const c_char, + modname: &str, openf: ffi::lua_CFunction, glb: c_int, ) -> Result<()> { - protect_lua!(state, 0, 0, |state| { - ffi::luaL_requiref(state, modname, openf, glb) + let modname = mlua_expect!(CString::new(modname), "modname contains nil byte"); + protect_lua!(state, 0, 1, |state| { + ffi::luaL_requiref(state, modname.as_ptr() as *const c_char, openf, glb) }) } @@ -1389,30 +1391,36 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> { if libs.contains(StdLib::COROUTINE) { requiref(state, ffi::LUA_COLIBNAME, ffi::luaopen_coroutine, 1)?; + ffi::lua_pop(state, 1); } } if libs.contains(StdLib::TABLE) { requiref(state, ffi::LUA_TABLIBNAME, ffi::luaopen_table, 1)?; + ffi::lua_pop(state, 1); } #[cfg(not(feature = "luau"))] if libs.contains(StdLib::IO) { requiref(state, ffi::LUA_IOLIBNAME, ffi::luaopen_io, 1)?; + ffi::lua_pop(state, 1); } if libs.contains(StdLib::OS) { requiref(state, ffi::LUA_OSLIBNAME, ffi::luaopen_os, 1)?; + ffi::lua_pop(state, 1); } if libs.contains(StdLib::STRING) { requiref(state, ffi::LUA_STRLIBNAME, ffi::luaopen_string, 1)?; + ffi::lua_pop(state, 1); } #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] { if libs.contains(StdLib::UTF8) { requiref(state, ffi::LUA_UTF8LIBNAME, ffi::luaopen_utf8, 1)?; + ffi::lua_pop(state, 1); } } @@ -1420,6 +1428,7 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> { if libs.contains(StdLib::BIT) { requiref(state, ffi::LUA_BITLIBNAME, ffi::luaopen_bit32, 1)?; + ffi::lua_pop(state, 1); } } @@ -1427,30 +1436,36 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> { if libs.contains(StdLib::BIT) { requiref(state, ffi::LUA_BITLIBNAME, ffi::luaopen_bit, 1)?; + ffi::lua_pop(state, 1); } } #[cfg(feature = "luau")] if libs.contains(StdLib::BUFFER) { requiref(state, ffi::LUA_BUFFERLIBNAME, ffi::luaopen_buffer, 1)?; + ffi::lua_pop(state, 1); } #[cfg(feature = "luau")] if libs.contains(StdLib::VECTOR) { requiref(state, ffi::LUA_VECLIBNAME, ffi::luaopen_vector, 1)?; + ffi::lua_pop(state, 1); } if libs.contains(StdLib::MATH) { requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?; + ffi::lua_pop(state, 1); } if libs.contains(StdLib::DEBUG) { requiref(state, ffi::LUA_DBLIBNAME, ffi::luaopen_debug, 1)?; + ffi::lua_pop(state, 1); } #[cfg(not(feature = "luau"))] if libs.contains(StdLib::PACKAGE) { requiref(state, ffi::LUA_LOADLIBNAME, ffi::luaopen_package, 1)?; + ffi::lua_pop(state, 1); } #[cfg(feature = "luau")] if libs.contains(StdLib::PACKAGE) { @@ -1461,76 +1476,13 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> #[cfg(feature = "luajit")] if libs.contains(StdLib::JIT) { requiref(state, ffi::LUA_JITLIBNAME, ffi::luaopen_jit, 1)?; + ffi::lua_pop(state, 1); } #[cfg(feature = "luajit")] if libs.contains(StdLib::FFI) { requiref(state, ffi::LUA_FFILIBNAME, ffi::luaopen_ffi, 1)?; - } - - // Preloaded pluto libraries - #[cfg(feature = "pluto")] - { - unsafe fn preload( - state: *mut ffi::lua_State, - modname: *const c_char, - openf: ffi::lua_CFunction, - ) -> Result<()> { - protect_lua!(state, 0, 0, |state| { - ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_PRELOAD_TABLE); - ffi::lua_pushcfunction(state, openf); - ffi::lua_setfield(state, -2, modname); - }) - } - - if libs.contains(StdLib::ASSERT) { - preload(state, cstr!("assert"), ffi::luaopen_assert)?; - } - if libs.contains(StdLib::BASE32) { - preload(state, cstr!("base32"), ffi::luaopen_base32)?; - } - if libs.contains(StdLib::BASE64) { - preload(state, cstr!("base64"), ffi::luaopen_base64)?; - } - if libs.contains(StdLib::BIGINT) { - preload(state, cstr!("bigint"), ffi::luaopen_bigint)?; - } - if libs.contains(StdLib::CANVAS) { - preload(state, cstr!("canvas"), ffi::luaopen_canvas)?; - } - if libs.contains(StdLib::CAT) { - preload(state, cstr!("cat"), ffi::luaopen_cat)?; - } - if libs.contains(StdLib::CRYPTO) { - preload(state, cstr!("crypto"), ffi::luaopen_crypto)?; - } - if libs.contains(StdLib::FFI) { - preload(state, cstr!("ffi"), ffi::luaopen_ffi)?; - } - if libs.contains(StdLib::HTTP) { - preload(state, cstr!("http"), ffi::luaopen_http)?; - } - if libs.contains(StdLib::JSON) { - preload(state, cstr!("json"), ffi::luaopen_json)?; - } - if libs.contains(StdLib::REGEX) { - preload(state, cstr!("regex"), ffi::luaopen_regex)?; - } - if libs.contains(StdLib::SCHEDULER) { - preload(state, cstr!("scheduler"), ffi::luaopen_scheduler)?; - } - if libs.contains(StdLib::SOCKET) { - preload(state, cstr!("socket"), ffi::luaopen_socket)?; - } - if libs.contains(StdLib::URL) { - preload(state, cstr!("url"), ffi::luaopen_url)?; - } - if libs.contains(StdLib::VECTOR3) { - preload(state, cstr!("vector3"), ffi::luaopen_vector3)?; - } - if libs.contains(StdLib::XML) { - preload(state, cstr!("xml"), ffi::luaopen_xml)?; - } + ffi::lua_pop(state, 1); } Ok(()) diff --git a/src/stdlib.rs b/src/stdlib.rs index e9531ce6..787b2fcb 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -60,9 +60,11 @@ impl StdLib { #[cfg_attr(docsrs, doc(cfg(feature = "luajit")))] pub const JIT: StdLib = StdLib(1 << 11); - /// (**unsafe**) FFI library - #[cfg(any(feature = "luajit", feature = "pluto", doc))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "luajit", feature = "pluto"))))] + /// (**unsafe**) [`ffi`](http://luajit.org/ext_ffi.html) library + /// + /// Requires `feature = "luajit"` + #[cfg(any(feature = "luajit", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luajit")))] pub const FFI: StdLib = StdLib(1 << 30); /// (**unsafe**) [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10) library @@ -83,57 +85,6 @@ impl StdLib { } } -#[cfg(feature = "pluto")] -#[cfg_attr(docsrs, doc(cfg(feature = "pluto")))] -impl StdLib { - /// Extended assertion utilities library - pub const ASSERT: StdLib = StdLib(1 << 12); - - /// Base32 encoding/decoding library - pub const BASE32: StdLib = StdLib(1 << 13); - - /// Base64 encoding/decoding library - pub const BASE64: StdLib = StdLib(1 << 14); - - /// Arbitrary-precision integer arithmetic library - pub const BIGINT: StdLib = StdLib(1 << 15); - - /// 2D graphics library - pub const CANVAS: StdLib = StdLib(1 << 16); - - /// Encoding and decoding library for the [Colons and Tabs] format. - /// - /// [Colons and Tabs]: https://github.com/calamity-inc/Soup/blob/senpai/docs/user/cat.md - pub const CAT: StdLib = StdLib(1 << 17); - - /// Cryptographic library - pub const CRYPTO: StdLib = StdLib(1 << 18); - - /// HTTP client library - pub const HTTP: StdLib = StdLib(1 << 19); - - /// JSON encoding/decoding library - pub const JSON: StdLib = StdLib(1 << 20); - - /// Regular expression library - pub const REGEX: StdLib = StdLib(1 << 21); - - /// Task scheduling library - pub const SCHEDULER: StdLib = StdLib(1 << 22); - - /// Network socket library - pub const SOCKET: StdLib = StdLib(1 << 23); - - /// URL parsing library - pub const URL: StdLib = StdLib(1 << 24); - - /// 3D vector library - pub const VECTOR3: StdLib = StdLib(1 << 25); - - /// XML encoding/decoding library - pub const XML: StdLib = StdLib(1 << 26); -} - impl BitAnd for StdLib { type Output = Self; fn bitand(self, rhs: Self) -> Self::Output { diff --git a/src/util/error.rs b/src/util/error.rs index 7f0c6ddf..3bf08c6c 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -130,19 +130,17 @@ pub(crate) unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> E } } _ => { - let message = to_string(state, -1); + let err_string = to_string(state, -1); ffi::lua_pop(state, 1); match err_code { - ffi::LUA_ERRRUN => Error::RuntimeError(message), + ffi::LUA_ERRRUN => Error::RuntimeError(err_string), ffi::LUA_ERRSYNTAX => { Error::SyntaxError { // This seems terrible, but as far as I can tell, this is exactly what the // stock Lua REPL does. - incomplete_input: message.ends_with("") - || message.ends_with("''") - || message.contains("near ''"), - message, + incomplete_input: err_string.ends_with("") || err_string.ends_with("''"), + message: err_string, } } ffi::LUA_ERRERR => { @@ -150,11 +148,11 @@ pub(crate) unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> E // recursively, and continuing to trigger the error handler would cause a stack // overflow. It is not very useful to differentiate between this and "ordinary" // runtime errors, so we handle them the same way. - Error::RuntimeError(message) + Error::RuntimeError(err_string) } - ffi::LUA_ERRMEM => Error::MemoryError(message), + ffi::LUA_ERRMEM => Error::MemoryError(err_string), #[cfg(any(feature = "lua53", feature = "lua52"))] - ffi::LUA_ERRGCMM => Error::GarbageCollectorError(message), + ffi::LUA_ERRGCMM => Error::GarbageCollectorError(err_string), _ => mlua_panic!("unrecognized lua error code"), } } diff --git a/tests/pluto.rs b/tests/pluto.rs deleted file mode 100644 index d837c37d..00000000 --- a/tests/pluto.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![cfg(feature = "pluto")] - -use mlua::{Lua, Result}; - -#[test] -fn test_pluto_libs() -> Result<()> { - let lua = Lua::new(); - - lua.load( - r#" - local json = require("pluto:json") - local assert = require("pluto:assert") - local data = { foo = "bar" } - local str = json.encode(data) - assert.equal(str, '{"foo":"bar"}') - "#, - ) - .exec() - .unwrap(); - - Ok(()) -} From edbf1e91500b4703ec2b6dfc590593b3ce632acd Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 14 Mar 2025 23:10:14 +0000 Subject: [PATCH 350/635] Change Lua library name constants from `&str` to `*const c_char`. It makes easier to pass them to Lua API functions. --- mlua-sys/src/lua51/lualib.rs | 24 ++++++++++++------------ mlua-sys/src/lua52/lualib.rs | 20 ++++++++++---------- mlua-sys/src/lua53/lauxlib.rs | 4 ++-- mlua-sys/src/lua53/lualib.rs | 22 +++++++++++----------- mlua-sys/src/lua54/lauxlib.rs | 4 ++-- mlua-sys/src/lua54/lualib.rs | 20 ++++++++++---------- mlua-sys/src/luau/lualib.rs | 22 +++++++++++----------- src/state/raw.rs | 27 +++++---------------------- 8 files changed, 63 insertions(+), 80 deletions(-) diff --git a/mlua-sys/src/lua51/lualib.rs b/mlua-sys/src/lua51/lualib.rs index 9ef0b214..de994ce1 100644 --- a/mlua-sys/src/lua51/lualib.rs +++ b/mlua-sys/src/lua51/lualib.rs @@ -1,24 +1,24 @@ //! Contains definitions from `lualib.h`. -use std::os::raw::c_int; +use std::os::raw::{c_char, c_int}; use super::lua::lua_State; -pub const LUA_COLIBNAME: &str = "coroutine"; -pub const LUA_TABLIBNAME: &str = "table"; -pub const LUA_IOLIBNAME: &str = "io"; -pub const LUA_OSLIBNAME: &str = "os"; -pub const LUA_STRLIBNAME: &str = "string"; -pub const LUA_MATHLIBNAME: &str = "math"; -pub const LUA_DBLIBNAME: &str = "debug"; -pub const LUA_LOADLIBNAME: &str = "package"; +pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); +pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); +pub const LUA_IOLIBNAME: *const c_char = cstr!("io"); +pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); +pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); +pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); +pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); +pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); #[cfg(feature = "luajit")] -pub const LUA_BITLIBNAME: &str = "bit"; +pub const LUA_BITLIBNAME: *const c_char = cstr!("bit"); #[cfg(feature = "luajit")] -pub const LUA_JITLIBNAME: &str = "jit"; +pub const LUA_JITLIBNAME: *const c_char = cstr!("jit"); #[cfg(feature = "luajit")] -pub const LUA_FFILIBNAME: &str = "ffi"; +pub const LUA_FFILIBNAME: *const c_char = cstr!("ffi"); #[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))] extern "C-unwind" { diff --git a/mlua-sys/src/lua52/lualib.rs b/mlua-sys/src/lua52/lualib.rs index daf1e2a6..a9ed21f7 100644 --- a/mlua-sys/src/lua52/lualib.rs +++ b/mlua-sys/src/lua52/lualib.rs @@ -1,18 +1,18 @@ //! Contains definitions from `lualib.h`. -use std::os::raw::c_int; +use std::os::raw::{c_char, c_int}; use super::lua::lua_State; -pub const LUA_COLIBNAME: &str = "coroutine"; -pub const LUA_TABLIBNAME: &str = "table"; -pub const LUA_IOLIBNAME: &str = "io"; -pub const LUA_OSLIBNAME: &str = "os"; -pub const LUA_STRLIBNAME: &str = "string"; -pub const LUA_BITLIBNAME: &str = "bit32"; -pub const LUA_MATHLIBNAME: &str = "math"; -pub const LUA_DBLIBNAME: &str = "debug"; -pub const LUA_LOADLIBNAME: &str = "package"; +pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); +pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); +pub const LUA_IOLIBNAME: *const c_char = cstr!("io"); +pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); +pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); +pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32"); +pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); +pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); +pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); #[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))] extern "C-unwind" { diff --git a/mlua-sys/src/lua53/lauxlib.rs b/mlua-sys/src/lua53/lauxlib.rs index 7c851ac1..53c45bd0 100644 --- a/mlua-sys/src/lua53/lauxlib.rs +++ b/mlua-sys/src/lua53/lauxlib.rs @@ -9,10 +9,10 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State}; pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1; // Key, in the registry, for table of loaded modules -pub const LUA_LOADED_TABLE: &str = "_LOADED"; +pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED"); // Key, in the registry, for table of preloaded loaders -pub const LUA_PRELOAD_TABLE: &str = "_PRELOAD"; +pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD"); #[repr(C)] pub struct luaL_Reg { diff --git a/mlua-sys/src/lua53/lualib.rs b/mlua-sys/src/lua53/lualib.rs index 5d7509e2..0b556d41 100644 --- a/mlua-sys/src/lua53/lualib.rs +++ b/mlua-sys/src/lua53/lualib.rs @@ -1,19 +1,19 @@ //! Contains definitions from `lualib.h`. -use std::os::raw::c_int; +use std::os::raw::{c_char, c_int}; use super::lua::lua_State; -pub const LUA_COLIBNAME: &str = "coroutine"; -pub const LUA_TABLIBNAME: &str = "table"; -pub const LUA_IOLIBNAME: &str = "io"; -pub const LUA_OSLIBNAME: &str = "os"; -pub const LUA_STRLIBNAME: &str = "string"; -pub const LUA_UTF8LIBNAME: &str = "utf8"; -pub const LUA_BITLIBNAME: &str = "bit32"; -pub const LUA_MATHLIBNAME: &str = "math"; -pub const LUA_DBLIBNAME: &str = "debug"; -pub const LUA_LOADLIBNAME: &str = "package"; +pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); +pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); +pub const LUA_IOLIBNAME: *const c_char = cstr!("io"); +pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); +pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); +pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8"); +pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32"); +pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); +pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); +pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] extern "C-unwind" { diff --git a/mlua-sys/src/lua54/lauxlib.rs b/mlua-sys/src/lua54/lauxlib.rs index 78b0881e..8a1fd12d 100644 --- a/mlua-sys/src/lua54/lauxlib.rs +++ b/mlua-sys/src/lua54/lauxlib.rs @@ -9,10 +9,10 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State}; pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1; // Key, in the registry, for table of loaded modules -pub const LUA_LOADED_TABLE: &str = "_LOADED"; +pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED"); // Key, in the registry, for table of preloaded loaders -pub const LUA_PRELOAD_TABLE: &str = "_PRELOAD"; +pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD"); #[repr(C)] pub struct luaL_Reg { diff --git a/mlua-sys/src/lua54/lualib.rs b/mlua-sys/src/lua54/lualib.rs index c104375f..dec577bd 100644 --- a/mlua-sys/src/lua54/lualib.rs +++ b/mlua-sys/src/lua54/lualib.rs @@ -1,18 +1,18 @@ //! Contains definitions from `lualib.h`. -use std::os::raw::c_int; +use std::os::raw::{c_char, c_int}; use super::lua::lua_State; -pub const LUA_COLIBNAME: &str = "coroutine"; -pub const LUA_TABLIBNAME: &str = "table"; -pub const LUA_IOLIBNAME: &str = "io"; -pub const LUA_OSLIBNAME: &str = "os"; -pub const LUA_STRLIBNAME: &str = "string"; -pub const LUA_UTF8LIBNAME: &str = "utf8"; -pub const LUA_MATHLIBNAME: &str = "math"; -pub const LUA_DBLIBNAME: &str = "debug"; -pub const LUA_LOADLIBNAME: &str = "package"; +pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); +pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); +pub const LUA_IOLIBNAME: *const c_char = cstr!("io"); +pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); +pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); +pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8"); +pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); +pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); +pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); #[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] extern "C-unwind" { diff --git a/mlua-sys/src/luau/lualib.rs b/mlua-sys/src/luau/lualib.rs index 0469bfd1..834f09e6 100644 --- a/mlua-sys/src/luau/lualib.rs +++ b/mlua-sys/src/luau/lualib.rs @@ -1,19 +1,19 @@ //! Contains definitions from `lualib.h`. -use std::os::raw::c_int; +use std::os::raw::{c_char, c_int}; use super::lua::lua_State; -pub const LUA_COLIBNAME: &str = "coroutine"; -pub const LUA_TABLIBNAME: &str = "table"; -pub const LUA_OSLIBNAME: &str = "os"; -pub const LUA_STRLIBNAME: &str = "string"; -pub const LUA_BITLIBNAME: &str = "bit32"; -pub const LUA_BUFFERLIBNAME: &str = "buffer"; -pub const LUA_UTF8LIBNAME: &str = "utf8"; -pub const LUA_MATHLIBNAME: &str = "math"; -pub const LUA_DBLIBNAME: &str = "debug"; -pub const LUA_VECLIBNAME: &str = "vector"; +pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); +pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); +pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); +pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); +pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32"); +pub const LUA_BUFFERLIBNAME: *const c_char = cstr!("buffer"); +pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8"); +pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); +pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); +pub const LUA_VECLIBNAME: *const c_char = cstr!("vector"); extern "C-unwind" { pub fn luaopen_base(L: *mut lua_State) -> c_int; diff --git a/src/state/raw.rs b/src/state/raw.rs index d91d8bd6..17420696 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -1,6 +1,6 @@ use std::any::TypeId; use std::cell::{Cell, UnsafeCell}; -use std::ffi::{CStr, CString}; +use std::ffi::CStr; use std::mem; use std::os::raw::{c_char, c_int, c_void}; use std::panic::resume_unwind; @@ -1351,16 +1351,14 @@ impl RawLua { // Uses 3 stack spaces unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> { - #[inline(always)] - pub unsafe fn requiref( + unsafe fn requiref( state: *mut ffi::lua_State, - modname: &str, + modname: *const c_char, openf: ffi::lua_CFunction, glb: c_int, ) -> Result<()> { - let modname = mlua_expect!(CString::new(modname), "modname contains nil byte"); - protect_lua!(state, 0, 1, |state| { - ffi::luaL_requiref(state, modname.as_ptr() as *const c_char, openf, glb) + protect_lua!(state, 0, 0, |state| { + ffi::luaL_requiref(state, modname, openf, glb) }) } @@ -1391,36 +1389,30 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> { if libs.contains(StdLib::COROUTINE) { requiref(state, ffi::LUA_COLIBNAME, ffi::luaopen_coroutine, 1)?; - ffi::lua_pop(state, 1); } } if libs.contains(StdLib::TABLE) { requiref(state, ffi::LUA_TABLIBNAME, ffi::luaopen_table, 1)?; - ffi::lua_pop(state, 1); } #[cfg(not(feature = "luau"))] if libs.contains(StdLib::IO) { requiref(state, ffi::LUA_IOLIBNAME, ffi::luaopen_io, 1)?; - ffi::lua_pop(state, 1); } if libs.contains(StdLib::OS) { requiref(state, ffi::LUA_OSLIBNAME, ffi::luaopen_os, 1)?; - ffi::lua_pop(state, 1); } if libs.contains(StdLib::STRING) { requiref(state, ffi::LUA_STRLIBNAME, ffi::luaopen_string, 1)?; - ffi::lua_pop(state, 1); } #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] { if libs.contains(StdLib::UTF8) { requiref(state, ffi::LUA_UTF8LIBNAME, ffi::luaopen_utf8, 1)?; - ffi::lua_pop(state, 1); } } @@ -1428,7 +1420,6 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> { if libs.contains(StdLib::BIT) { requiref(state, ffi::LUA_BITLIBNAME, ffi::luaopen_bit32, 1)?; - ffi::lua_pop(state, 1); } } @@ -1436,36 +1427,30 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> { if libs.contains(StdLib::BIT) { requiref(state, ffi::LUA_BITLIBNAME, ffi::luaopen_bit, 1)?; - ffi::lua_pop(state, 1); } } #[cfg(feature = "luau")] if libs.contains(StdLib::BUFFER) { requiref(state, ffi::LUA_BUFFERLIBNAME, ffi::luaopen_buffer, 1)?; - ffi::lua_pop(state, 1); } #[cfg(feature = "luau")] if libs.contains(StdLib::VECTOR) { requiref(state, ffi::LUA_VECLIBNAME, ffi::luaopen_vector, 1)?; - ffi::lua_pop(state, 1); } if libs.contains(StdLib::MATH) { requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?; - ffi::lua_pop(state, 1); } if libs.contains(StdLib::DEBUG) { requiref(state, ffi::LUA_DBLIBNAME, ffi::luaopen_debug, 1)?; - ffi::lua_pop(state, 1); } #[cfg(not(feature = "luau"))] if libs.contains(StdLib::PACKAGE) { requiref(state, ffi::LUA_LOADLIBNAME, ffi::luaopen_package, 1)?; - ffi::lua_pop(state, 1); } #[cfg(feature = "luau")] if libs.contains(StdLib::PACKAGE) { @@ -1476,13 +1461,11 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> #[cfg(feature = "luajit")] if libs.contains(StdLib::JIT) { requiref(state, ffi::LUA_JITLIBNAME, ffi::luaopen_jit, 1)?; - ffi::lua_pop(state, 1); } #[cfg(feature = "luajit")] if libs.contains(StdLib::FFI) { requiref(state, ffi::LUA_FFILIBNAME, ffi::luaopen_ffi, 1)?; - ffi::lua_pop(state, 1); } Ok(()) From 4fe7d151a17a676c76a186c36b4b310c49bca5cc Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 21 Mar 2025 12:29:47 +0000 Subject: [PATCH 351/635] Refactor `userdata-wrappers` feature. Support borrowing underlying data in `UserDataRef` and `UserDataRefMut`. --- src/state.rs | 3 +- src/state/raw.rs | 21 +- src/userdata.rs | 55 +++-- src/userdata/cell.rs | 362 +++++------------------------- src/userdata/lock.rs | 39 ++++ src/userdata/ref.rs | 474 +++++++++++++++++++++++++++++++++++++++ src/userdata/registry.rs | 274 ++++------------------ src/util/mod.rs | 5 +- src/util/userdata.rs | 197 +++++++++++++++- tests/userdata.rs | 356 +++++++++++++++++++++++------ 10 files changed, 1144 insertions(+), 642 deletions(-) create mode 100644 src/userdata/ref.rs diff --git a/src/state.rs b/src/state.rs index fb0bda47..fbfb8db2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1330,7 +1330,7 @@ impl Lua { /// This methods provides a way to add fields or methods to userdata objects of a type `T`. pub fn register_userdata_type(&self, f: impl FnOnce(&mut UserDataRegistry)) -> Result<()> { let type_id = TypeId::of::(); - let mut registry = UserDataRegistry::new(self, type_id); + let mut registry = UserDataRegistry::new(self); f(&mut registry); let lua = self.lock(); @@ -1499,7 +1499,6 @@ impl Lua { &self, f: impl for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> Result, ) -> Result { - // TODO: Update to `&Scope` in next major release f(&Scope::new(self.lock_arc())) } diff --git a/src/state/raw.rs b/src/state/raw.rs index 17420696..e24ec879 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -864,7 +864,7 @@ impl RawLua { } // Create a new metatable from `UserData` definition - let mut registry = UserDataRegistry::new(self.lua(), type_id); + let mut registry = UserDataRegistry::new(self.lua()); T::register(&mut registry); self.create_userdata_metatable(registry.into_raw()) @@ -885,7 +885,7 @@ impl RawLua { // Check if metatable creation is pending or create an empty metatable otherwise let registry = match (*self.extra.get()).pending_userdata_reg.remove(&type_id) { Some(registry) => registry, - None => UserDataRegistry::::new(self.lua(), type_id).into_raw(), + None => UserDataRegistry::::new(self.lua()).into_raw(), }; self.create_userdata_metatable(registry) }) @@ -1103,17 +1103,22 @@ impl RawLua { // Returns `TypeId` for the userdata ref, checking that it's registered and not destructed. // // Returns `None` if the userdata is registered but non-static. - pub(crate) unsafe fn get_userdata_ref_type_id(&self, vref: &ValueRef) -> Result> { - self.get_userdata_type_id_inner(self.ref_thread(), vref.index) + #[inline(always)] + pub(crate) fn get_userdata_ref_type_id(&self, vref: &ValueRef) -> Result> { + unsafe { self.get_userdata_type_id_inner(self.ref_thread(), vref.index) } } // Same as `get_userdata_ref_type_id` but assumes the userdata is already on the stack. - pub(crate) unsafe fn get_userdata_type_id(&self, idx: c_int) -> Result> { - match self.get_userdata_type_id_inner(self.state(), idx) { + pub(crate) unsafe fn get_userdata_type_id( + &self, + state: *mut ffi::lua_State, + idx: c_int, + ) -> Result> { + match self.get_userdata_type_id_inner(state, idx) { Ok(type_id) => Ok(type_id), - Err(Error::UserDataTypeMismatch) if ffi::lua_type(self.state(), idx) != ffi::LUA_TUSERDATA => { + Err(Error::UserDataTypeMismatch) if ffi::lua_type(state, idx) != ffi::LUA_TUSERDATA => { // Report `FromLuaConversionError` instead - let idx_type_name = CStr::from_ptr(ffi::luaL_typename(self.state(), idx)); + let idx_type_name = CStr::from_ptr(ffi::luaL_typename(state, idx)); let idx_type_name = idx_type_name.to_str().unwrap(); let message = format!("expected userdata of type '{}'", short_type_name::()); Err(Error::from_lua_conversion(idx_type_name, "userdata", message)) diff --git a/src/userdata.rs b/src/userdata.rs index 83c5efcd..86cdbc6d 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -12,7 +12,10 @@ use crate::string::String; use crate::table::{Table, TablePairs}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::types::{MaybeSend, ValueRef}; -use crate::util::{check_stack, get_userdata, push_string, take_userdata, StackGuard}; +use crate::util::{ + borrow_userdata_scoped, borrow_userdata_scoped_mut, check_stack, get_userdata, push_string, + take_userdata, StackGuard, TypeIdHints, +}; use crate::value::Value; #[cfg(feature = "async")] @@ -26,7 +29,7 @@ use { // Re-export for convenience pub(crate) use cell::UserDataStorage; -pub use cell::{UserDataRef, UserDataRefMut}; +pub use r#ref::{UserDataRef, UserDataRefMut}; pub use registry::UserDataRegistry; pub(crate) use registry::{RawUserDataRegistry, UserDataProxy}; @@ -622,7 +625,10 @@ impl AnyUserData { /// Checks whether the type of this userdata is `T`. #[inline] pub fn is(&self) -> bool { - self.inspect::(|_| Ok(())).is_ok() + let lua = self.0.lua.lock(); + let type_id = lua.get_userdata_ref_type_id(&self.0); + // We do not use wrapped types here, rather prefer to check the "real" type of the userdata + matches!(type_id, Ok(Some(type_id)) if type_id == TypeId::of::()) } /// Borrow this userdata immutably if it is of type `T`. @@ -637,7 +643,8 @@ impl AnyUserData { /// [`DataTypeMismatch`]: crate::Error::UserDataTypeMismatch #[inline] pub fn borrow(&self) -> Result> { - self.inspect(|ud| ud.try_borrow_owned()) + let lua = self.0.lua.lock(); + unsafe { UserDataRef::borrow_from_stack(&lua, lua.ref_thread(), self.0.index) } } /// Borrow this userdata immutably if it is of type `T`, passing the borrowed value @@ -645,7 +652,10 @@ impl AnyUserData { /// /// This method is the only way to borrow scoped userdata (created inside [`Lua::scope`]). pub fn borrow_scoped(&self, f: impl FnOnce(&T) -> R) -> Result { - self.inspect(|ud| ud.try_borrow_scoped(|ud| f(ud))) + let lua = self.0.lua.lock(); + let type_id = lua.get_userdata_ref_type_id(&self.0)?; + let type_hints = TypeIdHints::new::(); + unsafe { borrow_userdata_scoped(lua.ref_thread(), self.0.index, type_id, type_hints, f) } } /// Borrow this userdata mutably if it is of type `T`. @@ -660,7 +670,8 @@ impl AnyUserData { /// [`UserDataTypeMismatch`]: crate::Error::UserDataTypeMismatch #[inline] pub fn borrow_mut(&self) -> Result> { - self.inspect(|ud| ud.try_borrow_owned_mut()) + let lua = self.0.lua.lock(); + unsafe { UserDataRefMut::borrow_from_stack(&lua, lua.ref_thread(), self.0.index) } } /// Borrow this userdata mutably if it is of type `T`, passing the borrowed value @@ -668,7 +679,10 @@ impl AnyUserData { /// /// This method is the only way to borrow scoped userdata (created inside [`Lua::scope`]). pub fn borrow_mut_scoped(&self, f: impl FnOnce(&mut T) -> R) -> Result { - self.inspect(|ud| ud.try_borrow_scoped_mut(|ud| f(ud))) + let lua = self.0.lua.lock(); + let type_id = lua.get_userdata_ref_type_id(&self.0)?; + let type_hints = TypeIdHints::new::(); + unsafe { borrow_userdata_scoped_mut(lua.ref_thread(), self.0.index, type_id, type_hints, f) } } /// Takes the value out of this userdata. @@ -687,9 +701,11 @@ impl AnyUserData { let type_id = lua.push_userdata_ref(&self.0)?; match type_id { Some(type_id) if type_id == TypeId::of::() => { - // Try to borrow userdata exclusively - let _ = (*get_userdata::>(state, -1)).try_borrow_mut()?; - take_userdata::>(state).into_inner() + if (*get_userdata::>(state, -1)).has_exclusive_access() { + take_userdata::>(state).into_inner() + } else { + Err(Error::UserDataBorrowMutError) + } } _ => Err(Error::UserDataTypeMismatch), } @@ -965,24 +981,6 @@ impl AnyUserData { }; is_serializable().unwrap_or(false) } - - pub(crate) fn inspect(&self, func: F) -> Result - where - T: 'static, - F: FnOnce(&UserDataStorage) -> Result, - { - let lua = self.0.lua.lock(); - unsafe { - let type_id = lua.get_userdata_ref_type_id(&self.0)?; - match type_id { - Some(type_id) if type_id == TypeId::of::() => { - let ud = get_userdata::>(lua.ref_thread(), self.0.index); - func(&*ud) - } - _ => Err(Error::UserDataTypeMismatch), - } - } - } } /// Handle to a [`AnyUserData`] metatable. @@ -1106,6 +1104,7 @@ where mod cell; mod lock; mod object; +mod r#ref; mod registry; mod util; diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index 70b6dd34..538e33d7 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -1,22 +1,13 @@ -use std::any::{type_name, TypeId}; use std::cell::{RefCell, UnsafeCell}; -use std::fmt; -use std::ops::{Deref, DerefMut}; -use std::os::raw::c_int; #[cfg(feature = "serialize")] use serde::ser::{Serialize, Serializer}; use crate::error::{Error, Result}; -use crate::state::{Lua, RawLua}; -use crate::traits::FromLua; use crate::types::XRc; -use crate::userdata::AnyUserData; -use crate::util::get_userdata; -use crate::value::Value; use super::lock::{RawLock, UserDataLock}; -use super::util::is_sync; +use super::r#ref::{UserDataRef, UserDataRefMut}; #[cfg(all(feature = "serialize", not(feature = "send")))] type DynSerialize = dyn erased_serde::Serialize; @@ -34,7 +25,7 @@ pub(crate) enum UserDataStorage { pub(crate) enum UserDataVariant { Default(XRc>), #[cfg(feature = "serialize")] - Serializable(XRc>>), + Serializable(XRc>>, bool), // bool is `is_sync` } impl Clone for UserDataVariant { @@ -43,28 +34,34 @@ impl Clone for UserDataVariant { match self { Self::Default(inner) => Self::Default(XRc::clone(inner)), #[cfg(feature = "serialize")] - Self::Serializable(inner) => Self::Serializable(XRc::clone(inner)), + Self::Serializable(inner, is_sync) => Self::Serializable(XRc::clone(inner), *is_sync), } } } impl UserDataVariant { - // Immutably borrows the wrapped value in-place. #[inline(always)] - fn try_borrow(&self) -> Result> { - UserDataBorrowRef::try_from(self) + pub(super) fn try_borrow_scoped(&self, f: impl FnOnce(&T) -> R) -> Result { + // We don't need to check for `T: Sync` because when this method is used (internally), + // Lua mutex is already locked. + // If non-`Sync` userdata is already borrowed by another thread (via `UserDataRef`), it will be + // exclusively locked. + let _guard = (self.raw_lock().try_lock_shared_guarded()).map_err(|_| Error::UserDataBorrowError)?; + Ok(f(unsafe { &*self.as_ptr() })) } - // Immutably borrows the wrapped value and returns an owned reference. + // Mutably borrows the wrapped value in-place. #[inline(always)] - fn try_borrow_owned(&self) -> Result> { - UserDataRef::try_from(self.clone()) + fn try_borrow_scoped_mut(&self, f: impl FnOnce(&mut T) -> R) -> Result { + let _guard = + (self.raw_lock().try_lock_exclusive_guarded()).map_err(|_| Error::UserDataBorrowMutError)?; + Ok(f(unsafe { &mut *self.as_ptr() })) } - // Mutably borrows the wrapped value in-place. + // Immutably borrows the wrapped value and returns an owned reference. #[inline(always)] - fn try_borrow_mut(&self) -> Result> { - UserDataBorrowMut::try_from(self) + fn try_borrow_owned(&self) -> Result> { + UserDataRef::try_from(self.clone()) } // Mutably borrows the wrapped value and returns an owned reference. @@ -83,7 +80,7 @@ impl UserDataVariant { Ok(match self { Self::Default(inner) => XRc::into_inner(inner).unwrap().value.into_inner(), #[cfg(feature = "serialize")] - Self::Serializable(inner) => unsafe { + Self::Serializable(inner, _) => unsafe { let raw = Box::into_raw(XRc::into_inner(inner).unwrap().value.into_inner()); *Box::from_raw(raw as *mut T) }, @@ -95,25 +92,25 @@ impl UserDataVariant { match self { Self::Default(inner) => XRc::strong_count(inner), #[cfg(feature = "serialize")] - Self::Serializable(inner) => XRc::strong_count(inner), + Self::Serializable(inner, _) => XRc::strong_count(inner), } } #[inline(always)] - fn raw_lock(&self) -> &RawLock { + pub(super) fn raw_lock(&self) -> &RawLock { match self { Self::Default(inner) => &inner.raw_lock, #[cfg(feature = "serialize")] - Self::Serializable(inner) => &inner.raw_lock, + Self::Serializable(inner, _) => &inner.raw_lock, } } #[inline(always)] - fn as_ptr(&self) -> *mut T { + pub(super) fn as_ptr(&self) -> *mut T { match self { Self::Default(inner) => inner.value.get(), #[cfg(feature = "serialize")] - Self::Serializable(inner) => unsafe { &mut **(inner.value.get() as *mut Box) }, + Self::Serializable(inner, _) => unsafe { &mut **(inner.value.get() as *mut Box) }, } } } @@ -122,14 +119,24 @@ impl UserDataVariant { impl Serialize for UserDataStorage<()> { fn serialize(&self, serializer: S) -> std::result::Result { match self { - Self::Owned(UserDataVariant::Serializable(inner)) => unsafe { - // We need to borrow the inner value exclusively to serialize it. + Self::Owned(variant @ UserDataVariant::Serializable(inner, is_sync)) => unsafe { #[cfg(feature = "send")] - let _guard = self.try_borrow_mut().map_err(serde::ser::Error::custom)?; - // No need to do this if the `send` feature is disabled. + if *is_sync { + let _guard = (variant.raw_lock().try_lock_shared_guarded()) + .map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?; + (*inner.value.get()).serialize(serializer) + } else { + let _guard = (variant.raw_lock().try_lock_exclusive_guarded()) + .map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?; + (*inner.value.get()).serialize(serializer) + } #[cfg(not(feature = "send"))] - let _guard = self.try_borrow().map_err(serde::ser::Error::custom)?; - (*inner.value.get()).serialize(serializer) + { + let _ = is_sync; + let _guard = (variant.raw_lock().try_lock_shared_guarded()) + .map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?; + (*inner.value.get()).serialize(serializer) + } }, _ => Err(serde::ser::Error::custom("cannot serialize ")), } @@ -157,232 +164,6 @@ impl UserDataCell { } } -/// A wrapper type for a userdata value that provides read access. -/// -/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua. -pub struct UserDataRef(UserDataVariant); - -impl Deref for UserDataRef { - type Target = T; - - #[inline] - fn deref(&self) -> &T { - unsafe { &*self.0.as_ptr() } - } -} - -impl Drop for UserDataRef { - #[inline] - fn drop(&mut self) { - if !cfg!(feature = "send") || is_sync::() { - unsafe { self.0.raw_lock().unlock_shared() }; - } else { - unsafe { self.0.raw_lock().unlock_exclusive() }; - } - } -} - -impl fmt::Debug for UserDataRef { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - (**self).fmt(f) - } -} - -impl fmt::Display for UserDataRef { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - (**self).fmt(f) - } -} - -impl TryFrom> for UserDataRef { - type Error = Error; - - #[inline] - fn try_from(variant: UserDataVariant) -> Result { - if !cfg!(feature = "send") || is_sync::() { - if !variant.raw_lock().try_lock_shared() { - return Err(Error::UserDataBorrowError); - } - } else if !variant.raw_lock().try_lock_exclusive() { - return Err(Error::UserDataBorrowError); - } - Ok(UserDataRef(variant)) - } -} - -impl FromLua for UserDataRef { - fn from_lua(value: Value, _: &Lua) -> Result { - try_value_to_userdata::(value)?.borrow() - } - - unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - let type_id = lua.get_userdata_type_id::(idx)?; - match type_id { - Some(type_id) if type_id == TypeId::of::() => { - (*get_userdata::>(lua.state(), idx)).try_borrow_owned() - } - _ => Err(Error::UserDataTypeMismatch), - } - } -} - -/// A wrapper type for a userdata value that provides read and write access. -/// -/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua. -pub struct UserDataRefMut(UserDataVariant); - -impl Deref for UserDataRefMut { - type Target = T; - - #[inline] - fn deref(&self) -> &Self::Target { - unsafe { &*self.0.as_ptr() } - } -} - -impl DerefMut for UserDataRefMut { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { &mut *self.0.as_ptr() } - } -} - -impl Drop for UserDataRefMut { - #[inline] - fn drop(&mut self) { - unsafe { self.0.raw_lock().unlock_exclusive() }; - } -} - -impl fmt::Debug for UserDataRefMut { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - (**self).fmt(f) - } -} - -impl fmt::Display for UserDataRefMut { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - (**self).fmt(f) - } -} - -impl TryFrom> for UserDataRefMut { - type Error = Error; - - #[inline] - fn try_from(variant: UserDataVariant) -> Result { - if !variant.raw_lock().try_lock_exclusive() { - return Err(Error::UserDataBorrowMutError); - } - Ok(UserDataRefMut(variant)) - } -} - -impl FromLua for UserDataRefMut { - fn from_lua(value: Value, _: &Lua) -> Result { - try_value_to_userdata::(value)?.borrow_mut() - } - - unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - let type_id = lua.get_userdata_type_id::(idx)?; - match type_id { - Some(type_id) if type_id == TypeId::of::() => { - (*get_userdata::>(lua.state(), idx)).try_borrow_owned_mut() - } - _ => Err(Error::UserDataTypeMismatch), - } - } -} - -/// A type that provides read access to a userdata value (borrowing the value). -pub(crate) struct UserDataBorrowRef<'a, T>(&'a UserDataVariant); - -impl Drop for UserDataBorrowRef<'_, T> { - #[inline] - fn drop(&mut self) { - unsafe { - self.0.raw_lock().unlock_shared(); - } - } -} - -impl Deref for UserDataBorrowRef<'_, T> { - type Target = T; - - #[inline] - fn deref(&self) -> &T { - // SAFETY: `UserDataBorrowRef` is only created with shared access to the value. - unsafe { &*self.0.as_ptr() } - } -} - -impl<'a, T> TryFrom<&'a UserDataVariant> for UserDataBorrowRef<'a, T> { - type Error = Error; - - #[inline(always)] - fn try_from(variant: &'a UserDataVariant) -> Result { - // We don't need to check for `T: Sync` because when this method is used (internally), - // Lua mutex is already locked. - // If non-`Sync` userdata is already borrowed by another thread (via `UserDataRef`), it will be - // exclusively locked. - if !variant.raw_lock().try_lock_shared() { - return Err(Error::UserDataBorrowError); - } - Ok(UserDataBorrowRef(variant)) - } -} - -pub(crate) struct UserDataBorrowMut<'a, T>(&'a UserDataVariant); - -impl Drop for UserDataBorrowMut<'_, T> { - #[inline] - fn drop(&mut self) { - unsafe { - self.0.raw_lock().unlock_exclusive(); - } - } -} - -impl Deref for UserDataBorrowMut<'_, T> { - type Target = T; - - #[inline] - fn deref(&self) -> &T { - unsafe { &*self.0.as_ptr() } - } -} - -impl DerefMut for UserDataBorrowMut<'_, T> { - #[inline] - fn deref_mut(&mut self) -> &mut T { - unsafe { &mut *self.0.as_ptr() } - } -} - -impl<'a, T> TryFrom<&'a UserDataVariant> for UserDataBorrowMut<'a, T> { - type Error = Error; - - #[inline(always)] - fn try_from(variant: &'a UserDataVariant) -> Result { - if !variant.raw_lock().try_lock_exclusive() { - return Err(Error::UserDataBorrowMutError); - } - Ok(UserDataBorrowMut(variant)) - } -} - -#[inline] -fn try_value_to_userdata(value: Value) -> Result { - match value { - Value::UserData(ud) => Ok(ud), - _ => Err(Error::FromLuaConversionError { - from: value.type_name(), - to: "userdata".to_string(), - message: Some(format!("expected userdata of type {}", type_name::())), - }), - } -} - pub(crate) enum ScopedUserDataVariant { Ref(*const T), RefMut(RefCell<*mut T>), @@ -423,13 +204,15 @@ impl UserDataStorage { T: Serialize + crate::types::MaybeSend, { let data = Box::new(data) as Box; - Self::Owned(UserDataVariant::Serializable(XRc::new(UserDataCell::new(data)))) + let is_sync = super::util::is_sync::(); + let variant = UserDataVariant::Serializable(XRc::new(UserDataCell::new(data)), is_sync); + Self::Owned(variant) } #[cfg(feature = "serialize")] #[inline(always)] pub(crate) fn is_serializable(&self) -> bool { - matches!(self, Self::Owned(UserDataVariant::Serializable(_))) + matches!(self, Self::Owned(UserDataVariant::Serializable(..))) } // Immutably borrows the wrapped value and returns an owned reference. @@ -441,23 +224,6 @@ impl UserDataStorage { } } - #[allow(unused)] - #[inline(always)] - pub(crate) fn try_borrow(&self) -> Result> { - match self { - Self::Owned(data) => data.try_borrow(), - Self::Scoped(_) => Err(Error::UserDataTypeMismatch), - } - } - - #[inline(always)] - pub(crate) fn try_borrow_mut(&self) -> Result> { - match self { - Self::Owned(data) => data.try_borrow_mut(), - Self::Scoped(_) => Err(Error::UserDataTypeMismatch), - } - } - // Mutably borrows the wrapped value and returns an owned reference. #[inline(always)] pub(crate) fn try_borrow_owned_mut(&self) -> Result> { @@ -495,10 +261,19 @@ impl UserDataStorage { } } + /// Returns `true` if the container has exclusive access to the value. + #[inline(always)] + pub(crate) fn has_exclusive_access(&self) -> bool { + match self { + Self::Owned(variant) => !variant.raw_lock().is_locked(), + Self::Scoped(_) => false, + } + } + #[inline] pub(crate) fn try_borrow_scoped(&self, f: impl FnOnce(&T) -> R) -> Result { match self { - Self::Owned(data) => Ok(f(&*data.try_borrow()?)), + Self::Owned(data) => data.try_borrow_scoped(f), Self::Scoped(ScopedUserDataVariant::Ref(value)) => Ok(f(unsafe { &**value })), Self::Scoped(ScopedUserDataVariant::RefMut(value) | ScopedUserDataVariant::Boxed(value)) => { let t = value.try_borrow().map_err(|_| Error::UserDataBorrowError)?; @@ -510,7 +285,7 @@ impl UserDataStorage { #[inline] pub(crate) fn try_borrow_scoped_mut(&self, f: impl FnOnce(&mut T) -> R) -> Result { match self { - Self::Owned(data) => Ok(f(&mut *data.try_borrow_mut()?)), + Self::Owned(data) => data.try_borrow_scoped_mut(f), Self::Scoped(ScopedUserDataVariant::Ref(_)) => Err(Error::UserDataBorrowMutError), Self::Scoped(ScopedUserDataVariant::RefMut(value) | ScopedUserDataVariant::Boxed(value)) => { let mut t = value @@ -521,30 +296,3 @@ impl UserDataStorage { } } } - -#[cfg(test)] -mod assertions { - use super::*; - - #[cfg(feature = "send")] - static_assertions::assert_impl_all!(UserDataRef<()>: Send, Sync); - #[cfg(feature = "send")] - static_assertions::assert_not_impl_all!(UserDataRef>: Send, Sync); - #[cfg(feature = "send")] - static_assertions::assert_impl_all!(UserDataRefMut<()>: Sync, Send); - #[cfg(feature = "send")] - static_assertions::assert_not_impl_all!(UserDataRefMut>: Send, Sync); - #[cfg(feature = "send")] - static_assertions::assert_impl_all!(UserDataBorrowRef<'_, ()>: Send, Sync); - #[cfg(feature = "send")] - static_assertions::assert_impl_all!(UserDataBorrowMut<'_, ()>: Send, Sync); - - #[cfg(not(feature = "send"))] - static_assertions::assert_not_impl_all!(UserDataRef<()>: Send, Sync); - #[cfg(not(feature = "send"))] - static_assertions::assert_not_impl_all!(UserDataRefMut<()>: Send, Sync); - #[cfg(not(feature = "send"))] - static_assertions::assert_not_impl_all!(UserDataBorrowRef<'_, ()>: Send, Sync); - #[cfg(not(feature = "send"))] - static_assertions::assert_not_impl_all!(UserDataBorrowMut<'_, ()>: Send, Sync); -} diff --git a/src/userdata/lock.rs b/src/userdata/lock.rs index 4843ff4c..e0e5d1af 100644 --- a/src/userdata/lock.rs +++ b/src/userdata/lock.rs @@ -7,6 +7,45 @@ pub(crate) trait UserDataLock { unsafe fn unlock_shared(&self); unsafe fn unlock_exclusive(&self); + + fn try_lock_shared_guarded(&self) -> Result, ()> { + if self.try_lock_shared() { + Ok(LockGuard { + lock: self, + exclusive: false, + }) + } else { + Err(()) + } + } + + fn try_lock_exclusive_guarded(&self) -> Result, ()> { + if self.try_lock_exclusive() { + Ok(LockGuard { + lock: self, + exclusive: true, + }) + } else { + Err(()) + } + } +} + +pub(crate) struct LockGuard<'a, L: UserDataLock + ?Sized> { + lock: &'a L, + exclusive: bool, +} + +impl Drop for LockGuard<'_, L> { + fn drop(&mut self) { + unsafe { + if self.exclusive { + self.lock.unlock_exclusive(); + } else { + self.lock.unlock_shared(); + } + } + } } pub(crate) use lock_impl::RawLock; diff --git a/src/userdata/ref.rs b/src/userdata/ref.rs new file mode 100644 index 00000000..750443a9 --- /dev/null +++ b/src/userdata/ref.rs @@ -0,0 +1,474 @@ +use std::any::{type_name, TypeId}; +use std::ops::{Deref, DerefMut}; +use std::os::raw::c_int; +use std::{fmt, mem}; + +use crate::error::{Error, Result}; +use crate::state::{Lua, RawLua}; +use crate::traits::FromLua; +use crate::userdata::AnyUserData; +use crate::util::get_userdata; +use crate::value::Value; + +use super::cell::{UserDataStorage, UserDataVariant}; +use super::lock::{LockGuard, RawLock, UserDataLock}; +use super::util::is_sync; + +#[cfg(feature = "userdata-wrappers")] +use { + parking_lot::{ + Mutex as MutexPL, MutexGuard as MutexGuardPL, RwLock as RwLockPL, + RwLockReadGuard as RwLockReadGuardPL, RwLockWriteGuard as RwLockWriteGuardPL, + }, + std::sync::Arc, +}; +#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] +use { + std::cell::{Ref, RefCell, RefMut}, + std::rc::Rc, +}; + +/// A wrapper type for a userdata value that provides read access. +/// +/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua. +pub struct UserDataRef { + // It's important to drop the guard first, as it refers to the `inner` data. + _guard: LockGuard<'static, RawLock>, + inner: UserDataRefInner, +} + +impl Deref for UserDataRef { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + &self.inner + } +} + +impl fmt::Debug for UserDataRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +impl fmt::Display for UserDataRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +impl TryFrom> for UserDataRef { + type Error = Error; + + #[inline] + fn try_from(variant: UserDataVariant) -> Result { + let guard = if !cfg!(feature = "send") || is_sync::() { + variant.raw_lock().try_lock_shared_guarded() + } else { + variant.raw_lock().try_lock_exclusive_guarded() + }; + let guard = guard.map_err(|_| Error::UserDataBorrowError)?; + let guard = unsafe { mem::transmute::, LockGuard<'static, _>>(guard) }; + Ok(UserDataRef::from_parts(UserDataRefInner::Default(variant), guard)) + } +} + +impl FromLua for UserDataRef { + fn from_lua(value: Value, _: &Lua) -> Result { + try_value_to_userdata::(value)?.borrow() + } + + #[inline] + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { + Self::borrow_from_stack(lua, lua.state(), idx) + } +} + +impl UserDataRef { + #[inline(always)] + fn from_parts(inner: UserDataRefInner, guard: LockGuard<'static, RawLock>) -> Self { + Self { _guard: guard, inner } + } + + #[cfg(feature = "userdata-wrappers")] + fn remap( + self, + f: impl FnOnce(UserDataVariant) -> Result>, + ) -> Result> { + match &self.inner { + UserDataRefInner::Default(variant) => { + let inner = f(variant.clone())?; + Ok(UserDataRef::from_parts(inner, self._guard)) + } + _ => Err(Error::UserDataTypeMismatch), + } + } + + pub(crate) unsafe fn borrow_from_stack( + lua: &RawLua, + state: *mut ffi::lua_State, + idx: c_int, + ) -> Result { + let type_id = lua.get_userdata_type_id::(state, idx)?; + match type_id { + Some(type_id) if type_id == TypeId::of::() => { + let ud = get_userdata::>(state, idx); + (*ud).try_borrow_owned() + } + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Some(type_id) if type_id == TypeId::of::>() => { + let ud = get_userdata::>>(state, idx); + ((*ud).try_borrow_owned()).and_then(|ud| ud.transform_rc()) + } + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Some(type_id) if type_id == TypeId::of::>>() => { + let ud = get_userdata::>>>(state, idx); + ((*ud).try_borrow_owned()).and_then(|ud| ud.transform_rc_refcell()) + } + + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == TypeId::of::>() => { + let ud = get_userdata::>>(state, idx); + ((*ud).try_borrow_owned()).and_then(|ud| ud.transform_arc()) + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == TypeId::of::>>() => { + let ud = get_userdata::>>>(state, idx); + ((*ud).try_borrow_owned()).and_then(|ud| ud.transform_arc_mutex_pl()) + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == TypeId::of::>>() => { + let ud = get_userdata::>>>(state, idx); + ((*ud).try_borrow_owned()).and_then(|ud| ud.transform_arc_rwlock_pl()) + } + _ => Err(Error::UserDataTypeMismatch), + } + } +} + +#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] +impl UserDataRef> { + fn transform_rc(self) -> Result> { + self.remap(|variant| Ok(UserDataRefInner::Rc(variant))) + } +} + +#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] +impl UserDataRef>> { + fn transform_rc_refcell(self) -> Result> { + self.remap(|variant| unsafe { + let obj = &*variant.as_ptr(); + let r#ref = obj.try_borrow().map_err(|_| Error::UserDataBorrowError)?; + let borrow = std::mem::transmute::, Ref<'static, T>>(r#ref); + Ok(UserDataRefInner::RcRefCell(borrow, variant)) + }) + } +} + +#[cfg(feature = "userdata-wrappers")] +impl UserDataRef> { + fn transform_arc(self) -> Result> { + self.remap(|variant| Ok(UserDataRefInner::Arc(variant))) + } +} + +#[cfg(feature = "userdata-wrappers")] +impl UserDataRef>> { + fn transform_arc_mutex_pl(self) -> Result> { + self.remap(|variant| unsafe { + let obj = &*variant.as_ptr(); + let guard = obj.try_lock().ok_or(Error::UserDataBorrowError)?; + let borrow = std::mem::transmute::, MutexGuardPL<'static, T>>(guard); + Ok(UserDataRefInner::ArcMutexPL(borrow, variant)) + }) + } +} + +#[cfg(feature = "userdata-wrappers")] +impl UserDataRef>> { + fn transform_arc_rwlock_pl(self) -> Result> { + self.remap(|variant| unsafe { + let obj = &*variant.as_ptr(); + let guard = obj.try_read().ok_or(Error::UserDataBorrowError)?; + let borrow = std::mem::transmute::, RwLockReadGuardPL<'static, T>>(guard); + Ok(UserDataRefInner::ArcRwLockPL(borrow, variant)) + }) + } +} + +#[allow(unused)] +enum UserDataRefInner { + Default(UserDataVariant), + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Rc(UserDataVariant>), + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + RcRefCell(Ref<'static, T>, UserDataVariant>>), + + #[cfg(feature = "userdata-wrappers")] + Arc(UserDataVariant>), + #[cfg(feature = "userdata-wrappers")] + ArcMutexPL(MutexGuardPL<'static, T>, UserDataVariant>>), + #[cfg(feature = "userdata-wrappers")] + ArcRwLockPL(RwLockReadGuardPL<'static, T>, UserDataVariant>>), +} + +impl Deref for UserDataRefInner { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + match self { + Self::Default(inner) => unsafe { &*inner.as_ptr() }, + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Self::Rc(inner) => unsafe { &*Rc::as_ptr(&*inner.as_ptr()) }, + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Self::RcRefCell(x, ..) => x, + + #[cfg(feature = "userdata-wrappers")] + Self::Arc(inner) => unsafe { &*Arc::as_ptr(&*inner.as_ptr()) }, + #[cfg(feature = "userdata-wrappers")] + Self::ArcMutexPL(x, ..) => x, + #[cfg(feature = "userdata-wrappers")] + Self::ArcRwLockPL(x, ..) => x, + } + } +} + +/// A wrapper type for a userdata value that provides read and write access. +/// +/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua. +pub struct UserDataRefMut { + // It's important to drop the guard first, as it refers to the `inner` data. + _guard: LockGuard<'static, RawLock>, + inner: UserDataRefMutInner, +} + +impl Deref for UserDataRefMut { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for UserDataRefMut { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl fmt::Debug for UserDataRefMut { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +impl fmt::Display for UserDataRefMut { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +impl TryFrom> for UserDataRefMut { + type Error = Error; + + #[inline] + fn try_from(variant: UserDataVariant) -> Result { + let guard = variant.raw_lock().try_lock_exclusive_guarded(); + let guard = guard.map_err(|_| Error::UserDataBorrowMutError)?; + let guard = unsafe { mem::transmute::, LockGuard<'static, _>>(guard) }; + Ok(UserDataRefMut::from_parts( + UserDataRefMutInner::Default(variant), + guard, + )) + } +} + +impl FromLua for UserDataRefMut { + fn from_lua(value: Value, _: &Lua) -> Result { + try_value_to_userdata::(value)?.borrow_mut() + } + + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { + Self::borrow_from_stack(lua, lua.state(), idx) + } +} + +impl UserDataRefMut { + #[inline(always)] + fn from_parts(inner: UserDataRefMutInner, guard: LockGuard<'static, RawLock>) -> Self { + Self { _guard: guard, inner } + } + + #[cfg(feature = "userdata-wrappers")] + fn remap( + self, + f: impl FnOnce(UserDataVariant) -> Result>, + ) -> Result> { + match &self.inner { + UserDataRefMutInner::Default(variant) => { + let inner = f(variant.clone())?; + Ok(UserDataRefMut::from_parts(inner, self._guard)) + } + _ => Err(Error::UserDataTypeMismatch), + } + } + + pub(crate) unsafe fn borrow_from_stack( + lua: &RawLua, + state: *mut ffi::lua_State, + idx: c_int, + ) -> Result { + let type_id = lua.get_userdata_type_id::(state, idx)?; + match type_id { + Some(type_id) if type_id == TypeId::of::() => { + let ud = get_userdata::>(state, idx); + (*ud).try_borrow_owned_mut() + } + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Some(type_id) if type_id == TypeId::of::>() => Err(Error::UserDataBorrowMutError), + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Some(type_id) if type_id == TypeId::of::>>() => { + let ud = get_userdata::>>>(state, idx); + ((*ud).try_borrow_owned_mut()).and_then(|ud| ud.transform_rc_refcell()) + } + + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == TypeId::of::>() => Err(Error::UserDataBorrowMutError), + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == TypeId::of::>>() => { + let ud = get_userdata::>>>(state, idx); + ((*ud).try_borrow_owned_mut()).and_then(|ud| ud.transform_arc_mutex_pl()) + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == TypeId::of::>>() => { + let ud = get_userdata::>>>(state, idx); + ((*ud).try_borrow_owned_mut()).and_then(|ud| ud.transform_arc_rwlock_pl()) + } + _ => Err(Error::UserDataTypeMismatch), + } + } +} + +#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] +impl UserDataRefMut>> { + fn transform_rc_refcell(self) -> Result> { + self.remap(|variant| unsafe { + let obj = &*variant.as_ptr(); + let refmut = obj.try_borrow_mut().map_err(|_| Error::UserDataBorrowMutError)?; + let borrow = std::mem::transmute::, RefMut<'static, T>>(refmut); + Ok(UserDataRefMutInner::RcRefCell(borrow, variant)) + }) + } +} + +#[cfg(feature = "userdata-wrappers")] +impl UserDataRefMut>> { + fn transform_arc_mutex_pl(self) -> Result> { + self.remap(|variant| unsafe { + let obj = &*variant.as_ptr(); + let guard = obj.try_lock().ok_or(Error::UserDataBorrowMutError)?; + let borrow = std::mem::transmute::, MutexGuardPL<'static, T>>(guard); + Ok(UserDataRefMutInner::ArcMutexPL(borrow, variant)) + }) + } +} + +#[cfg(feature = "userdata-wrappers")] +impl UserDataRefMut>> { + fn transform_arc_rwlock_pl(self) -> Result> { + self.remap(|variant| unsafe { + let obj = &*variant.as_ptr(); + let guard = obj.try_write().ok_or(Error::UserDataBorrowMutError)?; + let borrow = std::mem::transmute::, RwLockWriteGuardPL<'static, T>>(guard); + Ok(UserDataRefMutInner::ArcRwLockPL(borrow, variant)) + }) + } +} + +#[allow(unused)] +enum UserDataRefMutInner { + Default(UserDataVariant), + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + RcRefCell(RefMut<'static, T>, UserDataVariant>>), + + #[cfg(feature = "userdata-wrappers")] + ArcMutexPL(MutexGuardPL<'static, T>, UserDataVariant>>), + #[cfg(feature = "userdata-wrappers")] + ArcRwLockPL(RwLockWriteGuardPL<'static, T>, UserDataVariant>>), +} + +impl Deref for UserDataRefMutInner { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + match self { + Self::Default(inner) => unsafe { &*inner.as_ptr() }, + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Self::RcRefCell(x, ..) => x, + + #[cfg(feature = "userdata-wrappers")] + Self::ArcMutexPL(x, ..) => x, + #[cfg(feature = "userdata-wrappers")] + Self::ArcRwLockPL(x, ..) => x, + } + } +} + +impl DerefMut for UserDataRefMutInner { + #[inline] + fn deref_mut(&mut self) -> &mut T { + match self { + Self::Default(inner) => unsafe { &mut *inner.as_ptr() }, + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Self::RcRefCell(x, ..) => x, + + #[cfg(feature = "userdata-wrappers")] + Self::ArcMutexPL(x, ..) => x, + #[cfg(feature = "userdata-wrappers")] + Self::ArcRwLockPL(x, ..) => x, + } + } +} + +#[inline] +fn try_value_to_userdata(value: Value) -> Result { + match value { + Value::UserData(ud) => Ok(ud), + _ => Err(Error::FromLuaConversionError { + from: value.type_name(), + to: "userdata".to_string(), + message: Some(format!("expected userdata of type {}", type_name::())), + }), + } +} + +#[cfg(test)] +mod assertions { + use super::*; + + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(UserDataRef<()>: Send, Sync); + #[cfg(feature = "send")] + static_assertions::assert_not_impl_all!(UserDataRef>: Send, Sync); + #[cfg(feature = "send")] + static_assertions::assert_impl_all!(UserDataRefMut<()>: Sync, Send); + #[cfg(feature = "send")] + static_assertions::assert_not_impl_all!(UserDataRefMut>: Send, Sync); + + #[cfg(not(feature = "send"))] + static_assertions::assert_not_impl_all!(UserDataRef<()>: Send, Sync); + #[cfg(not(feature = "send"))] + static_assertions::assert_not_impl_all!(UserDataRefMut<()>: Send, Sync); +} diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index ec5a989b..f9e88c9b 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -11,7 +11,9 @@ use crate::state::{Lua, LuaGuard}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::types::{Callback, MaybeSend}; use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataStorage}; -use crate::util::{get_userdata, short_type_name}; +use crate::util::{ + borrow_userdata_scoped, borrow_userdata_scoped_mut, get_userdata, short_type_name, TypeIdHints, +}; use crate::value::Value; #[cfg(feature = "async")] @@ -21,38 +23,18 @@ use { std::future::{self, Future}, }; -#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] -use std::rc::Rc; -#[cfg(feature = "userdata-wrappers")] -use std::sync::{Arc, Mutex, RwLock}; - #[derive(Clone, Copy)] -enum UserDataTypeId { - Shared(TypeId), +enum UserDataType { + Shared(TypeIdHints), Unique(*mut c_void), - - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - Rc(TypeId), - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - RcRefCell(TypeId), - #[cfg(feature = "userdata-wrappers")] - Arc(TypeId), - #[cfg(feature = "userdata-wrappers")] - ArcMutex(TypeId), - #[cfg(feature = "userdata-wrappers")] - ArcRwLock(TypeId), - #[cfg(feature = "userdata-wrappers")] - ArcParkingLotMutex(TypeId), - #[cfg(feature = "userdata-wrappers")] - ArcParkingLotRwLock(TypeId), } /// Handle to registry for userdata methods and metamethods. pub struct UserDataRegistry { lua: LuaGuard, raw: RawUserDataRegistry, - ud_type_id: UserDataTypeId, - _type: PhantomData, + r#type: UserDataType, + _phantom: PhantomData, } pub(crate) struct RawUserDataRegistry { @@ -75,46 +57,34 @@ pub(crate) struct RawUserDataRegistry { pub(crate) type_name: StdString, } -impl UserDataTypeId { +impl UserDataType { #[inline] - pub(crate) fn type_id(self) -> Option { + pub(crate) fn type_id(&self) -> Option { match self { - UserDataTypeId::Shared(type_id) => Some(type_id), - UserDataTypeId::Unique(_) => None, - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - UserDataTypeId::Rc(type_id) => Some(type_id), - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - UserDataTypeId::RcRefCell(type_id) => Some(type_id), - #[cfg(feature = "userdata-wrappers")] - UserDataTypeId::Arc(type_id) => Some(type_id), - #[cfg(feature = "userdata-wrappers")] - UserDataTypeId::ArcMutex(type_id) => Some(type_id), - #[cfg(feature = "userdata-wrappers")] - UserDataTypeId::ArcRwLock(type_id) => Some(type_id), - #[cfg(feature = "userdata-wrappers")] - UserDataTypeId::ArcParkingLotMutex(type_id) => Some(type_id), - #[cfg(feature = "userdata-wrappers")] - UserDataTypeId::ArcParkingLotRwLock(type_id) => Some(type_id), + UserDataType::Shared(hints) => Some(hints.type_id()), + UserDataType::Unique(_) => None, } } } #[cfg(feature = "send")] -unsafe impl Send for UserDataTypeId {} +unsafe impl Send for UserDataType {} -impl UserDataRegistry { +impl UserDataRegistry { #[inline(always)] - pub(crate) fn new(lua: &Lua, type_id: TypeId) -> Self { - Self::with_type_id(lua, UserDataTypeId::Shared(type_id)) + pub(crate) fn new(lua: &Lua) -> Self { + Self::with_type(lua, UserDataType::Shared(TypeIdHints::new::())) } +} +impl UserDataRegistry { #[inline(always)] pub(crate) fn new_unique(lua: &Lua, ud_ptr: *mut c_void) -> Self { - Self::with_type_id(lua, UserDataTypeId::Unique(ud_ptr)) + Self::with_type(lua, UserDataType::Unique(ud_ptr)) } #[inline(always)] - fn with_type_id(lua: &Lua, ud_type_id: UserDataTypeId) -> Self { + fn with_type(lua: &Lua, r#type: UserDataType) -> Self { let raw = RawUserDataRegistry { fields: Vec::new(), field_getters: Vec::new(), @@ -127,15 +97,15 @@ impl UserDataRegistry { #[cfg(feature = "async")] async_meta_methods: Vec::new(), destructor: super::util::userdata_destructor::, - type_id: ud_type_id.type_id(), + type_id: r#type.type_id(), type_name: short_type_name::(), }; UserDataRegistry { lua: lua.lock_arc(), raw, - ud_type_id, - _type: PhantomData, + r#type, + _phantom: PhantomData, } } @@ -152,7 +122,7 @@ impl UserDataRegistry { }; } - let target_type_id = self.ud_type_id; + let target_type = self.r#type; Box::new(move |rawlua, nargs| unsafe { if nargs == 0 { let err = Error::from_lua_conversion("missing argument", "userdata", None); @@ -164,18 +134,16 @@ impl UserDataRegistry { // Self was at position 1, so we pass 2 here let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua); - match target_type_id { + match target_type { #[rustfmt::skip] - UserDataTypeId::Shared(target_type_id) - if try_self_arg!(rawlua.get_userdata_type_id::(self_index)) == Some(target_type_id) => - { - let ud = get_userdata::>(state, self_index); - try_self_arg!((*ud).try_borrow_scoped(|ud| { + UserDataType::Shared(type_hints) => { + let type_id = try_self_arg!(rawlua.get_userdata_type_id::(state, self_index)); + try_self_arg!(borrow_userdata_scoped(state, self_index, type_id, type_hints, |ud| { method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) })) } #[rustfmt::skip] - UserDataTypeId::Unique(target_ptr) + UserDataType::Unique(target_ptr) if get_userdata::>(state, self_index) as *mut c_void == target_ptr => { let ud = target_ptr as *mut UserDataStorage; @@ -183,83 +151,6 @@ impl UserDataRegistry { method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) })) } - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - #[rustfmt::skip] - UserDataTypeId::Rc(target_type_id) - if try_self_arg!(rawlua.get_userdata_type_id::>(self_index)) == Some(target_type_id) => - { - let ud = get_userdata::>>(state, self_index); - try_self_arg!((*ud).try_borrow_scoped(|ud| { - method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) - })) - } - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - #[rustfmt::skip] - UserDataTypeId::RcRefCell(target_type_id) - if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) == Some(target_type_id) => - { - let ud = get_userdata::>>>(state, self_index); - try_self_arg!((*ud).try_borrow_scoped(|ud| { - let ud = ud.try_borrow().map_err(|_| Error::UserDataBorrowError)?; - method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua) - })) - } - #[cfg(feature = "userdata-wrappers")] - #[rustfmt::skip] - UserDataTypeId::Arc(target_type_id) - if try_self_arg!(rawlua.get_userdata_type_id::>(self_index)) == Some(target_type_id) => - { - let ud = get_userdata::>>(state, self_index); - try_self_arg!((*ud).try_borrow_scoped(|ud| { - method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) - })) - } - #[cfg(feature = "userdata-wrappers")] - #[rustfmt::skip] - UserDataTypeId::ArcMutex(target_type_id) - if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) == Some(target_type_id) => - { - let ud = get_userdata::>>>(state, self_index); - try_self_arg!((*ud).try_borrow_scoped(|ud| { - let ud = ud.try_lock().map_err(|_| Error::UserDataBorrowError)?; - method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua) - })) - } - #[cfg(feature = "userdata-wrappers")] - #[rustfmt::skip] - UserDataTypeId::ArcRwLock(target_type_id) - if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) == Some(target_type_id) => - { - let ud = get_userdata::>>>(state, self_index); - try_self_arg!((*ud).try_borrow_scoped(|ud| { - let ud = ud.try_read().map_err(|_| Error::UserDataBorrowError)?; - method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua) - })) - } - #[cfg(feature = "userdata-wrappers")] - #[rustfmt::skip] - UserDataTypeId::ArcParkingLotMutex(target_type_id) - if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) - == Some(target_type_id) => - { - let ud = get_userdata::>>>(state, self_index); - try_self_arg!((*ud).try_borrow_scoped(|ud| { - let ud = ud.try_lock().ok_or(Error::UserDataBorrowError)?; - method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua) - })) - } - #[cfg(feature = "userdata-wrappers")] - #[rustfmt::skip] - UserDataTypeId::ArcParkingLotRwLock(target_type_id) - if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) - == Some(target_type_id) => - { - let ud = get_userdata::>>>(state, self_index); - try_self_arg!((*ud).try_borrow_scoped(|ud| { - let ud = ud.try_read().ok_or(Error::UserDataBorrowError)?; - method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua) - })) - } _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), } }) @@ -279,7 +170,7 @@ impl UserDataRegistry { } let method = RefCell::new(method); - let target_type_id = self.ud_type_id; + let target_type = self.r#type; Box::new(move |rawlua, nargs| unsafe { let mut method = method.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?; if nargs == 0 { @@ -292,18 +183,16 @@ impl UserDataRegistry { // Self was at position 1, so we pass 2 here let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua); - match target_type_id { + match target_type { #[rustfmt::skip] - UserDataTypeId::Shared(target_type_id) - if try_self_arg!(rawlua.get_userdata_type_id::(self_index)) == Some(target_type_id) => - { - let ud = get_userdata::>(state, self_index); - try_self_arg!((*ud).try_borrow_scoped_mut(|ud| { + UserDataType::Shared(type_hints) => { + let type_id = try_self_arg!(rawlua.get_userdata_type_id::(state, self_index)); + try_self_arg!(borrow_userdata_scoped_mut(state, self_index, type_id, type_hints, |ud| { method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) })) } #[rustfmt::skip] - UserDataTypeId::Unique(target_ptr) + UserDataType::Unique(target_ptr) if get_userdata::>(state, self_index) as *mut c_void == target_ptr => { let ud = target_ptr as *mut UserDataStorage; @@ -311,77 +200,6 @@ impl UserDataRegistry { method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) })) } - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - #[rustfmt::skip] - UserDataTypeId::Rc(target_type_id) - if try_self_arg!(rawlua.get_userdata_type_id::>(self_index)) == Some(target_type_id) => - { - Err(Error::UserDataBorrowMutError) - }, - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - #[rustfmt::skip] - UserDataTypeId::RcRefCell(target_type_id) - if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) == Some(target_type_id) => - { - let ud = get_userdata::>>>(state, self_index); - try_self_arg!((*ud).try_borrow_scoped(|ud| { - let mut ud = ud.try_borrow_mut().map_err(|_| Error::UserDataBorrowMutError)?; - method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua) - })) - } - #[cfg(feature = "userdata-wrappers")] - #[rustfmt::skip] - UserDataTypeId::Arc(target_type_id) - if try_self_arg!(rawlua.get_userdata_type_id::>(self_index)) == Some(target_type_id) => - { - Err(Error::UserDataBorrowMutError) - }, - #[cfg(feature = "userdata-wrappers")] - #[rustfmt::skip] - UserDataTypeId::ArcMutex(target_type_id) - if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) == Some(target_type_id) => - { - let ud = get_userdata::>>>(state, self_index); - try_self_arg!((*ud).try_borrow_scoped(|ud| { - let mut ud = ud.try_lock().map_err(|_| Error::UserDataBorrowMutError)?; - method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua) - })) - } - #[cfg(feature = "userdata-wrappers")] - #[rustfmt::skip] - UserDataTypeId::ArcRwLock(target_type_id) - if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) == Some(target_type_id) => - { - let ud = get_userdata::>>>(state, self_index); - try_self_arg!((*ud).try_borrow_scoped(|ud| { - let mut ud = ud.try_write().map_err(|_| Error::UserDataBorrowMutError)?; - method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua) - })) - } - #[cfg(feature = "userdata-wrappers")] - #[rustfmt::skip] - UserDataTypeId::ArcParkingLotMutex(target_type_id) - if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) - == Some(target_type_id) => - { - let ud = get_userdata::>>>(state, self_index); - try_self_arg!((*ud).try_borrow_scoped(|ud| { - let mut ud = ud.try_lock().ok_or(Error::UserDataBorrowMutError)?; - method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua) - })) - } - #[cfg(feature = "userdata-wrappers")] - #[rustfmt::skip] - UserDataTypeId::ArcParkingLotRwLock(target_type_id) - if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) - == Some(target_type_id) => - { - let ud = get_userdata::>>>(state, self_index); - try_self_arg!((*ud).try_borrow_scoped(|ud| { - let mut ud = ud.try_write().ok_or(Error::UserDataBorrowMutError)?; - method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua) - })) - } _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), } }) @@ -789,14 +607,10 @@ impl UserDataMethods for UserDataRegistry { } macro_rules! lua_userdata_impl { - ($type:ty => $type_variant:tt) => { - lua_userdata_impl!($type, UserDataTypeId::$type_variant(TypeId::of::<$type>())); - }; - - ($type:ty, $type_id:expr) => { + ($type:ty) => { impl UserData for $type { fn register(registry: &mut UserDataRegistry) { - let mut orig_registry = UserDataRegistry::with_type_id(registry.lua.lua(), $type_id); + let mut orig_registry = UserDataRegistry::new(registry.lua.lua()); T::register(&mut orig_registry); // Copy all fields, methods, etc. from the original registry @@ -818,22 +632,22 @@ macro_rules! lua_userdata_impl { // A special proxy object for UserData pub(crate) struct UserDataProxy(pub(crate) PhantomData); -lua_userdata_impl!(UserDataProxy, UserDataTypeId::Shared(TypeId::of::())); +lua_userdata_impl!(UserDataProxy); #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] -lua_userdata_impl!(Rc => Rc); +lua_userdata_impl!(std::rc::Rc); #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] -lua_userdata_impl!(Rc> => RcRefCell); +lua_userdata_impl!(std::rc::Rc>); #[cfg(feature = "userdata-wrappers")] -lua_userdata_impl!(Arc => Arc); +lua_userdata_impl!(std::sync::Arc); #[cfg(feature = "userdata-wrappers")] -lua_userdata_impl!(Arc> => ArcMutex); +lua_userdata_impl!(std::sync::Arc>); #[cfg(feature = "userdata-wrappers")] -lua_userdata_impl!(Arc> => ArcRwLock); +lua_userdata_impl!(std::sync::Arc>); #[cfg(feature = "userdata-wrappers")] -lua_userdata_impl!(Arc> => ArcParkingLotMutex); +lua_userdata_impl!(std::sync::Arc>); #[cfg(feature = "userdata-wrappers")] -lua_userdata_impl!(Arc> => ArcParkingLotRwLock); +lua_userdata_impl!(std::sync::Arc>); #[cfg(test)] mod assertions { diff --git a/src/util/mod.rs b/src/util/mod.rs index 48e7d8fa..d53f1b7b 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -12,8 +12,9 @@ pub(crate) use error::{ pub(crate) use short_names::short_type_name; pub(crate) use types::TypeKey; pub(crate) use userdata::{ - get_destructed_userdata_metatable, get_internal_metatable, get_internal_userdata, get_userdata, - init_internal_metatable, init_userdata_metatable, push_internal_userdata, take_userdata, + borrow_userdata_scoped, borrow_userdata_scoped_mut, get_destructed_userdata_metatable, + get_internal_metatable, get_internal_userdata, get_userdata, init_internal_metatable, + init_userdata_metatable, push_internal_userdata, take_userdata, TypeIdHints, DESTRUCTED_USERDATA_METATABLE, }; diff --git a/src/util/userdata.rs b/src/util/userdata.rs index 119b8c8d..359dcf81 100644 --- a/src/util/userdata.rs +++ b/src/util/userdata.rs @@ -1,7 +1,9 @@ +use std::any::TypeId; use std::os::raw::{c_int, c_void}; use std::{ptr, str}; -use crate::error::Result; +use crate::error::{Error, Result}; +use crate::userdata::UserDataStorage; use crate::util::{check_stack, get_metatable_ptr, push_table, rawget_field, rawset_field, TypeKey}; // Pushes the userdata and attaches a metatable with __gc method. @@ -339,6 +341,199 @@ unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::lua_State) - 0 } +// Userdata type hints, used to match types of wrapped userdata +#[derive(Clone, Copy)] +pub(crate) struct TypeIdHints { + t: TypeId, + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + rc: TypeId, + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + rc_refcell: TypeId, + + #[cfg(feature = "userdata-wrappers")] + arc: TypeId, + #[cfg(feature = "userdata-wrappers")] + arc_mutex: TypeId, + #[cfg(feature = "userdata-wrappers")] + arc_rwlock: TypeId, + #[cfg(feature = "userdata-wrappers")] + arc_pl_mutex: TypeId, + #[cfg(feature = "userdata-wrappers")] + arc_pl_rwlock: TypeId, +} + +impl TypeIdHints { + pub(crate) fn new() -> Self { + Self { + t: TypeId::of::(), + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + rc: TypeId::of::>(), + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + rc_refcell: TypeId::of::>>(), + + #[cfg(feature = "userdata-wrappers")] + arc: TypeId::of::>(), + #[cfg(feature = "userdata-wrappers")] + arc_mutex: TypeId::of::>>(), + #[cfg(feature = "userdata-wrappers")] + arc_rwlock: TypeId::of::>>(), + #[cfg(feature = "userdata-wrappers")] + arc_pl_mutex: TypeId::of::>>(), + #[cfg(feature = "userdata-wrappers")] + arc_pl_rwlock: TypeId::of::>>(), + } + } + + #[inline(always)] + pub(crate) fn type_id(&self) -> TypeId { + self.t + } +} + +pub(crate) unsafe fn borrow_userdata_scoped( + state: *mut ffi::lua_State, + idx: c_int, + type_id: Option, + type_hints: TypeIdHints, + f: impl FnOnce(&T) -> R, +) -> Result { + match type_id { + Some(type_id) if type_id == type_hints.t => { + let ud = get_userdata::>(state, idx); + (*ud).try_borrow_scoped(|ud| f(ud)) + } + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Some(type_id) if type_id == type_hints.rc => { + let ud = get_userdata::>>(state, idx); + (*ud).try_borrow_scoped(|ud| f(ud)) + } + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Some(type_id) if type_id == type_hints.rc_refcell => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped(|ud| { + let ud = ud.try_borrow().map_err(|_| Error::UserDataBorrowError)?; + Ok(f(&ud)) + })? + } + + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc => { + let ud = get_userdata::>>(state, idx); + (*ud).try_borrow_scoped(|ud| f(ud)) + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_mutex => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped(|ud| { + let ud = ud.try_lock().map_err(|_| Error::UserDataBorrowError)?; + Ok(f(&ud)) + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_rwlock => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped(|ud| { + let ud = ud.try_read().map_err(|_| Error::UserDataBorrowError)?; + Ok(f(&ud)) + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_pl_mutex => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped(|ud| { + let ud = ud.try_lock().ok_or(Error::UserDataBorrowError)?; + Ok(f(&ud)) + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_pl_rwlock => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped(|ud| { + let ud = ud.try_read().ok_or(Error::UserDataBorrowError)?; + Ok(f(&ud)) + })? + } + _ => Err(Error::UserDataTypeMismatch), + } +} + +pub(crate) unsafe fn borrow_userdata_scoped_mut( + state: *mut ffi::lua_State, + idx: c_int, + type_id: Option, + type_hints: TypeIdHints, + f: impl FnOnce(&mut T) -> R, +) -> Result { + match type_id { + Some(type_id) if type_id == type_hints.t => { + let ud = get_userdata::>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| f(ud)) + } + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Some(type_id) if type_id == type_hints.rc => { + let ud = get_userdata::>>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| match std::rc::Rc::get_mut(ud) { + Some(ud) => Ok(f(ud)), + None => Err(Error::UserDataBorrowMutError), + })? + } + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Some(type_id) if type_id == type_hints.rc_refcell => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped(|ud| { + let mut ud = ud.try_borrow_mut().map_err(|_| Error::UserDataBorrowMutError)?; + Ok(f(&mut ud)) + })? + } + + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc => { + let ud = get_userdata::>>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| match std::sync::Arc::get_mut(ud) { + Some(ud) => Ok(f(ud)), + None => Err(Error::UserDataBorrowMutError), + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_mutex => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| { + let mut ud = ud.try_lock().map_err(|_| Error::UserDataBorrowMutError)?; + Ok(f(&mut ud)) + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_rwlock => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| { + let mut ud = ud.try_write().map_err(|_| Error::UserDataBorrowMutError)?; + Ok(f(&mut ud)) + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_pl_mutex => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| { + let mut ud = ud.try_lock().ok_or(Error::UserDataBorrowMutError)?; + Ok(f(&mut ud)) + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_pl_rwlock => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| { + let mut ud = ud.try_write().ok_or(Error::UserDataBorrowMutError)?; + Ok(f(&mut ud)) + })? + } + _ => Err(Error::UserDataTypeMismatch), + } +} + pub(crate) static DESTRUCTED_USERDATA_METATABLE: u8 = 0; static USERDATA_METATABLE_INDEX: u8 = 0; static USERDATA_METATABLE_NEWINDEX: u8 = 0; diff --git a/tests/userdata.rs b/tests/userdata.rs index 77dbfcf5..c85b75d0 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -913,6 +913,7 @@ fn test_nested_userdata_gc() -> Result<()> { #[cfg(feature = "userdata-wrappers")] #[test] fn test_userdata_wrappers() -> Result<()> { + #[derive(Debug)] struct MyUserData(i64); impl UserData for MyUserData { @@ -924,6 +925,10 @@ fn test_userdata_wrappers() -> Result<()> { Ok(()) }) } + + fn add_methods>(methods: &mut M) { + methods.add_method("dbg", |_, this, ()| Ok(format!("{this:?}"))); + } } let lua = Lua::new(); @@ -932,136 +937,359 @@ fn test_userdata_wrappers() -> Result<()> { // Rc #[cfg(not(feature = "send"))] { - let ud = std::rc::Rc::new(MyUserData(1)); - globals.set("rc_ud", ud.clone())?; + use std::rc::Rc; + + let ud = Rc::new(MyUserData(1)); + globals.set("ud", ud.clone())?; lua.load( r#" - assert(rc_ud.static == "constant") - local ok, err = pcall(function() rc_ud.data = 2 end) + assert(ud.static == "constant") + local ok, err = pcall(function() ud.data = 2 end) assert( - tostring(err):sub(1, 32) == "error mutably borrowing userdata", - "expected error mutably borrowing userdata, got " .. tostring(err) + tostring(err):find("error mutably borrowing userdata") ~= nil, + "expected 'error mutably borrowing userdata', got '" .. tostring(err) .. "'" ) - assert(rc_ud.data == 1) + assert(ud.data == 1) + assert(ud:dbg(), "MyUserData(1)") "#, ) .exec() .unwrap(); - globals.set("rc_ud", Nil)?; + + // Test borrowing original userdata + { + let ud = globals.get::("ud")?; + assert!(ud.is::>()); + assert!(!ud.is::()); + + assert_eq!(ud.borrow::()?.0, 1); + assert!(matches!( + ud.borrow_mut::(), + Err(Error::UserDataBorrowMutError) + )); + assert!(ud.borrow_mut::>().is_ok()); + + assert_eq!(ud.borrow_scoped::(|x| x.0)?, 1); + assert!(matches!( + ud.borrow_mut_scoped::(|_| ()), + Err(Error::UserDataBorrowMutError) + )); + } + + // Collect userdata + globals.set("ud", Nil)?; lua.gc_collect()?; - assert_eq!(std::rc::Rc::strong_count(&ud), 1); + assert_eq!(Rc::strong_count(&ud), 1); + + // We must be able to mutate userdata when having one reference only + globals.set("ud", ud)?; + lua.load( + r#" + ud.data = 2 + assert(ud.data == 2) + "#, + ) + .exec() + .unwrap(); } // Rc> #[cfg(not(feature = "send"))] { - let ud = std::rc::Rc::new(std::cell::RefCell::new(MyUserData(2))); - globals.set("rc_refcell_ud", ud.clone())?; + use std::cell::RefCell; + use std::rc::Rc; + + let ud = Rc::new(RefCell::new(MyUserData(2))); + globals.set("ud", ud.clone())?; lua.load( r#" - assert(rc_refcell_ud.static == "constant") - rc_refcell_ud.data = rc_refcell_ud.data + 1 - assert(rc_refcell_ud.data == 3) - "#, + assert(ud.static == "constant") + assert(ud.data == 2) + ud.data = 10 + assert(ud.data == 10) + assert(ud:dbg() == "MyUserData(10)") + "#, ) - .exec()?; - assert_eq!(ud.borrow().0, 3); - globals.set("rc_refcell_ud", Nil)?; + .exec() + .unwrap(); + + // Test borrowing original userdata + { + let ud = globals.get::("ud")?; + assert!(ud.is::>>()); + assert!(!ud.is::()); + + assert_eq!(ud.borrow::()?.0, 10); + assert_eq!(ud.borrow_mut::()?.0, 10); + ud.borrow_mut::()?.0 = 20; + assert_eq!(ud.borrow::()?.0, 20); + + assert_eq!(ud.borrow_scoped::(|x| x.0)?, 20); + ud.borrow_mut_scoped::(|x| x.0 = 30)?; + assert_eq!(ud.borrow::()?.0, 30); + + // Double (read) borrow is okay + let _borrow = ud.borrow::()?; + assert_eq!(ud.borrow::()?.0, 30); + assert!(matches!( + ud.borrow_mut::(), + Err(Error::UserDataBorrowMutError) + )); + } + + // Collect userdata + globals.set("ud", Nil)?; + lua.gc_collect()?; + assert_eq!(Rc::strong_count(&ud), 1); + + // Check destroying wrapped UserDataRef without references in Lua + let ud = lua.convert::>(ud)?; lua.gc_collect()?; - assert_eq!(std::rc::Rc::strong_count(&ud), 1); + assert_eq!(ud.0, 30); + drop(ud); } // Arc { let ud = Arc::new(MyUserData(3)); - globals.set("arc_ud", ud.clone())?; + globals.set("ud", ud.clone())?; lua.load( r#" - assert(arc_ud.static == "constant") - local ok, err = pcall(function() arc_ud.data = 10 end) + assert(ud.static == "constant") + local ok, err = pcall(function() ud.data = 4 end) assert( - tostring(err):sub(1, 32) == "error mutably borrowing userdata", - "expected error mutably borrowing userdata, got " .. tostring(err) + tostring(err):find("error mutably borrowing userdata") ~= nil, + "expected 'error mutably borrowing userdata', got '" .. tostring(err) .. "'" ) - assert(arc_ud.data == 3) - "#, + assert(ud.data == 3) + assert(ud:dbg() == "MyUserData(3)") + "#, ) - .exec()?; - globals.set("arc_ud", Nil)?; + .exec() + .unwrap(); + + // Test borrowing original userdata + { + let ud = globals.get::("ud")?; + assert!(ud.is::>()); + assert!(!ud.is::()); + + assert_eq!(ud.borrow::()?.0, 3); + assert!(matches!( + ud.borrow_mut::(), + Err(Error::UserDataBorrowMutError) + )); + assert!(ud.borrow_mut::>().is_ok()); + + assert_eq!(ud.borrow_scoped::(|x| x.0)?, 3); + assert!(matches!( + ud.borrow_mut_scoped::(|_| ()), + Err(Error::UserDataBorrowMutError) + )); + } + + // Collect userdata + globals.set("ud", Nil)?; lua.gc_collect()?; assert_eq!(Arc::strong_count(&ud), 1); + + // We must be able to mutate userdata when having one reference only + globals.set("ud", ud)?; + lua.load( + r#" + ud.data = 4 + assert(ud.data == 4) + "#, + ) + .exec() + .unwrap(); } // Arc> { - let ud = Arc::new(std::sync::Mutex::new(MyUserData(4))); - globals.set("arc_mutex_ud", ud.clone())?; + use std::sync::Mutex; + + let ud = Arc::new(Mutex::new(MyUserData(5))); + globals.set("ud", ud.clone())?; lua.load( r#" - assert(arc_mutex_ud.static == "constant") - arc_mutex_ud.data = arc_mutex_ud.data + 1 - assert(arc_mutex_ud.data == 5) - "#, + assert(ud.static == "constant") + assert(ud.data == 5) + ud.data = 6 + assert(ud.data == 6) + assert(ud:dbg() == "MyUserData(6)") + "#, ) - .exec()?; - assert_eq!(ud.lock().unwrap().0, 5); - globals.set("arc_mutex_ud", Nil)?; + .exec() + .unwrap(); + + // Test borrowing original userdata + { + let ud = globals.get::("ud")?; + assert!(ud.is::>>()); + assert!(!ud.is::()); + + #[rustfmt::skip] + assert!(matches!(ud.borrow::(), Err(Error::UserDataTypeMismatch))); + #[rustfmt::skip] + assert!(matches!(ud.borrow_mut::(), Err(Error::UserDataTypeMismatch))); + + assert_eq!(ud.borrow_scoped::(|x| x.0)?, 6); + ud.borrow_mut_scoped::(|x| x.0 = 8)?; + assert_eq!(ud.borrow_scoped::(|x| x.0)?, 8); + } + + // Collect userdata + globals.set("ud", Nil)?; lua.gc_collect()?; assert_eq!(Arc::strong_count(&ud), 1); } // Arc> { - let ud = Arc::new(std::sync::RwLock::new(MyUserData(6))); - globals.set("arc_rwlock_ud", ud.clone())?; + use std::sync::RwLock; + + let ud = Arc::new(RwLock::new(MyUserData(9))); + globals.set("ud", ud.clone())?; lua.load( r#" - assert(arc_rwlock_ud.static == "constant") - arc_rwlock_ud.data = arc_rwlock_ud.data + 1 - assert(arc_rwlock_ud.data == 7) - "#, + assert(ud.static == "constant") + assert(ud.data == 9) + ud.data = 10 + assert(ud.data == 10) + assert(ud:dbg() == "MyUserData(10)") + "#, ) - .exec()?; - assert_eq!(ud.read().unwrap().0, 7); - globals.set("arc_rwlock_ud", Nil)?; + .exec() + .unwrap(); + + // Test borrowing original userdata + { + let ud = globals.get::("ud")?; + assert!(ud.is::>>()); + assert!(!ud.is::()); + + #[rustfmt::skip] + assert!(matches!(ud.borrow::(), Err(Error::UserDataTypeMismatch))); + #[rustfmt::skip] + assert!(matches!(ud.borrow_mut::(), Err(Error::UserDataTypeMismatch))); + + assert_eq!(ud.borrow_scoped::(|x| x.0)?, 10); + ud.borrow_mut_scoped::(|x| x.0 = 12)?; + assert_eq!(ud.borrow_scoped::(|x| x.0)?, 12); + } + + // Collect userdata + globals.set("ud", Nil)?; lua.gc_collect()?; assert_eq!(Arc::strong_count(&ud), 1); } // Arc> { - let ud = Arc::new(parking_lot::Mutex::new(MyUserData(8))); - globals.set("arc_parking_lot_mutex_ud", ud.clone())?; + use parking_lot::Mutex; + + let ud = Arc::new(Mutex::new(MyUserData(13))); + globals.set("ud", ud.clone())?; lua.load( r#" - assert(arc_parking_lot_mutex_ud.static == "constant") - arc_parking_lot_mutex_ud.data = arc_parking_lot_mutex_ud.data + 1 - assert(arc_parking_lot_mutex_ud.data == 9) - "#, + assert(ud.static == "constant") + assert(ud.data == 13) + ud.data = 14 + assert(ud.data == 14) + assert(ud:dbg() == "MyUserData(14)") + "#, ) - .exec()?; - assert_eq!(ud.lock().0, 9); - globals.set("arc_parking_lot_mutex_ud", Nil)?; + .exec() + .unwrap(); + + // Test borrowing original userdata + { + let ud = globals.get::("ud")?; + assert!(ud.is::>>()); + assert!(!ud.is::()); + + assert_eq!(ud.borrow::()?.0, 14); + assert_eq!(ud.borrow_mut::()?.0, 14); + ud.borrow_mut::()?.0 = 15; + assert_eq!(ud.borrow::()?.0, 15); + + assert_eq!(ud.borrow_scoped::(|x| x.0)?, 15); + ud.borrow_mut_scoped::(|x| x.0 = 16)?; + assert_eq!(ud.borrow::()?.0, 16); + + // Double borrow is not allowed + let _borrow = ud.borrow::()?; + assert!(matches!( + ud.borrow::(), + Err(Error::UserDataBorrowError) + )); + } + + // Collect userdata + globals.set("ud", Nil)?; lua.gc_collect()?; assert_eq!(Arc::strong_count(&ud), 1); + + // Check destroying wrapped UserDataRef without references in Lua + let ud = lua.convert::>(ud)?; + lua.gc_collect()?; + assert_eq!(ud.0, 16); + drop(ud); } // Arc> { - let ud = Arc::new(parking_lot::RwLock::new(MyUserData(10))); - globals.set("arc_parking_lot_rwlock_ud", ud.clone())?; + use parking_lot::RwLock; + + let ud = Arc::new(RwLock::new(MyUserData(17))); + globals.set("ud", ud.clone())?; lua.load( r#" - assert(arc_parking_lot_rwlock_ud.static == "constant") - arc_parking_lot_rwlock_ud.data = arc_parking_lot_rwlock_ud.data + 1 - assert(arc_parking_lot_rwlock_ud.data == 11) - "#, + assert(ud.static == "constant") + assert(ud.data == 17) + ud.data = 18 + assert(ud.data == 18) + assert(ud:dbg() == "MyUserData(18)") + "#, ) - .exec()?; - assert_eq!(ud.read().0, 11); - globals.set("arc_parking_lot_rwlock_ud", Nil)?; + .exec() + .unwrap(); + + // Test borrowing original userdata + { + let ud = globals.get::("ud")?; + assert!(ud.is::>>()); + assert!(!ud.is::()); + + assert_eq!(ud.borrow::()?.0, 18); + assert_eq!(ud.borrow_mut::()?.0, 18); + ud.borrow_mut::()?.0 = 19; + assert_eq!(ud.borrow::()?.0, 19); + + assert_eq!(ud.borrow_scoped::(|x| x.0)?, 19); + ud.borrow_mut_scoped::(|x| x.0 = 20)?; + assert_eq!(ud.borrow::()?.0, 20); + + // Multiple read borrows are allowed with parking_lot::RwLock + let _borrow1 = ud.borrow::()?; + let _borrow2 = ud.borrow::()?; + assert!(matches!( + ud.borrow_mut::(), + Err(Error::UserDataBorrowMutError) + )); + } + + // Collect userdata + globals.set("ud", Nil)?; lua.gc_collect()?; assert_eq!(Arc::strong_count(&ud), 1); + + // Check destroying wrapped UserDataRef without references in Lua + let ud = lua.convert::>(ud)?; + lua.gc_collect()?; + assert_eq!(ud.0, 20); + drop(ud); } Ok(()) From 0a1a1fe0b631db6f59fe17741ea59a680124052c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 21 Mar 2025 15:11:09 +0000 Subject: [PATCH 352/635] Move some userdata helpers from `crate::util` to `crate::userdata::util` --- src/state/raw.rs | 9 +- src/userdata.rs | 8 +- src/userdata/registry.rs | 7 +- src/userdata/util.rs | 394 ++++++++++++++++++++++++++++++++++++- src/util/mod.rs | 6 +- src/util/userdata.rs | 410 +-------------------------------------- 6 files changed, 416 insertions(+), 418 deletions(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index e24ec879..485ed405 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -23,13 +23,14 @@ use crate::types::{ MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc, }; use crate::userdata::{ - AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, UserDataStorage, + init_userdata_metatable, AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, + UserDataStorage, }; use crate::util::{ assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state, - get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, init_userdata_metatable, - pop_error, push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, - short_type_name, StackGuard, WrappedFailure, + get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, pop_error, + push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name, + StackGuard, WrappedFailure, }; use crate::value::{Nil, Value}; diff --git a/src/userdata.rs b/src/userdata.rs index 86cdbc6d..b3be682d 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -12,10 +12,7 @@ use crate::string::String; use crate::table::{Table, TablePairs}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::types::{MaybeSend, ValueRef}; -use crate::util::{ - borrow_userdata_scoped, borrow_userdata_scoped_mut, check_stack, get_userdata, push_string, - take_userdata, StackGuard, TypeIdHints, -}; +use crate::util::{check_stack, get_userdata, push_string, take_userdata, StackGuard}; use crate::value::Value; #[cfg(feature = "async")] @@ -32,6 +29,9 @@ pub(crate) use cell::UserDataStorage; pub use r#ref::{UserDataRef, UserDataRefMut}; pub use registry::UserDataRegistry; pub(crate) use registry::{RawUserDataRegistry, UserDataProxy}; +pub(crate) use util::{ + borrow_userdata_scoped, borrow_userdata_scoped_mut, init_userdata_metatable, TypeIdHints, +}; /// Kinds of metamethods that can be overridden. /// diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index f9e88c9b..cfb6fe60 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -10,10 +10,11 @@ use crate::error::{Error, Result}; use crate::state::{Lua, LuaGuard}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::types::{Callback, MaybeSend}; -use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataStorage}; -use crate::util::{ - borrow_userdata_scoped, borrow_userdata_scoped_mut, get_userdata, short_type_name, TypeIdHints, +use crate::userdata::{ + borrow_userdata_scoped, borrow_userdata_scoped_mut, AnyUserData, MetaMethod, TypeIdHints, UserData, + UserDataFields, UserDataMethods, UserDataStorage, }; +use crate::util::{get_userdata, short_type_name}; use crate::value::Value; #[cfg(feature = "async")] diff --git a/src/userdata/util.rs b/src/userdata/util.rs index 8cff934c..bc62a491 100644 --- a/src/userdata/util.rs +++ b/src/userdata/util.rs @@ -1,9 +1,11 @@ +use std::any::TypeId; use std::cell::Cell; use std::marker::PhantomData; use std::os::raw::c_int; use super::UserDataStorage; -use crate::util::{get_userdata, take_userdata}; +use crate::error::{Error, Result}; +use crate::util::{get_userdata, rawget_field, rawset_field, take_userdata}; // This is a trick to check if a type is `Sync` or not. // It uses leaked specialization feature from stdlib. @@ -34,6 +36,393 @@ pub(crate) fn is_sync() -> bool { is_sync.get() } +// Userdata type hints, used to match types of wrapped userdata +#[derive(Clone, Copy)] +pub(crate) struct TypeIdHints { + t: TypeId, + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + rc: TypeId, + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + rc_refcell: TypeId, + + #[cfg(feature = "userdata-wrappers")] + arc: TypeId, + #[cfg(feature = "userdata-wrappers")] + arc_mutex: TypeId, + #[cfg(feature = "userdata-wrappers")] + arc_rwlock: TypeId, + #[cfg(feature = "userdata-wrappers")] + arc_pl_mutex: TypeId, + #[cfg(feature = "userdata-wrappers")] + arc_pl_rwlock: TypeId, +} + +impl TypeIdHints { + pub(crate) fn new() -> Self { + Self { + t: TypeId::of::(), + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + rc: TypeId::of::>(), + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + rc_refcell: TypeId::of::>>(), + + #[cfg(feature = "userdata-wrappers")] + arc: TypeId::of::>(), + #[cfg(feature = "userdata-wrappers")] + arc_mutex: TypeId::of::>>(), + #[cfg(feature = "userdata-wrappers")] + arc_rwlock: TypeId::of::>>(), + #[cfg(feature = "userdata-wrappers")] + arc_pl_mutex: TypeId::of::>>(), + #[cfg(feature = "userdata-wrappers")] + arc_pl_rwlock: TypeId::of::>>(), + } + } + + #[inline(always)] + pub(crate) fn type_id(&self) -> TypeId { + self.t + } +} + +pub(crate) unsafe fn borrow_userdata_scoped( + state: *mut ffi::lua_State, + idx: c_int, + type_id: Option, + type_hints: TypeIdHints, + f: impl FnOnce(&T) -> R, +) -> Result { + match type_id { + Some(type_id) if type_id == type_hints.t => { + let ud = get_userdata::>(state, idx); + (*ud).try_borrow_scoped(|ud| f(ud)) + } + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Some(type_id) if type_id == type_hints.rc => { + let ud = get_userdata::>>(state, idx); + (*ud).try_borrow_scoped(|ud| f(ud)) + } + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Some(type_id) if type_id == type_hints.rc_refcell => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped(|ud| { + let ud = ud.try_borrow().map_err(|_| Error::UserDataBorrowError)?; + Ok(f(&ud)) + })? + } + + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc => { + let ud = get_userdata::>>(state, idx); + (*ud).try_borrow_scoped(|ud| f(ud)) + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_mutex => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped(|ud| { + let ud = ud.try_lock().map_err(|_| Error::UserDataBorrowError)?; + Ok(f(&ud)) + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_rwlock => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped(|ud| { + let ud = ud.try_read().map_err(|_| Error::UserDataBorrowError)?; + Ok(f(&ud)) + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_pl_mutex => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped(|ud| { + let ud = ud.try_lock().ok_or(Error::UserDataBorrowError)?; + Ok(f(&ud)) + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_pl_rwlock => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped(|ud| { + let ud = ud.try_read().ok_or(Error::UserDataBorrowError)?; + Ok(f(&ud)) + })? + } + _ => Err(Error::UserDataTypeMismatch), + } +} + +pub(crate) unsafe fn borrow_userdata_scoped_mut( + state: *mut ffi::lua_State, + idx: c_int, + type_id: Option, + type_hints: TypeIdHints, + f: impl FnOnce(&mut T) -> R, +) -> Result { + match type_id { + Some(type_id) if type_id == type_hints.t => { + let ud = get_userdata::>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| f(ud)) + } + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Some(type_id) if type_id == type_hints.rc => { + let ud = get_userdata::>>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| match std::rc::Rc::get_mut(ud) { + Some(ud) => Ok(f(ud)), + None => Err(Error::UserDataBorrowMutError), + })? + } + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Some(type_id) if type_id == type_hints.rc_refcell => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped(|ud| { + let mut ud = ud.try_borrow_mut().map_err(|_| Error::UserDataBorrowMutError)?; + Ok(f(&mut ud)) + })? + } + + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc => { + let ud = get_userdata::>>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| match std::sync::Arc::get_mut(ud) { + Some(ud) => Ok(f(ud)), + None => Err(Error::UserDataBorrowMutError), + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_mutex => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| { + let mut ud = ud.try_lock().map_err(|_| Error::UserDataBorrowMutError)?; + Ok(f(&mut ud)) + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_rwlock => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| { + let mut ud = ud.try_write().map_err(|_| Error::UserDataBorrowMutError)?; + Ok(f(&mut ud)) + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_pl_mutex => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| { + let mut ud = ud.try_lock().ok_or(Error::UserDataBorrowMutError)?; + Ok(f(&mut ud)) + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_pl_rwlock => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| { + let mut ud = ud.try_write().ok_or(Error::UserDataBorrowMutError)?; + Ok(f(&mut ud)) + })? + } + _ => Err(Error::UserDataTypeMismatch), + } +} + +// Populates the given table with the appropriate members to be a userdata metatable for the given +// type. This function takes the given table at the `metatable` index, and adds an appropriate +// `__gc` member to it for the given type and a `__metatable` entry to protect the table from script +// access. The function also, if given a `field_getters` or `methods` tables, will create an +// `__index` metamethod (capturing previous one) to lookup in `field_getters` first, then `methods` +// and falling back to the captured `__index` if no matches found. +// The same is also applicable for `__newindex` metamethod and `field_setters` table. +// Internally uses 9 stack spaces and does not call checkstack. +pub(crate) unsafe fn init_userdata_metatable( + state: *mut ffi::lua_State, + metatable: c_int, + field_getters: Option, + field_setters: Option, + methods: Option, +) -> Result<()> { + if field_getters.is_some() || methods.is_some() { + // Push `__index` generator function + init_userdata_metatable_index(state)?; + + let index_type = rawget_field(state, metatable, "__index")?; + match index_type { + ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => { + for &idx in &[field_getters, methods] { + if let Some(idx) = idx { + ffi::lua_pushvalue(state, idx); + } else { + ffi::lua_pushnil(state); + } + } + + // Generate `__index` + protect_lua!(state, 4, 1, fn(state) ffi::lua_call(state, 3, 1))?; + } + _ => mlua_panic!("improper `__index` type: {}", index_type), + } + + rawset_field(state, metatable, "__index")?; + } + + if let Some(field_setters) = field_setters { + // Push `__newindex` generator function + init_userdata_metatable_newindex(state)?; + + let newindex_type = rawget_field(state, metatable, "__newindex")?; + match newindex_type { + ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => { + ffi::lua_pushvalue(state, field_setters); + // Generate `__newindex` + protect_lua!(state, 3, 1, fn(state) ffi::lua_call(state, 2, 1))?; + } + _ => mlua_panic!("improper `__newindex` type: {}", newindex_type), + } + + rawset_field(state, metatable, "__newindex")?; + } + + ffi::lua_pushboolean(state, 0); + rawset_field(state, metatable, "__metatable")?; + + Ok(()) +} + +unsafe extern "C-unwind" fn lua_error_impl(state: *mut ffi::lua_State) -> c_int { + ffi::lua_error(state); +} + +unsafe extern "C-unwind" fn lua_isfunction_impl(state: *mut ffi::lua_State) -> c_int { + ffi::lua_pushboolean(state, ffi::lua_isfunction(state, -1)); + 1 +} + +unsafe extern "C-unwind" fn lua_istable_impl(state: *mut ffi::lua_State) -> c_int { + ffi::lua_pushboolean(state, ffi::lua_istable(state, -1)); + 1 +} + +unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()> { + let index_key = &USERDATA_METATABLE_INDEX as *const u8 as *const _; + if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, index_key) == ffi::LUA_TFUNCTION { + return Ok(()); + } + ffi::lua_pop(state, 1); + + // Create and cache `__index` generator + let code = cr#" + local error, isfunction, istable = ... + return function (__index, field_getters, methods) + -- Common case: has field getters and index is a table + if field_getters ~= nil and methods == nil and istable(__index) then + return function (self, key) + local field_getter = field_getters[key] + if field_getter ~= nil then + return field_getter(self) + end + return __index[key] + end + end + + return function (self, key) + if field_getters ~= nil then + local field_getter = field_getters[key] + if field_getter ~= nil then + return field_getter(self) + end + end + + if methods ~= nil then + local method = methods[key] + if method ~= nil then + return method + end + end + + if isfunction(__index) then + return __index(self, key) + elseif __index == nil then + error("attempt to get an unknown field '"..key.."'") + else + return __index[key] + end + end + end + "#; + protect_lua!(state, 0, 1, |state| { + let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_index")); + if ret != ffi::LUA_OK { + ffi::lua_error(state); + } + ffi::lua_pushcfunction(state, lua_error_impl); + ffi::lua_pushcfunction(state, lua_isfunction_impl); + ffi::lua_pushcfunction(state, lua_istable_impl); + ffi::lua_call(state, 3, 1); + + #[cfg(feature = "luau-jit")] + if ffi::luau_codegen_supported() != 0 { + ffi::luau_codegen_compile(state, -1); + } + + // Store in the registry + ffi::lua_pushvalue(state, -1); + ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, index_key); + }) +} + +unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result<()> { + let newindex_key = &USERDATA_METATABLE_NEWINDEX as *const u8 as *const _; + if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, newindex_key) == ffi::LUA_TFUNCTION { + return Ok(()); + } + ffi::lua_pop(state, 1); + + // Create and cache `__newindex` generator + let code = cr#" + local error, isfunction = ... + return function (__newindex, field_setters) + return function (self, key, value) + if field_setters ~= nil then + local field_setter = field_setters[key] + if field_setter ~= nil then + field_setter(self, value) + return + end + end + + if isfunction(__newindex) then + __newindex(self, key, value) + elseif __newindex == nil then + error("attempt to set an unknown field '"..key.."'") + else + __newindex[key] = value + end + end + end + "#; + protect_lua!(state, 0, 1, |state| { + let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_newindex")); + if ret != ffi::LUA_OK { + ffi::lua_error(state); + } + ffi::lua_pushcfunction(state, lua_error_impl); + ffi::lua_pushcfunction(state, lua_isfunction_impl); + ffi::lua_call(state, 2, 1); + + #[cfg(feature = "luau-jit")] + if ffi::luau_codegen_supported() != 0 { + ffi::luau_codegen_compile(state, -1); + } + + // Store in the registry + ffi::lua_pushvalue(state, -1); + ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, newindex_key); + }) +} + pub(super) unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::lua_State) -> c_int { let ud = get_userdata::>(state, -1); if (*ud).is_safe_to_destroy() { @@ -44,3 +433,6 @@ pub(super) unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::l } 1 } + +static USERDATA_METATABLE_INDEX: u8 = 0; +static USERDATA_METATABLE_NEWINDEX: u8 = 0; diff --git a/src/util/mod.rs b/src/util/mod.rs index d53f1b7b..4366ecd3 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -12,10 +12,8 @@ pub(crate) use error::{ pub(crate) use short_names::short_type_name; pub(crate) use types::TypeKey; pub(crate) use userdata::{ - borrow_userdata_scoped, borrow_userdata_scoped_mut, get_destructed_userdata_metatable, - get_internal_metatable, get_internal_userdata, get_userdata, init_internal_metatable, - init_userdata_metatable, push_internal_userdata, take_userdata, TypeIdHints, - DESTRUCTED_USERDATA_METATABLE, + get_destructed_userdata_metatable, get_internal_metatable, get_internal_userdata, get_userdata, + init_internal_metatable, push_internal_userdata, take_userdata, DESTRUCTED_USERDATA_METATABLE, }; #[cfg(not(feature = "luau"))] diff --git a/src/util/userdata.rs b/src/util/userdata.rs index 359dcf81..6108ddf7 100644 --- a/src/util/userdata.rs +++ b/src/util/userdata.rs @@ -1,10 +1,8 @@ -use std::any::TypeId; use std::os::raw::{c_int, c_void}; -use std::{ptr, str}; +use std::ptr; -use crate::error::{Error, Result}; -use crate::userdata::UserDataStorage; -use crate::util::{check_stack, get_metatable_ptr, push_table, rawget_field, rawset_field, TypeKey}; +use crate::error::Result; +use crate::util::{check_stack, get_metatable_ptr, push_table, rawset_field, TypeKey}; // Pushes the userdata and attaches a metatable with __gc method. // Internally uses 3 stack spaces, does not call checkstack. @@ -37,6 +35,11 @@ pub(crate) unsafe fn init_internal_metatable( #[cfg(not(feature = "luau"))] { + unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::lua_State) -> c_int { + take_userdata::(state); + 0 + } + ffi::lua_pushcfunction(state, userdata_destructor::); rawset_field(state, -2, "__gc")?; } @@ -139,401 +142,4 @@ pub(crate) unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_Stat ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, key); } -// Populates the given table with the appropriate members to be a userdata metatable for the given -// type. This function takes the given table at the `metatable` index, and adds an appropriate -// `__gc` member to it for the given type and a `__metatable` entry to protect the table from script -// access. The function also, if given a `field_getters` or `methods` tables, will create an -// `__index` metamethod (capturing previous one) to lookup in `field_getters` first, then `methods` -// and falling back to the captured `__index` if no matches found. -// The same is also applicable for `__newindex` metamethod and `field_setters` table. -// Internally uses 9 stack spaces and does not call checkstack. -pub(crate) unsafe fn init_userdata_metatable( - state: *mut ffi::lua_State, - metatable: c_int, - field_getters: Option, - field_setters: Option, - methods: Option, -) -> Result<()> { - if field_getters.is_some() || methods.is_some() { - // Push `__index` generator function - init_userdata_metatable_index(state)?; - - let index_type = rawget_field(state, metatable, "__index")?; - match index_type { - ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => { - for &idx in &[field_getters, methods] { - if let Some(idx) = idx { - ffi::lua_pushvalue(state, idx); - } else { - ffi::lua_pushnil(state); - } - } - - // Generate `__index` - protect_lua!(state, 4, 1, fn(state) ffi::lua_call(state, 3, 1))?; - } - _ => mlua_panic!("improper `__index` type: {}", index_type), - } - - rawset_field(state, metatable, "__index")?; - } - - if let Some(field_setters) = field_setters { - // Push `__newindex` generator function - init_userdata_metatable_newindex(state)?; - - let newindex_type = rawget_field(state, metatable, "__newindex")?; - match newindex_type { - ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => { - ffi::lua_pushvalue(state, field_setters); - // Generate `__newindex` - protect_lua!(state, 3, 1, fn(state) ffi::lua_call(state, 2, 1))?; - } - _ => mlua_panic!("improper `__newindex` type: {}", newindex_type), - } - - rawset_field(state, metatable, "__newindex")?; - } - - ffi::lua_pushboolean(state, 0); - rawset_field(state, metatable, "__metatable")?; - - Ok(()) -} - -unsafe extern "C-unwind" fn lua_error_impl(state: *mut ffi::lua_State) -> c_int { - ffi::lua_error(state); -} - -unsafe extern "C-unwind" fn lua_isfunction_impl(state: *mut ffi::lua_State) -> c_int { - ffi::lua_pushboolean(state, ffi::lua_isfunction(state, -1)); - 1 -} - -unsafe extern "C-unwind" fn lua_istable_impl(state: *mut ffi::lua_State) -> c_int { - ffi::lua_pushboolean(state, ffi::lua_istable(state, -1)); - 1 -} - -unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()> { - let index_key = &USERDATA_METATABLE_INDEX as *const u8 as *const _; - if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, index_key) == ffi::LUA_TFUNCTION { - return Ok(()); - } - ffi::lua_pop(state, 1); - - // Create and cache `__index` generator - let code = cr#" - local error, isfunction, istable = ... - return function (__index, field_getters, methods) - -- Common case: has field getters and index is a table - if field_getters ~= nil and methods == nil and istable(__index) then - return function (self, key) - local field_getter = field_getters[key] - if field_getter ~= nil then - return field_getter(self) - end - return __index[key] - end - end - - return function (self, key) - if field_getters ~= nil then - local field_getter = field_getters[key] - if field_getter ~= nil then - return field_getter(self) - end - end - - if methods ~= nil then - local method = methods[key] - if method ~= nil then - return method - end - end - - if isfunction(__index) then - return __index(self, key) - elseif __index == nil then - error("attempt to get an unknown field '"..key.."'") - else - return __index[key] - end - end - end - "#; - protect_lua!(state, 0, 1, |state| { - let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_index")); - if ret != ffi::LUA_OK { - ffi::lua_error(state); - } - ffi::lua_pushcfunction(state, lua_error_impl); - ffi::lua_pushcfunction(state, lua_isfunction_impl); - ffi::lua_pushcfunction(state, lua_istable_impl); - ffi::lua_call(state, 3, 1); - - #[cfg(feature = "luau-jit")] - if ffi::luau_codegen_supported() != 0 { - ffi::luau_codegen_compile(state, -1); - } - - // Store in the registry - ffi::lua_pushvalue(state, -1); - ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, index_key); - }) -} - -unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result<()> { - let newindex_key = &USERDATA_METATABLE_NEWINDEX as *const u8 as *const _; - if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, newindex_key) == ffi::LUA_TFUNCTION { - return Ok(()); - } - ffi::lua_pop(state, 1); - - // Create and cache `__newindex` generator - let code = cr#" - local error, isfunction = ... - return function (__newindex, field_setters) - return function (self, key, value) - if field_setters ~= nil then - local field_setter = field_setters[key] - if field_setter ~= nil then - field_setter(self, value) - return - end - end - - if isfunction(__newindex) then - __newindex(self, key, value) - elseif __newindex == nil then - error("attempt to set an unknown field '"..key.."'") - else - __newindex[key] = value - end - end - end - "#; - protect_lua!(state, 0, 1, |state| { - let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_newindex")); - if ret != ffi::LUA_OK { - ffi::lua_error(state); - } - ffi::lua_pushcfunction(state, lua_error_impl); - ffi::lua_pushcfunction(state, lua_isfunction_impl); - ffi::lua_call(state, 2, 1); - - #[cfg(feature = "luau-jit")] - if ffi::luau_codegen_supported() != 0 { - ffi::luau_codegen_compile(state, -1); - } - - // Store in the registry - ffi::lua_pushvalue(state, -1); - ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, newindex_key); - }) -} - -#[cfg(not(feature = "luau"))] -unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::lua_State) -> c_int { - // It's probably NOT a good idea to catch Rust panics in finalizer - // Lua 5.4 ignores it, other versions generates `LUA_ERRGCMM` without calling message handler - take_userdata::(state); - 0 -} - -// Userdata type hints, used to match types of wrapped userdata -#[derive(Clone, Copy)] -pub(crate) struct TypeIdHints { - t: TypeId, - - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - rc: TypeId, - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - rc_refcell: TypeId, - - #[cfg(feature = "userdata-wrappers")] - arc: TypeId, - #[cfg(feature = "userdata-wrappers")] - arc_mutex: TypeId, - #[cfg(feature = "userdata-wrappers")] - arc_rwlock: TypeId, - #[cfg(feature = "userdata-wrappers")] - arc_pl_mutex: TypeId, - #[cfg(feature = "userdata-wrappers")] - arc_pl_rwlock: TypeId, -} - -impl TypeIdHints { - pub(crate) fn new() -> Self { - Self { - t: TypeId::of::(), - - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - rc: TypeId::of::>(), - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - rc_refcell: TypeId::of::>>(), - - #[cfg(feature = "userdata-wrappers")] - arc: TypeId::of::>(), - #[cfg(feature = "userdata-wrappers")] - arc_mutex: TypeId::of::>>(), - #[cfg(feature = "userdata-wrappers")] - arc_rwlock: TypeId::of::>>(), - #[cfg(feature = "userdata-wrappers")] - arc_pl_mutex: TypeId::of::>>(), - #[cfg(feature = "userdata-wrappers")] - arc_pl_rwlock: TypeId::of::>>(), - } - } - - #[inline(always)] - pub(crate) fn type_id(&self) -> TypeId { - self.t - } -} - -pub(crate) unsafe fn borrow_userdata_scoped( - state: *mut ffi::lua_State, - idx: c_int, - type_id: Option, - type_hints: TypeIdHints, - f: impl FnOnce(&T) -> R, -) -> Result { - match type_id { - Some(type_id) if type_id == type_hints.t => { - let ud = get_userdata::>(state, idx); - (*ud).try_borrow_scoped(|ud| f(ud)) - } - - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - Some(type_id) if type_id == type_hints.rc => { - let ud = get_userdata::>>(state, idx); - (*ud).try_borrow_scoped(|ud| f(ud)) - } - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - Some(type_id) if type_id == type_hints.rc_refcell => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped(|ud| { - let ud = ud.try_borrow().map_err(|_| Error::UserDataBorrowError)?; - Ok(f(&ud)) - })? - } - - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc => { - let ud = get_userdata::>>(state, idx); - (*ud).try_borrow_scoped(|ud| f(ud)) - } - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc_mutex => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped(|ud| { - let ud = ud.try_lock().map_err(|_| Error::UserDataBorrowError)?; - Ok(f(&ud)) - })? - } - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc_rwlock => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped(|ud| { - let ud = ud.try_read().map_err(|_| Error::UserDataBorrowError)?; - Ok(f(&ud)) - })? - } - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc_pl_mutex => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped(|ud| { - let ud = ud.try_lock().ok_or(Error::UserDataBorrowError)?; - Ok(f(&ud)) - })? - } - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc_pl_rwlock => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped(|ud| { - let ud = ud.try_read().ok_or(Error::UserDataBorrowError)?; - Ok(f(&ud)) - })? - } - _ => Err(Error::UserDataTypeMismatch), - } -} - -pub(crate) unsafe fn borrow_userdata_scoped_mut( - state: *mut ffi::lua_State, - idx: c_int, - type_id: Option, - type_hints: TypeIdHints, - f: impl FnOnce(&mut T) -> R, -) -> Result { - match type_id { - Some(type_id) if type_id == type_hints.t => { - let ud = get_userdata::>(state, idx); - (*ud).try_borrow_scoped_mut(|ud| f(ud)) - } - - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - Some(type_id) if type_id == type_hints.rc => { - let ud = get_userdata::>>(state, idx); - (*ud).try_borrow_scoped_mut(|ud| match std::rc::Rc::get_mut(ud) { - Some(ud) => Ok(f(ud)), - None => Err(Error::UserDataBorrowMutError), - })? - } - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - Some(type_id) if type_id == type_hints.rc_refcell => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped(|ud| { - let mut ud = ud.try_borrow_mut().map_err(|_| Error::UserDataBorrowMutError)?; - Ok(f(&mut ud)) - })? - } - - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc => { - let ud = get_userdata::>>(state, idx); - (*ud).try_borrow_scoped_mut(|ud| match std::sync::Arc::get_mut(ud) { - Some(ud) => Ok(f(ud)), - None => Err(Error::UserDataBorrowMutError), - })? - } - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc_mutex => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped_mut(|ud| { - let mut ud = ud.try_lock().map_err(|_| Error::UserDataBorrowMutError)?; - Ok(f(&mut ud)) - })? - } - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc_rwlock => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped_mut(|ud| { - let mut ud = ud.try_write().map_err(|_| Error::UserDataBorrowMutError)?; - Ok(f(&mut ud)) - })? - } - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc_pl_mutex => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped_mut(|ud| { - let mut ud = ud.try_lock().ok_or(Error::UserDataBorrowMutError)?; - Ok(f(&mut ud)) - })? - } - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc_pl_rwlock => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped_mut(|ud| { - let mut ud = ud.try_write().ok_or(Error::UserDataBorrowMutError)?; - Ok(f(&mut ud)) - })? - } - _ => Err(Error::UserDataTypeMismatch), - } -} - pub(crate) static DESTRUCTED_USERDATA_METATABLE: u8 = 0; -static USERDATA_METATABLE_INDEX: u8 = 0; -static USERDATA_METATABLE_NEWINDEX: u8 = 0; From 95c623e94d7b569dad1f70850c4f78353a2f1945 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 21 Mar 2025 15:36:32 +0000 Subject: [PATCH 353/635] Use `c` string literal where appropriate --- mlua-sys/src/lua51/compat.rs | 20 ++++++++++---------- mlua-sys/src/lua51/lua.rs | 7 +++---- mlua-sys/src/lua52/compat.rs | 6 +++--- mlua-sys/src/lua52/lua.rs | 7 +++---- mlua-sys/src/lua53/lua.rs | 7 +++---- mlua-sys/src/lua54/lua.rs | 7 +++---- mlua-sys/src/luau/compat.rs | 24 ++++++++++++------------ mlua-sys/src/luau/lauxlib.rs | 2 +- mlua-sys/src/luau/lua.rs | 7 +++---- src/function.rs | 4 ++-- src/state/raw.rs | 4 ++-- src/userdata.rs | 4 +--- tests/conversion.rs | 6 +++--- 13 files changed, 49 insertions(+), 56 deletions(-) diff --git a/mlua-sys/src/lua51/compat.rs b/mlua-sys/src/lua51/compat.rs index 2294cd4f..8c2d7bfe 100644 --- a/mlua-sys/src/lua51/compat.rs +++ b/mlua-sys/src/lua51/compat.rs @@ -90,7 +90,7 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) -> } else if compat53_findfield(L, objidx, level - 1) != 0 { // try recursively lua_remove(L, -2); // remove table (but keep name) - lua_pushliteral(L, "."); + lua_pushliteral(L, c"."); lua_insert(L, -2); // place '.' between the two names lua_concat(L, 3); return 1; @@ -121,13 +121,13 @@ unsafe fn compat53_pushfuncname(L: *mut lua_State, ar: *mut lua_Debug) { lua_pushfstring(L, cstr!("function '%s'"), (*ar).name); } else if *(*ar).what == b'm' as c_char { // main? - lua_pushliteral(L, "main chunk"); + lua_pushliteral(L, c"main chunk"); } else if *(*ar).what == b'C' as c_char { if compat53_pushglobalfuncname(L, ar) != 0 { lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1)); lua_remove(L, -2); // remove name } else { - lua_pushliteral(L, "?"); + lua_pushliteral(L, c"?"); } } else { lua_pushfstring( @@ -377,7 +377,7 @@ pub unsafe fn luaL_checkstack(L: *mut lua_State, sz: c_int, msg: *const c_char) if !msg.is_null() { luaL_error(L, cstr!("stack overflow (%s)"), msg); } else { - lua_pushliteral(L, "stack overflow"); + lua_pushliteral(L, c"stack overflow"); lua_error(L); } } @@ -467,12 +467,12 @@ pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const if !msg.is_null() { lua_pushfstring(L, cstr!("%s\n"), msg); } - lua_pushliteral(L, "stack traceback:"); + lua_pushliteral(L, c"stack traceback:"); while lua_getstack(L1, level, &mut ar) != 0 { level += 1; if level == mark { // too many levels? - lua_pushliteral(L, "\n\t..."); // add a '...' + lua_pushliteral(L, c"\n\t..."); // add a '...' level = numlevels - COMPAT53_LEVELS2; // and skip to last ones } else { lua_getinfo(L1, cstr!("Slnt"), &mut ar); @@ -480,7 +480,7 @@ pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const if ar.currentline > 0 { lua_pushfstring(L, cstr!("%d:"), ar.currentline); } - lua_pushliteral(L, " in "); + lua_pushliteral(L, c" in "); compat53_pushfuncname(L, &mut ar); lua_concat(L, lua_gettop(L) - top); } @@ -493,16 +493,16 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 { match lua_type(L, idx) { LUA_TNIL => { - lua_pushliteral(L, "nil"); + lua_pushliteral(L, c"nil"); } LUA_TSTRING | LUA_TNUMBER => { lua_pushvalue(L, idx); } LUA_TBOOLEAN => { if lua_toboolean(L, idx) == 0 { - lua_pushliteral(L, "false"); + lua_pushliteral(L, c"false"); } else { - lua_pushliteral(L, "true"); + lua_pushliteral(L, c"true"); } } t => { diff --git a/mlua-sys/src/lua51/lua.rs b/mlua-sys/src/lua51/lua.rs index f222b383..4789c568 100644 --- a/mlua-sys/src/lua51/lua.rs +++ b/mlua-sys/src/lua51/lua.rs @@ -1,5 +1,6 @@ //! Contains definitions from `lua.h`. +use std::ffi::CStr; use std::marker::{PhantomData, PhantomPinned}; use std::os::raw::{c_char, c_double, c_int, c_void}; use std::ptr; @@ -312,10 +313,8 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int { } #[inline(always)] -pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) { - use std::ffi::CString; - let c_str = CString::new(s).unwrap(); - lua_pushlstring_(L, c_str.as_ptr(), c_str.as_bytes().len()) +pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) { + lua_pushstring_(L, s.as_ptr()); } #[inline(always)] diff --git a/mlua-sys/src/lua52/compat.rs b/mlua-sys/src/lua52/compat.rs index d6a79ac6..68c7029f 100644 --- a/mlua-sys/src/lua52/compat.rs +++ b/mlua-sys/src/lua52/compat.rs @@ -199,16 +199,16 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 { match lua_type(L, idx) { LUA_TNIL => { - lua_pushliteral(L, "nil"); + lua_pushliteral(L, c"nil"); } LUA_TSTRING | LUA_TNUMBER => { lua_pushvalue(L, idx); } LUA_TBOOLEAN => { if lua_toboolean(L, idx) == 0 { - lua_pushliteral(L, "false"); + lua_pushliteral(L, c"false"); } else { - lua_pushliteral(L, "true"); + lua_pushliteral(L, c"true"); } } t => { diff --git a/mlua-sys/src/lua52/lua.rs b/mlua-sys/src/lua52/lua.rs index 6714611d..b77ef14f 100644 --- a/mlua-sys/src/lua52/lua.rs +++ b/mlua-sys/src/lua52/lua.rs @@ -1,5 +1,6 @@ //! Contains definitions from `lua.h`. +use std::ffi::CStr; use std::marker::{PhantomData, PhantomPinned}; use std::os::raw::{c_char, c_double, c_int, c_uchar, c_uint, c_void}; use std::ptr; @@ -395,10 +396,8 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int { } #[inline(always)] -pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) -> *const c_char { - use std::ffi::CString; - let c_str = CString::new(s).unwrap(); - lua_pushlstring_(L, c_str.as_ptr(), c_str.as_bytes().len()) +pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) { + lua_pushstring(L, s.as_ptr()); } #[inline(always)] diff --git a/mlua-sys/src/lua53/lua.rs b/mlua-sys/src/lua53/lua.rs index 47010f5d..3fd84f57 100644 --- a/mlua-sys/src/lua53/lua.rs +++ b/mlua-sys/src/lua53/lua.rs @@ -1,5 +1,6 @@ //! Contains definitions from `lua.h`. +use std::ffi::CStr; use std::marker::{PhantomData, PhantomPinned}; use std::os::raw::{c_char, c_double, c_int, c_uchar, c_void}; use std::{mem, ptr}; @@ -407,10 +408,8 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int { } #[inline(always)] -pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) -> *const c_char { - use std::ffi::CString; - let c_str = CString::new(s).unwrap(); - lua_pushlstring(L, c_str.as_ptr(), c_str.as_bytes().len()) +pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) { + lua_pushstring(L, s.as_ptr()); } #[inline(always)] diff --git a/mlua-sys/src/lua54/lua.rs b/mlua-sys/src/lua54/lua.rs index 796bec7f..afad4a36 100644 --- a/mlua-sys/src/lua54/lua.rs +++ b/mlua-sys/src/lua54/lua.rs @@ -1,5 +1,6 @@ //! Contains definitions from `lua.h`. +use std::ffi::CStr; use std::marker::{PhantomData, PhantomPinned}; use std::os::raw::{c_char, c_double, c_int, c_uchar, c_ushort, c_void}; use std::{mem, ptr}; @@ -434,10 +435,8 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int { } #[inline(always)] -pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) -> *const c_char { - use std::ffi::CString; - let c_str = CString::new(s).unwrap(); - lua_pushlstring(L, c_str.as_ptr(), c_str.as_bytes().len()) +pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) { + lua_pushstring(L, s.as_ptr()); } #[inline(always)] diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index af5512a3..36b900bd 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -43,7 +43,7 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) -> } else if compat53_findfield(L, objidx, level - 1) != 0 { // try recursively lua_remove(L, -2); // remove table (but keep name) - lua_pushliteral(L, "."); + lua_pushliteral(L, c"."); lua_insert(L, -2); // place '.' between the two names lua_concat(L, 3); return 1; @@ -77,7 +77,7 @@ unsafe fn compat53_pushfuncname(L: *mut lua_State, level: c_int, ar: *mut lua_De lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1)); lua_remove(L, -2); // remove name } else { - lua_pushliteral(L, "?"); + lua_pushliteral(L, c"?"); } } @@ -198,7 +198,7 @@ pub unsafe fn lua_rawgetp(L: *mut lua_State, idx: c_int, p: *const c_void) -> c_ pub unsafe fn lua_getuservalue(L: *mut lua_State, mut idx: c_int) -> c_int { luaL_checkstack(L, 2, cstr!("not enough stack slots available")); idx = lua_absindex(L, idx); - lua_pushliteral(L, "__mlua_uservalues"); + lua_pushliteral(L, c"__mlua_uservalues"); if lua_rawget(L, LUA_REGISTRYINDEX) != LUA_TTABLE { return LUA_TNIL; } @@ -236,13 +236,13 @@ pub unsafe fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void) { pub unsafe fn lua_setuservalue(L: *mut lua_State, mut idx: c_int) { luaL_checkstack(L, 4, cstr!("not enough stack slots available")); idx = lua_absindex(L, idx); - lua_pushliteral(L, "__mlua_uservalues"); + lua_pushliteral(L, c"__mlua_uservalues"); lua_pushvalue(L, -1); if lua_rawget(L, LUA_REGISTRYINDEX) != LUA_TTABLE { lua_pop(L, 1); lua_createtable(L, 0, 2); // main table lua_createtable(L, 0, 1); // metatable - lua_pushliteral(L, "k"); + lua_pushliteral(L, c"k"); lua_setfield(L, -2, cstr!("__mode")); lua_setmetatable(L, -2); lua_pushvalue(L, -2); @@ -316,7 +316,7 @@ pub unsafe fn luaL_checkstack(L: *mut lua_State, sz: c_int, msg: *const c_char) if !msg.is_null() { luaL_error(L, cstr!("stack overflow (%s)"), msg); } else { - lua_pushliteral(L, "stack overflow"); + lua_pushliteral(L, c"stack overflow"); lua_error(L); } } @@ -455,11 +455,11 @@ pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const if !msg.is_null() { lua_pushfstring(L, cstr!("%s\n"), msg); } - lua_pushliteral(L, "stack traceback:"); + lua_pushliteral(L, c"stack traceback:"); while lua_getinfo(L1, level, cstr!(""), &mut ar) != 0 { if level + 1 == mark { // too many levels? - lua_pushliteral(L, "\n\t..."); // add a '...' + lua_pushliteral(L, c"\n\t..."); // add a '...' level = numlevels - COMPAT53_LEVELS2; // and skip to last ones } else { lua_getinfo(L1, level, cstr!("sln"), &mut ar); @@ -467,7 +467,7 @@ pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const if ar.currentline > 0 { lua_pushfstring(L, cstr!("%d:"), ar.currentline); } - lua_pushliteral(L, " in "); + lua_pushliteral(L, c" in "); compat53_pushfuncname(L, level, &mut ar); lua_concat(L, lua_gettop(L) - top); } @@ -481,16 +481,16 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 { match lua_type(L, idx) { LUA_TNIL => { - lua_pushliteral(L, "nil"); + lua_pushliteral(L, c"nil"); } LUA_TSTRING | LUA_TNUMBER => { lua_pushvalue(L, idx); } LUA_TBOOLEAN => { if lua_toboolean(L, idx) == 0 { - lua_pushliteral(L, "false"); + lua_pushliteral(L, c"false"); } else { - lua_pushliteral(L, "true"); + lua_pushliteral(L, c"true"); } } t => { diff --git a/mlua-sys/src/luau/lauxlib.rs b/mlua-sys/src/luau/lauxlib.rs index 0b75cbe2..ddedb2c6 100644 --- a/mlua-sys/src/luau/lauxlib.rs +++ b/mlua-sys/src/luau/lauxlib.rs @@ -143,7 +143,7 @@ pub unsafe fn luaL_sandbox(L: *mut lua_State, enabled: c_int) { } // set all builtin metatables to read-only - lua_pushliteral(L, ""); + lua_pushliteral(L, c""); if lua_getmetatable(L, -1) != 0 { lua_setreadonly(L, -1, enabled); lua_pop(L, 2); diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index a916f80c..f97a80c4 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -1,5 +1,6 @@ //! Contains definitions from `lua.h`. +use std::ffi::CStr; use std::marker::{PhantomData, PhantomPinned}; use std::os::raw::{c_char, c_double, c_float, c_int, c_uint, c_void}; use std::{mem, ptr}; @@ -405,10 +406,8 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int { } #[inline(always)] -pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) { - use std::ffi::CString; - let c_str = CString::new(s).unwrap(); - lua_pushlstring_(L, c_str.as_ptr(), c_str.as_bytes().len()) +pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) { + lua_pushstring_(L, s.as_ptr()); } #[inline(always)] diff --git a/src/function.rs b/src/function.rs index 6ef9afae..7e0a6d97 100644 --- a/src/function.rs +++ b/src/function.rs @@ -280,7 +280,7 @@ impl Function { // Traverse upvalues until we find the _ENV one match ffi::lua_getupvalue(state, -1, i) { s if s.is_null() => break, - s if std::ffi::CStr::from_ptr(s as _).to_bytes() == b"_ENV" => break, + s if std::ffi::CStr::from_ptr(s as _) == c"_ENV" => break, _ => ffi::lua_pop(state, 1), } } @@ -319,7 +319,7 @@ impl Function { for i in 1..=255 { match ffi::lua_getupvalue(state, -1, i) { s if s.is_null() => return Ok(false), - s if std::ffi::CStr::from_ptr(s as _).to_bytes() == b"_ENV" => { + s if std::ffi::CStr::from_ptr(s as _) == c"_ENV" => { ffi::lua_pop(state, 1); // Create an anonymous function with the new environment let f_with_env = lua diff --git a/src/state/raw.rs b/src/state/raw.rs index 485ed405..169801cb 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -401,7 +401,7 @@ impl RawLua { } #[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))] { - ffi::lua_pushliteral(state, "attempt to yield from a hook"); + ffi::lua_pushliteral(state, c"attempt to yield from a hook"); ffi::lua_error(state); } } @@ -473,7 +473,7 @@ impl RawLua { protect_lua!(state, 0, 0, |state| { if ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, HOOKS_KEY) == 0 { // Table just created, initialize it - ffi::lua_pushliteral(state, "k"); + ffi::lua_pushliteral(state, c"k"); ffi::lua_setfield(state, -2, cstr!("__mode")); // hooktable.__mode = "k" ffi::lua_pushvalue(state, -1); ffi::lua_setmetatable(state, -2); // metatable(hooktable) = hooktable diff --git a/src/userdata.rs b/src/userdata.rs index b3be682d..b8479ced 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -220,9 +220,7 @@ impl MetaMethod { pub(crate) const fn as_cstr(self) -> &'static CStr { match self { #[rustfmt::skip] - MetaMethod::Type => unsafe { - CStr::from_bytes_with_nul_unchecked(if cfg!(feature = "luau") { b"__type\0" } else { b"__name\0" }) - }, + MetaMethod::Type => if cfg!(feature = "luau") { c"__type" } else { c"__name" }, _ => unreachable!(), } } diff --git a/tests/conversion.rs b/tests/conversion.rs index e75a3a06..d724fa81 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; -use std::ffi::{CStr, CString, OsString}; +use std::ffi::{CString, OsString}; use std::path::PathBuf; use bstr::BString; @@ -450,8 +450,8 @@ fn test_conv_cstring() -> Result<()> { let s2: CString = lua.globals().get("s")?; assert_eq!(s, s2); - let cs = CStr::from_bytes_with_nul(b"hello\0").unwrap(); - lua.globals().set("cs", cs)?; + let cs = c"hello"; + lua.globals().set("cs", c"hello")?; let cs2: CString = lua.globals().get("cs")?; assert_eq!(cs, cs2.as_c_str()); From 806817212e2489a0206eb2c0beed9017403c8443 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 21 Mar 2025 21:28:30 +0000 Subject: [PATCH 354/635] Rename `Lua::init_from_ptr` to `Lua::get_or_init_from_ptr`. Return reference to `&Lua` with unbound lifetime (chosen by user). --- src/state.rs | 27 +++++++++++++++------------ src/state/extra.rs | 3 --- src/state/raw.rs | 12 +++++++++++- tests/tests.rs | 24 ++++++++++++++++++++++-- 4 files changed, 48 insertions(+), 18 deletions(-) diff --git a/src/state.rs b/src/state.rs index fbfb8db2..dc582d9e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -261,16 +261,23 @@ impl Lua { lua } - /// Constructs a new Lua instance from an existing raw state. + /// Returns or constructs Lua instance from a raw state. /// - /// Once called, a returned Lua state is cached in the registry and can be retrieved + /// Once initialized, the returned Lua instance is cached in the registry and can be retrieved /// by calling this function again. - #[allow(clippy::missing_safety_doc)] + /// + /// # Safety + /// The `Lua` must outlive the chosen lifetime `'a`. #[inline] - pub unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> Lua { - Lua { - raw: RawLua::init_from_ptr(state, false), - collect_garbage: true, + pub unsafe fn get_or_init_from_ptr<'a>(state: *mut ffi::lua_State) -> &'a Lua { + debug_assert!(!state.is_null(), "Lua state is null"); + match ExtraData::get(state) { + extra if !extra.is_null() => (*extra).lua(), + _ => { + // The `owned` flag is set to `false` as we don't own the Lua state. + RawLua::init_from_ptr(state, false); + (*ExtraData::get(state)).lua() + } } } @@ -410,11 +417,7 @@ impl Lua { R: IntoLua, { // Make sure that Lua is initialized - let mut lua = Self::init_from_ptr(state); - lua.collect_garbage = false; - // `Lua` is no longer needed and must be dropped at this point to avoid memory leak - // in case of possible longjmp (lua_error) below - drop(lua); + let _ = Self::get_or_init_from_ptr(state); callback_error_ext(state, ptr::null_mut(), move |extra, nargs| { let rawlua = (*extra).raw_lua(); diff --git a/src/state/extra.rs b/src/state/extra.rs index 697ef311..90026e43 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -195,9 +195,6 @@ impl ExtraData { raw: XRc::clone(raw), collect_garbage: false, }); - if self.owned { - XRc::decrement_strong_count(XRc::as_ptr(raw)); - } self.weak.write(WeakLua(XRc::downgrade(raw))); } diff --git a/src/state/raw.rs b/src/state/raw.rs index 169801cb..78c3c0fb 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -58,12 +58,13 @@ pub struct RawLua { pub(super) state: Cell<*mut ffi::lua_State>, pub(super) main_state: Option>, pub(super) extra: XRc>, + owned: bool, } impl Drop for RawLua { fn drop(&mut self) { unsafe { - if !(*self.extra.get()).owned { + if !self.owned { return; } @@ -233,8 +234,17 @@ impl RawLua { // Make sure that we don't store current state as main state (if it's not available) main_state: get_main_state(state).and_then(NonNull::new), extra: XRc::clone(&extra), + owned, })); (*extra.get()).set_lua(&rawlua); + if owned { + // If Lua state is managed by us, then make internal `RawLua` reference "weak" + XRc::decrement_strong_count(XRc::as_ptr(&rawlua)); + } else { + // If Lua state is not managed by us, then keep internal `RawLua` reference "strong" + // but `Extra` reference weak (it will be collected from registry at lua_close time) + XRc::decrement_strong_count(XRc::as_ptr(&extra)); + } rawlua } diff --git a/tests/tests.rs b/tests/tests.rs index 262b1ab4..e0b9dc22 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -8,8 +8,8 @@ use std::sync::Arc; use std::{error, f32, f64, fmt}; use mlua::{ - ChunkMode, Error, ExternalError, Function, Lua, LuaOptions, Nil, Result, StdLib, String, Table, UserData, - Value, Variadic, + ffi, ChunkMode, Error, ExternalError, Function, Lua, LuaOptions, Nil, Result, StdLib, String, Table, + UserData, Value, Variadic, }; #[cfg(not(feature = "luau"))] @@ -1425,3 +1425,23 @@ fn test_gc_drop_ref_thread() -> Result<()> { Ok(()) } + +#[cfg(not(feature = "luau"))] +#[test] +fn test_get_or_init_from_ptr() -> Result<()> { + // This would not work with Luau, the state must be init by mlua internally + let state = unsafe { ffi::luaL_newstate() }; + + let mut lua = unsafe { Lua::get_or_init_from_ptr(state) }; + lua.globals().set("hello", "world678")?; + + // The same Lua instance must be returned + lua = unsafe { Lua::get_or_init_from_ptr(state) }; + assert_eq!(lua.globals().get::("hello")?, "world678"); + + unsafe { ffi::lua_close(state) }; + + // Lua must not be accessed after closing + + Ok(()) +} From b0140cdb9e5920f96447f79a29e9cc9a989cc875 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 22 Mar 2025 00:17:28 +0000 Subject: [PATCH 355/635] Add `Chunk::name()`, `Chunk::environment()` and `Chunk::mode()` functions. They can be used to retrieve existing chunk params. --- src/chunk.rs | 15 +++++++++++++++ src/state.rs | 4 +++- tests/chunk.rs | 20 +++++++++++++++++++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index 02dd35a9..41db2445 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -428,6 +428,11 @@ impl Compiler { } impl Chunk<'_> { + /// Returns the name of this chunk. + pub fn name(&self) -> &str { + &self.name + } + /// Sets the name of this chunk, which results in more informative error traces. /// /// Possible name prefixes: @@ -439,6 +444,11 @@ impl Chunk<'_> { self } + /// Returns the environment of this chunk. + pub fn environment(&self) -> Option<&Table> { + self.env.as_ref().ok()?.as_ref() + } + /// Sets the environment of the loaded chunk to the given value. /// /// In Lua >=5.2 main chunks always have exactly one upvalue, and this upvalue is used as the @@ -455,6 +465,11 @@ impl Chunk<'_> { self } + /// Returns the mode (auto-detected by default) of this chunk. + pub fn mode(&self) -> ChunkMode { + self.detect_mode() + } + /// Sets whether the chunk is text or binary (autodetected by default). /// /// Be aware, Lua does not check the consistency of the code inside binary chunks. diff --git a/src/state.rs b/src/state.rs index dc582d9e..2d006fed 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1033,7 +1033,9 @@ impl Lua { ) -> Chunk<'a> { Chunk { lua: self.weak(), - name: chunk.name().unwrap_or_else(|| location.to_string()), + name: chunk + .name() + .unwrap_or_else(|| format!("@{}:{}", location.file(), location.line())), env: chunk.environment(self), mode: chunk.mode(), source: chunk.source(), diff --git a/tests/chunk.rs b/tests/chunk.rs index 17becbe2..45ad95ec 100644 --- a/tests/chunk.rs +++ b/tests/chunk.rs @@ -1,6 +1,24 @@ use std::{fs, io}; -use mlua::{Chunk, Lua, Result}; +use mlua::{Chunk, ChunkMode, Lua, Result}; + +#[test] +fn test_chunk_methods() -> Result<()> { + let lua = Lua::new(); + + #[cfg(unix)] + assert!(lua.load("return 123").name().starts_with("@tests/chunk.rs")); + let chunk2 = lua.load("return 123").set_name("@new_name"); + assert_eq!(chunk2.name(), "@new_name"); + + let env = lua.create_table_from([("a", 987)])?; + let chunk3 = lua.load("return a").set_environment(env.clone()); + assert_eq!(chunk3.environment().unwrap(), &env); + assert_eq!(chunk3.mode(), ChunkMode::Text); + assert_eq!(chunk3.call::(())?, 987); + + Ok(()) +} #[test] fn test_chunk_path() -> Result<()> { From 8d864e26875833193d0592d97135f5c79c32d581 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 22 Mar 2025 14:23:34 +0000 Subject: [PATCH 356/635] Reduce stack operations when creating userdata --- src/state/raw.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index 78c3c0fb..1aeb181b 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -911,12 +911,11 @@ impl RawLua { let _sg = StackGuard::new(state); check_stack(state, 3)?; - // We push metatable first to ensure having correct metatable with `__gc` method - ffi::lua_pushnil(state); - ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, get_metatable_id()?); + // We generate metatable first to make sure it *always* available when userdata pushed + let mt_id = get_metatable_id()?; let protect = !self.unlikely_memory_error(); crate::util::push_userdata(state, data, protect)?; - ffi::lua_replace(state, -3); + ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, mt_id); ffi::lua_setmetatable(state, -2); // Set empty environment for Lua 5.1 From 1f970824b4f8187a86794fb08e0a1093a58737af Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 28 Mar 2025 11:51:35 +0000 Subject: [PATCH 357/635] Support user thread creation callback (Luau) --- src/state.rs | 62 ++++++++++++++++++++++++++++++++++++++--- src/state/extra.rs | 4 +++ src/state/raw.rs | 23 ++++++++++++---- src/state/util.rs | 8 ++++++ src/types.rs | 6 ++++ tests/hooks.rs | 11 ++------ tests/luau.rs | 69 ++++++++++++++++++++++++++++++++++++++++++---- tests/tests.rs | 3 +- 8 files changed, 160 insertions(+), 26 deletions(-) diff --git a/src/state.rs b/src/state.rs index 2d006fed..cdafc716 100644 --- a/src/state.rs +++ b/src/state.rs @@ -419,7 +419,7 @@ impl Lua { // Make sure that Lua is initialized let _ = Self::get_or_init_from_ptr(state); - callback_error_ext(state, ptr::null_mut(), move |extra, nargs| { + callback_error_ext(state, ptr::null_mut(), true, move |extra, nargs| { let rawlua = (*extra).raw_lua(); let _guard = StateGuard::new(rawlua, state); let args = A::from_stack_args(nargs, 1, None, rawlua)?; @@ -652,7 +652,7 @@ impl Lua { // We don't support GC interrupts since they cannot survive Lua exceptions return; } - let result = callback_error_ext(state, ptr::null_mut(), move |extra, _| { + let result = callback_error_ext(state, ptr::null_mut(), false, move |extra, _| { let interrupt_cb = (*extra).interrupt_callback.clone(); let interrupt_cb = mlua_expect!(interrupt_cb, "no interrupt callback set in interrupt_proc"); if XRc::strong_count(&interrupt_cb) > 2 { @@ -690,6 +690,60 @@ impl Lua { } } + /// Sets a thread event callback that will be called when a thread is created or destroyed. + /// + /// The callback is called with a [`Value`] argument that is either: + /// - A [`Thread`] object when thread is created + /// - A [`LightUserData`] when thread is destroyed + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn set_thread_event_callback(&self, callback: F) + where + F: Fn(&Lua, Value) -> Result<()> + MaybeSend + 'static, + { + unsafe extern "C-unwind" fn userthread_proc(parent: *mut ffi::lua_State, child: *mut ffi::lua_State) { + let extra = ExtraData::get(child); + let thread_cb = match (*extra).userthread_callback { + Some(ref cb) => cb.clone(), + None => return, + }; + if XRc::strong_count(&thread_cb) > 2 { + return; // Don't allow recursion + } + let value = match parent.is_null() { + // Thread is about to be destroyed, pass light userdata + true => Value::LightUserData(crate::LightUserData(child as _)), + false => { + // Thread is created, pass thread object + ffi::lua_pushthread(child); + ffi::lua_xmove(child, (*extra).ref_thread, 1); + Value::Thread(Thread((*extra).raw_lua().pop_ref_thread(), child)) + } + }; + callback_error_ext((*extra).raw_lua().state(), extra, false, move |extra, _| { + thread_cb((*extra).lua(), value) + }) + } + + // Set thread callback + let lua = self.lock(); + unsafe { + (*lua.extra.get()).userthread_callback = Some(XRc::new(callback)); + (*ffi::lua_callbacks(lua.main_state())).userthread = Some(userthread_proc); + } + } + + /// Removes any thread event callback previously set by `set_thread_event_callback`. + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn remove_thread_event_callback(&self) { + let lua = self.lock(); + unsafe { + (*lua.extra.get()).userthread_callback = None; + (*ffi::lua_callbacks(lua.main_state())).userthread = None; + } + } + /// Sets the warning function to be used by Lua to emit warnings. /// /// Requires `feature = "lua54"` @@ -705,7 +759,7 @@ impl Lua { unsafe extern "C-unwind" fn warn_proc(ud: *mut c_void, msg: *const c_char, tocont: c_int) { let extra = ud as *mut ExtraData; - callback_error_ext((*extra).raw_lua().state(), extra, |extra, _| { + callback_error_ext((*extra).raw_lua().state(), extra, false, |extra, _| { let warn_callback = (*extra).warn_callback.clone(); let warn_callback = mlua_expect!(warn_callback, "no warning callback set in warn_proc"); if XRc::strong_count(&warn_callback) > 2 { @@ -1444,7 +1498,7 @@ impl Lua { Err(_) => return, }, ffi::LUA_TTHREAD => { - ffi::lua_newthread(state); + ffi::lua_pushthread(state); } #[cfg(feature = "luau")] ffi::LUA_TBUFFER => { diff --git a/src/state/extra.rs b/src/state/extra.rs index 90026e43..865db6d6 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -80,6 +80,8 @@ pub(crate) struct ExtraData { pub(super) warn_callback: Option, #[cfg(feature = "luau")] pub(super) interrupt_callback: Option, + #[cfg(feature = "luau")] + pub(super) userthread_callback: Option, #[cfg(feature = "luau")] pub(super) sandboxed: bool, @@ -177,6 +179,8 @@ impl ExtraData { #[cfg(feature = "luau")] interrupt_callback: None, #[cfg(feature = "luau")] + userthread_callback: None, + #[cfg(feature = "luau")] sandboxed: false, #[cfg(feature = "luau")] compiler: None, diff --git a/src/state/raw.rs b/src/state/raw.rs index 1aeb181b..b7c7f625 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -70,6 +70,13 @@ impl Drop for RawLua { let mem_state = MemoryState::get(self.main_state()); + #[cfg(feature = "luau")] + { + // Reset any callbacks + (*ffi::lua_callbacks(self.main_state())).interrupt = None; + (*ffi::lua_callbacks(self.main_state())).userthread = None; + } + ffi::lua_close(self.main_state()); // Deallocate `MemoryState` @@ -420,7 +427,7 @@ impl RawLua { } unsafe extern "C-unwind" fn global_hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) { - let status = callback_error_ext(state, ptr::null_mut(), move |extra, _| { + let status = callback_error_ext(state, ptr::null_mut(), false, move |extra, _| { match (*extra).hook_callback.clone() { Some(hook_callback) => { let rawlua = (*extra).raw_lua(); @@ -453,7 +460,7 @@ impl RawLua { return; } - let status = callback_error_ext(state, ptr::null_mut(), |extra, _| { + let status = callback_error_ext(state, ptr::null_mut(), false, |extra, _| { let rawlua = (*extra).raw_lua(); let _guard = StateGuard::new(rawlua, state); let debug = Debug::new(rawlua, ar); @@ -564,7 +571,11 @@ impl RawLua { let _sg = StackGuard::new(state); check_stack(state, 3)?; - let thread_state = if self.unlikely_memory_error() { + let protect = !self.unlikely_memory_error(); + #[cfg(feature = "luau")] + let protect = protect || (*self.extra.get()).userthread_callback.is_some(); + + let thread_state = if !protect { ffi::lua_newthread(state) } else { protect_lua!(state, 0, 1, |state| ffi::lua_newthread(state))? @@ -1177,7 +1188,7 @@ impl RawLua { pub(crate) fn create_callback(&self, func: Callback) -> Result { unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int { let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); - callback_error_ext(state, (*upvalue).extra.get(), |extra, nargs| { + callback_error_ext(state, (*upvalue).extra.get(), true, |extra, nargs| { // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) // The lock must be already held as the callback is executed let rawlua = (*extra).raw_lua(); @@ -1226,7 +1237,7 @@ impl RawLua { // so the first upvalue is always valid let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); let extra = (*upvalue).extra.get(); - callback_error_ext(state, extra, |extra, nargs| { + callback_error_ext(state, extra, true, |extra, nargs| { // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) // The lock must be already held as the callback is executed let rawlua = (*extra).raw_lua(); @@ -1251,7 +1262,7 @@ impl RawLua { unsafe extern "C-unwind" fn poll_future(state: *mut ffi::lua_State) -> c_int { let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); - callback_error_ext(state, (*upvalue).extra.get(), |extra, _| { + callback_error_ext(state, (*upvalue).extra.get(), true, |extra, _| { // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) // The lock must be already held as the future is polled let rawlua = (*extra).raw_lua(); diff --git a/src/state/util.rs b/src/state/util.rs index c0dc015b..ea482e06 100644 --- a/src/state/util.rs +++ b/src/state/util.rs @@ -27,6 +27,7 @@ impl Drop for StateGuard<'_> { pub(super) unsafe fn callback_error_ext( state: *mut ffi::lua_State, mut extra: *mut ExtraData, + wrap_error: bool, f: F, ) -> R where @@ -110,6 +111,13 @@ where Ok(Err(err)) => { let wrapped_error = prealloc_failure.r#use(state, extra); + if !wrap_error { + ptr::write(wrapped_error, WrappedFailure::Error(err)); + get_internal_metatable::(state); + ffi::lua_setmetatable(state, -2); + ffi::lua_error(state) + } + // Build `CallbackError` with traceback let traceback = if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 { ffi::luaL_traceback(state, state, ptr::null(), 0); diff --git a/src/types.rs b/src/types.rs index 537e1feb..72fef44f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -90,6 +90,12 @@ pub(crate) type InterruptCallback = XRc Result + Send>; #[cfg(all(not(feature = "send"), feature = "luau"))] pub(crate) type InterruptCallback = XRc Result>; +#[cfg(all(feature = "send", feature = "luau"))] +pub(crate) type UserThreadCallback = XRc Result<()> + Send>; + +#[cfg(all(not(feature = "send"), feature = "luau"))] +pub(crate) type UserThreadCallback = XRc Result<()>>; + #[cfg(all(feature = "send", feature = "lua54"))] pub(crate) type WarnCallback = XRc Result<()> + Send>; diff --git a/tests/hooks.rs b/tests/hooks.rs index ab9a89ac..f0094b29 100644 --- a/tests/hooks.rs +++ b/tests/hooks.rs @@ -1,6 +1,5 @@ #![cfg(not(feature = "luau"))] -use std::ops::Deref; use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::{Arc, Mutex}; @@ -104,14 +103,10 @@ fn test_error_within_hook() -> Result<()> { })?; let err = lua.load("x = 1").exec().expect_err("panic didn't propagate"); - match err { - Error::CallbackError { cause, .. } => match cause.deref() { - Error::RuntimeError(s) => assert_eq!(s, "Something happened in there!"), - _ => panic!("wrong callback error kind caught"), - }, - _ => panic!("wrong error kind caught"), - }; + Error::RuntimeError(msg) => assert_eq!(msg, "Something happened in there!"), + err => panic!("expected `RuntimeError` with a specific message, got {err:?}"), + } Ok(()) } diff --git a/tests/luau.rs b/tests/luau.rs index c3bc8a63..5ad82843 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -2,8 +2,9 @@ use std::fmt::Debug; use std::fs; +use std::os::raw::c_void; use std::panic::{catch_unwind, AssertUnwindSafe}; -use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering}; use std::sync::Arc; use mlua::{Compiler, Error, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, Vector, VmState}; @@ -395,11 +396,8 @@ fn test_interrupts() -> Result<()> { // lua.set_interrupt(|_| Err(Error::runtime("error from interrupt"))); match f.call::<()>(()) { - Err(Error::CallbackError { cause, .. }) => match *cause { - Error::RuntimeError(ref m) if m == "error from interrupt" => {} - ref e => panic!("expected RuntimeError with a specific message, got {:?}", e), - }, - r => panic!("expected CallbackError, got {:?}", r), + Err(Error::RuntimeError(ref msg)) => assert_eq!(msg, "error from interrupt"), + res => panic!("expected `RuntimeError` with a specific message, got {res:?}"), } lua.remove_interrupt(); @@ -412,3 +410,62 @@ fn test_fflags() { // We cannot really on any particular feature flag to be present assert!(Lua::set_fflag("UnknownFlag", true).is_err()); } + +#[test] +fn test_thread_events() -> Result<()> { + let lua = Lua::new(); + + let count = Arc::new(AtomicU64::new(0)); + let thread_data: Arc<(AtomicPtr, AtomicBool)> = Arc::new(Default::default()); + + let (count2, thread_data2) = (count.clone(), thread_data.clone()); + lua.set_thread_event_callback(move |_, value| { + count2.fetch_add(1, Ordering::Relaxed); + (thread_data2.0).store(value.to_pointer() as *mut _, Ordering::Relaxed); + if value.is_thread() { + thread_data2.1.store(false, Ordering::Relaxed); + } + if value.is_light_userdata() { + thread_data2.1.store(true, Ordering::Relaxed); + } + Ok(()) + }); + + let t = lua.create_thread(lua.load("return 123").into_function()?)?; + assert_eq!(count.load(Ordering::Relaxed), 1); + let t_ptr = t.to_pointer(); + assert_eq!(t_ptr, thread_data.0.load(Ordering::Relaxed)); + assert!(!thread_data.1.load(Ordering::Relaxed)); + + // Thead will be destroyed after GC cycle + drop(t); + lua.gc_collect()?; + assert_eq!(count.load(Ordering::Relaxed), 2); + assert_eq!(t_ptr, thread_data.0.load(Ordering::Relaxed)); + assert!(thread_data.1.load(Ordering::Relaxed)); + + // Check that recursion is not allowed + let count3 = count.clone(); + lua.set_thread_event_callback(move |lua, _value| { + count3.fetch_add(1, Ordering::Relaxed); + let _ = lua.create_thread(lua.load("return 123").into_function().unwrap())?; + Ok(()) + }); + let t = lua.create_thread(lua.load("return 123").into_function()?)?; + assert_eq!(count.load(Ordering::Relaxed), 3); + + lua.remove_thread_event_callback(); + drop(t); + lua.gc_collect()?; + assert_eq!(count.load(Ordering::Relaxed), 3); + + // Test error inside callback + lua.set_thread_event_callback(move |_, _| Err(Error::runtime("error when processing thread event"))); + let result = lua.create_thread(lua.load("return 123").into_function()?); + assert!(result.is_err()); + assert!( + matches!(result, Err(Error::RuntimeError(err)) if err.contains("error when processing thread event")) + ); + + Ok(()) +} diff --git a/tests/tests.rs b/tests/tests.rs index e0b9dc22..f8f4b89c 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1308,8 +1308,7 @@ fn test_warnings() -> Result<()> { lua.set_warning_function(|_, _, _| Err(Error::runtime("warning error"))); assert!(matches!( lua.load(r#"warn("test")"#).exec(), - Err(Error::CallbackError { cause, .. }) - if matches!(*cause, Error::RuntimeError(ref err) if err == "warning error") + Err(Error::RuntimeError(ref err)) if err == "warning error" )); // Recursive warning From 87fa663ac11a857ab69723949a7895da3fe6dfb5 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 28 Mar 2025 13:24:15 +0000 Subject: [PATCH 358/635] Remove deprecated functions --- src/state.rs | 6 ------ src/table.rs | 7 ------- src/userdata.rs | 6 ------ 3 files changed, 19 deletions(-) diff --git a/src/state.rs b/src/state.rs index cdafc716..239139c9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -326,12 +326,6 @@ impl Lua { R::from_stack_multi(nresults, &lua) } - #[doc(hidden)] - #[deprecated(since = "0.10.0", note = "please use `load_std_libs` instead")] - pub fn load_from_std_lib(&self, libs: StdLib) -> Result<()> { - self.load_std_libs(libs) - } - /// Loads the specified subset of the standard libraries into an existing Lua state. /// /// Use the [`StdLib`] flags to specify the libraries you want to load. diff --git a/src/table.rs b/src/table.rs index 3f100cad..ae643119 100644 --- a/src/table.rs +++ b/src/table.rs @@ -511,13 +511,6 @@ impl Table { } } - #[doc(hidden)] - #[deprecated(since = "0.10.0", note = "please use `metatable` instead")] - #[cfg(not(tarpaulin_include))] - pub fn get_metatable(&self) -> Option
{ - self.metatable() - } - /// Sets or removes the metatable of this table. /// /// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does diff --git a/src/userdata.rs b/src/userdata.rs index b8479ced..bb4ae219 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -895,12 +895,6 @@ impl AnyUserData { self.raw_metatable().map(UserDataMetatable) } - #[doc(hidden)] - #[deprecated(since = "0.10.0", note = "please use `metatable` instead")] - pub fn get_metatable(&self) -> Result { - self.metatable() - } - fn raw_metatable(&self) -> Result
{ let lua = self.0.lua.lock(); let state = lua.state(); From 72682198ebb6391fd1e68a686ce276d88344a30e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 30 Mar 2025 11:12:14 +0100 Subject: [PATCH 359/635] Bump Luau to 0.667 --- mlua-sys/Cargo.toml | 2 +- mlua-sys/src/luau/lua.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 26736289..651563d8 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -40,7 +40,7 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 547.0.0, < 547.1.0", optional = true } luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } -luau0-src = { version = "0.12.0", optional = true } +luau0-src = { version = "0.13.0", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index f97a80c4..5b5f114b 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -290,7 +290,7 @@ extern "C-unwind" { pub fn lua_setuserdatatag(L: *mut lua_State, idx: c_int, tag: c_int); pub fn lua_setuserdatadtor(L: *mut lua_State, tag: c_int, dtor: Option); pub fn lua_getuserdatadtor(L: *mut lua_State, tag: c_int) -> Option; - pub fn lua_setuserdatametatable(L: *mut lua_State, tag: c_int, idx: c_int); + pub fn lua_setuserdatametatable(L: *mut lua_State, tag: c_int); pub fn lua_getuserdatametatable(L: *mut lua_State, tag: c_int); pub fn lua_setlightuserdataname(L: *mut lua_State, tag: c_int, name: *const c_char); pub fn lua_getlightuserdataname(L: *mut lua_State, tag: c_int) -> *const c_char; From 4d2ff45a8bdb096cb8bf0ab9e54ec77f2661bf87 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 31 Mar 2025 22:26:47 +0100 Subject: [PATCH 360/635] Update `Lua::poll_pending` doc (still hidden) --- src/state.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/state.rs b/src/state.rs index 239139c9..d151afc5 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1976,6 +1976,8 @@ impl Lua { } /// Returns an internal `Poll::Pending` constant used for executing async callbacks. + /// + /// Every time when [`Future`] is Pending, Lua corotine is suspended with this constant. #[cfg(feature = "async")] #[doc(hidden)] #[inline(always)] From c1a018a60d31fb6ad008ff816592b081a18f4797 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 31 Mar 2025 22:41:42 +0100 Subject: [PATCH 361/635] Add `AnyUserData::type_id` method --- src/userdata.rs | 14 +++++++++++--- tests/userdata.rs | 3 +++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/userdata.rs b/src/userdata.rs index bb4ae219..ee33c514 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -623,10 +623,9 @@ impl AnyUserData { /// Checks whether the type of this userdata is `T`. #[inline] pub fn is(&self) -> bool { - let lua = self.0.lua.lock(); - let type_id = lua.get_userdata_ref_type_id(&self.0); + let type_id = self.type_id(); // We do not use wrapped types here, rather prefer to check the "real" type of the userdata - matches!(type_id, Ok(Some(type_id)) if type_id == TypeId::of::()) + matches!(type_id, Some(type_id) if type_id == TypeId::of::()) } /// Borrow this userdata immutably if it is of type `T`. @@ -918,6 +917,15 @@ impl AnyUserData { self.0.to_pointer() } + /// Returns [`TypeId`] of this userdata if it is registered and `'static`. + /// + /// This method is not available for scoped userdata. + #[inline] + pub fn type_id(&self) -> Option { + let lua = self.0.lua.lock(); + lua.get_userdata_ref_type_id(&self.0).ok().flatten() + } + /// Returns a type name of this `UserData` (from a metatable field). pub(crate) fn type_name(&self) -> Result> { let lua = self.0.lua.lock(); diff --git a/tests/userdata.rs b/tests/userdata.rs index c85b75d0..bc4e4d0c 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -1,3 +1,4 @@ +use std::any::TypeId; use std::collections::HashMap; use std::string::String as StdString; use std::sync::Arc; @@ -23,9 +24,11 @@ fn test_userdata() -> Result<()> { let userdata2 = lua.create_userdata(UserData2(Box::new(2)))?; assert!(userdata1.is::()); + assert!(userdata1.type_id() == Some(TypeId::of::())); assert!(!userdata1.is::()); assert!(userdata2.is::()); assert!(!userdata2.is::()); + assert!(userdata2.type_id() == Some(TypeId::of::())); assert_eq!(userdata1.borrow::()?.0, 1); assert_eq!(*userdata2.borrow::()?.0, 2); From 1cb081d20a5060c8ae705300e854d9f7fde1a5f4 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 4 Apr 2025 10:58:08 +0100 Subject: [PATCH 362/635] Do not propagate `collect_garbage` flag when clonning Lua --- src/state.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/state.rs b/src/state.rs index d151afc5..11716a67 100644 --- a/src/state.rs +++ b/src/state.rs @@ -49,7 +49,6 @@ pub use raw::RawLua; use util::{callback_error_ext, StateGuard}; /// Top level Lua struct which represents an instance of Lua VM. -#[derive(Clone)] pub struct Lua { pub(self) raw: XRc>, // Controls whether garbage collection should be run on drop @@ -151,6 +150,16 @@ impl Drop for Lua { } } +impl Clone for Lua { + #[inline] + fn clone(&self) -> Self { + Lua { + raw: XRc::clone(&self.raw), + collect_garbage: false, + } + } +} + impl fmt::Debug for Lua { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Lua({:p})", self.lock().state()) From 53ff494ab9fc0427e62f46382a502d2d36ead4bc Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 4 Apr 2025 16:36:06 +0100 Subject: [PATCH 363/635] Prepare for custom (Luau) userdata destructors. We need to add logic later to prevent calling any Lua functions when UserData destructor is running. --- mlua-sys/Cargo.toml | 2 +- mlua-sys/src/luau/compat.rs | 2 +- mlua-sys/src/luau/lua.rs | 11 +++---- src/luau/package.rs | 3 +- src/scope.rs | 4 +-- src/userdata.rs | 3 +- src/userdata/registry.rs | 2 +- src/userdata/util.rs | 25 +++++++++++++++- src/util/error.rs | 18 +++--------- src/util/mod.rs | 4 +-- src/util/userdata.rs | 57 ++++++++++++++++++++++++++----------- tests/userdata.rs | 2 +- 12 files changed, 86 insertions(+), 47 deletions(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 651563d8..dc038dc4 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -40,7 +40,7 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 547.0.0, < 547.1.0", optional = true } luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } -luau0-src = { version = "0.13.0", optional = true } +luau0-src = { git = "https://github.com/mlua-rs/luau-src-rs", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index 36b900bd..479def3f 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -372,7 +372,7 @@ pub unsafe fn luaL_loadbufferenv( fn free(p: *mut c_void); } - unsafe extern "C-unwind" fn data_dtor(data: *mut c_void) { + unsafe extern "C-unwind" fn data_dtor(_: *mut lua_State, data: *mut c_void) { free(*(data as *mut *mut c_char) as *mut c_void); } diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 5b5f114b..807682f5 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -84,7 +84,6 @@ pub type lua_CFunction = unsafe extern "C-unwind" fn(L: *mut lua_State) -> c_int pub type lua_Continuation = unsafe extern "C-unwind" fn(L: *mut lua_State, status: c_int) -> c_int; /// Type for userdata destructor functions. -pub type lua_Udestructor = unsafe extern "C-unwind" fn(*mut c_void); pub type lua_Destructor = unsafe extern "C-unwind" fn(L: *mut lua_State, *mut c_void); /// Type for memory-allocation functions. @@ -191,7 +190,7 @@ extern "C-unwind" { pub fn lua_pushlightuserdatatagged(L: *mut lua_State, p: *mut c_void, tag: c_int); pub fn lua_newuserdatatagged(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void; pub fn lua_newuserdatataggedwithmetatable(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void; - pub fn lua_newuserdatadtor(L: *mut lua_State, sz: usize, dtor: lua_Udestructor) -> *mut c_void; + pub fn lua_newuserdatadtor(L: *mut lua_State, sz: usize, dtor: lua_Destructor) -> *mut c_void; pub fn lua_newbuffer(L: *mut lua_State, sz: usize) -> *mut c_void; @@ -345,12 +344,14 @@ pub unsafe fn lua_newuserdata(L: *mut lua_State, sz: usize) -> *mut c_void { } #[inline(always)] -pub unsafe fn lua_newuserdata_t(L: *mut lua_State) -> *mut T { - unsafe extern "C-unwind" fn destructor(ud: *mut c_void) { +pub unsafe fn lua_newuserdata_t(L: *mut lua_State, data: T) -> *mut T { + unsafe extern "C-unwind" fn destructor(_: *mut lua_State, ud: *mut c_void) { ptr::drop_in_place(ud as *mut T); } - lua_newuserdatadtor(L, mem::size_of::(), destructor::) as *mut T + let ud_ptr = lua_newuserdatadtor(L, const { mem::size_of::() }, destructor::) as *mut T; + ptr::write(ud_ptr, data); + ud_ptr } // TODO: lua_strlen diff --git a/src/luau/package.rs b/src/luau/package.rs index fc1aa1ac..1db11bcf 100644 --- a/src/luau/package.rs +++ b/src/luau/package.rs @@ -124,8 +124,7 @@ unsafe extern "C-unwind" fn lua_require(state: *mut ffi::lua_State) -> c_int { ffi::lua_pop(state, 1); // remove nil // load the module - let err_buf = ffi::lua_newuserdata_t::(state); - err_buf.write(StdString::new()); + let err_buf = ffi::lua_newuserdata_t(state, StdString::new()); ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADERS")); // _LOADERS is at index 3 for i in 1.. { if ffi::lua_rawgeti(state, -1, i) == ffi::LUA_TNIL { diff --git a/src/scope.rs b/src/scope.rs index bbe6dd79..7c155710 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -168,7 +168,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { #[cfg(feature = "luau")] let ud_ptr = { let data = UserDataStorage::new_scoped(data); - util::push_userdata::>(state, data, protect)? + util::push_userdata(state, data, protect)? }; #[cfg(not(feature = "luau"))] let ud_ptr = util::push_uninit_userdata::>(state, protect)?; @@ -216,7 +216,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { #[cfg(feature = "luau")] let ud_ptr = { let data = UserDataStorage::new_scoped(data); - util::push_userdata::>(state, data, protect)? + util::push_userdata(state, data, protect)? }; #[cfg(not(feature = "luau"))] let ud_ptr = util::push_uninit_userdata::>(state, protect)?; diff --git a/src/userdata.rs b/src/userdata.rs index ee33c514..f8d5ebec 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -30,7 +30,8 @@ pub use r#ref::{UserDataRef, UserDataRefMut}; pub use registry::UserDataRegistry; pub(crate) use registry::{RawUserDataRegistry, UserDataProxy}; pub(crate) use util::{ - borrow_userdata_scoped, borrow_userdata_scoped_mut, init_userdata_metatable, TypeIdHints, + borrow_userdata_scoped, borrow_userdata_scoped_mut, collect_userdata, init_userdata_metatable, + TypeIdHints, }; /// Kinds of metamethods that can be overridden. diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index cfb6fe60..5ad93c91 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -97,7 +97,7 @@ impl UserDataRegistry { meta_methods: Vec::new(), #[cfg(feature = "async")] async_meta_methods: Vec::new(), - destructor: super::util::userdata_destructor::, + destructor: super::util::destroy_userdata_storage::, type_id: r#type.type_id(), type_name: short_type_name::(), }; diff --git a/src/userdata/util.rs b/src/userdata/util.rs index bc62a491..3c3b4c63 100644 --- a/src/userdata/util.rs +++ b/src/userdata/util.rs @@ -2,6 +2,7 @@ use std::any::TypeId; use std::cell::Cell; use std::marker::PhantomData; use std::os::raw::c_int; +use std::ptr; use super::UserDataStorage; use crate::error::{Error, Result}; @@ -423,7 +424,29 @@ unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result }) } -pub(super) unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::lua_State) -> c_int { +// This method is called by Lua GC when it's time to collect the userdata. +// +// This method is usually used to collect internal userdata. +#[cfg(not(feature = "luau"))] +pub(crate) unsafe extern "C-unwind" fn collect_userdata(state: *mut ffi::lua_State) -> c_int { + let ud = get_userdata::(state, -1); + ptr::drop_in_place(ud); + 0 +} + +// This method is called by Luau GC when it's time to collect the userdata. +#[cfg(feature = "luau")] +pub(crate) unsafe extern "C-unwind" fn collect_userdata( + _state: *mut ffi::lua_State, + ud: *mut std::os::raw::c_void, +) { + ptr::drop_in_place(ud as *mut T); +} + +// This method can be called by user or Lua GC to destroy the userdata. +// It checks if the userdata is safe to destroy and sets the "destroyed" metatable +// to prevent further GC collection. +pub(super) unsafe extern "C-unwind" fn destroy_userdata_storage(state: *mut ffi::lua_State) -> c_int { let ud = get_userdata::>(state, -1); if (*ud).is_safe_to_destroy() { take_userdata::>(state); diff --git a/src/util/error.rs b/src/util/error.rs index 3bf08c6c..ef9184e2 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -9,9 +9,8 @@ use std::sync::Arc; use crate::error::{Error, Result}; use crate::memory::MemoryState; use crate::util::{ - check_stack, get_internal_metatable, get_internal_userdata, init_internal_metatable, - push_internal_userdata, push_string, push_table, rawset_field, to_string, TypeKey, - DESTRUCTED_USERDATA_METATABLE, + check_stack, get_internal_userdata, init_internal_metatable, push_internal_userdata, push_string, + push_table, rawset_field, to_string, TypeKey, DESTRUCTED_USERDATA_METATABLE, }; static WRAPPED_FAILURE_TYPE_KEY: u8 = 0; @@ -31,12 +30,8 @@ impl TypeKey for WrappedFailure { impl WrappedFailure { pub(crate) unsafe fn new_userdata(state: *mut ffi::lua_State) -> *mut Self { - #[cfg(feature = "luau")] - let ud = ffi::lua_newuserdata_t::(state); - #[cfg(not(feature = "luau"))] - let ud = ffi::lua_newuserdata(state, std::mem::size_of::()) as *mut Self; - ptr::write(ud, WrappedFailure::None); - ud + // Unprotected calls always return `Ok` + push_internal_userdata(state, WrappedFailure::None, false).unwrap() } } @@ -90,16 +85,11 @@ where let cause = Arc::new(err); let wrapped_error = WrappedFailure::Error(Error::CallbackError { traceback, cause }); ptr::write(ud, wrapped_error); - get_internal_metatable::(state); - ffi::lua_setmetatable(state, -2); - ffi::lua_error(state) } Err(p) => { ffi::lua_settop(state, 1); ptr::write(ud, WrappedFailure::Panic(Some(p))); - get_internal_metatable::(state); - ffi::lua_setmetatable(state, -2); ffi::lua_error(state) } } diff --git a/src/util/mod.rs b/src/util/mod.rs index 4366ecd3..f5fbae52 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -13,12 +13,12 @@ pub(crate) use short_names::short_type_name; pub(crate) use types::TypeKey; pub(crate) use userdata::{ get_destructed_userdata_metatable, get_internal_metatable, get_internal_userdata, get_userdata, - init_internal_metatable, push_internal_userdata, take_userdata, DESTRUCTED_USERDATA_METATABLE, + init_internal_metatable, push_internal_userdata, push_userdata, take_userdata, + DESTRUCTED_USERDATA_METATABLE, }; #[cfg(not(feature = "luau"))] pub(crate) use userdata::push_uninit_userdata; -pub(crate) use userdata::push_userdata; // Checks that Lua has enough free stack space for future stack operations. On failure, this will // panic with an internal error message. diff --git a/src/util/userdata.rs b/src/util/userdata.rs index 6108ddf7..2edbf0ba 100644 --- a/src/util/userdata.rs +++ b/src/util/userdata.rs @@ -1,7 +1,8 @@ use std::os::raw::{c_int, c_void}; -use std::ptr; +use std::{mem, ptr}; use crate::error::Result; +use crate::userdata::collect_userdata; use crate::util::{check_stack, get_metatable_ptr, push_table, rawset_field, TypeKey}; // Pushes the userdata and attaches a metatable with __gc method. @@ -10,11 +11,30 @@ pub(crate) unsafe fn push_internal_userdata( state: *mut ffi::lua_State, t: T, protect: bool, -) -> Result<()> { - push_userdata(state, t, protect)?; +) -> Result<*mut T> { + #[cfg(not(feature = "luau"))] + let ud_ptr = if protect { + protect_lua!(state, 0, 1, move |state| { + let ud_ptr = ffi::lua_newuserdata(state, const { mem::size_of::() }) as *mut T; + ptr::write(ud_ptr, t); + ud_ptr + })? + } else { + let ud_ptr = ffi::lua_newuserdata(state, const { mem::size_of::() }) as *mut T; + ptr::write(ud_ptr, t); + ud_ptr + }; + + #[cfg(feature = "luau")] + let ud_ptr = if protect { + protect_lua!(state, 0, 1, move |state| ffi::lua_newuserdata_t::(state, t))? + } else { + ffi::lua_newuserdata_t::(state, t) + }; + get_internal_metatable::(state); ffi::lua_setmetatable(state, -2); - Ok(()) + Ok(ud_ptr) } #[track_caller] @@ -35,12 +55,7 @@ pub(crate) unsafe fn init_internal_metatable( #[cfg(not(feature = "luau"))] { - unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::lua_State) -> c_int { - take_userdata::(state); - 0 - } - - ffi::lua_pushcfunction(state, userdata_destructor::); + ffi::lua_pushcfunction(state, collect_userdata::); rawset_field(state, -2, "__gc")?; } @@ -86,24 +101,34 @@ pub(crate) unsafe fn get_internal_userdata( pub(crate) unsafe fn push_uninit_userdata(state: *mut ffi::lua_State, protect: bool) -> Result<*mut T> { if protect { protect_lua!(state, 0, 1, |state| { - ffi::lua_newuserdata(state, std::mem::size_of::()) as *mut T + ffi::lua_newuserdata(state, const { mem::size_of::() }) as *mut T }) } else { - Ok(ffi::lua_newuserdata(state, std::mem::size_of::()) as *mut T) + Ok(ffi::lua_newuserdata(state, const { mem::size_of::() }) as *mut T) } } // Internally uses 3 stack spaces, does not call checkstack. #[inline] pub(crate) unsafe fn push_userdata(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<*mut T> { + let size = const { mem::size_of::() }; + #[cfg(not(feature = "luau"))] - let ud_ptr = push_uninit_userdata(state, protect)?; + let ud_ptr = if protect { + protect_lua!(state, 0, 1, move |state| ffi::lua_newuserdata(state, size))? + } else { + ffi::lua_newuserdata(state, size) + } as *mut T; + #[cfg(feature = "luau")] let ud_ptr = if protect { - protect_lua!(state, 0, 1, |state| { ffi::lua_newuserdata_t::(state) })? + protect_lua!(state, 0, 1, |state| { + ffi::lua_newuserdatadtor(state, size, collect_userdata::) + })? } else { - ffi::lua_newuserdata_t::(state) - }; + ffi::lua_newuserdatadtor(state, size, collect_userdata::) + } as *mut T; + ptr::write(ud_ptr, t); Ok(ud_ptr) } diff --git a/tests/userdata.rs b/tests/userdata.rs index bc4e4d0c..63f9e0f5 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -264,7 +264,7 @@ fn test_gc_userdata() -> Result<()> { impl UserData for MyUserdata { fn add_methods>(methods: &mut M) { methods.add_method("access", |_, this, ()| { - assert!(this.id == 123); + assert_eq!(this.id, 123); Ok(()) }); } From 12e61e01f6787653b9eba69bbd987c90c0b316c1 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 4 Apr 2025 22:18:14 +0100 Subject: [PATCH 364/635] Restrict access to Luau VM from UserData destructors. It's unsafe to make almost any Lua calls when userdata destructor is running. This can cause recursive GC run and crash. See https://github.com/luau-lang/luau/pull/510 for some details. --- mlua-sys/src/lua51/lua.rs | 4 ++-- mlua-sys/src/lua52/lua.rs | 4 ++-- mlua-sys/src/lua53/lua.rs | 4 ++-- mlua-sys/src/lua54/lua.rs | 4 ++-- mlua-sys/src/luau/compat.rs | 2 +- mlua-sys/src/luau/lua.rs | 10 +++++----- src/memory.rs | 2 +- src/state.rs | 14 ++++++++++++-- src/state/extra.rs | 4 ++++ src/userdata/util.rs | 11 +++++++++-- 10 files changed, 40 insertions(+), 19 deletions(-) diff --git a/mlua-sys/src/lua51/lua.rs b/mlua-sys/src/lua51/lua.rs index 4789c568..adbcd306 100644 --- a/mlua-sys/src/lua51/lua.rs +++ b/mlua-sys/src/lua51/lua.rs @@ -84,10 +84,10 @@ pub type lua_Reader = pub type lua_Writer = unsafe extern "C-unwind" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int; -/// Type for memory-allocation functions +/// Type for memory-allocation functions (no unwinding) #[rustfmt::skip] pub type lua_Alloc = - unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void; + unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void; #[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))] extern "C-unwind" { diff --git a/mlua-sys/src/lua52/lua.rs b/mlua-sys/src/lua52/lua.rs index b77ef14f..a5fed2b7 100644 --- a/mlua-sys/src/lua52/lua.rs +++ b/mlua-sys/src/lua52/lua.rs @@ -89,10 +89,10 @@ pub type lua_Reader = pub type lua_Writer = unsafe extern "C-unwind" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int; -/// Type for memory-allocation functions +/// Type for memory-allocation functions (no unwinding) #[rustfmt::skip] pub type lua_Alloc = - unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void; + unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void; #[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))] extern "C-unwind" { diff --git a/mlua-sys/src/lua53/lua.rs b/mlua-sys/src/lua53/lua.rs index 3fd84f57..efb85f0c 100644 --- a/mlua-sys/src/lua53/lua.rs +++ b/mlua-sys/src/lua53/lua.rs @@ -96,10 +96,10 @@ pub type lua_Reader = pub type lua_Writer = unsafe extern "C-unwind" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int; -/// Type for memory-allocation functions +/// Type for memory-allocation functions (no unwinding) #[rustfmt::skip] pub type lua_Alloc = - unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void; + unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void; #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] extern "C-unwind" { diff --git a/mlua-sys/src/lua54/lua.rs b/mlua-sys/src/lua54/lua.rs index afad4a36..09c7c6a3 100644 --- a/mlua-sys/src/lua54/lua.rs +++ b/mlua-sys/src/lua54/lua.rs @@ -95,10 +95,10 @@ pub type lua_Reader = pub type lua_Writer = unsafe extern "C-unwind" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int; -/// Type for memory-allocation functions +/// Type for memory-allocation functions (no unwinding) #[rustfmt::skip] pub type lua_Alloc = - unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void; + unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void; /// Type for warning functions pub type lua_WarnFunction = unsafe extern "C-unwind" fn(ud: *mut c_void, msg: *const c_char, tocont: c_int); diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index 479def3f..41212934 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -372,7 +372,7 @@ pub unsafe fn luaL_loadbufferenv( fn free(p: *mut c_void); } - unsafe extern "C-unwind" fn data_dtor(_: *mut lua_State, data: *mut c_void) { + unsafe extern "C" fn data_dtor(_: *mut lua_State, data: *mut c_void) { free(*(data as *mut *mut c_char) as *mut c_void); } diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 807682f5..017ea664 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -83,12 +83,12 @@ pub type lua_Unsigned = c_uint; pub type lua_CFunction = unsafe extern "C-unwind" fn(L: *mut lua_State) -> c_int; pub type lua_Continuation = unsafe extern "C-unwind" fn(L: *mut lua_State, status: c_int) -> c_int; -/// Type for userdata destructor functions. -pub type lua_Destructor = unsafe extern "C-unwind" fn(L: *mut lua_State, *mut c_void); +/// Type for userdata destructor functions (no unwinding). +pub type lua_Destructor = unsafe extern "C" fn(L: *mut lua_State, *mut c_void); -/// Type for memory-allocation functions. +/// Type for memory-allocation functions (no unwinding). pub type lua_Alloc = - unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void; + unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void; /// Returns Luau release version (eg. `0.xxx`). pub const fn luau_version() -> Option<&'static str> { @@ -345,7 +345,7 @@ pub unsafe fn lua_newuserdata(L: *mut lua_State, sz: usize) -> *mut c_void { #[inline(always)] pub unsafe fn lua_newuserdata_t(L: *mut lua_State, data: T) -> *mut T { - unsafe extern "C-unwind" fn destructor(_: *mut lua_State, ud: *mut c_void) { + unsafe extern "C" fn destructor(_: *mut lua_State, ud: *mut c_void) { ptr::drop_in_place(ud as *mut T); } diff --git a/src/memory.rs b/src/memory.rs index 92c60326..ac160fe7 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -96,7 +96,7 @@ impl MemoryState { } } -unsafe extern "C-unwind" fn allocator( +unsafe extern "C" fn allocator( extra: *mut c_void, ptr: *mut c_void, osize: usize, diff --git a/src/state.rs b/src/state.rs index 11716a67..43a474cb 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2025,7 +2025,12 @@ impl Lua { #[inline(always)] pub(crate) fn lock(&self) -> ReentrantMutexGuard { - self.raw.lock() + let rawlua = self.raw.lock(); + #[cfg(feature = "luau")] + if unsafe { (*rawlua.extra.get()).running_userdata_gc } { + panic!("Luau VM is suspended while userdata destructor is running"); + } + rawlua } #[inline(always)] @@ -2052,7 +2057,12 @@ impl WeakLua { #[track_caller] #[inline(always)] pub(crate) fn lock(&self) -> LuaGuard { - LuaGuard::new(self.0.upgrade().expect("Lua instance is destroyed")) + let guard = LuaGuard::new(self.0.upgrade().expect("Lua instance is destroyed")); + #[cfg(feature = "luau")] + if unsafe { (*guard.extra.get()).running_userdata_gc } { + panic!("Luau VM is suspended while userdata destructor is running"); + } + guard } #[inline(always)] diff --git a/src/state/extra.rs b/src/state/extra.rs index 865db6d6..a84fa20c 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -83,6 +83,8 @@ pub(crate) struct ExtraData { #[cfg(feature = "luau")] pub(super) userthread_callback: Option, + #[cfg(feature = "luau")] + pub(crate) running_userdata_gc: bool, #[cfg(feature = "luau")] pub(super) sandboxed: bool, #[cfg(feature = "luau")] @@ -186,6 +188,8 @@ impl ExtraData { compiler: None, #[cfg(feature = "luau-jit")] enable_jit: true, + #[cfg(feature = "luau")] + running_userdata_gc: false, })); // Store it in the registry diff --git a/src/userdata/util.rs b/src/userdata/util.rs index 3c3b4c63..1c9275ac 100644 --- a/src/userdata/util.rs +++ b/src/userdata/util.rs @@ -436,11 +436,18 @@ pub(crate) unsafe extern "C-unwind" fn collect_userdata(state: *mut ffi::lua_ // This method is called by Luau GC when it's time to collect the userdata. #[cfg(feature = "luau")] -pub(crate) unsafe extern "C-unwind" fn collect_userdata( - _state: *mut ffi::lua_State, +pub(crate) unsafe extern "C" fn collect_userdata( + state: *mut ffi::lua_State, ud: *mut std::os::raw::c_void, ) { + // Almost none Lua operations are allowed when destructor is running, + // so we need to set a flag to prevent calling any Lua functions + let extra = (*ffi::lua_callbacks(state)).userdata as *mut crate::state::ExtraData; + (*extra).running_userdata_gc = true; + // Luau does not support _any_ panics in destructors (they are declared as "C", NOT as "C-unwind"), + // so any panics will trigger `abort()`. ptr::drop_in_place(ud as *mut T); + (*extra).running_userdata_gc = false; } // This method can be called by user or Lua GC to destroy the userdata. From bd13300288ad40287cc477259a3998c912e31446 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 5 Apr 2025 18:30:22 +0100 Subject: [PATCH 365/635] Update dependencies --- Cargo.toml | 2 +- mlua_derive/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 91f373b6..49c225f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,7 @@ static_assertions = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] criterion = { version = "0.5", features = ["async_tokio"] } -rustyline = "14.0" +rustyline = "15.0" tokio = { version = "1.0", features = ["full"] } [lints.rust] diff --git a/mlua_derive/Cargo.toml b/mlua_derive/Cargo.toml index 1e121341..b53de434 100644 --- a/mlua_derive/Cargo.toml +++ b/mlua_derive/Cargo.toml @@ -19,6 +19,6 @@ quote = "1.0" proc-macro2 = { version = "1.0", features = ["span-locations"] } proc-macro-error2 = { version = "2.0.1", optional = true } syn = { version = "2.0", features = ["full"] } -itertools = { version = "0.13", optional = true } +itertools = { version = "0.14", optional = true } regex = { version = "1.4", optional = true } once_cell = { version = "1.0", optional = true } From b805939320f7d9b28ad2aac56df26d63660cdf37 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 5 Apr 2025 23:40:05 +0100 Subject: [PATCH 366/635] Make `Lua::weak()` method and `WeakLua` struct public. This can be useful to prevent circular dependencies between Rust and Lua or check that Lua instance is still alive. --- src/lib.rs | 2 +- src/prelude.rs | 2 +- src/state.rs | 37 ++++++++++++++++++++++++++++++------- tests/tests.rs | 18 ++++++++++++++++++ 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 913bf589..22a3b367 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,7 +104,7 @@ pub use crate::function::{Function, FunctionInfo}; pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack}; pub use crate::multi::{MultiValue, Variadic}; pub use crate::scope::Scope; -pub use crate::state::{GCMode, Lua, LuaOptions}; +pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua}; pub use crate::stdlib::StdLib; pub use crate::string::{BorrowedBytes, BorrowedStr, String}; pub use crate::table::{Table, TablePairs, TableSequence}; diff --git a/src/prelude.rs b/src/prelude.rs index 1f66e769..fc967237 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -13,7 +13,7 @@ pub use crate::{ UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, - Variadic as LuaVariadic, VmState as LuaVmState, + Variadic as LuaVariadic, VmState as LuaVmState, WeakLua, }; #[cfg(not(feature = "luau"))] diff --git a/src/state.rs b/src/state.rs index 43a474cb..c799e5b8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -55,8 +55,11 @@ pub struct Lua { pub(self) collect_garbage: bool, } +/// Weak reference to Lua instance. +/// +/// This can used to prevent circular references between Lua and Rust objects. #[derive(Clone)] -pub(crate) struct WeakLua(XWeak>); +pub struct WeakLua(XWeak>); pub(crate) struct LuaGuard(ArcReentrantMutexGuard); @@ -1995,6 +1998,15 @@ impl Lua { LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut std::os::raw::c_void) } + /// Returns a weak reference to the Lua instance. + /// + /// This is useful for creating a reference to the Lua instance that does not prevent it from + /// being deallocated. + #[inline(always)] + pub fn weak(&self) -> WeakLua { + WeakLua(XRc::downgrade(&self.raw)) + } + // Luau version located in `luau/mod.rs` #[cfg(not(feature = "luau"))] fn disable_c_modules(&self) -> Result<()> { @@ -2038,11 +2050,6 @@ impl Lua { LuaGuard(self.raw.lock_arc()) } - #[inline(always)] - pub(crate) fn weak(&self) -> WeakLua { - WeakLua(XRc::downgrade(&self.raw)) - } - /// Returns a handle to the unprotected Lua state without any synchronization. /// /// This is useful where we know that the lock is already held by the caller. @@ -2070,14 +2077,30 @@ impl WeakLua { Some(LuaGuard::new(self.0.upgrade()?)) } + /// Upgrades the weak Lua reference to a strong reference. + /// + /// # Panics + /// + /// Panics if the Lua instance is destroyed. #[track_caller] #[inline(always)] - pub(crate) fn upgrade(&self) -> Lua { + pub fn upgrade(&self) -> Lua { Lua { raw: self.0.upgrade().expect("Lua instance is destroyed"), collect_garbage: false, } } + + /// Tries to upgrade the weak Lua reference to a strong reference. + /// + /// Returns `None` if the Lua instance is destroyed. + #[inline(always)] + pub fn try_upgrade(&self) -> Option { + Some(Lua { + raw: self.0.upgrade()?, + collect_garbage: false, + }) + } } impl PartialEq for WeakLua { diff --git a/tests/tests.rs b/tests/tests.rs index f8f4b89c..94442999 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -12,6 +12,24 @@ use mlua::{ UserData, Value, Variadic, }; +#[test] +fn test_weak_lua() { + let lua = Lua::new(); + let weak_lua = lua.weak(); + assert!(weak_lua.try_upgrade().is_some()); + drop(lua); + assert!(weak_lua.try_upgrade().is_none()); +} + +#[test] +#[should_panic(expected = "Lua instance is destroyed")] +fn test_weak_lua_panic() { + let lua = Lua::new(); + let weak_lua = lua.weak(); + drop(lua); + let _ = weak_lua.upgrade(); +} + #[cfg(not(feature = "luau"))] #[test] fn test_safety() -> Result<()> { From 39b3af2ff2b95b92ceb7b7d37611d730dd1d5c14 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 5 Apr 2025 23:44:25 +0100 Subject: [PATCH 367/635] Fix warnings when testing documentation --- src/function.rs | 2 +- src/state.rs | 4 ++-- src/thread.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/function.rs b/src/function.rs index 7e0a6d97..08dbc832 100644 --- a/src/function.rs +++ b/src/function.rs @@ -146,7 +146,7 @@ impl Function { /// Ok(()) /// })?; /// - /// sleep.call_async(10).await?; + /// sleep.call_async::<()>(10).await?; /// /// # Ok(()) /// # } diff --git a/src/state.rs b/src/state.rs index c799e5b8..02921586 100644 --- a/src/state.rs +++ b/src/state.rs @@ -639,7 +639,7 @@ impl Lua { /// .into_function()?, /// )?; /// while co.status() == ThreadStatus::Resumable { - /// co.resume(())?; + /// co.resume::<()>(())?; /// } /// # Ok(()) /// # } @@ -1904,7 +1904,7 @@ impl Lua { /// fn main() -> Result<()> { /// let lua = Lua::new(); /// lua.set_app_data("hello"); - /// lua.create_function(hello)?.call(())?; + /// lua.create_function(hello)?.call::<()>(())?; /// let s = lua.app_data_ref::<&str>().unwrap(); /// assert_eq!(*s, "world"); /// Ok(()) diff --git a/src/thread.rs b/src/thread.rs index 72ddac31..14ff04ad 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -445,7 +445,7 @@ impl Thread { /// Ok(()) /// })?)?; /// thread.sandbox()?; - /// thread.resume(())?; + /// thread.resume::<()>(())?; /// /// // The global environment should be unchanged /// assert_eq!(lua.globals().get::>("var")?, None); From 49958f4827f62ed966354a8180a1536acd88e70c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 6 Apr 2025 13:57:15 +0100 Subject: [PATCH 368/635] Split Luau thread event callback to creation and collection callbacks. We need this because they have different requirements: - Thread creation callback can return Error or panic - Thread collection callback runs during Luau GC cycle and cannot make any Lua calls or trigger panics. --- src/state.rs | 103 +++++++++++++++++++++++++++++-------------- src/state/extra.rs | 12 +++-- src/state/raw.rs | 2 +- src/types.rs | 10 ++++- src/userdata/util.rs | 4 +- tests/luau.rs | 49 ++++++++++++++------ 6 files changed, 124 insertions(+), 56 deletions(-) diff --git a/src/state.rs b/src/state.rs index 02921586..b8e472b3 100644 --- a/src/state.rs +++ b/src/state.rs @@ -696,56 +696,91 @@ impl Lua { } } - /// Sets a thread event callback that will be called when a thread is created or destroyed. + /// Sets a thread creation callback that will be called when a thread is created. + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn set_thread_creation_callback(&self, callback: F) + where + F: Fn(&Lua, Thread) -> Result<()> + MaybeSend + 'static, + { + let lua = self.lock(); + unsafe { + (*lua.extra.get()).thread_creation_callback = Some(XRc::new(callback)); + (*ffi::lua_callbacks(lua.main_state())).userthread = Some(Self::userthread_proc); + } + } + + /// Sets a thread collection callback that will be called when a thread is destroyed. /// - /// The callback is called with a [`Value`] argument that is either: - /// - A [`Thread`] object when thread is created - /// - A [`LightUserData`] when thread is destroyed + /// Luau GC does not support exceptions during collection, so the callback must be + /// non-panicking. If the callback panics, the program will be aborted. #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - pub fn set_thread_event_callback(&self, callback: F) + pub fn set_thread_collection_callback(&self, callback: F) where - F: Fn(&Lua, Value) -> Result<()> + MaybeSend + 'static, + F: Fn(crate::LightUserData) + MaybeSend + 'static, { - unsafe extern "C-unwind" fn userthread_proc(parent: *mut ffi::lua_State, child: *mut ffi::lua_State) { - let extra = ExtraData::get(child); - let thread_cb = match (*extra).userthread_callback { + let lua = self.lock(); + unsafe { + (*lua.extra.get()).thread_collection_callback = Some(XRc::new(callback)); + (*ffi::lua_callbacks(lua.main_state())).userthread = Some(Self::userthread_proc); + } + } + + #[cfg(feature = "luau")] + unsafe extern "C-unwind" fn userthread_proc(parent: *mut ffi::lua_State, child: *mut ffi::lua_State) { + let extra = ExtraData::get(child); + if !parent.is_null() { + // Thread is created + let callback = match (*extra).thread_creation_callback { Some(ref cb) => cb.clone(), None => return, }; - if XRc::strong_count(&thread_cb) > 2 { + if XRc::strong_count(&callback) > 2 { return; // Don't allow recursion } - let value = match parent.is_null() { - // Thread is about to be destroyed, pass light userdata - true => Value::LightUserData(crate::LightUserData(child as _)), - false => { - // Thread is created, pass thread object - ffi::lua_pushthread(child); - ffi::lua_xmove(child, (*extra).ref_thread, 1); - Value::Thread(Thread((*extra).raw_lua().pop_ref_thread(), child)) - } - }; + ffi::lua_pushthread(child); + ffi::lua_xmove(child, (*extra).ref_thread, 1); + let value = Thread((*extra).raw_lua().pop_ref_thread(), child); + let _guard = StateGuard::new((*extra).raw_lua(), parent); callback_error_ext((*extra).raw_lua().state(), extra, false, move |extra, _| { - thread_cb((*extra).lua(), value) + callback((*extra).lua(), value) }) - } + } else { + // Thread is about to be collected + let callback = match (*extra).thread_collection_callback { + Some(ref cb) => cb.clone(), + None => return, + }; - // Set thread callback - let lua = self.lock(); - unsafe { - (*lua.extra.get()).userthread_callback = Some(XRc::new(callback)); - (*ffi::lua_callbacks(lua.main_state())).userthread = Some(userthread_proc); + // We need to wrap the callback call in non-unwind function as it's not safe to unwind when + // Luau GC is running. + // This will trigger `abort()` if the callback panics. + unsafe extern "C" fn run_callback( + callback: *const crate::types::ThreadCollectionCallback, + value: *mut ffi::lua_State, + ) { + (*callback)(crate::LightUserData(value as _)); + } + + (*extra).running_gc = true; + run_callback(&callback, child); + (*extra).running_gc = false; } } - /// Removes any thread event callback previously set by `set_thread_event_callback`. + /// Removes any thread creation or collection callbacks previously set by + /// [`Lua::set_thread_creation_callback`] or [`Lua::set_thread_collection_callback`]. + /// + /// This function has no effect if a thread callbacks were not previously set. #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - pub fn remove_thread_event_callback(&self) { + pub fn remove_thread_callbacks(&self) { let lua = self.lock(); unsafe { - (*lua.extra.get()).userthread_callback = None; + let extra = lua.extra.get(); + (*extra).thread_creation_callback = None; + (*extra).thread_collection_callback = None; (*ffi::lua_callbacks(lua.main_state())).userthread = None; } } @@ -2039,8 +2074,8 @@ impl Lua { pub(crate) fn lock(&self) -> ReentrantMutexGuard { let rawlua = self.raw.lock(); #[cfg(feature = "luau")] - if unsafe { (*rawlua.extra.get()).running_userdata_gc } { - panic!("Luau VM is suspended while userdata destructor is running"); + if unsafe { (*rawlua.extra.get()).running_gc } { + panic!("Luau VM is suspended while GC is running"); } rawlua } @@ -2066,8 +2101,8 @@ impl WeakLua { pub(crate) fn lock(&self) -> LuaGuard { let guard = LuaGuard::new(self.0.upgrade().expect("Lua instance is destroyed")); #[cfg(feature = "luau")] - if unsafe { (*guard.extra.get()).running_userdata_gc } { - panic!("Luau VM is suspended while userdata destructor is running"); + if unsafe { (*guard.extra.get()).running_gc } { + panic!("Luau VM is suspended while GC is running"); } guard } diff --git a/src/state/extra.rs b/src/state/extra.rs index a84fa20c..7567669e 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -81,10 +81,12 @@ pub(crate) struct ExtraData { #[cfg(feature = "luau")] pub(super) interrupt_callback: Option, #[cfg(feature = "luau")] - pub(super) userthread_callback: Option, + pub(super) thread_creation_callback: Option, + #[cfg(feature = "luau")] + pub(super) thread_collection_callback: Option, #[cfg(feature = "luau")] - pub(crate) running_userdata_gc: bool, + pub(crate) running_gc: bool, #[cfg(feature = "luau")] pub(super) sandboxed: bool, #[cfg(feature = "luau")] @@ -181,7 +183,9 @@ impl ExtraData { #[cfg(feature = "luau")] interrupt_callback: None, #[cfg(feature = "luau")] - userthread_callback: None, + thread_creation_callback: None, + #[cfg(feature = "luau")] + thread_collection_callback: None, #[cfg(feature = "luau")] sandboxed: false, #[cfg(feature = "luau")] @@ -189,7 +193,7 @@ impl ExtraData { #[cfg(feature = "luau-jit")] enable_jit: true, #[cfg(feature = "luau")] - running_userdata_gc: false, + running_gc: false, })); // Store it in the registry diff --git a/src/state/raw.rs b/src/state/raw.rs index b7c7f625..c8f3adda 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -573,7 +573,7 @@ impl RawLua { let protect = !self.unlikely_memory_error(); #[cfg(feature = "luau")] - let protect = protect || (*self.extra.get()).userthread_callback.is_some(); + let protect = protect || (*self.extra.get()).thread_creation_callback.is_some(); let thread_state = if !protect { ffi::lua_newthread(state) diff --git a/src/types.rs b/src/types.rs index 72fef44f..2f63906f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -91,10 +91,16 @@ pub(crate) type InterruptCallback = XRc Result + Send>; pub(crate) type InterruptCallback = XRc Result>; #[cfg(all(feature = "send", feature = "luau"))] -pub(crate) type UserThreadCallback = XRc Result<()> + Send>; +pub(crate) type ThreadCreationCallback = XRc Result<()> + Send>; #[cfg(all(not(feature = "send"), feature = "luau"))] -pub(crate) type UserThreadCallback = XRc Result<()>>; +pub(crate) type ThreadCreationCallback = XRc Result<()>>; + +#[cfg(all(feature = "send", feature = "luau"))] +pub(crate) type ThreadCollectionCallback = XRc; + +#[cfg(all(not(feature = "send"), feature = "luau"))] +pub(crate) type ThreadCollectionCallback = XRc; #[cfg(all(feature = "send", feature = "lua54"))] pub(crate) type WarnCallback = XRc Result<()> + Send>; diff --git a/src/userdata/util.rs b/src/userdata/util.rs index 1c9275ac..4622d5e8 100644 --- a/src/userdata/util.rs +++ b/src/userdata/util.rs @@ -443,11 +443,11 @@ pub(crate) unsafe extern "C" fn collect_userdata( // Almost none Lua operations are allowed when destructor is running, // so we need to set a flag to prevent calling any Lua functions let extra = (*ffi::lua_callbacks(state)).userdata as *mut crate::state::ExtraData; - (*extra).running_userdata_gc = true; + (*extra).running_gc = true; // Luau does not support _any_ panics in destructors (they are declared as "C", NOT as "C-unwind"), // so any panics will trigger `abort()`. ptr::drop_in_place(ud as *mut T); - (*extra).running_userdata_gc = false; + (*extra).running_gc = false; } // This method can be called by user or Lua GC to destroy the userdata. diff --git a/tests/luau.rs b/tests/luau.rs index 5ad82843..590125da 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -1,5 +1,6 @@ #![cfg(feature = "luau")] +use std::cell::Cell; use std::fmt::Debug; use std::fs; use std::os::raw::c_void; @@ -419,17 +420,19 @@ fn test_thread_events() -> Result<()> { let thread_data: Arc<(AtomicPtr, AtomicBool)> = Arc::new(Default::default()); let (count2, thread_data2) = (count.clone(), thread_data.clone()); - lua.set_thread_event_callback(move |_, value| { + lua.set_thread_creation_callback(move |_, thread| { count2.fetch_add(1, Ordering::Relaxed); - (thread_data2.0).store(value.to_pointer() as *mut _, Ordering::Relaxed); - if value.is_thread() { - thread_data2.1.store(false, Ordering::Relaxed); - } - if value.is_light_userdata() { - thread_data2.1.store(true, Ordering::Relaxed); - } + (thread_data2.0).store(thread.to_pointer() as *mut _, Ordering::Relaxed); + thread_data2.1.store(false, Ordering::Relaxed); Ok(()) }); + let (count3, thread_data3) = (count.clone(), thread_data.clone()); + lua.set_thread_collection_callback(move |thread_ptr| { + count3.fetch_add(1, Ordering::Relaxed); + if thread_data3.0.load(Ordering::Relaxed) == thread_ptr.0 { + thread_data3.1.store(true, Ordering::Relaxed); + } + }); let t = lua.create_thread(lua.load("return 123").into_function()?)?; assert_eq!(count.load(Ordering::Relaxed), 1); @@ -445,27 +448,47 @@ fn test_thread_events() -> Result<()> { assert!(thread_data.1.load(Ordering::Relaxed)); // Check that recursion is not allowed - let count3 = count.clone(); - lua.set_thread_event_callback(move |lua, _value| { - count3.fetch_add(1, Ordering::Relaxed); + let count4 = count.clone(); + lua.set_thread_creation_callback(move |lua, _value| { + count4.fetch_add(1, Ordering::Relaxed); let _ = lua.create_thread(lua.load("return 123").into_function().unwrap())?; Ok(()) }); let t = lua.create_thread(lua.load("return 123").into_function()?)?; assert_eq!(count.load(Ordering::Relaxed), 3); - lua.remove_thread_event_callback(); + lua.remove_thread_callbacks(); drop(t); lua.gc_collect()?; assert_eq!(count.load(Ordering::Relaxed), 3); // Test error inside callback - lua.set_thread_event_callback(move |_, _| Err(Error::runtime("error when processing thread event"))); + lua.set_thread_creation_callback(move |_, _| Err(Error::runtime("error when processing thread event"))); let result = lua.create_thread(lua.load("return 123").into_function()?); assert!(result.is_err()); assert!( matches!(result, Err(Error::RuntimeError(err)) if err.contains("error when processing thread event")) ); + // Test context switch when running Lua script + let count = Cell::new(0); + lua.set_thread_creation_callback(move |_, _| { + count.set(count.get() + 1); + if count.get() == 2 { + return Err(Error::runtime("thread limit exceeded")); + } + Ok(()) + }); + let result = lua + .load( + r#" + local co = coroutine.wrap(function() return coroutine.create(print) end) + co() + "#, + ) + .exec(); + assert!(result.is_err()); + assert!(matches!(result, Err(Error::RuntimeError(err)) if err.contains("thread limit exceeded"))); + Ok(()) } From 4e5677ae544589751a447a26cdf6969ce46037e6 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 6 Apr 2025 15:37:38 +0100 Subject: [PATCH 369/635] Update references to mlua repo --- Cargo.toml | 2 +- README.md | 12 ++++++------ docs/release_notes/v0.10.md | 2 +- docs/release_notes/v0.9.md | 4 ++-- mlua-sys/Cargo.toml | 2 +- mlua_derive/Cargo.toml | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 49c225f1..84fd595a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.10.3" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.79.0" edition = "2021" -repository = "https://github.com/khvzak/mlua" +repository = "https://github.com/mlua-rs/mlua" documentation = "https://docs.rs/mlua" readme = "README.md" keywords = ["lua", "luajit", "luau", "async", "scripting"] diff --git a/README.md b/README.md index 747f4821..1a39e860 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # mlua [![Build Status]][github-actions] [![Latest Version]][crates.io] [![API Documentation]][docs.rs] [![Coverage Status]][codecov.io] ![MSRV] -[Build Status]: https://github.com/khvzak/mlua/workflows/CI/badge.svg -[github-actions]: https://github.com/khvzak/mlua/actions +[Build Status]: https://github.com/mlua-rs/mlua/workflows/CI/badge.svg +[github-actions]: https://github.com/mlua-rs/mlua/actions [Latest Version]: https://img.shields.io/crates/v/mlua.svg [crates.io]: https://crates.io/crates/mlua [API Documentation]: https://docs.rs/mlua/badge.svg @@ -21,7 +21,7 @@ > **Note** > -> See v0.10 [release notes](https://github.com/khvzak/mlua/blob/main/docs/release_notes/v0.10.md). +> See v0.10 [release notes](https://github.com/mlua-rs/mlua/blob/main/docs/release_notes/v0.10.md). `mlua` is bindings to [Lua](https://www.lua.org) programming language for Rust with a goal to provide _safe_ (as far as it's possible), high level, easy to use, practical and flexible API. @@ -32,7 +32,7 @@ Started as `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua versions excluding JIT. -[GitHub Actions]: https://github.com/khvzak/mlua/actions +[GitHub Actions]: https://github.com/mlua-rs/mlua/actions [Luau]: https://luau.org ## Usage @@ -67,8 +67,8 @@ Below is a list of the available feature flags. By default `mlua` does not enabl [5.1]: https://www.lua.org/manual/5.1/manual.html [LuaJIT]: https://luajit.org/ [Luau]: https://github.com/luau-lang/luau -[lua-src]: https://github.com/khvzak/lua-src-rs -[luajit-src]: https://github.com/khvzak/luajit-src-rs +[lua-src]: https://github.com/mlua-rs/lua-src-rs +[luajit-src]: https://github.com/mlua-rs/luajit-src-rs [tokio]: https://github.com/tokio-rs/tokio [async-std]: https://github.com/async-rs/async-std [`Send`]: https://doc.rust-lang.org/std/marker/trait.Send.html diff --git a/docs/release_notes/v0.10.md b/docs/release_notes/v0.10.md index c2e09008..db01e8b2 100644 --- a/docs/release_notes/v0.10.md +++ b/docs/release_notes/v0.10.md @@ -3,7 +3,7 @@ The v0.10 version of mlua has goal to improve the user experience while keeping the same performance and safety guarantees. This document highlights the most notable features. For a full list of changes, see the [CHANGELOG]. -[CHANGELOG]: https://github.com/khvzak/mlua/blob/main/CHANGELOG.md +[CHANGELOG]: https://github.com/mlua-rs/mlua/blob/main/CHANGELOG.md ### New features diff --git a/docs/release_notes/v0.9.md b/docs/release_notes/v0.9.md index 33c6a2a5..cbc2d29b 100644 --- a/docs/release_notes/v0.9.md +++ b/docs/release_notes/v0.9.md @@ -3,7 +3,7 @@ The v0.9 version of mlua is a major release that includes a number of API changes and improvements. This release is a stepping stone towards the v1.0. This document highlights the most important changes. For a full list of changes, see the [CHANGELOG]. -[CHANGELOG]: https://github.com/khvzak/mlua/blob/main/CHANGELOG.md +[CHANGELOG]: https://github.com/mlua-rs/mlua/blob/main/CHANGELOG.md ### New features @@ -304,7 +304,7 @@ assert_eq!(f.call::<_, mlua::String>(())?, "hello"); The new mlua version has a number of performance improvements. Please check the [benchmarks results] to see how mlua compares to rlua and rhai. -[benchmarks results]: https://github.com/khvzak/script-bench-rs +[benchmarks results]: https://github.com/mlua-rs/script-bench-rs ### Changes in `module` mode diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index dc038dc4..6bdcbe6f 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -4,7 +4,7 @@ version = "0.6.7" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" -repository = "https://github.com/khvzak/mlua" +repository = "https://github.com/mlua-rs/mlua" documentation = "https://docs.rs/mlua-sys" readme = "README.md" categories = ["external-ffi-bindings"] diff --git a/mlua_derive/Cargo.toml b/mlua_derive/Cargo.toml index b53de434..59b96e84 100644 --- a/mlua_derive/Cargo.toml +++ b/mlua_derive/Cargo.toml @@ -4,7 +4,7 @@ version = "0.10.1" authors = ["Aleksandr Orlenko "] edition = "2021" description = "Procedural macros for the mlua crate." -repository = "https://github.com/khvzak/mlua" +repository = "https://github.com/mlua-rs/mlua" keywords = ["lua", "mlua"] license = "MIT" From 8173ef2fa858a934d38066373d4eca8ced6d647d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 6 Apr 2025 17:51:54 +0100 Subject: [PATCH 370/635] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a39e860..a660a19a 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Started as `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT `mlua` tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platform and cross-compilation to `aarch64` (other targets are also supported). -WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua versions excluding JIT. +WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua/Luau versions excluding JIT. [GitHub Actions]: https://github.com/mlua-rs/mlua/actions [Luau]: https://luau.org From 117f8377e25f4af2985e6291fa2b6bdf94989e41 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 20 Apr 2025 21:44:16 +0200 Subject: [PATCH 371/635] mlua-sys: Add definitions for Luau require library (since 0.669) --- mlua-sys/Cargo.toml | 2 +- mlua-sys/src/luau/luarequire.rs | 125 ++++++++++++++++++++++++++++++++ mlua-sys/src/luau/mod.rs | 2 + 3 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 mlua-sys/src/luau/luarequire.rs diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 6bdcbe6f..a88019b3 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -40,7 +40,7 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 547.0.0, < 547.1.0", optional = true } luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } -luau0-src = { git = "https://github.com/mlua-rs/luau-src-rs", optional = true } +luau0-src = { git = "https://github.com/mlua-rs/luau-src-rs", rev = "f89e9f2", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/mlua-sys/src/luau/luarequire.rs b/mlua-sys/src/luau/luarequire.rs new file mode 100644 index 00000000..d3726102 --- /dev/null +++ b/mlua-sys/src/luau/luarequire.rs @@ -0,0 +1,125 @@ +//! Contains definitions from `Require.h`. + +use std::os::raw::{c_char, c_int, c_void}; + +use super::lua::lua_State; + +#[repr(C)] +pub enum luarequire_NavigateResult { + Success, + Ambiguous, + NotFound, +} + +// Functions returning WriteSuccess are expected to set their size_out argument +// to the number of bytes written to the buffer. If WriteBufferTooSmall is +// returned, size_out should be set to the required buffer size. +#[repr(C)] +pub enum luarequire_WriteResult { + Success, + BufferTooSmall, + Failure, +} + +#[repr(C)] +pub struct luarequire_Configuration { + // Returns whether requires are permitted from the given chunkname. + pub is_require_allowed: + unsafe extern "C" fn(L: *mut lua_State, ctx: *mut c_void, requirer_chunkname: *const c_char) -> bool, + + // Resets the internal state to point at the requirer module. + pub reset: unsafe extern "C" fn( + L: *mut lua_State, + ctx: *mut c_void, + requirer_chunkname: *const c_char, + ) -> luarequire_NavigateResult, + + // Resets the internal state to point at an aliased module, given its exact path from a configuration + // file. This function is only called when an alias's path cannot be resolved relative to its + // configuration file. + pub jump_to_alias: unsafe extern "C" fn( + L: *mut lua_State, + ctx: *mut c_void, + path: *const c_char, + ) -> luarequire_NavigateResult, + + // Navigates through the context by making mutations to the internal state. + pub to_parent: unsafe extern "C" fn(L: *mut lua_State, ctx: *mut c_void) -> luarequire_NavigateResult, + pub to_child: unsafe extern "C" fn( + L: *mut lua_State, + ctx: *mut c_void, + name: *const c_char, + ) -> luarequire_NavigateResult, + + // Returns whether the context is currently pointing at a module. + pub is_module_present: unsafe extern "C" fn(L: *mut lua_State, ctx: *mut c_void) -> bool, + + // Provides the contents of the current module. This function is only called if is_module_present returns + // true. + pub get_contents: unsafe extern "C" fn( + L: *mut lua_State, + ctx: *mut c_void, + buffer: *mut c_char, + buffer_size: usize, + size_out: *mut usize, + ) -> luarequire_WriteResult, + + // Provides a chunkname for the current module. This will be accessible through the debug library. This + // function is only called if is_module_present returns true. + pub get_chunkname: unsafe extern "C" fn( + L: *mut lua_State, + ctx: *mut c_void, + buffer: *mut c_char, + buffer_size: usize, + size_out: *mut usize, + ) -> luarequire_WriteResult, + + // Provides a cache key representing the current module. This function is only called if + // is_module_present returns true. + pub get_cache_key: unsafe extern "C" fn( + L: *mut lua_State, + ctx: *mut c_void, + buffer: *mut c_char, + buffer_size: usize, + size_out: *mut usize, + ) -> luarequire_WriteResult, + + // Returns whether a configuration file is present in the current context. + // If not, require-by-string will call to_parent until either a configuration file is present or + // NAVIGATE_FAILURE is returned (at root). + pub is_config_present: unsafe extern "C" fn(L: *mut lua_State, ctx: *mut c_void) -> bool, + + // Provides the contents of the configuration file in the current context. + // This function is only called if is_config_present returns true. + pub get_config: unsafe extern "C" fn( + L: *mut lua_State, + ctx: *mut c_void, + buffer: *mut c_char, + buffer_size: usize, + size_out: *mut usize, + ) -> luarequire_WriteResult, + + // Executes the module and places the result on the stack. Returns the number of results placed on the + // stack. + pub load: unsafe extern "C" fn( + L: *mut lua_State, + ctx: *mut c_void, + chunkname: *const c_char, + contents: *const c_char, + ) -> c_int, +} + +// Populates function pointers in the given luarequire_Configuration. +pub type luarequire_Configuration_init = unsafe extern "C" fn(config: *mut luarequire_Configuration); + +extern "C-unwind" { + // Initializes and pushes the require closure onto the stack without registration. + pub fn lua_pushrequire( + L: *mut lua_State, + config_init: luarequire_Configuration_init, + ctx: *mut c_void, + ) -> c_int; + + // Initializes the require library and registers it globally. + pub fn luaopen_require(L: *mut lua_State, config_init: luarequire_Configuration_init, ctx: *mut c_void); +} diff --git a/mlua-sys/src/luau/mod.rs b/mlua-sys/src/luau/mod.rs index b36fee56..ea882a99 100644 --- a/mlua-sys/src/luau/mod.rs +++ b/mlua-sys/src/luau/mod.rs @@ -6,6 +6,7 @@ pub use lua::*; pub use luacode::*; pub use luacodegen::*; pub use lualib::*; +pub use luarequire::*; pub mod compat; pub mod lauxlib; @@ -13,3 +14,4 @@ pub mod lua; pub mod luacode; pub mod luacodegen; pub mod lualib; +pub mod luarequire; From 560a30ca02a4d926a2fb814d45cfcf94470110f3 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 25 Apr 2025 14:19:41 +0100 Subject: [PATCH 372/635] Support new Luau require system This commit introduces the `Require` trait that be used to change `require` behaviour. By default mlua implements behaviour same as `ReplRequirer` in the original Luau. Unfortunately binary Luau modules are no longer supported by the new system. --- .github/workflows/main.yml | 2 +- Cargo.toml | 5 +- mlua-sys/Cargo.toml | 2 +- mlua-sys/build/main_inner.rs | 4 +- mlua-sys/src/luau/luarequire.rs | 2 +- src/lib.rs | 3 +- src/luau/mod.rs | 20 +- src/luau/package.rs | 270 --------- src/luau/require.rs | 536 ++++++++++++++++++ src/prelude.rs | 3 +- src/state.rs | 28 +- src/state/extra.rs | 15 +- src/state/raw.rs | 18 +- src/state/util.rs | 2 +- src/stdlib.rs | 2 + tests/luau.rs | 81 +-- tests/luau/require.rs | 100 ++++ tests/luau/require/with_config/.luaurc | 6 + .../GlobalLuauLibraries/global_library.luau | 1 + .../ProjectLuauLibraries/library.luau | 1 + tests/luau/require/with_config/src/.luaurc | 6 + .../with_config/src/alias_requirer.luau | 1 + .../require/with_config/src/dependency.luau | 1 + .../src/directory_alias_requirer.luau | 1 + .../with_config/src/other_dependency.luau | 1 + .../src/parent_alias_requirer.luau | 1 + .../subdirectory/subdirectory_dependency.luau | 1 + .../ambiguous/directory/dependency.luau | 1 + .../ambiguous/directory/dependency/init.luau | 1 + .../ambiguous/file/dependency.lua | 1 + .../ambiguous/file/dependency.luau | 1 + .../ambiguous_directory_requirer.luau | 3 + .../ambiguous_file_requirer.luau | 3 + .../require/without_config/dependency.luau | 1 + .../luau/require/without_config/lua/init.lua | 1 + .../require/without_config/lua_dependency.lua | 1 + .../require/without_config/luau/init.luau | 1 + tests/luau/require/without_config/module.luau | 3 + .../require/without_config/nested/init.luau | 2 + .../without_config/nested/submodule.luau | 1 + .../nested_module_requirer.luau | 3 + .../without_config/validate_cache.luau | 4 + tests/module/Cargo.toml | 1 - tests/module/loader/Cargo.toml | 1 - 44 files changed, 757 insertions(+), 385 deletions(-) delete mode 100644 src/luau/package.rs create mode 100644 src/luau/require.rs create mode 100644 tests/luau/require.rs create mode 100644 tests/luau/require/with_config/.luaurc create mode 100644 tests/luau/require/with_config/GlobalLuauLibraries/global_library.luau create mode 100644 tests/luau/require/with_config/ProjectLuauLibraries/library.luau create mode 100644 tests/luau/require/with_config/src/.luaurc create mode 100644 tests/luau/require/with_config/src/alias_requirer.luau create mode 100644 tests/luau/require/with_config/src/dependency.luau create mode 100644 tests/luau/require/with_config/src/directory_alias_requirer.luau create mode 100644 tests/luau/require/with_config/src/other_dependency.luau create mode 100644 tests/luau/require/with_config/src/parent_alias_requirer.luau create mode 100644 tests/luau/require/with_config/src/subdirectory/subdirectory_dependency.luau create mode 100644 tests/luau/require/without_config/ambiguous/directory/dependency.luau create mode 100644 tests/luau/require/without_config/ambiguous/directory/dependency/init.luau create mode 100644 tests/luau/require/without_config/ambiguous/file/dependency.lua create mode 100644 tests/luau/require/without_config/ambiguous/file/dependency.luau create mode 100644 tests/luau/require/without_config/ambiguous_directory_requirer.luau create mode 100644 tests/luau/require/without_config/ambiguous_file_requirer.luau create mode 100644 tests/luau/require/without_config/dependency.luau create mode 100644 tests/luau/require/without_config/lua/init.lua create mode 100644 tests/luau/require/without_config/lua_dependency.lua create mode 100644 tests/luau/require/without_config/luau/init.luau create mode 100644 tests/luau/require/without_config/module.luau create mode 100644 tests/luau/require/without_config/nested/init.luau create mode 100644 tests/luau/require/without_config/nested/submodule.luau create mode 100644 tests/luau/require/without_config/nested_module_requirer.luau create mode 100644 tests/luau/require/without_config/validate_cache.luau diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cd12c620..44d79ddc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -194,7 +194,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] rust: [stable] - lua: [lua54, lua53, lua52, lua51, luajit, luau] + lua: [lua54, lua53, lua52, lua51, luajit] include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu diff --git a/Cargo.toml b/Cargo.toml index 84fd595a..f1203439 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ lua52 = ["ffi/lua52"] lua51 = ["ffi/lua51"] luajit = ["ffi/luajit"] luajit52 = ["luajit", "ffi/luajit52"] -luau = ["ffi/luau", "dep:libloading"] +luau = ["ffi/luau"] luau-jit = ["luau", "ffi/luau-codegen"] luau-vector4 = ["luau", "ffi/luau-vector4"] vendored = ["ffi/vendored"] @@ -61,9 +61,6 @@ rustversion = "1.0" ffi = { package = "mlua-sys", version = "0.6.6", path = "mlua-sys" } -[target.'cfg(unix)'.dependencies] -libloading = { version = "0.8", optional = true } - [dev-dependencies] trybuild = "1.0" hyper = { version = "1.2", features = ["full"] } diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index a88019b3..d52c419d 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -40,7 +40,7 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 547.0.0, < 547.1.0", optional = true } luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } -luau0-src = { git = "https://github.com/mlua-rs/luau-src-rs", rev = "f89e9f2", optional = true } +luau0-src = { git = "https://github.com/mlua-rs/luau-src-rs", rev = "37e1e34", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/mlua-sys/build/main_inner.rs b/mlua-sys/build/main_inner.rs index 05ac53b5..4f293e1c 100644 --- a/mlua-sys/build/main_inner.rs +++ b/mlua-sys/build/main_inner.rs @@ -11,8 +11,8 @@ cfg_if::cfg_if! { } fn main() { - #[cfg(all(feature = "luau", feature = "module", windows))] - compile_error!("Luau does not support `module` mode on Windows"); + #[cfg(all(feature = "luau", feature = "module"))] + compile_error!("Luau does not support `module` mode"); #[cfg(all(feature = "module", feature = "vendored"))] compile_error!("`vendored` and `module` features are mutually exclusive"); diff --git a/mlua-sys/src/luau/luarequire.rs b/mlua-sys/src/luau/luarequire.rs index d3726102..00613073 100644 --- a/mlua-sys/src/luau/luarequire.rs +++ b/mlua-sys/src/luau/luarequire.rs @@ -101,7 +101,7 @@ pub struct luarequire_Configuration { // Executes the module and places the result on the stack. Returns the number of results placed on the // stack. - pub load: unsafe extern "C" fn( + pub load: unsafe extern "C-unwind" fn( L: *mut lua_State, ctx: *mut c_void, chunkname: *const c_char, diff --git a/src/lib.rs b/src/lib.rs index 22a3b367..ea1d8f5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,7 +76,7 @@ mod conversion; mod error; mod function; mod hook; -#[cfg(feature = "luau")] +#[cfg(any(feature = "luau", doc))] mod luau; mod memory; mod multi; @@ -130,6 +130,7 @@ pub use crate::{ buffer::Buffer, chunk::{CompileConstant, Compiler}, function::CoverageInfo, + luau::{NavigateError, Require}, vector::Vector, }; diff --git a/src/luau/mod.rs b/src/luau/mod.rs index 29427ed7..20ec6c71 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -2,12 +2,14 @@ use std::ffi::CStr; use std::os::raw::c_int; use crate::error::Result; -use crate::state::Lua; +use crate::state::{ExtraData, Lua, LuaOptions}; + +pub use require::{NavigateError, Require}; // Since Luau has some missing standard functions, we re-implement them here impl Lua { - pub(crate) unsafe fn configure_luau(&self) -> Result<()> { + pub(crate) unsafe fn configure_luau(&self, mut options: LuaOptions) -> Result<()> { let globals = self.globals(); globals.raw_set("collectgarbage", self.create_c_function(lua_collectgarbage)?)?; @@ -18,11 +20,13 @@ impl Lua { globals.raw_set("_VERSION", format!("Luau {version}"))?; } - Ok(()) - } + // Enable `require` function + let requirer = (options.requirer.take()).unwrap_or_else(|| Box::new(require::TextRequirer::new())); + self.exec_raw::<()>((), |state| { + let requirer_ptr = (*ExtraData::get(state)).set_requirer(requirer); + ffi::luaopen_require(state, require::init_config, requirer_ptr as *mut _); + })?; - pub(crate) fn disable_c_modules(&self) -> Result<()> { - package::disable_dylibs(self); Ok(()) } } @@ -64,6 +68,4 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_ } } -pub(crate) use package::register_package_module; - -mod package; +mod require; diff --git a/src/luau/package.rs b/src/luau/package.rs deleted file mode 100644 index 1db11bcf..00000000 --- a/src/luau/package.rs +++ /dev/null @@ -1,270 +0,0 @@ -use std::ffi::CStr; -use std::fmt::Write; -use std::os::raw::c_int; -use std::path::{PathBuf, MAIN_SEPARATOR_STR}; -use std::string::String as StdString; -use std::{env, fs}; - -use crate::chunk::ChunkMode; -use crate::error::Result; -use crate::state::Lua; -use crate::table::Table; -use crate::traits::IntoLua; -use crate::value::Value; - -#[cfg(unix)] -use {libloading::Library, rustc_hash::FxHashMap}; - -// -// Luau package module -// - -#[cfg(unix)] -const TARGET_MLUA_LUAU_ABI_VERSION: u32 = 2; - -#[cfg(all(unix, feature = "module"))] -#[no_mangle] -#[used] -pub static MLUA_LUAU_ABI_VERSION: u32 = TARGET_MLUA_LUAU_ABI_VERSION; - -// We keep reference to the loaded dylibs in application data -#[cfg(unix)] -struct LoadedDylibs(FxHashMap); - -#[cfg(unix)] -impl std::ops::Deref for LoadedDylibs { - type Target = FxHashMap; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[cfg(unix)] -impl std::ops::DerefMut for LoadedDylibs { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -pub(crate) fn register_package_module(lua: &Lua) -> Result<()> { - // Create the package table - let package = lua.create_table()?; - - // Set `package.path` - let mut search_path = env::var("LUAU_PATH") - .or_else(|_| env::var("LUA_PATH")) - .unwrap_or_default(); - if search_path.is_empty() { - search_path = "?.luau;?.lua".to_string(); - } - package.raw_set("path", search_path)?; - - // Set `package.cpath` - #[cfg(unix)] - { - let mut search_cpath = env::var("LUAU_CPATH") - .or_else(|_| env::var("LUA_CPATH")) - .unwrap_or_default(); - if search_cpath.is_empty() { - if cfg!(any(target_os = "macos", target_os = "ios")) { - search_cpath = "?.dylib".to_string(); - } else { - search_cpath = "?.so".to_string(); - } - } - package.raw_set("cpath", search_cpath)?; - } - - // Set `package.loaded` (table with a list of loaded modules) - let loaded = if let Ok(Some(loaded)) = lua.named_registry_value::>("_LOADED") { - package.raw_set("loaded", &loaded)?; - loaded - } else { - let loaded = lua.create_table()?; - package.raw_set("loaded", &loaded)?; - lua.set_named_registry_value("_LOADED", &loaded)?; - loaded - }; - - // Set `package.loaders` - let loaders = lua.create_sequence_from([lua.create_function(lua_loader)?])?; - package.raw_set("loaders", &loaders)?; - #[cfg(unix)] - { - loaders.push(lua.create_function(dylib_loader)?)?; - lua.set_app_data(LoadedDylibs(FxHashMap::default())); - } - lua.set_named_registry_value("_LOADERS", loaders)?; - - // Register the module and `require` function in globals - let globals = lua.globals(); - globals.raw_set("package", &package)?; - loaded.raw_set("package", package)?; - globals.raw_set("require", unsafe { lua.create_c_function(lua_require)? })?; - - Ok(()) -} - -#[allow(unused_variables)] -pub(crate) fn disable_dylibs(lua: &Lua) { - // Presence of `LoadedDylibs` in app data is used as a flag - // to check whether binary modules are enabled - #[cfg(unix)] - lua.remove_app_data::(); -} - -unsafe extern "C-unwind" fn lua_require(state: *mut ffi::lua_State) -> c_int { - ffi::lua_settop(state, 1); - let name = ffi::luaL_checkstring(state, 1); - ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); // _LOADED is at index 2 - if ffi::lua_rawgetfield(state, 2, name) != ffi::LUA_TNIL { - return 1; // module is already loaded - } - ffi::lua_pop(state, 1); // remove nil - - // load the module - let err_buf = ffi::lua_newuserdata_t(state, StdString::new()); - ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADERS")); // _LOADERS is at index 3 - for i in 1.. { - if ffi::lua_rawgeti(state, -1, i) == ffi::LUA_TNIL { - // no more loaders? - if (*err_buf).is_empty() { - ffi::luaL_error(state, cstr!("module '%s' not found"), name); - } else { - let bytes = (*err_buf).as_bytes(); - let extra = ffi::lua_pushlstring(state, bytes.as_ptr() as *const _, bytes.len()); - ffi::luaL_error(state, cstr!("module '%s' not found:%s"), name, extra); - } - } - ffi::lua_pushvalue(state, 1); // name arg - ffi::lua_call(state, 1, 2); // call loader - match ffi::lua_type(state, -2) { - ffi::LUA_TFUNCTION => break, // loader found - ffi::LUA_TSTRING => { - // error message - let msg = ffi::lua_tostring(state, -2); - let msg = CStr::from_ptr(msg).to_string_lossy(); - _ = write!(&mut *err_buf, "\n\t{msg}"); - } - _ => {} - } - ffi::lua_pop(state, 2); // remove both results - } - ffi::lua_pushvalue(state, 1); // name is 1st argument to module loader - ffi::lua_rotate(state, -2, 1); // loader data <-> name - - // stack: ...; loader function; module name; loader data - ffi::lua_call(state, 2, 1); - // stack: ...; result from loader function - if ffi::lua_isnil(state, -1) != 0 { - ffi::lua_pop(state, 1); - ffi::lua_pushboolean(state, 1); // use true as result - } - ffi::lua_pushvalue(state, -1); // make copy of entrypoint result - ffi::lua_setfield(state, 2, name); /* _LOADED[name] = returned value */ - 1 -} - -/// Searches for the given `name` in the given `path`. -/// -/// `path` is a string containing a sequence of templates separated by semicolons. -fn package_searchpath(name: &str, search_path: &str, try_prefix: bool) -> Option { - let mut names = vec![name.replace('.', MAIN_SEPARATOR_STR)]; - if try_prefix && name.contains('.') { - let prefix = name.split_once('.').map(|(prefix, _)| prefix).unwrap(); - names.push(prefix.to_string()); - } - for path in search_path.split(';') { - for name in &names { - let file_path = PathBuf::from(path.replace('?', name)); - if let Ok(true) = fs::metadata(&file_path).map(|m| m.is_file()) { - return Some(file_path); - } - } - } - None -} - -// -// Module loaders -// - -/// Tries to load a lua (text) file -fn lua_loader(lua: &Lua, modname: StdString) -> Result { - let package = { - let loaded = lua.named_registry_value::
("_LOADED")?; - loaded.raw_get::
("package") - }?; - let search_path = package.get::("path").unwrap_or_default(); - - if let Some(file_path) = package_searchpath(&modname, &search_path, false) { - match fs::read(&file_path) { - Ok(buf) => { - return lua - .load(buf) - .set_name(format!("={}", file_path.display())) - .set_mode(ChunkMode::Text) - .into_function() - .map(Value::Function); - } - Err(err) => { - return format!("cannot open '{}': {err}", file_path.display()).into_lua(lua); - } - } - } - - Ok(Value::Nil) -} - -/// Tries to load a dynamic library -#[cfg(unix)] -fn dylib_loader(lua: &Lua, modname: StdString) -> Result { - let package = { - let loaded = lua.named_registry_value::
("_LOADED")?; - loaded.raw_get::
("package") - }?; - let search_cpath = package.get::("cpath").unwrap_or_default(); - - let find_symbol = |lib: &Library| unsafe { - if let Ok(entry) = lib.get::(format!("luaopen_{modname}\0").as_bytes()) { - return lua.create_c_function(*entry).map(Value::Function); - } - // Try all in one mode - if let Ok(entry) = - lib.get::(format!("luaopen_{}\0", modname.replace('.', "_")).as_bytes()) - { - return lua.create_c_function(*entry).map(Value::Function); - } - "cannot find module entrypoint".into_lua(lua) - }; - - if let Some(file_path) = package_searchpath(&modname, &search_cpath, true) { - let file_path = file_path.canonicalize()?; - // Load the library and check for symbol - unsafe { - let mut loaded_dylibs = match lua.app_data_mut::() { - Some(loaded_dylibs) => loaded_dylibs, - None => return "dynamic libraries are disabled in safe mode".into_lua(lua), - }; - // Check if it's already loaded - if let Some(lib) = loaded_dylibs.get(&file_path) { - return find_symbol(lib); - } - if let Ok(lib) = Library::new(&file_path) { - // Check version - let mod_version = lib.get::<*const u32>(b"MLUA_LUAU_ABI_VERSION"); - let mod_version = mod_version.map(|v| **v).unwrap_or_default(); - if mod_version != TARGET_MLUA_LUAU_ABI_VERSION { - let err = format!("wrong module ABI version (expected {TARGET_MLUA_LUAU_ABI_VERSION}, got {mod_version})"); - return err.into_lua(lua); - } - let symbol = find_symbol(&lib); - loaded_dylibs.insert(file_path, lib); - return symbol; - } - } - } - - Ok(Value::Nil) -} diff --git a/src/luau/require.rs b/src/luau/require.rs new file mode 100644 index 00000000..2270fa9b --- /dev/null +++ b/src/luau/require.rs @@ -0,0 +1,536 @@ +use std::cell::RefCell; +use std::collections::VecDeque; +use std::ffi::CStr; +use std::io::Result as IoResult; +use std::os::raw::{c_char, c_int, c_void}; +use std::path::{Component, Path, PathBuf}; +use std::result::Result as StdResult; +use std::{env, fmt, fs, ptr}; + +use crate::error::Result; +use crate::state::{callback_error_ext, Lua}; +use crate::value::Value; + +/// An error that can occur during navigation in the Luau `require` system. +pub enum NavigateError { + Ambiguous, + NotFound, +} + +#[cfg(feature = "luau")] +trait IntoNavigateResult { + fn into_nav_result(self) -> ffi::luarequire_NavigateResult; +} + +#[cfg(feature = "luau")] +impl IntoNavigateResult for StdResult<(), NavigateError> { + fn into_nav_result(self) -> ffi::luarequire_NavigateResult { + match self { + Ok(()) => ffi::luarequire_NavigateResult::Success, + Err(NavigateError::Ambiguous) => ffi::luarequire_NavigateResult::Ambiguous, + Err(NavigateError::NotFound) => ffi::luarequire_NavigateResult::NotFound, + } + } +} + +#[cfg(feature = "luau")] +type WriteResult = ffi::luarequire_WriteResult; + +/// A trait for handling modules loading and navigation in the Luau `require` system. +pub trait Require { + /// Returns `true` if "require" is permitted for the given chunk name. + fn is_require_allowed(&self, chunk_name: &str) -> bool; + + /// Resets the internal state to point at the requirer module. + fn reset(&self, chunk_name: &str) -> StdResult<(), NavigateError>; + + /// Resets the internal state to point at an aliased module. + /// + /// This function received an exact path from a configuration file. + /// It's only called when an alias's path cannot be resolved relative to its + /// configuration file. + fn jump_to_alias(&self, path: &str) -> StdResult<(), NavigateError>; + + // Navigate to parent directory + fn to_parent(&self) -> StdResult<(), NavigateError>; + + /// Navigate to the given child directory. + fn to_child(&self, name: &str) -> StdResult<(), NavigateError>; + + /// Returns whether the context is currently pointing at a module + fn is_module_present(&self) -> bool; + + /// Returns the contents of the current module + /// + /// This function is only called if `is_module_present` returns true. + fn contents(&self) -> IoResult>; + + /// Returns a chunk name for the current module. + /// + /// This function is only called if `is_module_present` returns true. + /// The chunk name is used to identify the module using the debug library. + fn chunk_name(&self) -> String; + + /// Provides a cache key representing the current module. + /// + /// This function is only called if `is_module_present` returns true. + fn cache_key(&self) -> Vec; + + /// Returns whether a configuration file is present in the current context. + fn is_config_present(&self) -> bool; + + /// Returns the contents of the configuration file in the current context. + /// + /// This function is only called if `is_config_present` returns true. + fn config(&self) -> IoResult>; + + /// Loads the module and returns the result (function or table). + fn load(&self, lua: &Lua, chunk_name: &str, content: &[u8]) -> Result { + lua.load(content).set_name(chunk_name).call(()) + } +} + +impl fmt::Debug for dyn Require { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "") + } +} + +/// The standard implementation of Luau `require` navigation. +#[derive(Default)] +pub(super) struct TextRequirer { + abs_path: RefCell, + rel_path: RefCell, + module_path: RefCell, +} + +impl TextRequirer { + pub(super) fn new() -> Self { + Self::default() + } + + fn normalize_chunk_name(chunk_name: &str) -> &str { + if let Some((path, line)) = chunk_name.split_once(':') { + if line.parse::().is_ok() { + return path; + } + } + chunk_name + } + + // Normalizes the path by removing unnecessary components + fn normalize_path(path: &Path) -> PathBuf { + let mut components = VecDeque::new(); + + for comp in path.components() { + match comp { + Component::Prefix(..) | Component::RootDir => { + components.push_back(comp); + } + Component::CurDir => {} + Component::ParentDir => { + if matches!(components.back(), None | Some(Component::ParentDir)) { + components.push_back(Component::ParentDir); + } else if matches!(components.back(), Some(Component::Normal(..))) { + components.pop_back(); + } + } + Component::Normal(..) => components.push_back(comp), + } + } + + if matches!(components.front(), None | Some(Component::Normal(..))) { + components.push_front(Component::CurDir); + } + + // Join the components back together + components.into_iter().collect() + } + + fn find_module_path(path: &Path) -> StdResult { + let mut found_path = None; + + let current_ext = (path.extension().and_then(|s| s.to_str())) + .map(|s| format!("{s}.")) + .unwrap_or_default(); + for ext in ["luau", "lua"] { + let candidate = path.with_extension(format!("{current_ext}{ext}")); + if candidate.is_file() { + if found_path.is_some() { + return Err(NavigateError::Ambiguous); + } + found_path = Some(candidate); + } + } + if path.is_dir() { + if found_path.is_some() { + return Err(NavigateError::Ambiguous); + } + + for component in ["init.luau", "init.lua"] { + let candidate = path.join(component); + if candidate.is_file() { + if found_path.is_some() { + return Err(NavigateError::Ambiguous); + } + found_path = Some(candidate); + } + } + + if found_path.is_none() { + found_path = Some(PathBuf::new()); + } + } + + found_path.ok_or(NavigateError::NotFound) + } +} + +impl Require for TextRequirer { + fn is_require_allowed(&self, chunk_name: &str) -> bool { + chunk_name.starts_with('@') + } + + fn reset(&self, chunk_name: &str) -> StdResult<(), NavigateError> { + if !chunk_name.starts_with('@') { + return Err(NavigateError::NotFound); + } + let chunk_name = &Self::normalize_chunk_name(chunk_name)[1..]; + let path = Self::normalize_path(chunk_name.as_ref()); + + if path.extension() == Some("rs".as_ref()) { + let cwd = match env::current_dir() { + Ok(cwd) => cwd, + Err(_) => return Err(NavigateError::NotFound), + }; + self.abs_path.replace(Self::normalize_path(&cwd.join(&path))); + self.rel_path.replace(path); + self.module_path.replace(PathBuf::new()); + + return Ok(()); + } + + if path.is_absolute() { + let module_path = Self::find_module_path(&path)?; + self.abs_path.replace(path.clone()); + self.rel_path.replace(path); + self.module_path.replace(module_path); + } else { + // Relative path + let cwd = match env::current_dir() { + Ok(cwd) => cwd, + Err(_) => return Err(NavigateError::NotFound), + }; + let abs_path = cwd.join(&path); + let module_path = Self::find_module_path(&abs_path)?; + self.abs_path.replace(Self::normalize_path(&abs_path)); + self.rel_path.replace(path); + self.module_path.replace(module_path); + } + + Ok(()) + } + + fn jump_to_alias(&self, path: &str) -> StdResult<(), NavigateError> { + let path = Self::normalize_path(path.as_ref()); + let module_path = Self::find_module_path(&path)?; + + self.abs_path.replace(path.clone()); + self.rel_path.replace(path); + self.module_path.replace(module_path); + + Ok(()) + } + + fn to_parent(&self) -> StdResult<(), NavigateError> { + let mut abs_path = self.abs_path.borrow().clone(); + if !abs_path.pop() { + return Err(NavigateError::NotFound); + } + let mut rel_parent = self.rel_path.borrow().clone(); + rel_parent.pop(); + let module_path = Self::find_module_path(&abs_path)?; + + self.abs_path.replace(abs_path); + self.rel_path.replace(Self::normalize_path(&rel_parent)); + self.module_path.replace(module_path); + + Ok(()) + } + + fn to_child(&self, name: &str) -> StdResult<(), NavigateError> { + let abs_path = self.abs_path.borrow().join(name); + let rel_path = self.rel_path.borrow().join(name); + let module_path = Self::find_module_path(&abs_path)?; + + self.abs_path.replace(abs_path); + self.rel_path.replace(rel_path); + self.module_path.replace(module_path); + + Ok(()) + } + + fn is_module_present(&self) -> bool { + self.module_path.borrow().is_file() + } + + fn contents(&self) -> IoResult> { + fs::read(&*self.module_path.borrow()) + } + + fn chunk_name(&self) -> String { + format!("@{}", self.rel_path.borrow().display()) + } + + fn cache_key(&self) -> Vec { + self.module_path.borrow().display().to_string().into_bytes() + } + + fn is_config_present(&self) -> bool { + self.abs_path.borrow().join(".luaurc").is_file() + } + + fn config(&self) -> IoResult> { + fs::read(self.abs_path.borrow().join(".luaurc")) + } +} + +#[cfg(feature = "luau")] +pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configuration) { + if config.is_null() { + return; + } + + unsafe extern "C" fn is_require_allowed( + _state: *mut ffi::lua_State, + ctx: *mut c_void, + requirer_chunkname: *const c_char, + ) -> bool { + if requirer_chunkname.is_null() { + return false; + } + + let this = &*(ctx as *const Box); + let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy(); + this.is_require_allowed(&chunk_name) + } + + unsafe extern "C" fn reset( + _state: *mut ffi::lua_State, + ctx: *mut c_void, + requirer_chunkname: *const c_char, + ) -> ffi::luarequire_NavigateResult { + let this = &*(ctx as *const Box); + let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy(); + this.reset(&chunk_name).into_nav_result() + } + + unsafe extern "C" fn jump_to_alias( + _state: *mut ffi::lua_State, + ctx: *mut c_void, + path: *const c_char, + ) -> ffi::luarequire_NavigateResult { + let this = &*(ctx as *const Box); + let path = CStr::from_ptr(path).to_string_lossy(); + this.jump_to_alias(&path).into_nav_result() + } + + unsafe extern "C" fn to_parent( + _state: *mut ffi::lua_State, + ctx: *mut c_void, + ) -> ffi::luarequire_NavigateResult { + let this = &*(ctx as *const Box); + this.to_parent().into_nav_result() + } + + unsafe extern "C" fn to_child( + _state: *mut ffi::lua_State, + ctx: *mut c_void, + name: *const c_char, + ) -> ffi::luarequire_NavigateResult { + let this = &*(ctx as *const Box); + let name = CStr::from_ptr(name).to_string_lossy(); + this.to_child(&name).into_nav_result() + } + + unsafe extern "C" fn is_module_present(_state: *mut ffi::lua_State, ctx: *mut c_void) -> bool { + let this = &*(ctx as *const Box); + this.is_module_present() + } + + unsafe extern "C" fn get_contents( + state: *mut ffi::lua_State, + ctx: *mut c_void, + buffer: *mut c_char, + buffer_size: usize, + size_out: *mut usize, + ) -> WriteResult { + let this = &*(ctx as *const Box); + write_to_buffer(state, buffer, buffer_size, size_out, || this.contents()) + } + + unsafe extern "C" fn get_chunkname( + state: *mut ffi::lua_State, + ctx: *mut c_void, + buffer: *mut c_char, + buffer_size: usize, + size_out: *mut usize, + ) -> WriteResult { + let this = &*(ctx as *const Box); + write_to_buffer(state, buffer, buffer_size, size_out, || { + Ok(this.chunk_name().into_bytes()) + }) + } + + unsafe extern "C" fn get_cache_key( + state: *mut ffi::lua_State, + ctx: *mut c_void, + buffer: *mut c_char, + buffer_size: usize, + size_out: *mut usize, + ) -> WriteResult { + let this = &*(ctx as *const Box); + write_to_buffer(state, buffer, buffer_size, size_out, || Ok(this.cache_key())) + } + + unsafe extern "C" fn is_config_present(_state: *mut ffi::lua_State, ctx: *mut c_void) -> bool { + let this = &*(ctx as *const Box); + this.is_config_present() + } + + unsafe extern "C" fn get_config( + state: *mut ffi::lua_State, + ctx: *mut c_void, + buffer: *mut c_char, + buffer_size: usize, + size_out: *mut usize, + ) -> WriteResult { + let this = &*(ctx as *const Box); + write_to_buffer(state, buffer, buffer_size, size_out, || this.config()) + } + + unsafe extern "C-unwind" fn load( + state: *mut ffi::lua_State, + ctx: *mut c_void, + chunk_name: *const c_char, + contents: *const c_char, + ) -> c_int { + let this = &*(ctx as *const Box); + let chunk_name = CStr::from_ptr(chunk_name).to_string_lossy(); + let contents = CStr::from_ptr(contents).to_bytes(); + let lua = Lua::get_or_init_from_ptr(state); + callback_error_ext(state, ptr::null_mut(), false, move |_extra, _| { + match this.load(lua, &chunk_name, contents)? { + Value::Nil => lua.lock().push(true)?, + value => lua.lock().push(value)?, + }; + Ok(1) + }) + } + + (*config).is_require_allowed = is_require_allowed; + (*config).reset = reset; + (*config).jump_to_alias = jump_to_alias; + (*config).to_parent = to_parent; + (*config).to_child = to_child; + (*config).is_module_present = is_module_present; + (*config).get_contents = get_contents; + (*config).get_chunkname = get_chunkname; + (*config).get_cache_key = get_cache_key; + (*config).is_config_present = is_config_present; + (*config).get_config = get_config; + (*config).load = load; +} + +/// Helper function to write data to a buffer +#[cfg(feature = "luau")] +unsafe fn write_to_buffer( + state: *mut ffi::lua_State, + buffer: *mut c_char, + buffer_size: usize, + size_out: *mut usize, + data_fetcher: impl Fn() -> IoResult>, +) -> WriteResult { + struct DataCache(Vec); + + // The initial buffer size can be too small, to avoid making a second data fetch call, + // we cache the content in the first call, and then re-use it. + + let lua = Lua::get_or_init_from_ptr(state); + if let Some(data_cache) = lua.app_data_ref::() { + let data_len = data_cache.0.len(); + mlua_assert!(data_len <= buffer_size, "buffer is too small"); + *size_out = data_len; + ptr::copy_nonoverlapping(data_cache.0.as_ptr(), buffer as *mut _, data_len); + drop(data_cache); + lua.remove_app_data::(); + return WriteResult::Success; + } + + match data_fetcher() { + Ok(data) => { + let data_len = data.len(); + *size_out = data_len; + if data_len > buffer_size { + // Cache the data for the next call to avoid getting the contents again + lua.set_app_data(DataCache(data)); + *size_out = data_len; + return WriteResult::BufferTooSmall; + } + ptr::copy_nonoverlapping(data.as_ptr(), buffer as *mut _, data_len); + *size_out = data_len; + WriteResult::Success + } + Err(_) => WriteResult::Failure, + } +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use super::TextRequirer; + + #[test] + fn test_path_normalize() { + for (input, expected) in [ + // Basic formatting checks + ("", "./"), + (".", "./"), + ("a/relative/path", "./a/relative/path"), + // Paths containing extraneous '.' and '/' symbols + ("./remove/extraneous/symbols/", "./remove/extraneous/symbols"), + ("./remove/extraneous//symbols", "./remove/extraneous/symbols"), + ("./remove/extraneous/symbols/.", "./remove/extraneous/symbols"), + ("./remove/extraneous/./symbols", "./remove/extraneous/symbols"), + ("../remove/extraneous/symbols/", "../remove/extraneous/symbols"), + ("../remove/extraneous//symbols", "../remove/extraneous/symbols"), + ("../remove/extraneous/symbols/.", "../remove/extraneous/symbols"), + ("../remove/extraneous/./symbols", "../remove/extraneous/symbols"), + ("/remove/extraneous/symbols/", "/remove/extraneous/symbols"), + ("/remove/extraneous//symbols", "/remove/extraneous/symbols"), + ("/remove/extraneous/symbols/.", "/remove/extraneous/symbols"), + ("/remove/extraneous/./symbols", "/remove/extraneous/symbols"), + // Paths containing '..' + ("./remove/me/..", "./remove"), + ("./remove/me/../", "./remove"), + ("../remove/me/..", "../remove"), + ("../remove/me/../", "../remove"), + ("/remove/me/..", "/remove"), + ("/remove/me/../", "/remove"), + ("./..", "../"), + ("./../", "../"), + ("../..", "../../"), + ("../../", "../../"), + // '..' disappears if path is absolute and component is non-erasable + ("/../", "/"), + ] { + let path = TextRequirer::normalize_path(input.as_ref()); + assert_eq!( + &path, + expected.as_ref() as &Path, + "wrong normalization for {input}" + ); + } + } +} diff --git a/src/prelude.rs b/src/prelude.rs index fc967237..aaa94f56 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -23,7 +23,8 @@ pub use crate::HookTriggers as LuaHookTriggers; #[cfg(feature = "luau")] #[doc(no_inline)] pub use crate::{ - CompileConstant as LuaCompileConstant, CoverageInfo as LuaCoverageInfo, Vector as LuaVector, + CompileConstant as LuaCompileConstant, CoverageInfo as LuaCoverageInfo, + NavigateError as LuaNavigateError, Require as LuaRequire, Vector as LuaVector, }; #[cfg(feature = "async")] diff --git a/src/state.rs b/src/state.rs index b8e472b3..5e20bb60 100644 --- a/src/state.rs +++ b/src/state.rs @@ -46,7 +46,8 @@ use serde::Serialize; pub(crate) use extra::ExtraData; pub use raw::RawLua; -use util::{callback_error_ext, StateGuard}; +pub(crate) use util::callback_error_ext; +use util::StateGuard; /// Top level Lua struct which represents an instance of Lua VM. pub struct Lua { @@ -81,7 +82,7 @@ pub enum GCMode { } /// Controls Lua interpreter behavior such as Rust panics handling. -#[derive(Clone, Debug)] +#[derive(Debug)] #[non_exhaustive] pub struct LuaOptions { /// Catch Rust panics when using [`pcall`]/[`xpcall`]. @@ -106,6 +107,11 @@ pub struct LuaOptions { #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] pub thread_pool_size: usize, + + /// A custom [`crate::Require`] trait object to load Luau modules. + #[cfg(feature = "luau")] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub requirer: Option>, } impl Default for LuaOptions { @@ -121,6 +127,8 @@ impl LuaOptions { catch_rust_panics: true, #[cfg(feature = "async")] thread_pool_size: 0, + #[cfg(feature = "luau")] + requirer: None, } } @@ -143,6 +151,17 @@ impl LuaOptions { self.thread_pool_size = size; self } + + /// Sets a custom [`crate::Require`] trait object to load Luau modules. + /// + /// By default, the standard Luau `ReplRequirer` implementation is used. + #[cfg(feature = "luau")] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + #[must_use] + pub fn with_requirer(mut self, requirer: R) -> Self { + self.requirer = Some(Box::new(requirer)); + self + } } impl Drop for Lua { @@ -224,6 +243,7 @@ impl Lua { let lua = unsafe { Self::inner_new(libs, options) }; + #[cfg(not(feature = "luau"))] if libs.contains(StdLib::PACKAGE) { mlua_expect!(lua.disable_c_modules(), "Error disabling C modules"); } @@ -263,12 +283,12 @@ impl Lua { /// Creates a new Lua state with required `libs` and `options` unsafe fn inner_new(libs: StdLib, options: LuaOptions) -> Lua { let lua = Lua { - raw: RawLua::new(libs, options), + raw: RawLua::new(libs, &options), collect_garbage: true, }; #[cfg(feature = "luau")] - mlua_expect!(lua.configure_luau(), "Error configuring Luau"); + mlua_expect!(lua.configure_luau(options), "Error configuring Luau"); lua } diff --git a/src/state/extra.rs b/src/state/extra.rs index 7567669e..af911dd4 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -93,6 +93,8 @@ pub(crate) struct ExtraData { pub(super) compiler: Option, #[cfg(feature = "luau-jit")] pub(super) enable_jit: bool, + #[cfg(feature = "luau")] + pub(super) requirer: Option>, } impl Drop for ExtraData { @@ -194,6 +196,8 @@ impl ExtraData { enable_jit: true, #[cfg(feature = "luau")] running_gc: false, + #[cfg(feature = "luau")] + requirer: None, })); // Store it in the registry @@ -210,7 +214,7 @@ impl ExtraData { self.weak.write(WeakLua(XRc::downgrade(raw))); } - pub(super) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { + pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { #[cfg(feature = "luau")] if cfg!(not(feature = "module")) { // In the main app we can use `lua_callbacks` to access ExtraData @@ -257,4 +261,13 @@ impl ExtraData { pub(super) unsafe fn weak(&self) -> &WeakLua { self.weak.assume_init_ref() } + + #[cfg(feature = "luau")] + pub(crate) fn set_requirer( + &mut self, + requirer: Box, + ) -> *mut Box { + self.requirer.replace(requirer); + self.requirer.as_mut().unwrap() + } } diff --git a/src/state/raw.rs b/src/state/raw.rs index c8f3adda..0b700ca1 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -121,7 +121,7 @@ impl RawLua { unsafe { (*self.extra.get()).ref_thread } } - pub(super) unsafe fn new(libs: StdLib, options: LuaOptions) -> XRc> { + pub(super) unsafe fn new(libs: StdLib, options: &LuaOptions) -> XRc> { let mem_state: *mut MemoryState = Box::into_raw(Box::default()); let mut state = ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void); // If state is null then switch to Lua internal allocator @@ -293,10 +293,15 @@ impl RawLua { let res = load_std_libs(self.main_state(), libs); // If `package` library loaded into a safe lua state then disable C modules - let curr_libs = (*self.extra.get()).libs; - if is_safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) { - mlua_expect!(self.lua().disable_c_modules(), "Error during disabling C modules"); + #[cfg(not(feature = "luau"))] + if is_safe { + let curr_libs = (*self.extra.get()).libs; + if (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) { + mlua_expect!(self.lua().disable_c_modules(), "Error during disabling C modules"); + } } + #[cfg(feature = "luau")] + let _ = is_safe; unsafe { (*self.extra.get()).libs |= libs }; res @@ -1478,11 +1483,6 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> if libs.contains(StdLib::PACKAGE) { requiref(state, ffi::LUA_LOADLIBNAME, ffi::luaopen_package, 1)?; } - #[cfg(feature = "luau")] - if libs.contains(StdLib::PACKAGE) { - let lua = (*ExtraData::get(state)).lua(); - crate::luau::register_package_module(lua)?; - } #[cfg(feature = "luajit")] if libs.contains(StdLib::JIT) { diff --git a/src/state/util.rs b/src/state/util.rs index ea482e06..ba9a7d49 100644 --- a/src/state/util.rs +++ b/src/state/util.rs @@ -24,7 +24,7 @@ impl Drop for StateGuard<'_> { // An optimized version of `callback_error` that does not allocate `WrappedFailure` userdata // and instead reuses unused values from previous calls (or allocates new). -pub(super) unsafe fn callback_error_ext( +pub(crate) unsafe fn callback_error_ext( state: *mut ffi::lua_State, mut extra: *mut ExtraData, wrap_error: bool, diff --git a/src/stdlib.rs b/src/stdlib.rs index 787b2fcb..c6a26af0 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -41,6 +41,8 @@ impl StdLib { pub const MATH: StdLib = StdLib(1 << 7); /// [`package`](https://www.lua.org/manual/5.4/manual.html#6.3) library + #[cfg(not(feature = "luau"))] + #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub const PACKAGE: StdLib = StdLib(1 << 8); /// [`buffer`](https://luau.org/library#buffer-library) library diff --git a/tests/luau.rs b/tests/luau.rs index 590125da..52a42dae 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -2,7 +2,6 @@ use std::cell::Cell; use std::fmt::Debug; -use std::fs; use std::os::raw::c_void; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering}; @@ -17,83 +16,6 @@ fn test_version() -> Result<()> { Ok(()) } -#[test] -fn test_require() -> Result<()> { - // Ensure that require() is not available if package module is not loaded - let mut lua = Lua::new_with(StdLib::NONE, LuaOptions::default())?; - assert!(lua.globals().get::>("require")?.is_none()); - assert!(lua.globals().get::>("package")?.is_none()); - - if cfg!(target_arch = "wasm32") { - // TODO: figure out why emscripten fails on file operations - // Also see https://github.com/rust-lang/rust/issues/119250 - return Ok(()); - } - - lua = Lua::new(); - - // Check that require() can load stdlib modules (including `package`) - lua.load( - r#" - local math = require("math") - assert(math == _G.math, "math module does not match _G.math") - local package = require("package") - assert(package == _G.package, "package module does not match _G.package") - "#, - ) - .exec()?; - - let temp_dir = tempfile::tempdir().unwrap(); - fs::write( - temp_dir.path().join("module.luau"), - r#" - counter = (counter or 0) + 1 - return { - counter = counter, - error = function() error("test") end, - } - "#, - )?; - - lua.globals() - .get::
("package")? - .set("path", temp_dir.path().join("?.luau").to_string_lossy())?; - - lua.load( - r#" - local module = require("module") - assert(module.counter == 1) - module = require("module") - assert(module.counter == 1) - - local ok, err = pcall(module.error) - assert(not ok and string.find(err, "module.luau") ~= nil) - "#, - ) - .exec()?; - - // Require non-existent module - match lua.load("require('non-existent')").exec() { - Err(Error::RuntimeError(e)) if e.contains("module 'non-existent' not found") => {} - r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"), - } - - // Require binary module in safe mode - lua.globals() - .get::
("package")? - .set("cpath", temp_dir.path().join("?.so").to_string_lossy())?; - fs::write(temp_dir.path().join("dylib.so"), "")?; - match lua.load("require('dylib')").exec() { - Err(Error::RuntimeError(e)) if cfg!(unix) && e.contains("module 'dylib' not found") => { - assert!(e.contains("dynamic libraries are disabled in safe mode")) - } - Err(Error::RuntimeError(e)) if e.contains("module 'dylib' not found") => {} - r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"), - } - - Ok(()) -} - #[cfg(not(feature = "luau-vector4"))] #[test] fn test_vectors() -> Result<()> { @@ -492,3 +414,6 @@ fn test_thread_events() -> Result<()> { Ok(()) } + +#[path = "luau/require.rs"] +mod require; diff --git a/tests/luau/require.rs b/tests/luau/require.rs new file mode 100644 index 00000000..ad077352 --- /dev/null +++ b/tests/luau/require.rs @@ -0,0 +1,100 @@ +use mlua::{IntoLua, Lua, Result, Value}; + +fn run_require(lua: &Lua, path: &str) -> Result { + lua.load(r#"return require(...)"#).call(path) +} + +#[track_caller] +fn get_str(value: &Value, key: impl IntoLua) -> String { + value.as_table().unwrap().get::(key).unwrap() +} + +#[test] +fn test_require_errors() { + let lua = Lua::new(); + + // RequireAbsolutePath + let res = run_require(&lua, "/an/absolute/path"); + assert!(res.is_err()); + assert!( + (res.unwrap_err().to_string()).contains("require path must start with a valid prefix: ./, ../, or @") + ); + + // RequireUnprefixedPath + let res = run_require(&lua, "an/unprefixed/path"); + assert!(res.is_err()); + assert!( + (res.unwrap_err().to_string()).contains("require path must start with a valid prefix: ./, ../, or @") + ); +} + +#[test] +fn test_require_without_config() { + let lua = Lua::new(); + + // RequireSimpleRelativePath + let res = run_require(&lua, "./require/without_config/dependency").unwrap(); + assert_eq!("result from dependency", get_str(&res, 1)); + + // RequireRelativeToRequiringFile + let res = run_require(&lua, "./require/without_config/module").unwrap(); + assert_eq!("result from dependency", get_str(&res, 1)); + assert_eq!("required into module", get_str(&res, 2)); + + // RequireLua + let res = run_require(&lua, "./require/without_config/lua_dependency").unwrap(); + assert_eq!("result from lua_dependency", get_str(&res, 1)); + + // RequireInitLuau + let res = run_require(&lua, "./require/without_config/luau").unwrap(); + assert_eq!("result from init.luau", get_str(&res, 1)); + + // RequireInitLua + let res = run_require(&lua, "./require/without_config/lua").unwrap(); + assert_eq!("result from init.lua", get_str(&res, 1)); + + // RequireSubmoduleUsingSelf + let res = run_require(&lua, "./require/without_config/nested_module_requirer").unwrap(); + assert_eq!("result from submodule", get_str(&res, 1)); + + // RequireWithFileAmbiguity + let res = run_require(&lua, "./require/without_config/ambiguous_file_requirer"); + assert!(res.is_err()); + assert!((res.unwrap_err().to_string()).contains("require path could not be resolved to a unique file")); + + // RequireWithDirectoryAmbiguity + let res = run_require(&lua, "./require/without_config/ambiguous_directory_requirer"); + assert!(res.is_err()); + assert!((res.unwrap_err().to_string()).contains("require path could not be resolved to a unique file")); + + // CheckCachedResult + let res = run_require(&lua, "./require/without_config/validate_cache").unwrap(); + assert!(res.is_table()); +} + +#[test] +fn test_require_with_config() { + let lua = Lua::new(); + + // RequirePathWithAlias + let res = run_require(&lua, "./require/with_config/src/alias_requirer").unwrap(); + assert_eq!("result from dependency", get_str(&res, 1)); + + // RequirePathWithParentAlias + let res = run_require(&lua, "./require/with_config/src/parent_alias_requirer").unwrap(); + assert_eq!("result from other_dependency", get_str(&res, 1)); + + // RequirePathWithAliasPointingToDirectory + let res = run_require(&lua, "./require/with_config/src/directory_alias_requirer").unwrap(); + assert_eq!("result from subdirectory_dependency", get_str(&res, 1)); + + // RequireAliasThatDoesNotExist + let res = run_require(&lua, "@this.alias.does.not.exist"); + assert!(res.is_err()); + assert!((res.unwrap_err().to_string()).contains("@this.alias.does.not.exist is not a valid alias")); + + // IllegalAlias + let res = run_require(&lua, "@"); + assert!(res.is_err()); + assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias")); +} diff --git a/tests/luau/require/with_config/.luaurc b/tests/luau/require/with_config/.luaurc new file mode 100644 index 00000000..2b64ad06 --- /dev/null +++ b/tests/luau/require/with_config/.luaurc @@ -0,0 +1,6 @@ +{ + "aliases": { + "dep": "./this_should_be_overwritten_by_child_luaurc", + "otherdep": "./src/other_dependency" + } +} diff --git a/tests/luau/require/with_config/GlobalLuauLibraries/global_library.luau b/tests/luau/require/with_config/GlobalLuauLibraries/global_library.luau new file mode 100644 index 00000000..0508e0bd --- /dev/null +++ b/tests/luau/require/with_config/GlobalLuauLibraries/global_library.luau @@ -0,0 +1 @@ +return {"result from global_library"} diff --git a/tests/luau/require/with_config/ProjectLuauLibraries/library.luau b/tests/luau/require/with_config/ProjectLuauLibraries/library.luau new file mode 100644 index 00000000..9470401b --- /dev/null +++ b/tests/luau/require/with_config/ProjectLuauLibraries/library.luau @@ -0,0 +1 @@ +return {"result from library"} diff --git a/tests/luau/require/with_config/src/.luaurc b/tests/luau/require/with_config/src/.luaurc new file mode 100644 index 00000000..27263339 --- /dev/null +++ b/tests/luau/require/with_config/src/.luaurc @@ -0,0 +1,6 @@ +{ + "aliases": { + "dep": "./dependency", + "subdir": "./subdirectory" + } +} diff --git a/tests/luau/require/with_config/src/alias_requirer.luau b/tests/luau/require/with_config/src/alias_requirer.luau new file mode 100644 index 00000000..4375a783 --- /dev/null +++ b/tests/luau/require/with_config/src/alias_requirer.luau @@ -0,0 +1 @@ +return require("@dep") diff --git a/tests/luau/require/with_config/src/dependency.luau b/tests/luau/require/with_config/src/dependency.luau new file mode 100644 index 00000000..07466f42 --- /dev/null +++ b/tests/luau/require/with_config/src/dependency.luau @@ -0,0 +1 @@ +return {"result from dependency"} diff --git a/tests/luau/require/with_config/src/directory_alias_requirer.luau b/tests/luau/require/with_config/src/directory_alias_requirer.luau new file mode 100644 index 00000000..3b19d4ff --- /dev/null +++ b/tests/luau/require/with_config/src/directory_alias_requirer.luau @@ -0,0 +1 @@ +return(require("@subdir/subdirectory_dependency")) diff --git a/tests/luau/require/with_config/src/other_dependency.luau b/tests/luau/require/with_config/src/other_dependency.luau new file mode 100644 index 00000000..8c582dc2 --- /dev/null +++ b/tests/luau/require/with_config/src/other_dependency.luau @@ -0,0 +1 @@ +return {"result from other_dependency"} diff --git a/tests/luau/require/with_config/src/parent_alias_requirer.luau b/tests/luau/require/with_config/src/parent_alias_requirer.luau new file mode 100644 index 00000000..a8e8de09 --- /dev/null +++ b/tests/luau/require/with_config/src/parent_alias_requirer.luau @@ -0,0 +1 @@ +return require("@otherdep") diff --git a/tests/luau/require/with_config/src/subdirectory/subdirectory_dependency.luau b/tests/luau/require/with_config/src/subdirectory/subdirectory_dependency.luau new file mode 100644 index 00000000..8bbd0beb --- /dev/null +++ b/tests/luau/require/with_config/src/subdirectory/subdirectory_dependency.luau @@ -0,0 +1 @@ +return {"result from subdirectory_dependency"} diff --git a/tests/luau/require/without_config/ambiguous/directory/dependency.luau b/tests/luau/require/without_config/ambiguous/directory/dependency.luau new file mode 100644 index 00000000..07466f42 --- /dev/null +++ b/tests/luau/require/without_config/ambiguous/directory/dependency.luau @@ -0,0 +1 @@ +return {"result from dependency"} diff --git a/tests/luau/require/without_config/ambiguous/directory/dependency/init.luau b/tests/luau/require/without_config/ambiguous/directory/dependency/init.luau new file mode 100644 index 00000000..07466f42 --- /dev/null +++ b/tests/luau/require/without_config/ambiguous/directory/dependency/init.luau @@ -0,0 +1 @@ +return {"result from dependency"} diff --git a/tests/luau/require/without_config/ambiguous/file/dependency.lua b/tests/luau/require/without_config/ambiguous/file/dependency.lua new file mode 100644 index 00000000..07466f42 --- /dev/null +++ b/tests/luau/require/without_config/ambiguous/file/dependency.lua @@ -0,0 +1 @@ +return {"result from dependency"} diff --git a/tests/luau/require/without_config/ambiguous/file/dependency.luau b/tests/luau/require/without_config/ambiguous/file/dependency.luau new file mode 100644 index 00000000..07466f42 --- /dev/null +++ b/tests/luau/require/without_config/ambiguous/file/dependency.luau @@ -0,0 +1 @@ +return {"result from dependency"} diff --git a/tests/luau/require/without_config/ambiguous_directory_requirer.luau b/tests/luau/require/without_config/ambiguous_directory_requirer.luau new file mode 100644 index 00000000..e46be806 --- /dev/null +++ b/tests/luau/require/without_config/ambiguous_directory_requirer.luau @@ -0,0 +1,3 @@ +local result = require("./ambiguous/directory/dependency") +result[#result+1] = "required into module" +return result diff --git a/tests/luau/require/without_config/ambiguous_file_requirer.luau b/tests/luau/require/without_config/ambiguous_file_requirer.luau new file mode 100644 index 00000000..8e3a576d --- /dev/null +++ b/tests/luau/require/without_config/ambiguous_file_requirer.luau @@ -0,0 +1,3 @@ +local result = require("./ambiguous/file/dependency") +result[#result+1] = "required into module" +return result diff --git a/tests/luau/require/without_config/dependency.luau b/tests/luau/require/without_config/dependency.luau new file mode 100644 index 00000000..07466f42 --- /dev/null +++ b/tests/luau/require/without_config/dependency.luau @@ -0,0 +1 @@ +return {"result from dependency"} diff --git a/tests/luau/require/without_config/lua/init.lua b/tests/luau/require/without_config/lua/init.lua new file mode 100644 index 00000000..7c28b735 --- /dev/null +++ b/tests/luau/require/without_config/lua/init.lua @@ -0,0 +1 @@ +return {"result from init.lua"} diff --git a/tests/luau/require/without_config/lua_dependency.lua b/tests/luau/require/without_config/lua_dependency.lua new file mode 100644 index 00000000..aec2d82b --- /dev/null +++ b/tests/luau/require/without_config/lua_dependency.lua @@ -0,0 +1 @@ +return {"result from lua_dependency"} diff --git a/tests/luau/require/without_config/luau/init.luau b/tests/luau/require/without_config/luau/init.luau new file mode 100644 index 00000000..72463463 --- /dev/null +++ b/tests/luau/require/without_config/luau/init.luau @@ -0,0 +1 @@ +return {"result from init.luau"} diff --git a/tests/luau/require/without_config/module.luau b/tests/luau/require/without_config/module.luau new file mode 100644 index 00000000..1d1393ff --- /dev/null +++ b/tests/luau/require/without_config/module.luau @@ -0,0 +1,3 @@ +local result = require("./dependency") +result[#result+1] = "required into module" +return result diff --git a/tests/luau/require/without_config/nested/init.luau b/tests/luau/require/without_config/nested/init.luau new file mode 100644 index 00000000..75b9617d --- /dev/null +++ b/tests/luau/require/without_config/nested/init.luau @@ -0,0 +1,2 @@ +local result = require("@self/submodule") +return result diff --git a/tests/luau/require/without_config/nested/submodule.luau b/tests/luau/require/without_config/nested/submodule.luau new file mode 100644 index 00000000..9221587e --- /dev/null +++ b/tests/luau/require/without_config/nested/submodule.luau @@ -0,0 +1 @@ +return {"result from submodule"} diff --git a/tests/luau/require/without_config/nested_module_requirer.luau b/tests/luau/require/without_config/nested_module_requirer.luau new file mode 100644 index 00000000..fc8d5e79 --- /dev/null +++ b/tests/luau/require/without_config/nested_module_requirer.luau @@ -0,0 +1,3 @@ +local result = require("./nested") +result[#result+1] = "required into module" +return result diff --git a/tests/luau/require/without_config/validate_cache.luau b/tests/luau/require/without_config/validate_cache.luau new file mode 100644 index 00000000..dad139b3 --- /dev/null +++ b/tests/luau/require/without_config/validate_cache.luau @@ -0,0 +1,4 @@ +local result1 = require("./dependency") +local result2 = require("./dependency") +assert(result1 == result2, "expect the same result when requiring the same module twice") +return {} \ No newline at end of file diff --git a/tests/module/Cargo.toml b/tests/module/Cargo.toml index f107ad73..c2e0da8d 100644 --- a/tests/module/Cargo.toml +++ b/tests/module/Cargo.toml @@ -18,7 +18,6 @@ lua53 = ["mlua/lua53"] lua52 = ["mlua/lua52"] lua51 = ["mlua/lua51"] luajit = ["mlua/luajit"] -luau = ["mlua/luau"] [dependencies] mlua = { path = "../..", features = ["module"] } diff --git a/tests/module/loader/Cargo.toml b/tests/module/loader/Cargo.toml index 64b196ff..b51f002c 100644 --- a/tests/module/loader/Cargo.toml +++ b/tests/module/loader/Cargo.toml @@ -10,7 +10,6 @@ lua53 = ["mlua/lua53"] lua52 = ["mlua/lua52"] lua51 = ["mlua/lua51"] luajit = ["mlua/luajit"] -luau = ["mlua/luau"] vendored = ["mlua/vendored"] [dependencies] From 30d5e08a8a00b7df70609062c5a6c838ad6ec8c3 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 25 Apr 2025 15:51:33 +0100 Subject: [PATCH 373/635] Add `MaybeSend` to `Require` trait --- src/luau/require.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/luau/require.rs b/src/luau/require.rs index 2270fa9b..254edea3 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -9,6 +9,7 @@ use std::{env, fmt, fs, ptr}; use crate::error::Result; use crate::state::{callback_error_ext, Lua}; +use crate::types::MaybeSend; use crate::value::Value; /// An error that can occur during navigation in the Luau `require` system. @@ -37,7 +38,7 @@ impl IntoNavigateResult for StdResult<(), NavigateError> { type WriteResult = ffi::luarequire_WriteResult; /// A trait for handling modules loading and navigation in the Luau `require` system. -pub trait Require { +pub trait Require: MaybeSend { /// Returns `true` if "require" is permitted for the given chunk name. fn is_require_allowed(&self, chunk_name: &str) -> bool; From 21fc92445782b7a9e18a8c27f72454f1d9d155f9 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 25 Apr 2025 18:43:52 +0100 Subject: [PATCH 374/635] Add `Lua::create_require_function` method --- src/luau/mod.rs | 32 ++++++++++++++++++++++++-------- src/state.rs | 22 ++-------------------- src/state/extra.rs | 15 +-------------- 3 files changed, 27 insertions(+), 42 deletions(-) diff --git a/src/luau/mod.rs b/src/luau/mod.rs index 20ec6c71..32ace410 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -2,14 +2,33 @@ use std::ffi::CStr; use std::os::raw::c_int; use crate::error::Result; -use crate::state::{ExtraData, Lua, LuaOptions}; +use crate::function::Function; +use crate::state::Lua; pub use require::{NavigateError, Require}; // Since Luau has some missing standard functions, we re-implement them here impl Lua { - pub(crate) unsafe fn configure_luau(&self, mut options: LuaOptions) -> Result<()> { + /// Create a custom Luau `require` function using provided [`Require`] implementation to find + /// and load modules. + /// + /// The provided object is stored in the Lua registry and will not be garbage collected + /// until the Lua state is closed. + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn create_require_function(&self, require: R) -> Result { + unsafe { + self.exec_raw((), move |state| { + let requirer_ptr = ffi::lua_newuserdata_t::>(state, Box::new(require)); + // Keep the require object in the registry to prevent it from being garbage collected + ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, requirer_ptr as *const _); + ffi::lua_pushrequire(state, require::init_config, requirer_ptr as *mut _); + }) + } + } + + pub(crate) unsafe fn configure_luau(&self) -> Result<()> { let globals = self.globals(); globals.raw_set("collectgarbage", self.create_c_function(lua_collectgarbage)?)?; @@ -20,12 +39,9 @@ impl Lua { globals.raw_set("_VERSION", format!("Luau {version}"))?; } - // Enable `require` function - let requirer = (options.requirer.take()).unwrap_or_else(|| Box::new(require::TextRequirer::new())); - self.exec_raw::<()>((), |state| { - let requirer_ptr = (*ExtraData::get(state)).set_requirer(requirer); - ffi::luaopen_require(state, require::init_config, requirer_ptr as *mut _); - })?; + // Enable default `require` implementation + let require = self.create_require_function(require::TextRequirer::new())?; + self.globals().raw_set("require", require)?; Ok(()) } diff --git a/src/state.rs b/src/state.rs index 5e20bb60..ffed0187 100644 --- a/src/state.rs +++ b/src/state.rs @@ -82,7 +82,7 @@ pub enum GCMode { } /// Controls Lua interpreter behavior such as Rust panics handling. -#[derive(Debug)] +#[derive(Clone, Debug)] #[non_exhaustive] pub struct LuaOptions { /// Catch Rust panics when using [`pcall`]/[`xpcall`]. @@ -107,11 +107,6 @@ pub struct LuaOptions { #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] pub thread_pool_size: usize, - - /// A custom [`crate::Require`] trait object to load Luau modules. - #[cfg(feature = "luau")] - #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - pub requirer: Option>, } impl Default for LuaOptions { @@ -127,8 +122,6 @@ impl LuaOptions { catch_rust_panics: true, #[cfg(feature = "async")] thread_pool_size: 0, - #[cfg(feature = "luau")] - requirer: None, } } @@ -151,17 +144,6 @@ impl LuaOptions { self.thread_pool_size = size; self } - - /// Sets a custom [`crate::Require`] trait object to load Luau modules. - /// - /// By default, the standard Luau `ReplRequirer` implementation is used. - #[cfg(feature = "luau")] - #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - #[must_use] - pub fn with_requirer(mut self, requirer: R) -> Self { - self.requirer = Some(Box::new(requirer)); - self - } } impl Drop for Lua { @@ -288,7 +270,7 @@ impl Lua { }; #[cfg(feature = "luau")] - mlua_expect!(lua.configure_luau(options), "Error configuring Luau"); + mlua_expect!(lua.configure_luau(), "Error configuring Luau"); lua } diff --git a/src/state/extra.rs b/src/state/extra.rs index af911dd4..7567669e 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -93,8 +93,6 @@ pub(crate) struct ExtraData { pub(super) compiler: Option, #[cfg(feature = "luau-jit")] pub(super) enable_jit: bool, - #[cfg(feature = "luau")] - pub(super) requirer: Option>, } impl Drop for ExtraData { @@ -196,8 +194,6 @@ impl ExtraData { enable_jit: true, #[cfg(feature = "luau")] running_gc: false, - #[cfg(feature = "luau")] - requirer: None, })); // Store it in the registry @@ -214,7 +210,7 @@ impl ExtraData { self.weak.write(WeakLua(XRc::downgrade(raw))); } - pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { + pub(super) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { #[cfg(feature = "luau")] if cfg!(not(feature = "module")) { // In the main app we can use `lua_callbacks` to access ExtraData @@ -261,13 +257,4 @@ impl ExtraData { pub(super) unsafe fn weak(&self) -> &WeakLua { self.weak.assume_init_ref() } - - #[cfg(feature = "luau")] - pub(crate) fn set_requirer( - &mut self, - requirer: Box, - ) -> *mut Box { - self.requirer.replace(requirer); - self.requirer.as_mut().unwrap() - } } From 9fda2ecfcc55de110737437c2080c7dfd91dfa5e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 25 Apr 2025 23:38:49 +0100 Subject: [PATCH 375/635] mlua-sys: Update Luau to 0.671 --- mlua-sys/Cargo.toml | 2 +- mlua-sys/src/luau/lauxlib.rs | 9 ++++++++- mlua-sys/src/luau/luarequire.rs | 20 +++++++++++++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index d52c419d..d85ea96e 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -40,7 +40,7 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 547.0.0, < 547.1.0", optional = true } luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } -luau0-src = { git = "https://github.com/mlua-rs/luau-src-rs", rev = "37e1e34", optional = true } +luau0-src = { git = "https://github.com/mlua-rs/luau-src-rs", rev = "dc1102e", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/mlua-sys/src/luau/lauxlib.rs b/mlua-sys/src/luau/lauxlib.rs index ddedb2c6..845bb285 100644 --- a/mlua-sys/src/luau/lauxlib.rs +++ b/mlua-sys/src/luau/lauxlib.rs @@ -71,10 +71,17 @@ extern "C-unwind" { pub fn luaL_newstate() -> *mut lua_State; - // TODO: luaL_findtable + pub fn luaL_findtable( + L: *mut lua_State, + idx: c_int, + fname: *const c_char, + szhint: c_int, + ) -> *const c_char; pub fn luaL_typename(L: *mut lua_State, idx: c_int) -> *const c_char; + pub fn luaL_callyieldable(L: *mut lua_State, nargs: c_int, nresults: c_int) -> c_int; + // sandbox libraries and globals #[link_name = "luaL_sandbox"] pub fn luaL_sandbox_(L: *mut lua_State); diff --git a/mlua-sys/src/luau/luarequire.rs b/mlua-sys/src/luau/luarequire.rs index 00613073..64b1a2f1 100644 --- a/mlua-sys/src/luau/luarequire.rs +++ b/mlua-sys/src/luau/luarequire.rs @@ -104,6 +104,7 @@ pub struct luarequire_Configuration { pub load: unsafe extern "C-unwind" fn( L: *mut lua_State, ctx: *mut c_void, + path: *const c_char, chunkname: *const c_char, contents: *const c_char, ) -> c_int, @@ -114,7 +115,7 @@ pub type luarequire_Configuration_init = unsafe extern "C" fn(config: *mut luare extern "C-unwind" { // Initializes and pushes the require closure onto the stack without registration. - pub fn lua_pushrequire( + pub fn luarequire_pushrequire( L: *mut lua_State, config_init: luarequire_Configuration_init, ctx: *mut c_void, @@ -122,4 +123,21 @@ extern "C-unwind" { // Initializes the require library and registers it globally. pub fn luaopen_require(L: *mut lua_State, config_init: luarequire_Configuration_init, ctx: *mut c_void); + + // Initializes and pushes a "proxyrequire" closure onto the stack. + // + // The closure takes two parameters: the string path to resolve and the chunkname of an existing + // module. + pub fn luarequire_pushproxyrequire( + L: *mut lua_State, + config_init: luarequire_Configuration_init, + ctx: *mut c_void, + ) -> c_int; + + // Registers an aliased require path to a result. + // + // After registration, the given result will always be immediately returned when the given path is + // required. + // Expects the path and table to be passed as arguments on the stack. + pub fn luarequire_registermodule(L: *mut lua_State) -> c_int; } From a8a4aa8c930c9335b934c44bfea481f043b5ec3c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 25 Apr 2025 23:41:11 +0100 Subject: [PATCH 376/635] Switch to "proxyrequire" function (follow up Luau 0.671) --- src/luau/mod.rs | 22 ++++++++++++++++------ src/luau/require.rs | 7 +++++-- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/luau/mod.rs b/src/luau/mod.rs index 32ace410..592059cd 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -1,4 +1,5 @@ use std::ffi::CStr; +use std::mem; use std::os::raw::c_int; use crate::error::Result; @@ -12,18 +13,27 @@ pub use require::{NavigateError, Require}; impl Lua { /// Create a custom Luau `require` function using provided [`Require`] implementation to find /// and load modules. - /// - /// The provided object is stored in the Lua registry and will not be garbage collected - /// until the Lua state is closed. #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn create_require_function(&self, require: R) -> Result { + unsafe extern "C-unwind" fn mlua_require(state: *mut ffi::lua_State) -> c_int { + let mut ar: ffi::lua_Debug = mem::zeroed(); + if ffi::lua_getinfo(state, 1, cstr!("s"), &mut ar) == 0 { + ffi::luaL_error(state, cstr!("require is not supported in this context")); + } + let top = ffi::lua_gettop(state); + ffi::lua_pushvalue(state, ffi::lua_upvalueindex(2)); // the "proxy" require function + ffi::lua_pushvalue(state, 1); // require path + ffi::lua_pushstring(state, ar.source); // current file + ffi::lua_call(state, 2, ffi::LUA_MULTRET); + ffi::lua_gettop(state) - top + } + unsafe { self.exec_raw((), move |state| { let requirer_ptr = ffi::lua_newuserdata_t::>(state, Box::new(require)); - // Keep the require object in the registry to prevent it from being garbage collected - ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, requirer_ptr as *const _); - ffi::lua_pushrequire(state, require::init_config, requirer_ptr as *mut _); + ffi::luarequire_pushproxyrequire(state, require::init_config, requirer_ptr as *mut _); + ffi::lua_pushcclosured(state, mlua_require, cstr!("require"), 2); }) } } diff --git a/src/luau/require.rs b/src/luau/require.rs index 254edea3..2174b9f3 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -86,7 +86,8 @@ pub trait Require: MaybeSend { fn config(&self) -> IoResult>; /// Loads the module and returns the result (function or table). - fn load(&self, lua: &Lua, chunk_name: &str, content: &[u8]) -> Result { + fn load(&self, lua: &Lua, path: &str, chunk_name: &str, content: &[u8]) -> Result { + let _ = path; lua.load(content).set_name(chunk_name).call(()) } } @@ -413,15 +414,17 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu unsafe extern "C-unwind" fn load( state: *mut ffi::lua_State, ctx: *mut c_void, + path: *const c_char, chunk_name: *const c_char, contents: *const c_char, ) -> c_int { let this = &*(ctx as *const Box); + let path = CStr::from_ptr(path).to_string_lossy(); let chunk_name = CStr::from_ptr(chunk_name).to_string_lossy(); let contents = CStr::from_ptr(contents).to_bytes(); let lua = Lua::get_or_init_from_ptr(state); callback_error_ext(state, ptr::null_mut(), false, move |_extra, _| { - match this.load(lua, &chunk_name, contents)? { + match this.load(lua, &path, &chunk_name, contents)? { Value::Nil => lua.lock().push(true)?, value => lua.lock().push(value)?, }; From 64b1d152b97caab87c7e2fa388028dde3c9e6e0b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 26 Apr 2025 13:38:13 +0100 Subject: [PATCH 377/635] Deprecate `Lua::load_from_finction` and replace it with `Lua::register_module` and `Lua::preload_module` instead. --- mlua-sys/src/lua51/compat.rs | 2 +- mlua-sys/src/lua51/lauxlib.rs | 3 + mlua-sys/src/lua52/compat.rs | 2 +- mlua-sys/src/lua52/lauxlib.rs | 6 ++ src/state.rs | 112 +++++++++++++++++++++------------- tests/tests.rs | 88 +++++++++++++++++++------- 6 files changed, 146 insertions(+), 67 deletions(-) diff --git a/mlua-sys/src/lua51/compat.rs b/mlua-sys/src/lua51/compat.rs index 8c2d7bfe..29837f2a 100644 --- a/mlua-sys/src/lua51/compat.rs +++ b/mlua-sys/src/lua51/compat.rs @@ -548,7 +548,7 @@ pub unsafe fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_ch pub unsafe fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int) { luaL_checkstack(L, 3, cstr!("not enough stack slots available")); - luaL_getsubtable(L, LUA_REGISTRYINDEX, cstr!("_LOADED")); + luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); if lua_getfield(L, -1, modname) == LUA_TNIL { lua_pop(L, 1); lua_pushcfunction(L, openf); diff --git a/mlua-sys/src/lua51/lauxlib.rs b/mlua-sys/src/lua51/lauxlib.rs index 54238858..78cef292 100644 --- a/mlua-sys/src/lua51/lauxlib.rs +++ b/mlua-sys/src/lua51/lauxlib.rs @@ -8,6 +8,9 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State}; // Extra error code for 'luaL_load' pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1; +// Key, in the registry, for table of loaded modules +pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED"); + #[repr(C)] pub struct luaL_Reg { pub name: *const c_char, diff --git a/mlua-sys/src/lua52/compat.rs b/mlua-sys/src/lua52/compat.rs index 68c7029f..04829145 100644 --- a/mlua-sys/src/lua52/compat.rs +++ b/mlua-sys/src/lua52/compat.rs @@ -232,7 +232,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) pub unsafe fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int) { luaL_checkstack(L, 3, cstr!("not enough stack slots available")); - luaL_getsubtable(L, LUA_REGISTRYINDEX, cstr!("_LOADED")); + luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); if lua_getfield(L, -1, modname) == LUA_TNIL { lua_pop(L, 1); lua_pushcfunction(L, openf); diff --git a/mlua-sys/src/lua52/lauxlib.rs b/mlua-sys/src/lua52/lauxlib.rs index d5cdf664..fad19ebe 100644 --- a/mlua-sys/src/lua52/lauxlib.rs +++ b/mlua-sys/src/lua52/lauxlib.rs @@ -8,6 +8,12 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State, lua_Un // Extra error code for 'luaL_load' pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1; +// Key, in the registry, for table of loaded modules +pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED"); + +// Key, in the registry, for table of preloaded loaders +pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD"); + #[repr(C)] pub struct luaL_Reg { pub name: *const c_char, diff --git a/src/state.rs b/src/state.rs index ffed0187..e23faa3a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2,7 +2,7 @@ use std::any::TypeId; use std::cell::{BorrowError, BorrowMutError, RefCell}; use std::marker::PhantomData; use std::ops::Deref; -use std::os::raw::c_int; +use std::os::raw::{c_char, c_int}; use std::panic::Location; use std::result::Result as StdResult; use std::{fmt, mem, ptr}; @@ -347,40 +347,78 @@ impl Lua { unsafe { self.lock().load_std_libs(libs) } } - /// Loads module `modname` into an existing Lua state using the specified entrypoint - /// function. + /// Registers module into an existing Lua state using the specified value. /// - /// Internally calls the Lua function `func` with the string `modname` as an argument, - /// sets the call result to `package.loaded[modname]` and returns copy of the result. + /// After registration, the given value will always be immediately returned when the + /// given module is [required]. /// - /// If `package.loaded[modname]` value is not nil, returns copy of the value without - /// calling the function. + /// [required]: https://www.lua.org/manual/5.4/manual.html#pdf-require + pub fn register_module(&self, modname: &str, value: impl IntoLua) -> Result<()> { + #[cfg(not(feature = "luau"))] + const LOADED_MODULES_KEY: *const c_char = ffi::LUA_LOADED_TABLE; + #[cfg(feature = "luau")] + const LOADED_MODULES_KEY: *const c_char = cstr!("_REGISTEREDMODULES"); + + if cfg!(feature = "luau") && !modname.starts_with('@') { + return Err(Error::runtime("module name must begin with '@'")); + } + unsafe { + self.exec_raw::<()>(value, |state| { + ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, LOADED_MODULES_KEY); + ffi::lua_pushlstring(state, modname.as_ptr() as *const c_char, modname.len() as _); + ffi::lua_pushvalue(state, -3); + ffi::lua_rawset(state, -3); + }) + } + } + + /// Preloads module into an existing Lua state using the specified loader function. /// - /// If the function does not return a non-nil value then this method assigns true to - /// `package.loaded[modname]`. + /// When the module is required, the loader function will be called with module name as the + /// first argument. /// - /// Behavior is similar to Lua's [`require`] function. + /// This is similar to setting the [`package.preload[modname]`] field. /// - /// [`require`]: https://www.lua.org/manual/5.4/manual.html#pdf-require - pub fn load_from_function(&self, modname: &str, func: Function) -> Result - where - T: FromLua, - { - let lua = self.lock(); - let state = lua.state(); + /// [`package.preload[modname]`]: https://www.lua.org/manual/5.4/manual.html#pdf-package.preload + #[cfg(not(feature = "luau"))] + #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] + pub fn preload_module(&self, modname: &str, func: Function) -> Result<()> { + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + let preload = unsafe { + self.exec_raw::>((), |state| { + ffi::lua_getfield(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_PRELOAD_TABLE); + })? + }; + #[cfg(any(feature = "lua51", feature = "luajit"))] + let preload = unsafe { + self.exec_raw::>((), |state| { + if ffi::lua_getfield(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_LOADED_TABLE) != ffi::LUA_TNIL { + ffi::luaL_getsubtable(state, -1, ffi::LUA_LOADLIBNAME); + ffi::luaL_getsubtable(state, -1, cstr!("preload")); + ffi::lua_rotate(state, 1, 1); + } + })? + }; + if let Some(preload) = preload { + preload.raw_set(modname, func)?; + } + Ok(()) + } + + #[doc(hidden)] + #[deprecated(since = "0.11.0", note = "Use `register_module` instead")] + #[cfg(not(feature = "luau"))] + #[cfg(not(tarpaulin_include))] + pub fn load_from_function(&self, modname: &str, func: Function) -> Result { let loaded = unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 2)?; - protect_lua!(state, 0, 1, fn(state) { - ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); - })?; - Table(lua.pop_ref()) + self.exec_raw::
((), |state| { + ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_LOADED_TABLE); + })? }; - let modname = unsafe { lua.create_string(modname)? }; - let value = match loaded.raw_get(&modname)? { + let value = match loaded.raw_get(modname)? { Value::Nil => { - let result = match func.call(&modname)? { + let result = match func.call(modname)? { Value::Nil => Value::Boolean(true), res => res, }; @@ -394,24 +432,14 @@ impl Lua { /// Unloads module `modname`. /// - /// Removes module from the [`package.loaded`] table which allows to load it again. - /// It does not support unloading binary Lua modules since they are internally cached and can be - /// unloaded only by closing Lua state. + /// This method does not support unloading binary Lua modules since they are internally cached + /// and can be unloaded only by closing Lua state. + /// + /// This is similar to calling [`Lua::register_module`] with `Nil` value. /// /// [`package.loaded`]: https://www.lua.org/manual/5.4/manual.html#pdf-package.loaded - pub fn unload(&self, modname: &str) -> Result<()> { - let lua = self.lock(); - let state = lua.state(); - let loaded = unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 2)?; - protect_lua!(state, 0, 1, fn(state) { - ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); - })?; - Table(lua.pop_ref()) - }; - - loaded.raw_set(modname, Nil) + pub fn unload_module(&self, modname: &str) -> Result<()> { + self.register_module(modname, Nil) } // Executes module entrypoint function, which returns only one Value. diff --git a/tests/tests.rs b/tests/tests.rs index 94442999..61021ad9 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -3,7 +3,6 @@ use std::collections::HashMap; use std::iter::FromIterator; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::string::String as StdString; -use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; use std::{error, f32, f64, fmt}; @@ -1168,36 +1167,79 @@ fn test_jit_version() -> Result<()> { } #[test] -fn test_load_from_function() -> Result<()> { +fn test_register_module() -> Result<()> { let lua = Lua::new(); - let i = Arc::new(AtomicU32::new(0)); - let i2 = i.clone(); - let func = lua.create_function(move |lua, modname: String| { - i2.fetch_add(1, Ordering::Relaxed); + let t = lua.create_table()?; + t.set("name", "my_module")?; + lua.register_module("@my_module", &t)?; + + lua.load( + r#" + local my_module = require("@my_module") + assert(my_module.name == "my_module") + "#, + ) + .exec()?; + + lua.unload_module("@my_module")?; + lua.load( + r#" + local ok, err = pcall(function() return require("@my_module") end) + assert(not ok) + "#, + ) + .exec()?; + + #[cfg(feature = "luau")] + { + // Luau registered modules must have '@' prefix + let res = lua.register_module("my_module", 123); + assert!(res.is_err()); + assert_eq!( + res.unwrap_err().to_string(), + "runtime error: module name must begin with '@'" + ); + } + + Ok(()) +} + +#[test] +#[cfg(not(feature = "luau"))] +fn test_preload_module() -> Result<()> { + let lua = Lua::new(); + + let loader = lua.create_function(move |lua, modname: String| { let t = lua.create_table()?; - t.set("__name", modname)?; + t.set("name", modname)?; Ok(t) })?; - let t: Table = lua.load_from_function("my_module", func.clone())?; - assert_eq!(t.get::("__name")?, "my_module"); - assert_eq!(i.load(Ordering::Relaxed), 1); - - let _: Value = lua.load_from_function("my_module", func.clone())?; - assert_eq!(i.load(Ordering::Relaxed), 1); - - let func_nil = lua.create_function(move |_, _: String| Ok(Value::Nil))?; - let v: Value = lua.load_from_function("my_module2", func_nil)?; - assert_eq!(v, Value::Boolean(true)); + lua.preload_module("@my_module", loader.clone())?; + lua.load( + r#" + -- `my_module` is global for purposes of next test + my_module = require("@my_module") + assert(my_module.name == "@my_module") + local my_module2 = require("@my_module") + assert(my_module == my_module2) + "#, + ) + .exec() + .unwrap(); // Test unloading and loading again - lua.unload("my_module")?; - let _: Value = lua.load_from_function("my_module", func)?; - assert_eq!(i.load(Ordering::Relaxed), 2); - - // Unloading nonexistent module must not fail - lua.unload("my_module2")?; + lua.unload_module("@my_module")?; + lua.load( + r#" + local my_module3 = require("@my_module") + -- `my_module` is not equal to `my_module3` because it was reloaded + assert(my_module ~= my_module3) + "#, + ) + .exec() + .unwrap(); Ok(()) } From 5e1451a46574cd46462bcd78425359ffc4547690 Mon Sep 17 00:00:00 2001 From: krakow10 Date: Sat, 26 Apr 2025 05:49:50 -0700 Subject: [PATCH 378/635] Fix Reversed Comments & Typo (#560) * Fix reversed comments * Fix typos --- src/multi.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/multi.rs b/src/multi.rs index b4fb0e2e..c171962c 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -126,8 +126,7 @@ impl MultiValue { /// Creates a `MultiValue` container from vector of values. /// - /// This methods needs *O*(*n*) data movement if the circular buffer doesn't happen to be at the - /// beginning of the allocation. + /// This method works in *O*(1) time and does not allocate any additional memory. #[inline] pub fn from_vec(vec: Vec) -> MultiValue { vec.into() @@ -135,7 +134,8 @@ impl MultiValue { /// Consumes the `MultiValue` and returns a vector of values. /// - /// This methods works in *O*(1) time and does not allocate any additional memory. + /// This method needs *O*(*n*) data movement if the circular buffer doesn't happen to be at the + /// beginning of the allocation. #[inline] pub fn into_vec(self) -> Vec { self.into() From efd192b816549d6b774782132292fafbf4d09aff Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 26 Apr 2025 15:17:54 +0100 Subject: [PATCH 379/635] Optimize `Table::is_empty` --- src/state/extra.rs | 2 +- src/table.rs | 22 ++++++---------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/state/extra.rs b/src/state/extra.rs index 7567669e..c6f2c86f 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -28,7 +28,7 @@ use super::{Lua, WeakLua}; static EXTRA_REGISTRY_KEY: u8 = 0; const WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY: usize = 64; -const REF_STACK_RESERVE: c_int = 1; +const REF_STACK_RESERVE: c_int = 2; /// Data associated with the Lua state. pub(crate) struct ExtraData { diff --git a/src/table.rs b/src/table.rs index ae643119..193b1ad3 100644 --- a/src/table.rs +++ b/src/table.rs @@ -468,26 +468,16 @@ impl Table { /// /// It checks both the array part and the hash part. pub fn is_empty(&self) -> bool { - // Check array part - if self.raw_len() != 0 { - return false; - } - - // Check hash part let lua = self.0.lua.lock(); - let state = lua.state(); + let ref_thread = lua.ref_thread(); unsafe { - let _sg = StackGuard::new(state); - assert_stack(state, 4); - - lua.push_ref(&self.0); - ffi::lua_pushnil(state); - if ffi::lua_next(state, -2) != 0 { - return false; + ffi::lua_pushnil(ref_thread); + if ffi::lua_next(ref_thread, self.0.index) == 0 { + return true; } + ffi::lua_pop(ref_thread, 2); } - - true + false } /// Returns a reference to the metatable of this table, or `None` if no metatable is set. From 8e1df48e8110c4ea1dfa301c9a3326ea0fc36f83 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 26 Apr 2025 17:53:56 +0100 Subject: [PATCH 380/635] Add `encode_empty_tables_as_array` serialize option. This will change the behaviour of encoding empty Lua tables into array instead of map. --- src/serde/de.rs | 18 +++++++++++++++++ src/table.rs | 5 ++++- src/value.rs | 9 +++++++++ tests/serde.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/src/serde/de.rs b/src/serde/de.rs index 0a941170..69d9056c 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -49,6 +49,11 @@ pub struct Options { /// /// Default: **false** pub sort_keys: bool, + + /// If true, empty Lua tables will be encoded as array, instead of map. + /// + /// Default: **false** + pub encode_empty_tables_as_array: bool, } impl Default for Options { @@ -64,6 +69,7 @@ impl Options { deny_unsupported_types: true, deny_recursive_tables: true, sort_keys: false, + encode_empty_tables_as_array: false, } } @@ -93,6 +99,15 @@ impl Options { self.sort_keys = enabled; self } + + /// Sets [`encode_empty_tables_as_array`] option. + /// + /// [`encode_empty_tables_as_array`]: #structfield.encode_empty_tables_as_array + #[must_use] + pub const fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self { + self.encode_empty_tables_as_array = enabled; + self + } } impl Deserializer { @@ -141,6 +156,9 @@ impl<'de> serde::Deserializer<'de> for Deserializer { Err(_) => visitor.visit_bytes(&s.as_bytes()), }, Value::Table(ref t) if t.raw_len() > 0 || t.is_array() => self.deserialize_seq(visitor), + Value::Table(ref t) if self.options.encode_empty_tables_as_array && t.is_empty() => { + self.deserialize_seq(visitor) + } Value::Table(_) => self.deserialize_map(visitor), Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(), Value::UserData(ud) if ud.is_serializable() => { diff --git a/src/table.rs b/src/table.rs index 193b1ad3..aa8e680d 100644 --- a/src/table.rs +++ b/src/table.rs @@ -1013,7 +1013,10 @@ impl Serialize for SerializableTable<'_> { // Array let len = self.table.raw_len(); - if len > 0 || self.table.is_array() { + if len > 0 + || self.table.is_array() + || (self.options.encode_empty_tables_as_array && self.table.is_empty()) + { let mut seq = serializer.serialize_seq(Some(len))?; let mut serialize_err = None; let res = self.table.for_each_value::(|value| { diff --git a/src/value.rs b/src/value.rs index 428ce201..119f147e 100644 --- a/src/value.rs +++ b/src/value.rs @@ -700,6 +700,15 @@ impl<'a> SerializableValue<'a> { self.options.sort_keys = enabled; self } + + /// If true, empty Lua tables will be encoded as array, instead of map. + /// + /// Default: **false** + #[must_use] + pub const fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self { + self.options.encode_empty_tables_as_array = enabled; + self + } } #[cfg(feature = "serialize")] diff --git a/tests/serde.rs b/tests/serde.rs index 167095fd..9e3d5984 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -249,6 +249,26 @@ fn test_serialize_same_table_twice() -> LuaResult<()> { Ok(()) } +#[test] +fn test_serialize_empty_table() -> LuaResult<()> { + let lua = Lua::new(); + + let table = Value::Table(lua.create_table()?); + let json = serde_json::to_string(&table.to_serializable()).unwrap(); + assert_eq!(json, "{}"); + + // Set the option to encode empty tables as array + let json = serde_json::to_string(&table.to_serializable().encode_empty_tables_as_array(true)).unwrap(); + assert_eq!(json, "[]"); + + // Check hashmap table with this option + table.as_table().unwrap().set("hello", "world")?; + let json = serde_json::to_string(&table.to_serializable().encode_empty_tables_as_array(true)).unwrap(); + assert_eq!(json, r#"{"hello":"world"}"#); + + Ok(()) +} + #[test] fn test_to_value_struct() -> LuaResult<()> { let lua = Lua::new(); @@ -667,6 +687,37 @@ fn test_from_value_userdata() -> Result<(), Box> { Ok(()) } +#[test] +fn test_from_value_empty_table() -> Result<(), Box> { + let lua = Lua::new(); + + // By default we encode empty tables as objects + let t = lua.create_table()?; + let got = lua.from_value::(Value::Table(t.clone()))?; + assert_eq!(got, serde_json::json!({})); + + // Set the option to encode empty tables as array + let got = lua + .from_value_with::( + Value::Table(t.clone()), + DeserializeOptions::new().encode_empty_tables_as_array(true), + ) + .unwrap(); + assert_eq!(got, serde_json::json!([])); + + // Check hashmap table with this option + t.raw_set("hello", "world")?; + let got = lua + .from_value_with::( + Value::Table(t), + DeserializeOptions::new().encode_empty_tables_as_array(true), + ) + .unwrap(); + assert_eq!(got, serde_json::json!({"hello": "world"})); + + Ok(()) +} + #[test] fn test_from_value_sorted() -> Result<(), Box> { let lua = Lua::new(); From a30a7362915c1c87a2c0eb571c7da37a8253d800 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 27 Apr 2025 00:38:30 +0100 Subject: [PATCH 381/635] Don't borrow mutably appdata container when working with require content cache --- src/luau/require.rs | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/luau/require.rs b/src/luau/require.rs index 2174b9f3..52d1ada1 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -455,34 +455,39 @@ unsafe fn write_to_buffer( size_out: *mut usize, data_fetcher: impl Fn() -> IoResult>, ) -> WriteResult { - struct DataCache(Vec); + struct DataCache(Option>); // The initial buffer size can be too small, to avoid making a second data fetch call, // we cache the content in the first call, and then re-use it. let lua = Lua::get_or_init_from_ptr(state); - if let Some(data_cache) = lua.app_data_ref::() { - let data_len = data_cache.0.len(); - mlua_assert!(data_len <= buffer_size, "buffer is too small"); - *size_out = data_len; - ptr::copy_nonoverlapping(data_cache.0.as_ptr(), buffer as *mut _, data_len); - drop(data_cache); - lua.remove_app_data::(); - return WriteResult::Success; + match lua.try_app_data_mut::() { + Ok(Some(mut data_cache)) => { + if let Some(data) = data_cache.0.take() { + mlua_assert!(data.len() <= buffer_size, "buffer is too small"); + *size_out = data.len(); + ptr::copy_nonoverlapping(data.as_ptr(), buffer as *mut _, data.len()); + return WriteResult::Success; + } + } + Ok(None) => { + // Init the cache + _ = lua.try_set_app_data(DataCache(None)); + } + Err(_) => {} } match data_fetcher() { Ok(data) => { - let data_len = data.len(); - *size_out = data_len; - if data_len > buffer_size { + *size_out = data.len(); + if *size_out > buffer_size { // Cache the data for the next call to avoid getting the contents again - lua.set_app_data(DataCache(data)); - *size_out = data_len; + if let Ok(Some(mut data_cache)) = lua.try_app_data_mut::() { + data_cache.0 = Some(data); + } return WriteResult::BufferTooSmall; } - ptr::copy_nonoverlapping(data.as_ptr(), buffer as *mut _, data_len); - *size_out = data_len; + ptr::copy_nonoverlapping(data.as_ptr(), buffer as *mut _, data.len()); WriteResult::Success } Err(_) => WriteResult::Failure, From edd508ed1f2ffee7027e77073859678388c42f3d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 27 Apr 2025 23:09:20 +0100 Subject: [PATCH 382/635] Make `StateGuard` automatically enabled inside `callback_error_ext`. Remove manual usage of `StateGuard` in other places. Closes #567 --- src/state.rs | 6 +----- src/state/raw.rs | 10 ++-------- src/state/util.rs | 10 +++++++--- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/state.rs b/src/state.rs index e23faa3a..684eceb2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -47,7 +47,6 @@ use serde::Serialize; pub(crate) use extra::ExtraData; pub use raw::RawLua; pub(crate) use util::callback_error_ext; -use util::StateGuard; /// Top level Lua struct which represents an instance of Lua VM. pub struct Lua { @@ -457,7 +456,6 @@ impl Lua { callback_error_ext(state, ptr::null_mut(), true, move |extra, nargs| { let rawlua = (*extra).raw_lua(); - let _guard = StateGuard::new(rawlua, state); let args = A::from_stack_args(nargs, 1, None, rawlua)?; func(rawlua.lua(), args)?.push_into_stack(rawlua)?; Ok(1) @@ -694,7 +692,6 @@ impl Lua { if XRc::strong_count(&interrupt_cb) > 2 { return Ok(VmState::Continue); // Don't allow recursion } - let _guard = StateGuard::new((*extra).raw_lua(), state); interrupt_cb((*extra).lua()) }); match result { @@ -772,8 +769,7 @@ impl Lua { ffi::lua_pushthread(child); ffi::lua_xmove(child, (*extra).ref_thread, 1); let value = Thread((*extra).raw_lua().pop_ref_thread(), child); - let _guard = StateGuard::new((*extra).raw_lua(), parent); - callback_error_ext((*extra).raw_lua().state(), extra, false, move |extra, _| { + callback_error_ext(parent, extra, false, move |extra, _| { callback((*extra).lua(), value) }) } else { diff --git a/src/state/raw.rs b/src/state/raw.rs index 0b700ca1..40e06eed 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -12,7 +12,7 @@ use crate::chunk::ChunkMode; use crate::error::{Error, Result}; use crate::function::Function; use crate::memory::{MemoryState, ALLOCATOR}; -use crate::state::util::{callback_error_ext, ref_stack_pop, StateGuard}; +use crate::state::util::{callback_error_ext, ref_stack_pop}; use crate::stdlib::StdLib; use crate::string::String; use crate::table::Table; @@ -436,7 +436,6 @@ impl RawLua { match (*extra).hook_callback.clone() { Some(hook_callback) => { let rawlua = (*extra).raw_lua(); - let _guard = StateGuard::new(rawlua, state); let debug = Debug::new(rawlua, ar); hook_callback((*extra).lua(), debug) } @@ -467,7 +466,6 @@ impl RawLua { let status = callback_error_ext(state, ptr::null_mut(), false, |extra, _| { let rawlua = (*extra).raw_lua(); - let _guard = StateGuard::new(rawlua, state); let debug = Debug::new(rawlua, ar); let hook_callback = (*hook_callback_ptr).clone(); hook_callback((*extra).lua(), debug) @@ -1197,7 +1195,6 @@ impl RawLua { // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) // The lock must be already held as the callback is executed let rawlua = (*extra).raw_lua(); - let _guard = StateGuard::new(rawlua, state); match (*upvalue).data { Some(ref func) => func(rawlua, nargs), None => Err(Error::CallbackDestructed), @@ -1241,12 +1238,10 @@ impl RawLua { // Async functions cannot be scoped and therefore destroyed, // so the first upvalue is always valid let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); - let extra = (*upvalue).extra.get(); - callback_error_ext(state, extra, true, |extra, nargs| { + callback_error_ext(state, (*upvalue).extra.get(), true, |extra, nargs| { // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) // The lock must be already held as the callback is executed let rawlua = (*extra).raw_lua(); - let _guard = StateGuard::new(rawlua, state); let func = &*(*upvalue).data; let fut = func(rawlua, nargs); @@ -1271,7 +1266,6 @@ impl RawLua { // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) // The lock must be already held as the future is polled let rawlua = (*extra).raw_lua(); - let _guard = StateGuard::new(rawlua, state); let fut = &mut (*upvalue).data; let mut ctx = Context::from_waker(rawlua.waker()); diff --git a/src/state/util.rs b/src/state/util.rs index ba9a7d49..c3c79302 100644 --- a/src/state/util.rs +++ b/src/state/util.rs @@ -7,10 +7,10 @@ use crate::error::{Error, Result}; use crate::state::{ExtraData, RawLua}; use crate::util::{self, get_internal_metatable, WrappedFailure}; -pub(super) struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State); +struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State); impl<'a> StateGuard<'a> { - pub(super) fn new(inner: &'a RawLua, mut state: *mut ffi::lua_State) -> Self { + fn new(inner: &'a RawLua, mut state: *mut ffi::lua_State) -> Self { state = inner.state.replace(state); Self(inner, state) } @@ -102,7 +102,11 @@ where // to store a wrapped failure (error or panic) *before* we proceed. let prealloc_failure = PreallocatedFailure::reserve(state, extra); - match catch_unwind(AssertUnwindSafe(|| f(extra, nargs))) { + match catch_unwind(AssertUnwindSafe(|| { + let rawlua = (*extra).raw_lua(); + let _guard = StateGuard::new(rawlua, state); + f(extra, nargs) + })) { Ok(Ok(r)) => { // Return unused `WrappedFailure` to the pool prealloc_failure.release(state, extra); From d6fb32866036d38deba2bfde354dc496c08614ac Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 28 Apr 2025 10:07:11 +0100 Subject: [PATCH 383/635] Don't use `Lua::get_or_init_from_ptr` in Require::load wrapper --- src/luau/require.rs | 10 +++++----- src/state/extra.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/luau/require.rs b/src/luau/require.rs index 52d1ada1..adcd9d49 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -422,11 +422,11 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu let path = CStr::from_ptr(path).to_string_lossy(); let chunk_name = CStr::from_ptr(chunk_name).to_string_lossy(); let contents = CStr::from_ptr(contents).to_bytes(); - let lua = Lua::get_or_init_from_ptr(state); - callback_error_ext(state, ptr::null_mut(), false, move |_extra, _| { - match this.load(lua, &path, &chunk_name, contents)? { - Value::Nil => lua.lock().push(true)?, - value => lua.lock().push(value)?, + callback_error_ext(state, ptr::null_mut(), false, move |extra, _| { + let rawlua = (*extra).raw_lua(); + match this.load(rawlua.lua(), &path, &chunk_name, contents)? { + Value::Nil => rawlua.push(true)?, + value => rawlua.push_value(&value)?, }; Ok(1) }) diff --git a/src/state/extra.rs b/src/state/extra.rs index c6f2c86f..12133639 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -249,7 +249,7 @@ impl ExtraData { } #[inline(always)] - pub(super) unsafe fn raw_lua(&self) -> &RawLua { + pub(crate) unsafe fn raw_lua(&self) -> &RawLua { &*self.lua.assume_init_ref().raw.data_ptr() } From 97ca3e2c08621df10ad60fe8c845f3691fde9788 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 1 May 2025 11:15:31 +0100 Subject: [PATCH 384/635] Check that type passed to scoped userdata `self` argument is userdata. If passed type is non-userdata we try to get a pointer (which will be null) that triggers an assertion. Having a check also allow us to generate right error message. Fixes #569 --- src/userdata/registry.rs | 22 +++++++++++----------- tests/scope.rs | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index 5ad93c91..74e36e7d 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -14,7 +14,7 @@ use crate::userdata::{ borrow_userdata_scoped, borrow_userdata_scoped_mut, AnyUserData, MetaMethod, TypeIdHints, UserData, UserDataFields, UserDataMethods, UserDataStorage, }; -use crate::util::{get_userdata, short_type_name}; +use crate::util::short_type_name; use crate::value::Value; #[cfg(feature = "async")] @@ -143,16 +143,16 @@ impl UserDataRegistry { method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) })) } - #[rustfmt::skip] - UserDataType::Unique(target_ptr) - if get_userdata::>(state, self_index) as *mut c_void == target_ptr => - { + UserDataType::Unique(target_ptr) if ffi::lua_touserdata(state, self_index) == target_ptr => { let ud = target_ptr as *mut UserDataStorage; try_self_arg!((*ud).try_borrow_scoped(|ud| { method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) })) } - _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), + UserDataType::Unique(_) => { + try_self_arg!(rawlua.get_userdata_type_id::(state, self_index)); + Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)) + } } }) } @@ -192,16 +192,16 @@ impl UserDataRegistry { method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) })) } - #[rustfmt::skip] - UserDataType::Unique(target_ptr) - if get_userdata::>(state, self_index) as *mut c_void == target_ptr => - { + UserDataType::Unique(target_ptr) if ffi::lua_touserdata(state, self_index) == target_ptr => { let ud = target_ptr as *mut UserDataStorage; try_self_arg!((*ud).try_borrow_scoped_mut(|ud| { method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) })) } - _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), + UserDataType::Unique(_) => { + try_self_arg!(rawlua.get_userdata_type_id::(state, self_index)); + Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)) + } } }) } diff --git a/tests/scope.rs b/tests/scope.rs index 696b65f4..f5a7d3ba 100644 --- a/tests/scope.rs +++ b/tests/scope.rs @@ -276,6 +276,23 @@ fn test_scope_userdata_mismatch() -> Result<()> { Err(other) => panic!("wrong error type {other:?}"), Ok(_) => panic!("incorrectly returned Ok"), } + + // Pass non-userdata type + let err = inc.call::<()>((&au, 321)).err().unwrap(); + match err { + Error::CallbackError { ref cause, .. } => match cause.as_ref() { + Error::BadArgument { to, pos, name, cause } => { + assert_eq!(to.as_deref(), Some("MyUserData.inc")); + assert_eq!(*pos, 1); + assert_eq!(name.as_deref(), Some("self")); + assert!(matches!(*cause.as_ref(), Error::FromLuaConversionError { .. })); + } + other => panic!("wrong error type {other:?}"), + }, + other => panic!("wrong error type {other:?}"), + } + let err_msg = "bad argument `self` to `MyUserData.inc`: error converting Lua number to userdata (expected userdata of type 'MyUserData')"; + assert!(err.to_string().contains(err_msg)); Ok(()) })?; From 4a3dbafb6ec71867f72f1dca1ac4630f0507ef03 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 1 May 2025 13:12:00 +0100 Subject: [PATCH 385/635] Update scoped userdata mismatch tests --- tests/scope.rs | 82 +++++++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/tests/scope.rs b/tests/scope.rs index f5a7d3ba..9aa3ee19 100644 --- a/tests/scope.rs +++ b/tests/scope.rs @@ -235,12 +235,14 @@ fn test_scope_userdata_values() -> Result<()> { #[test] fn test_scope_userdata_mismatch() -> Result<()> { - struct MyUserData<'a>(&'a Cell); + struct MyUserData<'a>(&'a mut i64); impl<'a> UserData for MyUserData<'a> { fn register(reg: &mut UserDataRegistry) { - reg.add_method("inc", |_, data, ()| { - data.0.set(data.0.get() + 1); + reg.add_method("get", |_, data, ()| Ok(*data.0)); + + reg.add_method_mut("inc", |_, data, ()| { + *data.0 = data.0.wrapping_add(1); Ok(()) }); } @@ -251,48 +253,54 @@ fn test_scope_userdata_mismatch() -> Result<()> { lua.load( r#" function inc(a, b) a.inc(b) end + function get(a, b) a.get(b) end "#, ) .exec()?; - let a = Cell::new(1); - let b = Cell::new(1); + let mut a = 1; + let mut b = 1; - let inc: Function = lua.globals().get("inc")?; lua.scope(|scope| { - let au = scope.create_userdata(MyUserData(&a))?; - let bu = scope.create_userdata(MyUserData(&b))?; - assert!(inc.call::<()>((&au, &au)).is_ok()); - match inc.call::<()>((&au, &bu)) { - Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() { - Error::BadArgument { to, pos, name, cause } => { - assert_eq!(to.as_deref(), Some("MyUserData.inc")); - assert_eq!(*pos, 1); - assert_eq!(name.as_deref(), Some("self")); - assert!(matches!(*cause.as_ref(), Error::UserDataTypeMismatch)); - } - other => panic!("wrong error type {other:?}"), - }, - Err(other) => panic!("wrong error type {other:?}"), - Ok(_) => panic!("incorrectly returned Ok"), - } - - // Pass non-userdata type - let err = inc.call::<()>((&au, 321)).err().unwrap(); - match err { - Error::CallbackError { ref cause, .. } => match cause.as_ref() { - Error::BadArgument { to, pos, name, cause } => { - assert_eq!(to.as_deref(), Some("MyUserData.inc")); - assert_eq!(*pos, 1); - assert_eq!(name.as_deref(), Some("self")); - assert!(matches!(*cause.as_ref(), Error::FromLuaConversionError { .. })); - } + let au = scope.create_userdata(MyUserData(&mut a))?; + let bu = scope.create_userdata(MyUserData(&mut b))?; + for method_name in ["get", "inc"] { + let f: Function = lua.globals().get(method_name)?; + let full_name = format!("MyUserData.{method_name}"); + let full_name = full_name.as_str(); + + assert!(f.call::<()>((&au, &au)).is_ok()); + match f.call::<()>((&au, &bu)) { + Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() { + Error::BadArgument { to, pos, name, cause } => { + assert_eq!(to.as_deref(), Some(full_name)); + assert_eq!(*pos, 1); + assert_eq!(name.as_deref(), Some("self")); + assert!(matches!(*cause.as_ref(), Error::UserDataTypeMismatch)); + } + other => panic!("wrong error type {other:?}"), + }, + Err(other) => panic!("wrong error type {other:?}"), + Ok(_) => panic!("incorrectly returned Ok"), + } + + // Pass non-userdata type + let err = f.call::<()>((&au, 321)).err().unwrap(); + match err { + Error::CallbackError { ref cause, .. } => match cause.as_ref() { + Error::BadArgument { to, pos, name, cause } => { + assert_eq!(to.as_deref(), Some(full_name)); + assert_eq!(*pos, 1); + assert_eq!(name.as_deref(), Some("self")); + assert!(matches!(*cause.as_ref(), Error::FromLuaConversionError { .. })); + } + other => panic!("wrong error type {other:?}"), + }, other => panic!("wrong error type {other:?}"), - }, - other => panic!("wrong error type {other:?}"), + } + let err_msg = format!("bad argument `self` to `{full_name}`: error converting Lua number to userdata (expected userdata of type 'MyUserData')"); + assert!(err.to_string().contains(&err_msg)); } - let err_msg = "bad argument `self` to `MyUserData.inc`: error converting Lua number to userdata (expected userdata of type 'MyUserData')"; - assert!(err.to_string().contains(err_msg)); Ok(()) })?; From 2490dfeb38b741095396a496da26938fbb64efdd Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 1 May 2025 13:18:40 +0100 Subject: [PATCH 386/635] Silence clippy false positives --- src/lib.rs | 1 + src/memory.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index ea1d8f5d..213a7ea7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,6 +66,7 @@ // warnings at all. #![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(not(send), allow(clippy::arc_with_non_send_sync))] +#![allow(clippy::ptr_eq)] #[macro_use] mod macros; diff --git a/src/memory.rs b/src/memory.rs index ac160fe7..e5bab386 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -30,6 +30,7 @@ impl MemoryState { #[cfg(not(feature = "luau"))] #[rustversion::since(1.85)] #[inline] + #[allow(clippy::incompatible_msrv)] pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { let mut mem_state = ptr::null_mut(); if !ptr::fn_addr_eq(ffi::lua_getallocf(state, &mut mem_state), ALLOCATOR) { From f0c0527c2b01ed89f7b9e2ae5f603e8d4f3a1785 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 3 May 2025 17:58:33 +0100 Subject: [PATCH 387/635] Update Lua* dependencies --- mlua-sys/Cargo.toml | 6 +++--- mlua-sys/src/luau/luarequire.rs | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index d85ea96e..e5842d11 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -38,9 +38,9 @@ module = [] cc = "1.0" cfg-if = "1.0" pkg-config = "0.3.17" -lua-src = { version = ">= 547.0.0, < 547.1.0", optional = true } -luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true } -luau0-src = { git = "https://github.com/mlua-rs/luau-src-rs", rev = "dc1102e", optional = true } +lua-src = { version = ">= 547.1.0, < 547.2.0", optional = true } +luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true } +luau0-src = { version = "0.14.1", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/mlua-sys/src/luau/luarequire.rs b/mlua-sys/src/luau/luarequire.rs index 64b1a2f1..30a7267e 100644 --- a/mlua-sys/src/luau/luarequire.rs +++ b/mlua-sys/src/luau/luarequire.rs @@ -101,6 +101,8 @@ pub struct luarequire_Configuration { // Executes the module and places the result on the stack. Returns the number of results placed on the // stack. + // Returning -1 directs the requiring thread to yield. In this case, this thread should be resumed with + // the module result pushed onto its stack. pub load: unsafe extern "C-unwind" fn( L: *mut lua_State, ctx: *mut c_void, @@ -140,4 +142,11 @@ extern "C-unwind" { // required. // Expects the path and table to be passed as arguments on the stack. pub fn luarequire_registermodule(L: *mut lua_State) -> c_int; + + // Clears the entry associated with the given cache key from the require cache. + // Expects the cache key to be passed as an argument on the stack. + pub fn luarequire_clearcacheentry(L: *mut lua_State) -> c_int; + + // Clears all entries from the require cache. + pub fn luarequire_clearcache(L: *mut lua_State) -> c_int; } From 05a0abb8c28bc624bdfc19973743d53446902f05 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 3 May 2025 18:00:02 +0100 Subject: [PATCH 388/635] Replace `Requite::load` with `Require::loader` --- src/luau/require.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/luau/require.rs b/src/luau/require.rs index adcd9d49..f290d534 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -8,6 +8,7 @@ use std::result::Result as StdResult; use std::{env, fmt, fs, ptr}; use crate::error::Result; +use crate::function::Function; use crate::state::{callback_error_ext, Lua}; use crate::types::MaybeSend; use crate::value::Value; @@ -85,10 +86,10 @@ pub trait Require: MaybeSend { /// This function is only called if `is_config_present` returns true. fn config(&self) -> IoResult>; - /// Loads the module and returns the result (function or table). - fn load(&self, lua: &Lua, path: &str, chunk_name: &str, content: &[u8]) -> Result { + /// Returns a loader that when called, loads the module and returns the result. + fn loader(&self, lua: &Lua, path: &str, chunk_name: &str, content: &[u8]) -> Result { let _ = path; - lua.load(content).set_name(chunk_name).call(()) + lua.load(content).set_name(chunk_name).into_function() } } @@ -424,7 +425,7 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu let contents = CStr::from_ptr(contents).to_bytes(); callback_error_ext(state, ptr::null_mut(), false, move |extra, _| { let rawlua = (*extra).raw_lua(); - match this.load(rawlua.lua(), &path, &chunk_name, contents)? { + match (this.loader(rawlua.lua(), &path, &chunk_name, contents)?).call(())? { Value::Nil => rawlua.push(true)?, value => rawlua.push_value(&value)?, }; From 4fa8e645b7d27cc7c344a52635f23de29ac6624e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 3 May 2025 21:45:39 +0100 Subject: [PATCH 389/635] Fix Luau "require" tests --- mlua-sys/Cargo.toml | 2 +- tests/luau/require.rs | 6 ++++-- .../with_config/GlobalLuauLibraries/global_library.luau | 1 - .../require/with_config/ProjectLuauLibraries/library.luau | 1 - 4 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 tests/luau/require/with_config/GlobalLuauLibraries/global_library.luau delete mode 100644 tests/luau/require/with_config/ProjectLuauLibraries/library.luau diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index e5842d11..dcf709dd 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -40,7 +40,7 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 547.1.0, < 547.2.0", optional = true } luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true } -luau0-src = { version = "0.14.1", optional = true } +luau0-src = { version = "0.14.2", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/tests/luau/require.rs b/tests/luau/require.rs index ad077352..f17835ba 100644 --- a/tests/luau/require.rs +++ b/tests/luau/require.rs @@ -60,12 +60,14 @@ fn test_require_without_config() { // RequireWithFileAmbiguity let res = run_require(&lua, "./require/without_config/ambiguous_file_requirer"); assert!(res.is_err()); - assert!((res.unwrap_err().to_string()).contains("require path could not be resolved to a unique file")); + assert!((res.unwrap_err().to_string()) + .contains("could not resolve child component \"dependency\" (ambiguous)")); // RequireWithDirectoryAmbiguity let res = run_require(&lua, "./require/without_config/ambiguous_directory_requirer"); assert!(res.is_err()); - assert!((res.unwrap_err().to_string()).contains("require path could not be resolved to a unique file")); + assert!((res.unwrap_err().to_string()) + .contains("could not resolve child component \"dependency\" (ambiguous)")); // CheckCachedResult let res = run_require(&lua, "./require/without_config/validate_cache").unwrap(); diff --git a/tests/luau/require/with_config/GlobalLuauLibraries/global_library.luau b/tests/luau/require/with_config/GlobalLuauLibraries/global_library.luau deleted file mode 100644 index 0508e0bd..00000000 --- a/tests/luau/require/with_config/GlobalLuauLibraries/global_library.luau +++ /dev/null @@ -1 +0,0 @@ -return {"result from global_library"} diff --git a/tests/luau/require/with_config/ProjectLuauLibraries/library.luau b/tests/luau/require/with_config/ProjectLuauLibraries/library.luau deleted file mode 100644 index 9470401b..00000000 --- a/tests/luau/require/with_config/ProjectLuauLibraries/library.luau +++ /dev/null @@ -1 +0,0 @@ -return {"result from library"} From 65e5be81bafcd286000ce83b08cb50e526682134 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 4 May 2025 11:00:27 +0100 Subject: [PATCH 390/635] Update `__mlua_async_poll` chunk name --- src/state/raw.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index 40e06eed..44439da3 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -1356,7 +1356,7 @@ impl RawLua { "#, ) .try_cache() - .set_name("__mlua_async_poll") + .set_name("=__mlua_async_poll") .set_environment(env) .into_function() } From ed796fff51ebf12b3d100005d5159bf945840eb5 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 4 May 2025 11:00:59 +0100 Subject: [PATCH 391/635] Support async require loaders for Luau --- mlua-sys/src/luau/luarequire.rs | 2 + src/luau/mod.rs | 22 +------ src/luau/require.rs | 110 ++++++++++++++++++++++++++++++-- src/state.rs | 2 +- tests/luau/require.rs | 43 ++++++++++++- 5 files changed, 150 insertions(+), 29 deletions(-) diff --git a/mlua-sys/src/luau/luarequire.rs b/mlua-sys/src/luau/luarequire.rs index 30a7267e..dcdd0b83 100644 --- a/mlua-sys/src/luau/luarequire.rs +++ b/mlua-sys/src/luau/luarequire.rs @@ -4,6 +4,8 @@ use std::os::raw::{c_char, c_int, c_void}; use super::lua::lua_State; +pub const LUA_REGISTERED_MODULES_TABLE: *const c_char = cstr!("_REGISTEREDMODULES"); + #[repr(C)] pub enum luarequire_NavigateResult { Success, diff --git a/src/luau/mod.rs b/src/luau/mod.rs index 592059cd..635eebfe 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -1,5 +1,4 @@ use std::ffi::CStr; -use std::mem; use std::os::raw::c_int; use crate::error::Result; @@ -16,26 +15,7 @@ impl Lua { #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn create_require_function(&self, require: R) -> Result { - unsafe extern "C-unwind" fn mlua_require(state: *mut ffi::lua_State) -> c_int { - let mut ar: ffi::lua_Debug = mem::zeroed(); - if ffi::lua_getinfo(state, 1, cstr!("s"), &mut ar) == 0 { - ffi::luaL_error(state, cstr!("require is not supported in this context")); - } - let top = ffi::lua_gettop(state); - ffi::lua_pushvalue(state, ffi::lua_upvalueindex(2)); // the "proxy" require function - ffi::lua_pushvalue(state, 1); // require path - ffi::lua_pushstring(state, ar.source); // current file - ffi::lua_call(state, 2, ffi::LUA_MULTRET); - ffi::lua_gettop(state) - top - } - - unsafe { - self.exec_raw((), move |state| { - let requirer_ptr = ffi::lua_newuserdata_t::>(state, Box::new(require)); - ffi::luarequire_pushproxyrequire(state, require::init_config, requirer_ptr as *mut _); - ffi::lua_pushcclosured(state, mlua_require, cstr!("require"), 2); - }) - } + require::create_require_function(self, require) } pub(crate) unsafe fn configure_luau(&self) -> Result<()> { diff --git a/src/luau/require.rs b/src/luau/require.rs index f290d534..152114de 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -5,13 +5,13 @@ use std::io::Result as IoResult; use std::os::raw::{c_char, c_int, c_void}; use std::path::{Component, Path, PathBuf}; use std::result::Result as StdResult; -use std::{env, fmt, fs, ptr}; +use std::{env, fmt, fs, mem, ptr}; use crate::error::Result; use crate::function::Function; use crate::state::{callback_error_ext, Lua}; +use crate::table::Table; use crate::types::MaybeSend; -use crate::value::Value; /// An error that can occur during navigation in the Luau `require` system. pub enum NavigateError { @@ -87,6 +87,8 @@ pub trait Require: MaybeSend { fn config(&self) -> IoResult>; /// Returns a loader that when called, loads the module and returns the result. + /// + /// Loader can be sync or async. fn loader(&self, lua: &Lua, path: &str, chunk_name: &str, content: &[u8]) -> Result { let _ = path; lua.load(content).set_name(chunk_name).into_function() @@ -425,10 +427,7 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu let contents = CStr::from_ptr(contents).to_bytes(); callback_error_ext(state, ptr::null_mut(), false, move |extra, _| { let rawlua = (*extra).raw_lua(); - match (this.loader(rawlua.lua(), &path, &chunk_name, contents)?).call(())? { - Value::Nil => rawlua.push(true)?, - value => rawlua.push_value(&value)?, - }; + rawlua.push(this.loader(rawlua.lua(), &path, &chunk_name, contents)?)?; Ok(1) }) } @@ -495,6 +494,105 @@ unsafe fn write_to_buffer( } } +#[cfg(feature = "luau")] +pub fn create_require_function(lua: &Lua, require: R) -> Result { + unsafe extern "C-unwind" fn find_current_file(state: *mut ffi::lua_State) -> c_int { + let mut ar: ffi::lua_Debug = mem::zeroed(); + for level in 2.. { + if ffi::lua_getinfo(state, level, cstr!("s"), &mut ar) == 0 { + ffi::luaL_error(state, cstr!("require is not supported in this context")); + } + if CStr::from_ptr(ar.what) != c"C" { + break; + } + } + ffi::lua_pushstring(state, ar.source); + 1 + } + + unsafe extern "C-unwind" fn get_cache_key(state: *mut ffi::lua_State) -> c_int { + let requirer = ffi::lua_touserdata(state, ffi::lua_upvalueindex(1)) as *const Box; + let cache_key = (*requirer).cache_key(); + ffi::lua_pushlstring(state, cache_key.as_ptr() as *const _, cache_key.len()); + 1 + } + + let (get_cache_key, find_current_file, proxyrequire, registered_modules, loader_cache) = unsafe { + lua.exec_raw::<(Function, Function, Function, Table, Table)>((), move |state| { + let requirer_ptr = ffi::lua_newuserdata_t::>(state, Box::new(require)); + ffi::lua_pushcclosured(state, get_cache_key, cstr!("get_cache_key"), 1); + ffi::lua_pushcfunctiond(state, find_current_file, cstr!("find_current_file")); + ffi::luarequire_pushproxyrequire(state, init_config, requirer_ptr as *mut _); + ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_REGISTERED_MODULES_TABLE); + ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("__MLUA_LOADER_CACHE")); + }) + }?; + + unsafe extern "C-unwind" fn error(state: *mut ffi::lua_State) -> c_int { + ffi::luaL_where(state, 1); + ffi::lua_pushvalue(state, 1); + ffi::lua_concat(state, 2); + ffi::lua_error(state); + } + + unsafe extern "C-unwind" fn r#type(state: *mut ffi::lua_State) -> c_int { + ffi::lua_pushstring(state, ffi::lua_typename(state, ffi::lua_type(state, 1))); + 1 + } + + let (error, r#type) = unsafe { + lua.exec_raw::<(Function, Function)>((), move |state| { + ffi::lua_pushcfunctiond(state, error, cstr!("error")); + ffi::lua_pushcfunctiond(state, r#type, cstr!("type")); + }) + }?; + + // Prepare environment for the "require" function + let env = lua.create_table_with_capacity(0, 7)?; + env.raw_set("get_cache_key", get_cache_key)?; + env.raw_set("find_current_file", find_current_file)?; + env.raw_set("proxyrequire", proxyrequire)?; + env.raw_set("REGISTERED_MODULES", registered_modules)?; + env.raw_set("LOADER_CACHE", loader_cache)?; + env.raw_set("error", error)?; + env.raw_set("type", r#type)?; + + lua.load( + r#" + local path = ... + if type(path) ~= "string" then + error("bad argument #1 to 'require' (string expected, got " .. type(path) .. ")") + end + + -- Check if the module (path) is explicitly registered + local maybe_result = REGISTERED_MODULES[path] + if maybe_result ~= nil then + return maybe_result + end + + local loader = proxyrequire(path, find_current_file()) + local cache_key = get_cache_key() + -- Check if the loader result is already cached + local result = LOADER_CACHE[cache_key] + if result ~= nil then + return result + end + + -- Call the loader function and cache the result + result = loader() + if result == nil then + result = true + end + LOADER_CACHE[cache_key] = result + return result + "#, + ) + .try_cache() + .set_name("=__mlua_require") + .set_environment(env) + .into_function() +} + #[cfg(test)] mod tests { use std::path::Path; diff --git a/src/state.rs b/src/state.rs index 684eceb2..69b39e6d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -356,7 +356,7 @@ impl Lua { #[cfg(not(feature = "luau"))] const LOADED_MODULES_KEY: *const c_char = ffi::LUA_LOADED_TABLE; #[cfg(feature = "luau")] - const LOADED_MODULES_KEY: *const c_char = cstr!("_REGISTEREDMODULES"); + const LOADED_MODULES_KEY: *const c_char = ffi::LUA_REGISTERED_MODULES_TABLE; if cfg!(feature = "luau") && !modname.starts_with('@') { return Err(Error::runtime("module name must begin with '@'")); diff --git a/tests/luau/require.rs b/tests/luau/require.rs index f17835ba..a38b0cd6 100644 --- a/tests/luau/require.rs +++ b/tests/luau/require.rs @@ -1,6 +1,6 @@ use mlua::{IntoLua, Lua, Result, Value}; -fn run_require(lua: &Lua, path: &str) -> Result { +fn run_require(lua: &Lua, path: impl IntoLua) -> Result { lua.load(r#"return require(...)"#).call(path) } @@ -26,6 +26,12 @@ fn test_require_errors() { assert!( (res.unwrap_err().to_string()).contains("require path must start with a valid prefix: ./, ../, or @") ); + + // Pass non-string to require + let res = run_require(&lua, true); + assert!(res.is_err()); + assert!((res.unwrap_err().to_string()) + .contains("bad argument #1 to 'require' (string expected, got boolean)")); } #[test] @@ -100,3 +106,38 @@ fn test_require_with_config() { assert!(res.is_err()); assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias")); } + +#[cfg(feature = "async")] +#[tokio::test] +async fn test_async_require() -> Result<()> { + let lua = Lua::new(); + + let temp_dir = tempfile::tempdir().unwrap(); + let temp_path = temp_dir.path().join("async_chunk.luau"); + std::fs::write( + &temp_path, + r#" + sleep_ms(10) + return "result_after_async_sleep" + "#, + ) + .unwrap(); + + lua.globals().set( + "sleep_ms", + lua.create_async_function(|_, ms: u64| async move { + tokio::time::sleep(std::time::Duration::from_millis(ms)).await; + Ok(()) + })?, + )?; + + lua.load( + r#" + local result = require("./async_chunk") + assert(result == "result_after_async_sleep") + "#, + ) + .set_name(format!("@{}", temp_dir.path().join("require.rs").display())) + .exec_async() + .await +} From f9d9dc74a71dcb29fe38c2b84dc88dfaed54680b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 5 May 2025 13:16:26 +0100 Subject: [PATCH 392/635] Update CHANGELOG --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0366875..9882416e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## v0.10.4 (May 5th, 2025) + +- Luau updated to 0.672 +- New serde option `encode_empty_tables_as_array` to serialize empty tables as arrays +- Added `WeakLua` and `Lua::weak()` to create weak references to Lua state +- Trigger abort when Luau userdata destructors are panic (Luau GC does not support it) +- Added `AnyUserData::type_id()` method to get the type id of the userdata +- Added `Chunk::name()`, `Chunk::environment()` and `Chunk::mode()` functions +- Support borrowing underlying wrapped types for `UserDataRef` and `UserDataRefMut` (under `userdata-wrappers` feature) +- Added large (52bit) integers support for Luau +- Enable `serde` for `bstr` if `serialize` feature flag is enabled +- Recursive warnings (Lua 5.4) are no longer allowed +- Implemented `IntoLua`/`FromLua` for `BorrowedString` and `BorrowedBytes` +- Implemented `IntoLua`/`FromLua` for `char` +- Enable `Thread::reset()` for all Lua versions (limited support for 5.1-5.3) +- Bugfixes and improvements + ## v0.10.3 (Jan 27th, 2025) - Set `Default` for `Value` to be `Nil` From 5454cfa5edcc13edc14c3a7872791c7e7094e94c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 5 May 2025 13:25:38 +0100 Subject: [PATCH 393/635] mlua-sys: v0.7.0 --- Cargo.toml | 2 +- mlua-sys/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f1203439..da65c36c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ parking_lot = { version = "0.12", features = ["arc_lock"] } anyhow = { version = "1.0", optional = true } rustversion = "1.0" -ffi = { package = "mlua-sys", version = "0.6.6", path = "mlua-sys" } +ffi = { package = "mlua-sys", version = "0.7.0", path = "mlua-sys" } [dev-dependencies] trybuild = "1.0" diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index dcf709dd..4c1f8a00 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.6.7" +version = "0.7.0" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" From a4ff68a1207587de7e764a92bf53ee1c84753d36 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 5 May 2025 15:43:00 +0100 Subject: [PATCH 394/635] v0.10.4 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index da65c36c..1bab4f83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.10.3" # remember to update mlua_derive +version = "0.10.4" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.79.0" edition = "2021" From 8b7a85076a95c62e508e8a11b5212e7960e958d9 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 6 May 2025 15:45:48 +0100 Subject: [PATCH 395/635] mlua-sys: Prepare to 2024 edition --- mlua-sys/src/lib.rs | 3 ++- mlua-sys/src/lua51/lauxlib.rs | 4 ++-- mlua-sys/src/lua51/lua.rs | 8 ++++---- mlua-sys/src/lua51/lualib.rs | 2 +- mlua-sys/src/lua52/lauxlib.rs | 6 +++--- mlua-sys/src/lua52/lua.rs | 14 +++++++------- mlua-sys/src/lua52/lualib.rs | 2 +- mlua-sys/src/lua53/lauxlib.rs | 6 +++--- mlua-sys/src/lua53/lua.rs | 14 +++++++------- mlua-sys/src/lua53/lualib.rs | 2 +- mlua-sys/src/lua54/lauxlib.rs | 6 +++--- mlua-sys/src/lua54/lua.rs | 16 ++++++++-------- mlua-sys/src/lua54/lualib.rs | 2 +- mlua-sys/src/luau/compat.rs | 2 +- mlua-sys/src/luau/lauxlib.rs | 6 +++--- mlua-sys/src/luau/lua.rs | 20 ++++++++++---------- mlua-sys/src/luau/luacode.rs | 6 +++--- mlua-sys/src/luau/luacodegen.rs | 2 +- mlua-sys/src/luau/lualib.rs | 2 +- mlua-sys/src/luau/luarequire.rs | 2 +- 20 files changed, 63 insertions(+), 62 deletions(-) diff --git a/mlua-sys/src/lib.rs b/mlua-sys/src/lib.rs index 6bbb595c..730bf371 100644 --- a/mlua-sys/src/lib.rs +++ b/mlua-sys/src/lib.rs @@ -1,7 +1,8 @@ //! Low level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau. -#![allow(non_camel_case_types, non_snake_case, dead_code)] +#![allow(non_camel_case_types, non_snake_case)] #![allow(clippy::missing_safety_doc)] +#![allow(unsafe_op_in_unsafe_fn)] #![doc(test(attr(deny(warnings))))] #![cfg_attr(docsrs, feature(doc_cfg))] diff --git a/mlua-sys/src/lua51/lauxlib.rs b/mlua-sys/src/lua51/lauxlib.rs index 78cef292..b80b45fe 100644 --- a/mlua-sys/src/lua51/lauxlib.rs +++ b/mlua-sys/src/lua51/lauxlib.rs @@ -18,7 +18,7 @@ pub struct luaL_Reg { } #[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luaL_register(L: *mut lua_State, libname: *const c_char, l: *const luaL_Reg); #[link_name = "luaL_getmetafield"] pub fn luaL_getmetafield_(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int; @@ -61,7 +61,7 @@ pub const LUA_NOREF: c_int = -2; pub const LUA_REFNIL: c_int = -1; #[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int; pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int); diff --git a/mlua-sys/src/lua51/lua.rs b/mlua-sys/src/lua51/lua.rs index adbcd306..e47acd3b 100644 --- a/mlua-sys/src/lua51/lua.rs +++ b/mlua-sys/src/lua51/lua.rs @@ -90,7 +90,7 @@ pub type lua_Alloc = unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void; #[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { // // State manipulation // @@ -220,7 +220,7 @@ pub const LUA_GCSETPAUSE: c_int = 6; pub const LUA_GCSETSTEPMUL: c_int = 7; #[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int; } @@ -228,7 +228,7 @@ extern "C-unwind" { // Miscellaneous functions // #[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { #[link_name = "lua_error"] fn lua_error_(L: *mut lua_State) -> c_int; pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int; @@ -370,7 +370,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize); pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug); #[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int; pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int; pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char; diff --git a/mlua-sys/src/lua51/lualib.rs b/mlua-sys/src/lua51/lualib.rs index de994ce1..3ed0242f 100644 --- a/mlua-sys/src/lua51/lualib.rs +++ b/mlua-sys/src/lua51/lualib.rs @@ -21,7 +21,7 @@ pub const LUA_JITLIBNAME: *const c_char = cstr!("jit"); pub const LUA_FFILIBNAME: *const c_char = cstr!("ffi"); #[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luaopen_base(L: *mut lua_State) -> c_int; pub fn luaopen_table(L: *mut lua_State) -> c_int; pub fn luaopen_io(L: *mut lua_State) -> c_int; diff --git a/mlua-sys/src/lua52/lauxlib.rs b/mlua-sys/src/lua52/lauxlib.rs index fad19ebe..b4611d94 100644 --- a/mlua-sys/src/lua52/lauxlib.rs +++ b/mlua-sys/src/lua52/lauxlib.rs @@ -21,7 +21,7 @@ pub struct luaL_Reg { } #[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number); #[link_name = "luaL_getmetafield"] @@ -69,7 +69,7 @@ pub const LUA_NOREF: c_int = -2; pub const LUA_REFNIL: c_int = -1; #[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int; pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int); @@ -82,7 +82,7 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int { } #[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luaL_loadbufferx( L: *mut lua_State, buff: *const c_char, diff --git a/mlua-sys/src/lua52/lua.rs b/mlua-sys/src/lua52/lua.rs index a5fed2b7..e5239cee 100644 --- a/mlua-sys/src/lua52/lua.rs +++ b/mlua-sys/src/lua52/lua.rs @@ -95,7 +95,7 @@ pub type lua_Alloc = unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void; #[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { // // State manipulation // @@ -161,14 +161,14 @@ pub const LUA_OPLT: c_int = 1; pub const LUA_OPLE: c_int = 2; #[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn lua_arith(L: *mut lua_State, op: c_int); pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int; pub fn lua_compare(L: *mut lua_State, idx1: c_int, idx2: c_int, op: c_int) -> c_int; } #[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { // // Push functions (C -> stack) // @@ -257,7 +257,7 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in } #[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { // // Coroutine functions // @@ -289,12 +289,12 @@ pub const LUA_GCGEN: c_int = 10; pub const LUA_GCINC: c_int = 11; #[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int; } #[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { // // Miscellaneous functions // @@ -448,7 +448,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize); pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug); #[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int; pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int; pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char; diff --git a/mlua-sys/src/lua52/lualib.rs b/mlua-sys/src/lua52/lualib.rs index a9ed21f7..1b3c8445 100644 --- a/mlua-sys/src/lua52/lualib.rs +++ b/mlua-sys/src/lua52/lualib.rs @@ -15,7 +15,7 @@ pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); #[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luaopen_base(L: *mut lua_State) -> c_int; pub fn luaopen_coroutine(L: *mut lua_State) -> c_int; pub fn luaopen_table(L: *mut lua_State) -> c_int; diff --git a/mlua-sys/src/lua53/lauxlib.rs b/mlua-sys/src/lua53/lauxlib.rs index 53c45bd0..959c6367 100644 --- a/mlua-sys/src/lua53/lauxlib.rs +++ b/mlua-sys/src/lua53/lauxlib.rs @@ -21,7 +21,7 @@ pub struct luaL_Reg { } #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number, sz: usize); pub fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int; @@ -65,7 +65,7 @@ pub const LUA_NOREF: c_int = -2; pub const LUA_REFNIL: c_int = -1; #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int; pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int); @@ -78,7 +78,7 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int { } #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luaL_loadbufferx( L: *mut lua_State, buff: *const c_char, diff --git a/mlua-sys/src/lua53/lua.rs b/mlua-sys/src/lua53/lua.rs index efb85f0c..2729fdcd 100644 --- a/mlua-sys/src/lua53/lua.rs +++ b/mlua-sys/src/lua53/lua.rs @@ -102,7 +102,7 @@ pub type lua_Alloc = unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void; #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { // // State manipulation // @@ -172,14 +172,14 @@ pub const LUA_OPLT: c_int = 1; pub const LUA_OPLE: c_int = 2; #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn lua_arith(L: *mut lua_State, op: c_int); pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int; pub fn lua_compare(L: *mut lua_State, idx1: c_int, idx2: c_int, op: c_int) -> c_int; } #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { // // Push functions (C -> stack) // @@ -265,7 +265,7 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in } #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { // // Coroutine functions // @@ -300,12 +300,12 @@ pub const LUA_GCSETSTEPMUL: c_int = 7; pub const LUA_GCISRUNNING: c_int = 9; #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int; } #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { // // Miscellaneous functions // @@ -477,7 +477,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize); pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug); #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int; pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int; pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char; diff --git a/mlua-sys/src/lua53/lualib.rs b/mlua-sys/src/lua53/lualib.rs index 0b556d41..c29d8160 100644 --- a/mlua-sys/src/lua53/lualib.rs +++ b/mlua-sys/src/lua53/lualib.rs @@ -16,7 +16,7 @@ pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luaopen_base(L: *mut lua_State) -> c_int; pub fn luaopen_coroutine(L: *mut lua_State) -> c_int; pub fn luaopen_table(L: *mut lua_State) -> c_int; diff --git a/mlua-sys/src/lua54/lauxlib.rs b/mlua-sys/src/lua54/lauxlib.rs index 8a1fd12d..9c3de5b5 100644 --- a/mlua-sys/src/lua54/lauxlib.rs +++ b/mlua-sys/src/lua54/lauxlib.rs @@ -21,7 +21,7 @@ pub struct luaL_Reg { } #[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number, sz: usize); pub fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int; @@ -64,7 +64,7 @@ pub const LUA_NOREF: c_int = -2; pub const LUA_REFNIL: c_int = -1; #[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int; pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int); @@ -77,7 +77,7 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int { } #[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luaL_loadbufferx( L: *mut lua_State, buff: *const c_char, diff --git a/mlua-sys/src/lua54/lua.rs b/mlua-sys/src/lua54/lua.rs index 09c7c6a3..15a30444 100644 --- a/mlua-sys/src/lua54/lua.rs +++ b/mlua-sys/src/lua54/lua.rs @@ -104,7 +104,7 @@ pub type lua_Alloc = pub type lua_WarnFunction = unsafe extern "C-unwind" fn(ud: *mut c_void, msg: *const c_char, tocont: c_int); #[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { // // State manipulation // @@ -186,14 +186,14 @@ pub const LUA_OPLT: c_int = 1; pub const LUA_OPLE: c_int = 2; #[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn lua_arith(L: *mut lua_State, op: c_int); pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int; pub fn lua_compare(L: *mut lua_State, idx1: c_int, idx2: c_int, op: c_int) -> c_int; } #[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { // // Push functions (C -> stack) // @@ -279,7 +279,7 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in } #[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { // // Coroutine functions // @@ -303,7 +303,7 @@ pub unsafe fn lua_yield(L: *mut lua_State, n: c_int) -> c_int { // Warning-related functions // #[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn lua_setwarnf(L: *mut lua_State, f: Option, ud: *mut c_void); pub fn lua_warning(L: *mut lua_State, msg: *const c_char, tocont: c_int); } @@ -324,12 +324,12 @@ pub const LUA_GCGEN: c_int = 10; pub const LUA_GCINC: c_int = 11; #[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn lua_gc(L: *mut lua_State, what: c_int, ...) -> c_int; } #[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { // // Miscellaneous functions // @@ -519,7 +519,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize); pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug); #[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int; pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int; pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char; diff --git a/mlua-sys/src/lua54/lualib.rs b/mlua-sys/src/lua54/lualib.rs index dec577bd..0f666643 100644 --- a/mlua-sys/src/lua54/lualib.rs +++ b/mlua-sys/src/lua54/lualib.rs @@ -15,7 +15,7 @@ pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); #[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luaopen_base(L: *mut lua_State) -> c_int; pub fn luaopen_coroutine(L: *mut lua_State) -> c_int; pub fn luaopen_table(L: *mut lua_State) -> c_int; diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index 41212934..f4c39ea6 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -368,7 +368,7 @@ pub unsafe fn luaL_loadbufferenv( mode: *const c_char, mut env: c_int, ) -> c_int { - extern "C" { + unsafe extern "C" { fn free(p: *mut c_void); } diff --git a/mlua-sys/src/luau/lauxlib.rs b/mlua-sys/src/luau/lauxlib.rs index 845bb285..5aabf034 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_Number, lua_State, lua_Unsigned, LUA_REGISTRYINDEX}; +use super::lua::{self, LUA_REGISTRYINDEX, lua_CFunction, lua_Number, lua_State, lua_Unsigned}; #[repr(C)] pub struct luaL_Reg { @@ -11,7 +11,7 @@ pub struct luaL_Reg { pub func: lua_CFunction, } -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luaL_register(L: *mut lua_State, libname: *const c_char, l: *const luaL_Reg); #[link_name = "luaL_getmetafield"] pub fn luaL_getmetafield_(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int; @@ -182,7 +182,7 @@ pub struct luaL_Strbuf { // For compatibility pub type luaL_Buffer = luaL_Strbuf; -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Strbuf); pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Strbuf, size: usize) -> *mut c_char; pub fn luaL_prepbuffsize(B: *mut luaL_Strbuf, size: usize) -> *mut c_char; diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 017ea664..8a55eef1 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -12,10 +12,10 @@ pub const LUA_MULTRET: c_int = -1; const LUAI_MAXCSTACK: c_int = 1000000; // Number of valid Lua userdata tags -const LUA_UTAG_LIMIT: c_int = 128; +pub const LUA_UTAG_LIMIT: c_int = 128; // Number of valid Lua lightuserdata tags -const LUA_LUTAG_LIMIT: c_int = 128; +pub const LUA_LUTAG_LIMIT: c_int = 128; // // Pseudo-indices @@ -95,7 +95,7 @@ pub const fn luau_version() -> Option<&'static str> { option_env!("LUAU_VERSION") } -extern "C-unwind" { +unsafe extern "C-unwind" { // // State manipulation // @@ -264,14 +264,14 @@ pub const LUA_GCSETGOAL: c_int = 7; pub const LUA_GCSETSTEPMUL: c_int = 8; pub const LUA_GCSETSTEPSIZE: c_int = 9; -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int; } // // Memory statistics // -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn lua_setmemcat(L: *mut lua_State, category: c_int); pub fn lua_totalbytes(L: *mut lua_State, category: c_int) -> usize; } @@ -279,7 +279,7 @@ extern "C-unwind" { // // Miscellaneous functions // -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn lua_error(L: *mut lua_State) -> !; pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int; pub fn lua_rawiter(L: *mut lua_State, idx: c_int, iter: c_int) -> c_int; @@ -304,7 +304,7 @@ extern "C-unwind" { pub const LUA_NOREF: c_int = -1; pub const LUA_REFNIL: c_int = 0; -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn lua_ref(L: *mut lua_State, idx: c_int) -> c_int; pub fn lua_unref(L: *mut lua_State, r#ref: c_int); } @@ -470,7 +470,7 @@ pub type lua_Coverage = unsafe extern "C-unwind" fn( size: usize, ); -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn lua_stackdepth(L: *mut lua_State) -> c_int; pub fn lua_getinfo(L: *mut lua_State, level: c_int, what: *const c_char, ar: *mut lua_Debug) -> c_int; pub fn lua_getargument(L: *mut lua_State, level: c_int, n: c_int) -> c_int; @@ -536,12 +536,12 @@ pub struct lua_Callbacks { pub onallocate: Option, } -extern "C" { +unsafe extern "C" { pub fn lua_callbacks(L: *mut lua_State) -> *mut lua_Callbacks; } // Functions from customization lib -extern "C" { +unsafe extern "C" { pub fn luau_setfflag(name: *const c_char, value: c_int) -> c_int; pub fn lua_getmetatablepointer(L: *mut lua_State, idx: c_int) -> *const c_void; } diff --git a/mlua-sys/src/luau/luacode.rs b/mlua-sys/src/luau/luacode.rs index 425f2d2d..cd7ec376 100644 --- a/mlua-sys/src/luau/luacode.rs +++ b/mlua-sys/src/luau/luacode.rs @@ -76,7 +76,7 @@ pub type lua_LibraryMemberConstantCallback = unsafe extern "C-unwind" fn( constant: *mut lua_CompileConstant, ); -extern "C" { +unsafe extern "C" { pub fn luau_set_compile_constant_nil(cons: *mut lua_CompileConstant); pub fn luau_set_compile_constant_boolean(cons: *mut lua_CompileConstant, b: c_int); pub fn luau_set_compile_constant_number(cons: *mut lua_CompileConstant, n: f64); @@ -84,7 +84,7 @@ extern "C" { pub fn luau_set_compile_constant_string(cons: *mut lua_CompileConstant, s: *const c_char, l: usize); } -extern "C-unwind" { +unsafe extern "C-unwind" { #[link_name = "luau_compile"] pub fn luau_compile_( source: *const c_char, @@ -94,7 +94,7 @@ extern "C-unwind" { ) -> *mut c_char; } -extern "C" { +unsafe extern "C" { fn free(p: *mut c_void); } diff --git a/mlua-sys/src/luau/luacodegen.rs b/mlua-sys/src/luau/luacodegen.rs index ebbddc9a..9e063ed2 100644 --- a/mlua-sys/src/luau/luacodegen.rs +++ b/mlua-sys/src/luau/luacodegen.rs @@ -4,7 +4,7 @@ use std::os::raw::c_int; use super::lua::lua_State; -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luau_codegen_supported() -> c_int; pub fn luau_codegen_create(state: *mut lua_State); pub fn luau_codegen_compile(state: *mut lua_State, idx: c_int); diff --git a/mlua-sys/src/luau/lualib.rs b/mlua-sys/src/luau/lualib.rs index 834f09e6..a28a2a61 100644 --- a/mlua-sys/src/luau/lualib.rs +++ b/mlua-sys/src/luau/lualib.rs @@ -15,7 +15,7 @@ pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); pub const LUA_VECLIBNAME: *const c_char = cstr!("vector"); -extern "C-unwind" { +unsafe extern "C-unwind" { pub fn luaopen_base(L: *mut lua_State) -> c_int; pub fn luaopen_coroutine(L: *mut lua_State) -> c_int; pub fn luaopen_table(L: *mut lua_State) -> c_int; diff --git a/mlua-sys/src/luau/luarequire.rs b/mlua-sys/src/luau/luarequire.rs index dcdd0b83..7225cb9b 100644 --- a/mlua-sys/src/luau/luarequire.rs +++ b/mlua-sys/src/luau/luarequire.rs @@ -117,7 +117,7 @@ pub struct luarequire_Configuration { // Populates function pointers in the given luarequire_Configuration. pub type luarequire_Configuration_init = unsafe extern "C" fn(config: *mut luarequire_Configuration); -extern "C-unwind" { +unsafe extern "C-unwind" { // Initializes and pushes the require closure onto the stack without registration. pub fn luarequire_pushrequire( L: *mut lua_State, From da08f7210f6b0818de1aee6b1ce159a880d2ea6c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 6 May 2025 15:48:52 +0100 Subject: [PATCH 396/635] Revert disabling "module" feature flag for Luau in mlua-sys Dynamic linking should still be okay --- mlua-sys/build/main_inner.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mlua-sys/build/main_inner.rs b/mlua-sys/build/main_inner.rs index 4f293e1c..05ac53b5 100644 --- a/mlua-sys/build/main_inner.rs +++ b/mlua-sys/build/main_inner.rs @@ -11,8 +11,8 @@ cfg_if::cfg_if! { } fn main() { - #[cfg(all(feature = "luau", feature = "module"))] - compile_error!("Luau does not support `module` mode"); + #[cfg(all(feature = "luau", feature = "module", windows))] + compile_error!("Luau does not support `module` mode on Windows"); #[cfg(all(feature = "module", feature = "vendored"))] compile_error!("`vendored` and `module` features are mutually exclusive"); From 5dc6c3214b7b4ccda02b9743f4f9108e9fc0277e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 6 May 2025 21:28:21 +0100 Subject: [PATCH 397/635] Prepare for 2024 edition --- mlua-sys/src/luau/lauxlib.rs | 2 +- src/function.rs | 36 +++++++++++++++++++++++++++++++----- src/lib.rs | 1 + src/util/error.rs | 2 +- tests/tests.rs | 8 +++++--- tests/thread.rs | 2 +- 6 files changed, 40 insertions(+), 11 deletions(-) diff --git a/mlua-sys/src/luau/lauxlib.rs b/mlua-sys/src/luau/lauxlib.rs index 5aabf034..ab850892 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_REGISTRYINDEX, lua_CFunction, lua_Number, lua_State, lua_Unsigned}; +use super::lua::{self, lua_CFunction, lua_Number, lua_State, lua_Unsigned, LUA_REGISTRYINDEX}; #[repr(C)] pub struct luaL_Reg { diff --git a/src/function.rs b/src/function.rs index 08dbc832..ef39b2c3 100644 --- a/src/function.rs +++ b/src/function.rs @@ -14,9 +14,12 @@ use crate::value::Value; #[cfg(feature = "async")] use { + crate::thread::AsyncThread, crate::traits::LuaNativeAsyncFn, crate::types::AsyncCallback, std::future::{self, Future}, + std::pin::Pin, + std::task::{Context, Poll}, }; /// Handle to an internal Lua function. @@ -128,7 +131,8 @@ impl Function { /// Returns a future that, when polled, calls `self`, passing `args` as function arguments, /// and drives the execution. /// - /// Internally it wraps the function to an [`AsyncThread`]. + /// Internally it wraps the function to an [`AsyncThread`]. The returned type implements + /// `Future>` and can be awaited. /// /// Requires `feature = "async"` /// @@ -155,19 +159,18 @@ impl Function { /// [`AsyncThread`]: crate::AsyncThread #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> + pub fn call_async(&self, args: impl IntoLuaMulti) -> AsyncCallFuture where R: FromLuaMulti, { let lua = self.0.lua.lock(); - let thread_res = unsafe { + AsyncCallFuture(unsafe { lua.create_recycled_thread(self).and_then(|th| { let mut th = th.into_async(args)?; th.set_recyclable(true); Ok(th) }) - }; - async move { thread_res?.await } + }) } /// Returns a function that, when called, calls `self`, passing `args` as the first set of @@ -644,6 +647,26 @@ impl LuaType for Function { const TYPE_ID: c_int = ffi::LUA_TFUNCTION; } +#[cfg(feature = "async")] +pub struct AsyncCallFuture(Result>); + +#[cfg(feature = "async")] +impl Future for AsyncCallFuture { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Safety: We're not moving any pinned data + let this = unsafe { self.get_unchecked_mut() }; + match &mut this.0 { + Ok(thread) => { + let pinned_thread = unsafe { Pin::new_unchecked(thread) }; + pinned_thread.poll(cx) + } + Err(err) => Poll::Ready(Err(err.clone())), + } + } +} + #[cfg(test)] mod assertions { use super::*; @@ -652,4 +675,7 @@ mod assertions { static_assertions::assert_not_impl_any!(Function: Send); #[cfg(feature = "send")] static_assertions::assert_impl_all!(Function: Send, Sync); + + #[cfg(all(feature = "async", feature = "send"))] + static_assertions::assert_impl_all!(AsyncCallFuture<()>: Send); } diff --git a/src/lib.rs b/src/lib.rs index 213a7ea7..ae9c4171 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,6 +67,7 @@ #![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(not(send), allow(clippy::arc_with_non_send_sync))] #![allow(clippy::ptr_eq)] +#![allow(unsafe_op_in_unsafe_fn)] #[macro_use] mod macros; diff --git a/src/util/error.rs b/src/util/error.rs index ef9184e2..899522de 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -315,7 +315,7 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<( let _ = write!(&mut (*err_buf), "{error}"); Ok(err_buf) } - Some(WrappedFailure::Panic(Some(ref panic))) => { + Some(WrappedFailure::Panic(Some(panic))) => { let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void; ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key); let err_buf = ffi::lua_touserdata(state, -1) as *mut String; diff --git a/tests/tests.rs b/tests/tests.rs index 61021ad9..7bf40af5 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -944,9 +944,11 @@ fn test_rust_function() -> Result<()> { fn test_c_function() -> Result<()> { let lua = Lua::new(); - unsafe extern "C-unwind" fn c_function(state: *mut mlua::lua_State) -> std::os::raw::c_int { - ffi::lua_pushboolean(state, 1); - ffi::lua_setglobal(state, b"c_function\0" as *const _ as *const _); + extern "C-unwind" fn c_function(state: *mut mlua::lua_State) -> std::os::raw::c_int { + unsafe { + ffi::lua_pushboolean(state, 1); + ffi::lua_setglobal(state, b"c_function\0" as *const _ as *const _); + } 0 } diff --git a/tests/thread.rs b/tests/thread.rs index 560dcd3f..4cb6ab10 100644 --- a/tests/thread.rs +++ b/tests/thread.rs @@ -164,7 +164,7 @@ fn test_thread_reset() -> Result<()> { let result = thread.resume::<()>(()); assert!( matches!(result, Err(Error::CallbackError{ ref cause, ..}) - if matches!(cause.as_ref(), Error::RuntimeError(ref err) + if matches!(cause.as_ref(), Error::RuntimeError(err) if err == "cannot reset a running thread") ), "unexpected result: {result:?}", From 93adfa42d1b375e0540d8b2de802e2e5fe6668da Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 6 May 2025 22:36:23 +0100 Subject: [PATCH 398/635] Update doc comments --- src/chunk.rs | 8 -------- src/function.rs | 6 ------ src/hook.rs | 12 ++++++++---- src/serde/mod.rs | 12 ------------ src/state.rs | 23 ----------------------- src/stdlib.rs | 19 +++++++++---------- src/table.rs | 6 ------ src/thread.rs | 6 ------ src/traits.rs | 4 ---- src/userdata.rs | 48 ++++++++++++++++++++---------------------------- 10 files changed, 37 insertions(+), 107 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index 41db2445..022fa517 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -482,8 +482,6 @@ impl Chunk<'_> { /// Sets or overwrites a Luau compiler used for this chunk. /// /// See [`Compiler`] for details and possible options. - /// - /// Requires `feature = "luau"` #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn set_compiler(mut self, compiler: Compiler) -> Self { @@ -502,8 +500,6 @@ impl Chunk<'_> { /// /// See [`exec`] for more details. /// - /// Requires `feature = "async"` - /// /// [`exec`]: Chunk::exec #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] @@ -534,8 +530,6 @@ impl Chunk<'_> { /// /// See [`eval`] for more details. /// - /// Requires `feature = "async"` - /// /// [`eval`]: Chunk::eval #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] @@ -563,8 +557,6 @@ impl Chunk<'_> { /// /// See [`call`] for more details. /// - /// Requires `feature = "async"` - /// /// [`call`]: Chunk::call #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] diff --git a/src/function.rs b/src/function.rs index ef39b2c3..0fae332e 100644 --- a/src/function.rs +++ b/src/function.rs @@ -134,8 +134,6 @@ impl Function { /// Internally it wraps the function to an [`AsyncThread`]. The returned type implements /// `Future>` and can be awaited. /// - /// Requires `feature = "async"` - /// /// # Examples /// /// ``` @@ -433,8 +431,6 @@ impl Function { /// /// Recording of coverage information is controlled by [`Compiler::set_coverage_level`] option. /// - /// Requires `feature = "luau"` - /// /// [`Compiler::set_coverage_level`]: crate::chunk::Compiler::set_coverage_level #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] @@ -494,8 +490,6 @@ impl Function { /// Copies the function prototype and all its upvalues to the /// newly created function. /// This function returns shallow clone (same handle) for Rust/C functions. - /// - /// Requires `feature = "luau"` #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn deep_clone(&self) -> Self { diff --git a/src/hook.rs b/src/hook.rs index d650dd81..4c8c2569 100644 --- a/src/hook.rs +++ b/src/hook.rs @@ -265,14 +265,18 @@ pub struct DebugStack { /// Number of upvalues. pub num_ups: u8, /// Number of parameters. - /// - /// Requires `feature = "lua54/lua53/lua52/luau"` #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] + #[cfg_attr( + docsrs, + doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))) + )] pub num_params: u8, /// Whether the function is a vararg function. - /// - /// Requires `feature = "lua54/lua53/lua52/luau"` #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] + #[cfg_attr( + docsrs, + doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))) + )] pub is_vararg: bool, } diff --git a/src/serde/mod.rs b/src/serde/mod.rs index 2ba30e56..f4752145 100644 --- a/src/serde/mod.rs +++ b/src/serde/mod.rs @@ -17,8 +17,6 @@ use crate::value::Value; pub trait LuaSerdeExt: Sealed { /// A special value (lightuserdata) to encode/decode optional (none) values. /// - /// Requires `feature = "serialize"` - /// /// # Example /// /// ``` @@ -42,8 +40,6 @@ pub trait LuaSerdeExt: Sealed { /// As result, encoded Array will contain only sequence part of the table, with the same length /// as the `#` operator on that table. /// - /// Requires `feature = "serialize"` - /// /// # Example /// /// ``` @@ -71,8 +67,6 @@ pub trait LuaSerdeExt: Sealed { /// Converts `T` into a [`Value`] instance. /// - /// Requires `feature = "serialize"` - /// /// [`Value`]: crate::Value /// /// # Example @@ -104,8 +98,6 @@ pub trait LuaSerdeExt: Sealed { /// Converts `T` into a [`Value`] instance with options. /// - /// Requires `feature = "serialize"` - /// /// # Example /// /// ``` @@ -129,8 +121,6 @@ pub trait LuaSerdeExt: Sealed { /// Deserializes a [`Value`] into any serde deserializable object. /// - /// Requires `feature = "serialize"` - /// /// # Example /// /// ``` @@ -158,8 +148,6 @@ pub trait LuaSerdeExt: Sealed { /// Deserializes a [`Value`] into any serde deserializable object with options. /// - /// Requires `feature = "serialize"` - /// /// # Example /// /// ``` diff --git a/src/state.rs b/src/state.rs index 69b39e6d..53a19e6c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -74,7 +74,6 @@ pub(crate) struct LuaGuard(ArcReentrantMutexGuard); #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum GCMode { Incremental, - /// Requires `feature = "lua54"` #[cfg(feature = "lua54")] #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] Generational, @@ -511,8 +510,6 @@ impl Lua { /// # #[cfg(not(feature = "luau"))] /// # fn main() {} /// ``` - /// - /// Requires `feature = "luau"` #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn sandbox(&self, enabled: bool) -> Result<()> { @@ -812,8 +809,6 @@ impl Lua { } /// Sets the warning function to be used by Lua to emit warnings. - /// - /// Requires `feature = "lua54"` #[cfg(feature = "lua54")] #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] pub fn set_warning_function(&self, callback: F) @@ -847,8 +842,6 @@ impl Lua { /// Removes warning function previously set by `set_warning_function`. /// /// This function has no effect if a warning function was not previously set. - /// - /// Requires `feature = "lua54"` #[cfg(feature = "lua54")] #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] pub fn remove_warning_function(&self) { @@ -863,8 +856,6 @@ impl Lua { /// /// A message in a call with `incomplete` set to `true` should be continued in /// another call to this function. - /// - /// Requires `feature = "lua54"` #[cfg(feature = "lua54")] #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] pub fn warning(&self, msg: impl AsRef, incomplete: bool) { @@ -939,8 +930,6 @@ impl Lua { } /// Returns `true` if the garbage collector is currently running automatically. - /// - /// Requires `feature = "lua54/lua53/lua52/luau"` #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] pub fn gc_is_running(&self) -> bool { let lua = self.lock(); @@ -1078,8 +1067,6 @@ impl Lua { /// Returns the previous mode. More information about the generational GC /// can be found in the Lua 5.4 [documentation][lua_doc]. /// - /// Requires `feature = "lua54"` - /// /// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#2.5.2 #[cfg(feature = "lua54")] #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] @@ -1100,8 +1087,6 @@ impl Lua { /// including via `require` function. /// /// See [`Compiler`] for details and possible options. - /// - /// Requires `feature = "luau"` #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn set_compiler(&self, compiler: Compiler) { @@ -1176,8 +1161,6 @@ impl Lua { /// Create and return a Luau [buffer] object from a byte slice of data. /// - /// Requires `feature = "luau"` - /// /// [buffer]: https://luau.org/library#buffer-library #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] @@ -1342,8 +1325,6 @@ impl Lua { /// /// The family of `call_async()` functions takes care about creating [`Thread`]. /// - /// Requires `feature = "async"` - /// /// # Examples /// /// Non blocking sleep: @@ -1409,8 +1390,6 @@ impl Lua { } /// Creates a Lua userdata object from a custom serializable userdata type. - /// - /// Requires `feature = "serialize"` #[cfg(feature = "serialize")] #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] #[inline] @@ -1439,8 +1418,6 @@ impl Lua { /// Creates a Lua userdata object from a custom serializable Rust type. /// /// See [`Lua::create_any_userdata`] for more details. - /// - /// Requires `feature = "serialize"` #[cfg(feature = "serialize")] #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] #[inline] diff --git a/src/stdlib.rs b/src/stdlib.rs index c6a26af0..78cf772d 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -6,9 +6,11 @@ pub struct StdLib(u32); impl StdLib { /// [`coroutine`](https://www.lua.org/manual/5.4/manual.html#6.2) library - /// - /// Requires `feature = "lua54/lua53/lua52/luau"` #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] + #[cfg_attr( + docsrs, + doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))) + )] pub const COROUTINE: StdLib = StdLib(1); /// [`table`](https://www.lua.org/manual/5.4/manual.html#6.6) library @@ -26,15 +28,16 @@ impl StdLib { pub const STRING: StdLib = StdLib(1 << 4); /// [`utf8`](https://www.lua.org/manual/5.4/manual.html#6.5) library - /// - /// Requires `feature = "lua54/lua53/luau"` #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))))] pub const UTF8: StdLib = StdLib(1 << 5); /// [`bit`](https://www.lua.org/manual/5.2/manual.html#6.7) library - /// - /// Requires `feature = "lua52/luajit/luau"` #[cfg(any(feature = "lua52", feature = "luajit", feature = "luau", doc))] + #[cfg_attr( + docsrs, + doc(cfg(any(feature = "lua52", feature = "luajit", feature = "luau"))) + )] pub const BIT: StdLib = StdLib(1 << 6); /// [`math`](https://www.lua.org/manual/5.4/manual.html#6.7) library @@ -56,15 +59,11 @@ impl StdLib { pub const VECTOR: StdLib = StdLib(1 << 10); /// [`jit`](http://luajit.org/ext_jit.html) library - /// - /// Requires `feature = "luajit"` #[cfg(any(feature = "luajit", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luajit")))] pub const JIT: StdLib = StdLib(1 << 11); /// (**unsafe**) [`ffi`](http://luajit.org/ext_ffi.html) library - /// - /// Requires `feature = "luajit"` #[cfg(any(feature = "luajit", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luajit")))] pub const FFI: StdLib = StdLib(1 << 30); diff --git a/src/table.rs b/src/table.rs index aa8e680d..92228683 100644 --- a/src/table.rs +++ b/src/table.rs @@ -537,8 +537,6 @@ impl Table { } /// Sets `readonly` attribute on the table. - /// - /// Requires `feature = "luau"` #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn set_readonly(&self, enabled: bool) { @@ -554,8 +552,6 @@ impl Table { } /// Returns `readonly` attribute of the table. - /// - /// Requires `feature = "luau"` #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn is_readonly(&self) -> bool { @@ -573,8 +569,6 @@ impl Table { /// - Fast-path for some built-in functions (fastcall). /// /// For `safeenv` environments, monkey patching or modifying values may not work as expected. - /// - /// Requires `feature = "luau"` #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn set_safeenv(&self, enabled: bool) { diff --git a/src/thread.rs b/src/thread.rs index 14ff04ad..39394cee 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -79,8 +79,6 @@ unsafe impl Sync for Thread {} /// Thread (coroutine) representation as an async [`Future`] or [`Stream`]. /// -/// Requires `feature = "async"` -/// /// [`Future`]: std::future::Future /// [`Stream`]: futures_util::stream::Stream #[cfg(feature = "async")] @@ -356,8 +354,6 @@ impl Thread { /// values whereas [`Future`] version discards that values and poll until the final /// one (returned from the thread function). /// - /// Requires `feature = "async"` - /// /// [`Future`]: std::future::Future /// [`Stream`]: futures_util::stream::Stream /// [`resume`]: https://www.lua.org/manual/5.4/manual.html#lua_resume @@ -455,8 +451,6 @@ impl Thread { /// # #[cfg(not(feature = "luau"))] /// # fn main() { } /// ``` - /// - /// Requires `feature = "luau"` #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[doc(hidden)] diff --git a/src/traits.rs b/src/traits.rs index 794e9c3b..02373e2f 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -175,8 +175,6 @@ pub trait ObjectLike: Sealed { /// Gets the function associated to key `name` from the object and asynchronously calls it, /// passing the object itself along with `args` as function arguments. /// - /// Requires `feature = "async"` - /// /// This might invoke the `__index` metamethod. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] @@ -195,8 +193,6 @@ pub trait ObjectLike: Sealed { /// Gets the function associated to key `name` from the object and asynchronously calls it, /// passing `args` as function arguments. /// - /// Requires `feature = "async"` - /// /// This might invoke the `__index` metamethod. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] diff --git a/src/userdata.rs b/src/userdata.rs index f8d5ebec..2db8fa56 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -56,30 +56,32 @@ pub enum MetaMethod { /// The unary minus (`-`) operator. Unm, /// The floor division (//) operator. - /// Requires `feature = "lua54/lua53/luau"` #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))))] IDiv, /// The bitwise AND (&) operator. - /// Requires `feature = "lua54/lua53"` #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))] BAnd, /// The bitwise OR (|) operator. - /// Requires `feature = "lua54/lua53"` #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))] BOr, /// The bitwise XOR (binary ~) operator. - /// Requires `feature = "lua54/lua53"` #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))] BXor, /// The bitwise NOT (unary ~) operator. - /// Requires `feature = "lua54/lua53"` #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))] BNot, /// The bitwise left shift (<<) operator. #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))] Shl, /// The bitwise right shift (>>) operator. #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))] Shr, /// The string concatenation operator `..`. Concat, @@ -104,16 +106,16 @@ pub enum MetaMethod { /// The `__pairs` 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", feature = "luajit52",))] + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] + #[cfg_attr( + docsrs, + doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))) + )] Pairs, /// The `__ipairs` metamethod. /// /// This is not an operator, but it will be called by the built-in [`ipairs`] function. /// - /// Requires `feature = "lua52"` - /// /// [`ipairs`]: https://www.lua.org/manual/5.2/manual.html#pdf-ipairs #[cfg(any(feature = "lua52", feature = "luajit52", doc))] #[cfg_attr(docsrs, doc(cfg(any(feature = "lua52", feature = "luajit52"))))] @@ -122,8 +124,6 @@ pub enum MetaMethod { /// /// Executed before the iteration begins, and should return an iterator function like `next` /// (or a custom one). - /// - /// Requires `feature = "lua"` #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] Iter, @@ -134,8 +134,6 @@ pub enum MetaMethod { /// More information about to-be-closed variables can be found in the Lua 5.4 /// [documentation][lua_doc]. /// - /// Requires `feature = "lua54"` - /// /// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#3.3.8 #[cfg(feature = "lua54")] #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] @@ -272,8 +270,6 @@ pub trait UserDataMethods { /// /// Refer to [`add_method`] for more information about the implementation. /// - /// Requires `feature = "async"` - /// /// [`add_method`]: UserDataMethods::add_method #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] @@ -289,8 +285,6 @@ pub trait UserDataMethods { /// /// Refer to [`add_method`] for more information about the implementation. /// - /// Requires `feature = "async"` - /// /// [`add_method`]: UserDataMethods::add_method #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] @@ -329,8 +323,6 @@ pub trait UserDataMethods { /// /// This is an async version of [`add_function`]. /// - /// Requires `feature = "async"` - /// /// [`add_function`]: UserDataMethods::add_function #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] @@ -373,11 +365,12 @@ pub trait UserDataMethods { /// /// This is an async version of [`add_meta_method`]. /// - /// Requires `feature = "async"` - /// /// [`add_meta_method`]: UserDataMethods::add_meta_method #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + #[cfg_attr( + docsrs, + doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))) + )] fn add_async_meta_method(&mut self, name: impl ToString, method: M) where T: 'static, @@ -391,8 +384,6 @@ pub trait UserDataMethods { /// /// This is an async version of [`add_meta_method_mut`]. /// - /// Requires `feature = "async"` - /// /// [`add_meta_method_mut`]: UserDataMethods::add_meta_method_mut #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] @@ -430,11 +421,12 @@ pub trait UserDataMethods { /// /// This is an async version of [`add_meta_function`]. /// - /// Requires `feature = "async"` - /// /// [`add_meta_function`]: UserDataMethods::add_meta_function #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + #[cfg_attr( + docsrs, + doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))) + )] fn add_async_meta_function(&mut self, name: impl ToString, function: F) where F: Fn(Lua, A) -> FR + MaybeSend + 'static, From dda344bfc6d7af645789afe39d53448cb3618997 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 6 May 2025 22:42:19 +0100 Subject: [PATCH 399/635] Rename _typos.toml to typos.toml --- .github/workflows/typos.yml | 4 ++-- _typos.toml => typos.toml | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename _typos.toml => typos.toml (100%) diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 1056ec10..c904d668 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -2,7 +2,7 @@ name: Typos Check on: pull_request: workflow_dispatch: - + jobs: run: name: Spell Check with Typos @@ -14,4 +14,4 @@ jobs: - name: Check spelling uses: crate-ci/typos@master with: - config: ./_typos.toml + config: ./typos.toml diff --git a/_typos.toml b/typos.toml similarity index 100% rename from _typos.toml rename to typos.toml From c0c802262e3fe3767ccf5267e6393ec26ef92df3 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 6 May 2025 22:48:48 +0100 Subject: [PATCH 400/635] Add must_use to AsyncCallFuture --- src/function.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/function.rs b/src/function.rs index 0fae332e..916bf187 100644 --- a/src/function.rs +++ b/src/function.rs @@ -642,6 +642,7 @@ impl LuaType for Function { } #[cfg(feature = "async")] +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct AsyncCallFuture(Result>); #[cfg(feature = "async")] From 9b45663afdffbf0ae4aa179a296406fd51490b36 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 6 May 2025 23:21:46 +0100 Subject: [PATCH 401/635] v0.11.0-beta.1 --- CHANGELOG.md | 15 +++++++++++++++ Cargo.toml | 4 ++-- mlua_derive/Cargo.toml | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9882416e..a2c6f450 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## v0.11.0-beta.1 (May 7th, 2025) + +- New "require-by-string" for Luau (with `Require` trait and async support) +- Added `Thread::resume_error` support for Luau +- 52 bit integers support for Luau (this is a breaking change) +- New features for Luau compiler (constants, disabled builtins, known members) +- `AsyncThread` changed to `AsyncThread` (`A` pushed to stack immediately) +- Lifetime `'a` moved from `AsChunk<'a>` to `AsChunk::source where Self: 'a` +- `Lua::scope` pass `&Scope` instead of `&mut Scope` to closure +- Added global hooks support (Lua 5.1+) +- Added per-thread hooks support (Lua 5.1+) +- `Lua::init_from_ptr` renamed to `Lua::get_or_init_from_ptr` and returns `&Lua` +- `Lua:load_from_function` is deprecated (this is `register_module` now) +- Added `Lua::register_module` and `Lua::preload_module` + ## v0.10.4 (May 5th, 2025) - Luau updated to 0.672 diff --git a/Cargo.toml b/Cargo.toml index 1bab4f83..deb648f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.10.4" # remember to update mlua_derive +version = "0.11.0-beta.1" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.79.0" edition = "2021" @@ -46,7 +46,7 @@ anyhow = ["dep:anyhow", "error-send"] userdata-wrappers = [] [dependencies] -mlua_derive = { version = "=0.10.1", optional = true, path = "mlua_derive" } +mlua_derive = { version = "=0.11.0-beta.1", optional = true, path = "mlua_derive" } bstr = { version = "1.0", features = ["std"], default-features = false } either = "1.0" num-traits = { version = "0.2.14" } diff --git a/mlua_derive/Cargo.toml b/mlua_derive/Cargo.toml index 59b96e84..2f11887b 100644 --- a/mlua_derive/Cargo.toml +++ b/mlua_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua_derive" -version = "0.10.1" +version = "0.11.0-beta.1" authors = ["Aleksandr Orlenko "] edition = "2021" description = "Procedural macros for the mlua crate." From f16aca687dbdd9e581ce10200f86889614e8eeb5 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 9 May 2025 12:48:40 +0100 Subject: [PATCH 402/635] Terminate underlying Rust future when `AsyncThread` is dropped. Before this change, Lua GC was responsible to collect and destroy the future if `AsyncThread` dropped in yielded state. Now we will propagate "drop" event immediately so Lua GC need to only free the memory. --- src/state.rs | 7 +++++ src/state/raw.rs | 64 +++++++++++++--------------------------- src/thread.rs | 76 +++++++++++++++++++++++++++++++++--------------- src/types.rs | 2 +- tests/async.rs | 14 ++++++++- 5 files changed, 94 insertions(+), 69 deletions(-) diff --git a/src/state.rs b/src/state.rs index 53a19e6c..4be9ba4c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2036,6 +2036,13 @@ impl Lua { LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut std::os::raw::c_void) } + #[cfg(feature = "async")] + #[inline(always)] + pub(crate) fn poll_terminate() -> LightUserData { + static ASYNC_POLL_TERMINATE: u8 = 0; + LightUserData(&ASYNC_POLL_TERMINATE as *const u8 as *mut std::os::raw::c_void) + } + /// Returns a weak reference to the Lua instance. /// /// This is useful for creating a reference to the Lua instance that does not prevent it from diff --git a/src/state/raw.rs b/src/state/raw.rs index 44439da3..cc4ce1cf 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -613,46 +613,11 @@ impl RawLua { self.create_thread(func) } - /// Resets thread (coroutine) and returns it to the pool for later use. + /// Returns the thread to the pool for later use. #[cfg(feature = "async")] pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) { - let thread_state = thread.1; let extra = &mut *self.extra.get(); - if extra.thread_pool.len() == extra.thread_pool.capacity() { - #[cfg(feature = "lua54")] - if ffi::lua_status(thread_state) != ffi::LUA_OK { - // Close all to-be-closed variables without returning thread to the pool - #[cfg(not(feature = "vendored"))] - ffi::lua_resetthread(thread_state); - #[cfg(feature = "vendored")] - ffi::lua_closethread(thread_state, self.state()); - } - return; - } - - let mut reset_ok = false; - if ffi::lua_status(thread_state) == ffi::LUA_OK { - if ffi::lua_gettop(thread_state) > 0 { - ffi::lua_settop(thread_state, 0); - } - reset_ok = true; - } - - #[cfg(feature = "lua54")] - if !reset_ok { - #[cfg(not(feature = "vendored"))] - let status = ffi::lua_resetthread(thread_state); - #[cfg(feature = "vendored")] - let status = ffi::lua_closethread(thread_state, self.state()); - reset_ok = status == ffi::LUA_OK; - } - #[cfg(feature = "luau")] - if !reset_ok { - ffi::lua_resetthread(thread_state); - reset_ok = true; - } - - if reset_ok { + if extra.thread_pool.len() < extra.thread_pool.capacity() { extra.thread_pool.push(thread.0.index); thread.0.drop = false; // Prevent thread from being garbage collected } @@ -1244,7 +1209,7 @@ impl RawLua { let rawlua = (*extra).raw_lua(); let func = &*(*upvalue).data; - let fut = func(rawlua, nargs); + let fut = Some(func(rawlua, nargs)); let extra = XRc::clone(&(*upvalue).extra); let protect = !rawlua.unlikely_memory_error(); push_internal_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?; @@ -1262,20 +1227,27 @@ impl RawLua { unsafe extern "C-unwind" fn poll_future(state: *mut ffi::lua_State) -> c_int { let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); - callback_error_ext(state, (*upvalue).extra.get(), true, |extra, _| { + callback_error_ext(state, (*upvalue).extra.get(), true, |extra, nargs| { // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) // The lock must be already held as the future is polled let rawlua = (*extra).raw_lua(); + if nargs == 1 && ffi::lua_tolightuserdata(state, -1) == Lua::poll_terminate().0 { + // Destroy the future and terminate the Lua thread + (*upvalue).data.take(); + ffi::lua_pushinteger(state, 0); + return Ok(1); + } + let fut = &mut (*upvalue).data; let mut ctx = Context::from_waker(rawlua.waker()); - match fut.as_mut().poll(&mut ctx) { - Poll::Pending => { + match fut.as_mut().map(|fut| fut.as_mut().poll(&mut ctx)) { + Some(Poll::Pending) => { ffi::lua_pushnil(state); ffi::lua_pushlightuserdata(state, Lua::poll_pending().0); Ok(2) } - Poll::Ready(nresults) => { + Some(Poll::Ready(nresults)) => { match nresults? { nresults if nresults < 3 => { // Fast path for up to 2 results without creating a table @@ -1293,6 +1265,7 @@ impl RawLua { } } } + None => Err(Error::CallbackDestructed), } }) } @@ -1338,8 +1311,8 @@ impl RawLua { lua.load( r#" local poll = get_poll(...) + local nres, res, res2 = poll() while true do - local nres, res, res2 = poll() if nres ~= nil then if nres == 0 then return @@ -1351,7 +1324,10 @@ impl RawLua { return unpack(res, nres) end end - yield(res) -- `res` is a "pending" value + -- `res` is a "pending" value + -- `yield` can return a signal to drop the future that we should propagate + -- to the poller + nres, res, res2 = poll(yield(res)) end "#, ) diff --git a/src/thread.rs b/src/thread.rs index 39394cee..da7962b0 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -305,40 +305,57 @@ impl Thread { pub fn reset(&self, func: Function) -> Result<()> { let lua = self.0.lua.lock(); let thread_state = self.state(); - match self.status_inner(&lua) { - ThreadStatusInner::Running => return Err(Error::runtime("cannot reset a running thread")), - // Any Lua can reuse new or finished thread - ThreadStatusInner::New(_) => unsafe { ffi::lua_settop(thread_state, 0) }, - ThreadStatusInner::Finished => {} + unsafe { + let status = self.status_inner(&lua); + self.reset_inner(status)?; + + // Push function to the top of the thread stack + ffi::lua_xpush(lua.ref_thread(), thread_state, func.0.index); + + #[cfg(feature = "luau")] + { + // Inherit `LUA_GLOBALSINDEX` from the main thread + ffi::lua_xpush(lua.main_state(), thread_state, ffi::LUA_GLOBALSINDEX); + ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX); + } + + Ok(()) + } + } + + unsafe fn reset_inner(&self, status: ThreadStatusInner) -> Result<()> { + match status { + ThreadStatusInner::New(_) => { + // The thread is new, so we can just set the top to 0 + ffi::lua_settop(self.state(), 0); + Ok(()) + } + ThreadStatusInner::Running => Err(Error::runtime("cannot reset a running thread")), + ThreadStatusInner::Finished => Ok(()), #[cfg(not(any(feature = "lua54", feature = "luau")))] - _ => return Err(Error::runtime("cannot reset non-finished thread")), + ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => { + Err(Error::runtime("cannot reset non-finished thread")) + } #[cfg(any(feature = "lua54", feature = "luau"))] - _ => unsafe { + ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => { + let thread_state = self.state(); + #[cfg(all(feature = "lua54", not(feature = "vendored")))] let status = ffi::lua_resetthread(thread_state); #[cfg(all(feature = "lua54", feature = "vendored"))] - let status = ffi::lua_closethread(thread_state, lua.state()); + let status = { + let lua = self.0.lua.lock(); + ffi::lua_closethread(thread_state, lua.state()) + }; #[cfg(feature = "lua54")] if status != ffi::LUA_OK { return Err(pop_error(thread_state, status)); } #[cfg(feature = "luau")] ffi::lua_resetthread(thread_state); - }, - } - - unsafe { - // Push function to the top of the thread stack - ffi::lua_xpush(lua.ref_thread(), thread_state, func.0.index); - #[cfg(feature = "luau")] - { - // Inherit `LUA_GLOBALSINDEX` from the main thread - ffi::lua_xpush(lua.main_state(), thread_state, ffi::LUA_GLOBALSINDEX); - ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX); + Ok(()) } - - Ok(()) } } @@ -505,8 +522,21 @@ impl Drop for AsyncThread { fn drop(&mut self) { if self.recycle { if let Some(lua) = self.thread.0.lua.try_lock() { - // For Lua 5.4 this also closes all pending to-be-closed variables - unsafe { lua.recycle_thread(&mut self.thread) }; + unsafe { + let mut status = self.thread.status_inner(&lua); + if matches!(status, ThreadStatusInner::Yielded(0)) { + // The thread is dropped while yielded, resume it with the "terminate" signal + ffi::lua_pushlightuserdata(self.thread.1, crate::Lua::poll_terminate().0); + if let Ok((new_status, _)) = self.thread.resume_inner(&lua, 1) { + status = new_status; + } + } + + // For Lua 5.4 this also closes all pending to-be-closed variables + if self.thread.reset_inner(status).is_ok() { + lua.recycle_thread(&mut self.thread); + } + } } } } diff --git a/src/types.rs b/src/types.rs index 2f63906f..2589ea6e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -61,7 +61,7 @@ pub(crate) type AsyncCallback = pub(crate) type AsyncCallbackUpvalue = Upvalue; #[cfg(feature = "async")] -pub(crate) type AsyncPollUpvalue = Upvalue>>; +pub(crate) type AsyncPollUpvalue = Upvalue>>>; /// Type to set next Lua VM action after executing interrupt or hook function. pub enum VmState { diff --git a/tests/async.rs b/tests/async.rs index 6ae3b3de..752abb5c 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -9,7 +9,7 @@ use tokio::sync::Mutex; use mlua::{ Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, UserData, - UserDataMethods, Value, + UserDataMethods, UserDataRef, Value, }; #[cfg(not(target_arch = "wasm32"))] @@ -547,6 +547,7 @@ async fn test_async_thread_error() -> Result<()> { #[tokio::test] async fn test_async_terminate() -> Result<()> { + // Future captures `Lua` instance and dropped all together let mutex = Arc::new(Mutex::new(0u32)); { let lua = Lua::new(); @@ -565,6 +566,17 @@ async fn test_async_terminate() -> Result<()> { } assert!(mutex.try_lock().is_ok()); + // Future is dropped, but `Lua` instance is still alive + let lua = Lua::new(); + let func = lua.create_async_function(move |_, mutex: UserDataRef>>| async move { + let _guard = mutex.lock().await; + sleep_ms(100).await; + Ok(()) + })?; + let mutex2 = lua.create_any_userdata(mutex.clone())?; + let _ = tokio::time::timeout(Duration::from_millis(30), func.call_async::<()>(mutex2)).await; + assert!(mutex.try_lock().is_ok()); + Ok(()) } From 0cc4b15f6b00753734c4e9346f0946bcaf9b91f4 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 13 May 2025 21:56:11 +0100 Subject: [PATCH 403/635] Generate doc for `lua_module` macro using `doc` cfg instead of `docsrs` --- Cargo.toml | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index deb648f8..6d7a6b5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ luau = ["ffi/luau"] luau-jit = ["luau", "ffi/luau-codegen"] luau-vector4 = ["luau", "ffi/luau-vector4"] vendored = ["ffi/vendored"] -module = ["dep:mlua_derive", "ffi/module"] +module = ["mlua_derive", "ffi/module"] async = ["dep:futures-util"] send = ["parking_lot/send_guard", "error-send"] error-send = [] diff --git a/src/lib.rs b/src/lib.rs index ae9c4171..23a56131 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -254,7 +254,7 @@ pub use mlua_derive::FromLua; /// ... /// } /// ``` -#[cfg(any(feature = "module", docsrs))] +#[cfg(all(feature = "mlua_derive", any(feature = "module", doc)))] #[cfg_attr(docsrs, doc(cfg(feature = "module")))] pub use mlua_derive::lua_module; From c5c1fe3b8584dfdad4b5131dce0742b9e0c4ccbe Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 13 May 2025 23:45:46 +0100 Subject: [PATCH 404/635] Fix tests --- .github/workflows/main.yml | 4 +- src/lib.rs | 2 +- tests/compile/lua_norefunwindsafe.stderr | 80 ++++------- tests/compile/non_send.stderr | 2 +- tests/compile/ref_nounwindsafe.stderr | 151 ++++++++++++++------ tests/compile/scope_callback_capture.stderr | 2 +- tests/compile/scope_invariance.stderr | 2 +- tests/compile/scope_mutable_aliasing.stderr | 2 +- tests/compile/scope_userdata_borrow.stderr | 2 +- 9 files changed, 141 insertions(+), 106 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 44d79ddc..a0be1e5f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -129,8 +129,8 @@ jobs: - name: Run compile tests (macos lua54) if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }} run: | - TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" -- --ignored - TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros" -- --ignored + TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" --tests -- --ignored + TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros" --tests -- --ignored shell: bash test_with_sanitizer: diff --git a/src/lib.rs b/src/lib.rs index 23a56131..de321205 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -217,7 +217,7 @@ pub use mlua_derive::FromLua; /// /// You can register multiple entrypoints as required. /// -/// ``` +/// ```ignore /// use mlua::{Lua, Result, Table}; /// /// #[mlua::lua_module] diff --git a/tests/compile/lua_norefunwindsafe.stderr b/tests/compile/lua_norefunwindsafe.stderr index ea2442bd..a482a8d7 100644 --- a/tests/compile/lua_norefunwindsafe.stderr +++ b/tests/compile/lua_norefunwindsafe.stderr @@ -1,32 +1,28 @@ -error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary +error[E0277]: the type `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary --> tests/compile/lua_norefunwindsafe.rs:7:18 | 7 | catch_unwind(|| lua.create_table().unwrap()); - | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary | | | required by a bound introduced by this call | - = help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell`, which is required by `{closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:20}: UnwindSafe` -note: required because it appears within the type `lock_api::remutex::ReentrantMutex` - --> $CARGO/lock_api-0.4.12/src/remutex.rs - | - | pub struct ReentrantMutex { - | ^^^^^^^^^^^^^^ -note: required because it appears within the type `alloc::sync::ArcInner>` - --> $RUST/alloc/src/sync.rs + = help: within `mlua::types::sync::inner::ReentrantMutex`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<*mut lua_State>` +note: required because it appears within the type `Cell<*mut lua_State>` + --> $RUST/core/src/cell.rs | - | struct ArcInner { - | ^^^^^^^^ -note: required because it appears within the type `PhantomData>>` - --> $RUST/core/src/marker.rs + | pub struct Cell { + | ^^^^ +note: required because it appears within the type `mlua::state::raw::RawLua` + --> src/state/raw.rs | - | pub struct PhantomData; - | ^^^^^^^^^^^ -note: required because it appears within the type `Arc>` - --> $RUST/alloc/src/sync.rs + | pub struct RawLua { + | ^^^^^^ +note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex` + --> src/types/sync.rs | - | pub struct Arc< - | ^^^ + | pub(crate) struct ReentrantMutex(T); + | ^^^^^^^^^^^^^^ + = note: required for `Rc>` to implement `RefUnwindSafe` note: required because it appears within the type `Lua` --> src/state.rs | @@ -44,45 +40,27 @@ note: required by a bound in `std::panic::catch_unwind` | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { | ^^^^^^^^^^ required by this bound in `catch_unwind` -error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary +error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary --> tests/compile/lua_norefunwindsafe.rs:7:18 | 7 | catch_unwind(|| lua.create_table().unwrap()); - | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary | | | required by a bound introduced by this call | - = help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell`, which is required by `{closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:20}: UnwindSafe` -note: required because it appears within the type `Cell` - --> $RUST/core/src/cell.rs + = help: the trait `RefUnwindSafe` is not implemented for `UnsafeCell` + = note: required for `Rc>` to implement `RefUnwindSafe` +note: required because it appears within the type `mlua::state::raw::RawLua` + --> src/state/raw.rs | - | pub struct Cell { - | ^^^^ -note: required because it appears within the type `lock_api::remutex::RawReentrantMutex` - --> $CARGO/lock_api-0.4.12/src/remutex.rs - | - | pub struct RawReentrantMutex { - | ^^^^^^^^^^^^^^^^^ -note: required because it appears within the type `lock_api::remutex::ReentrantMutex` - --> $CARGO/lock_api-0.4.12/src/remutex.rs - | - | pub struct ReentrantMutex { - | ^^^^^^^^^^^^^^ -note: required because it appears within the type `alloc::sync::ArcInner>` - --> $RUST/alloc/src/sync.rs + | pub struct RawLua { + | ^^^^^^ +note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex` + --> src/types/sync.rs | - | struct ArcInner { - | ^^^^^^^^ -note: required because it appears within the type `PhantomData>>` - --> $RUST/core/src/marker.rs - | - | pub struct PhantomData; - | ^^^^^^^^^^^ -note: required because it appears within the type `Arc>` - --> $RUST/alloc/src/sync.rs - | - | pub struct Arc< - | ^^^ + | pub(crate) struct ReentrantMutex(T); + | ^^^^^^^^^^^^^^ + = note: required for `Rc>` to implement `RefUnwindSafe` note: required because it appears within the type `Lua` --> src/state.rs | diff --git a/tests/compile/non_send.stderr b/tests/compile/non_send.stderr index c7e28da1..c94b720f 100644 --- a/tests/compile/non_send.stderr +++ b/tests/compile/non_send.stderr @@ -8,7 +8,7 @@ error[E0277]: `Rc>` cannot be sent between threads safely | | within this `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}` | required by a bound introduced by this call | - = help: within `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}`, the trait `Send` is not implemented for `Rc>`, which is required by `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}: MaybeSend` + = help: within `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}`, the trait `Send` is not implemented for `Rc>` note: required because it's used within this closure --> tests/compile/non_send.rs:11:25 | diff --git a/tests/compile/ref_nounwindsafe.stderr b/tests/compile/ref_nounwindsafe.stderr index 39e70812..048f9d32 100644 --- a/tests/compile/ref_nounwindsafe.stderr +++ b/tests/compile/ref_nounwindsafe.stderr @@ -1,38 +1,38 @@ -error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary +error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary --> tests/compile/ref_nounwindsafe.rs:8:18 | 8 | catch_unwind(move || table.set("a", "b").unwrap()); - | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary | | | required by a bound introduced by this call | - = help: within `alloc::sync::ArcInner>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell`, which is required by `{closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:25}: UnwindSafe` -note: required because it appears within the type `lock_api::remutex::ReentrantMutex` - --> $CARGO/lock_api-0.4.12/src/remutex.rs + = help: within `rc::RcInner>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell` +note: required because it appears within the type `Cell` + --> $RUST/core/src/cell.rs | - | pub struct ReentrantMutex { - | ^^^^^^^^^^^^^^ -note: required because it appears within the type `alloc::sync::ArcInner>` - --> $RUST/alloc/src/sync.rs + | pub struct Cell { + | ^^^^ +note: required because it appears within the type `rc::RcInner>` + --> $RUST/alloc/src/rc.rs | - | struct ArcInner { - | ^^^^^^^^ - = note: required for `NonNull>>` to implement `UnwindSafe` -note: required because it appears within the type `std::sync::Weak>` - --> $RUST/alloc/src/sync.rs + | struct RcInner { + | ^^^^^^^ + = note: required for `NonNull>>` to implement `UnwindSafe` +note: required because it appears within the type `std::rc::Weak>` + --> $RUST/alloc/src/rc.rs | | pub struct Weak< | ^^^^ -note: required because it appears within the type `mlua::state::WeakLua` +note: required because it appears within the type `WeakLua` --> src/state.rs | - | pub(crate) struct WeakLua(XWeak>); - | ^^^^^^^ -note: required because it appears within the type `mlua::types::ValueRef` - --> src/types.rs + | pub struct WeakLua(XWeak>); + | ^^^^^^^ +note: required because it appears within the type `mlua::types::value_ref::ValueRef` + --> src/types/value_ref.rs | - | pub(crate) struct ValueRef { - | ^^^^^^^^ + | pub struct ValueRef { + | ^^^^^^^^ note: required because it appears within the type `LuaTable` --> src/table.rs | @@ -49,51 +49,108 @@ note: required by a bound in `std::panic::catch_unwind` | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { | ^^^^^^^^^^ required by this bound in `catch_unwind` -error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary +error[E0277]: the type `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary --> tests/compile/ref_nounwindsafe.rs:8:18 | 8 | catch_unwind(move || table.set("a", "b").unwrap()); - | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary | | | required by a bound introduced by this call | - = help: within `alloc::sync::ArcInner>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell`, which is required by `{closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:25}: UnwindSafe` -note: required because it appears within the type `Cell` + = help: within `rc::RcInner>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<*mut lua_State>` +note: required because it appears within the type `Cell<*mut lua_State>` --> $RUST/core/src/cell.rs | | pub struct Cell { | ^^^^ -note: required because it appears within the type `lock_api::remutex::RawReentrantMutex` - --> $CARGO/lock_api-0.4.12/src/remutex.rs +note: required because it appears within the type `mlua::state::raw::RawLua` + --> src/state/raw.rs + | + | pub struct RawLua { + | ^^^^^^ +note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex` + --> src/types/sync.rs + | + | pub(crate) struct ReentrantMutex(T); + | ^^^^^^^^^^^^^^ +note: required because it appears within the type `rc::RcInner>` + --> $RUST/alloc/src/rc.rs + | + | struct RcInner { + | ^^^^^^^ + = note: required for `NonNull>>` to implement `UnwindSafe` +note: required because it appears within the type `std::rc::Weak>` + --> $RUST/alloc/src/rc.rs + | + | pub struct Weak< + | ^^^^ +note: required because it appears within the type `WeakLua` + --> src/state.rs + | + | pub struct WeakLua(XWeak>); + | ^^^^^^^ +note: required because it appears within the type `mlua::types::value_ref::ValueRef` + --> src/types/value_ref.rs + | + | pub struct ValueRef { + | ^^^^^^^^ +note: required because it appears within the type `LuaTable` + --> src/table.rs + | + | pub struct Table(pub(crate) ValueRef); + | ^^^^^ +note: required because it's used within this closure + --> tests/compile/ref_nounwindsafe.rs:8:18 + | +8 | catch_unwind(move || table.set("a", "b").unwrap()); + | ^^^^^^^ +note: required by a bound in `std::panic::catch_unwind` + --> $RUST/std/src/panic.rs + | + | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { + | ^^^^^^^^^^ required by this bound in `catch_unwind` + +error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + --> tests/compile/ref_nounwindsafe.rs:8:18 + | +8 | catch_unwind(move || table.set("a", "b").unwrap()); + | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + | | + | required by a bound introduced by this call + | + = help: the trait `RefUnwindSafe` is not implemented for `UnsafeCell` + = note: required for `Rc>` to implement `RefUnwindSafe` +note: required because it appears within the type `mlua::state::raw::RawLua` + --> src/state/raw.rs | - | pub struct RawReentrantMutex { - | ^^^^^^^^^^^^^^^^^ -note: required because it appears within the type `lock_api::remutex::ReentrantMutex` - --> $CARGO/lock_api-0.4.12/src/remutex.rs + | pub struct RawLua { + | ^^^^^^ +note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex` + --> src/types/sync.rs | - | pub struct ReentrantMutex { - | ^^^^^^^^^^^^^^ -note: required because it appears within the type `alloc::sync::ArcInner>` - --> $RUST/alloc/src/sync.rs + | pub(crate) struct ReentrantMutex(T); + | ^^^^^^^^^^^^^^ +note: required because it appears within the type `rc::RcInner>` + --> $RUST/alloc/src/rc.rs | - | struct ArcInner { - | ^^^^^^^^ - = note: required for `NonNull>>` to implement `UnwindSafe` -note: required because it appears within the type `std::sync::Weak>` - --> $RUST/alloc/src/sync.rs + | struct RcInner { + | ^^^^^^^ + = note: required for `NonNull>>` to implement `UnwindSafe` +note: required because it appears within the type `std::rc::Weak>` + --> $RUST/alloc/src/rc.rs | | pub struct Weak< | ^^^^ -note: required because it appears within the type `mlua::state::WeakLua` +note: required because it appears within the type `WeakLua` --> src/state.rs | - | pub(crate) struct WeakLua(XWeak>); - | ^^^^^^^ -note: required because it appears within the type `mlua::types::ValueRef` - --> src/types.rs + | pub struct WeakLua(XWeak>); + | ^^^^^^^ +note: required because it appears within the type `mlua::types::value_ref::ValueRef` + --> src/types/value_ref.rs | - | pub(crate) struct ValueRef { - | ^^^^^^^^ + | pub struct ValueRef { + | ^^^^^^^^ note: required because it appears within the type `LuaTable` --> src/table.rs | diff --git a/tests/compile/scope_callback_capture.stderr b/tests/compile/scope_callback_capture.stderr index a6993916..94844dc9 100644 --- a/tests/compile/scope_callback_capture.stderr +++ b/tests/compile/scope_callback_capture.stderr @@ -2,7 +2,7 @@ error[E0373]: closure may outlive the current function, but it borrows `inner`, --> tests/compile/scope_callback_capture.rs:7:43 | 5 | lua.scope(|scope| { - | ----- has type `&'1 mut mlua::scope::Scope<'1, '_>` + | ----- has type `&'1 mlua::Scope<'1, '_>` 6 | let mut inner: Option
= None; 7 | let f = scope.create_function_mut(|_, t: Table| { | ^^^^^^^^^^^^^ may outlive borrowed value `inner` diff --git a/tests/compile/scope_invariance.stderr b/tests/compile/scope_invariance.stderr index 91158aa6..a3f218df 100644 --- a/tests/compile/scope_invariance.stderr +++ b/tests/compile/scope_invariance.stderr @@ -2,7 +2,7 @@ error[E0373]: closure may outlive the current function, but it borrows `test.fie --> tests/compile/scope_invariance.rs:13:39 | 9 | lua.scope(|scope| { - | ----- has type `&'1 mut mlua::scope::Scope<'1, '_>` + | ----- has type `&'1 mlua::Scope<'1, '_>` ... 13 | scope.create_function_mut(|_, ()| { | ^^^^^^^ may outlive borrowed value `test.field` diff --git a/tests/compile/scope_mutable_aliasing.stderr b/tests/compile/scope_mutable_aliasing.stderr index 362cf91d..e6e57f13 100644 --- a/tests/compile/scope_mutable_aliasing.stderr +++ b/tests/compile/scope_mutable_aliasing.stderr @@ -2,7 +2,7 @@ error[E0499]: cannot borrow `i` as mutable more than once at a time --> tests/compile/scope_mutable_aliasing.rs:12:51 | 10 | lua.scope(|scope| { - | ----- has type `&mut mlua::scope::Scope<'_, '1>` + | ----- has type `&mlua::Scope<'_, '1>` 11 | let _a = scope.create_userdata(MyUserData(&mut i)).unwrap(); | ----------------------------------------- | | | diff --git a/tests/compile/scope_userdata_borrow.stderr b/tests/compile/scope_userdata_borrow.stderr index 043a99b1..43025ddf 100644 --- a/tests/compile/scope_userdata_borrow.stderr +++ b/tests/compile/scope_userdata_borrow.stderr @@ -2,7 +2,7 @@ error[E0597]: `ibad` does not live long enough --> tests/compile/scope_userdata_borrow.rs:15:46 | 11 | lua.scope(|scope| { - | ----- has type `&mut mlua::scope::Scope<'_, '1>` + | ----- has type `&mlua::Scope<'_, '1>` ... 14 | let ibad = 42; | ---- binding `ibad` declared here From df38878278f65cc4d17bd87556d083338dde9ead Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 15 May 2025 11:03:30 +0100 Subject: [PATCH 405/635] Update `__mlua_index`/`__mlua_newindex` chunk names --- src/userdata/util.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/userdata/util.rs b/src/userdata/util.rs index 4622d5e8..9305eb00 100644 --- a/src/userdata/util.rs +++ b/src/userdata/util.rs @@ -354,7 +354,7 @@ unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<() end "#; protect_lua!(state, 0, 1, |state| { - let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_index")); + let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("=__mlua_index")); if ret != ffi::LUA_OK { ffi::lua_error(state); } @@ -405,7 +405,8 @@ unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result end "#; protect_lua!(state, 0, 1, |state| { - let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_newindex")); + let code_len = code.count_bytes(); + let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code_len, cstr!("=__mlua_newindex")); if ret != ffi::LUA_OK { ffi::lua_error(state); } From f36aaa5ce192270221ec5f5e1fb3325e941f1bc9 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 15 May 2025 11:07:44 +0100 Subject: [PATCH 406/635] Add `loadstring` function to Luau Closes #578 --- src/luau/mod.rs | 22 +++++++++++++++++++++- tests/luau.rs | 23 ++++++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/luau/mod.rs b/src/luau/mod.rs index 635eebfe..f773fce7 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -1,9 +1,12 @@ use std::ffi::CStr; use std::os::raw::c_int; +use std::ptr; +use crate::chunk::ChunkMode; use crate::error::Result; use crate::function::Function; -use crate::state::Lua; +use crate::state::{callback_error_ext, Lua}; +use crate::traits::{FromLuaMulti, IntoLua}; pub use require::{NavigateError, Require}; @@ -22,6 +25,7 @@ impl Lua { let globals = self.globals(); globals.raw_set("collectgarbage", self.create_c_function(lua_collectgarbage)?)?; + globals.raw_set("loadstring", self.create_c_function(lua_loadstring)?)?; // Set `_VERSION` global to include version number // The environment variable `LUAU_VERSION` set by the build script @@ -74,4 +78,20 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_ } } +unsafe extern "C-unwind" fn lua_loadstring(state: *mut ffi::lua_State) -> c_int { + callback_error_ext(state, ptr::null_mut(), false, move |extra, nargs| { + let rawlua = (*extra).raw_lua(); + let (chunk, chunk_name) = + <(String, Option)>::from_stack_args(nargs, 1, Some("loadstring"), rawlua)?; + let chunk_name = chunk_name.as_deref().unwrap_or("=(loadstring)"); + (rawlua.lua()) + .load(chunk) + .set_name(chunk_name) + .set_mode(ChunkMode::Text) + .into_function()? + .push_into_stack(rawlua)?; + Ok(1) + }) +} + mod require; diff --git a/tests/luau.rs b/tests/luau.rs index 52a42dae..1b2cfc12 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -7,7 +7,9 @@ use std::panic::{catch_unwind, AssertUnwindSafe}; use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering}; use std::sync::Arc; -use mlua::{Compiler, Error, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, Vector, VmState}; +use mlua::{ + Compiler, Error, Function, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, Vector, VmState, +}; #[test] fn test_version() -> Result<()> { @@ -415,5 +417,24 @@ fn test_thread_events() -> Result<()> { Ok(()) } +#[test] +fn test_loadstring() -> Result<()> { + let lua = Lua::new(); + + let f = lua.load(r#"loadstring("return 123")"#).eval::()?; + assert_eq!(f.call::(())?, 123); + + let err = lua + .load(r#"loadstring("retur 123", "chunk")"#) + .exec() + .err() + .unwrap(); + assert!(err.to_string().contains( + r#"syntax error: [string "chunk"]:1: Incomplete statement: expected assignment or a function call"# + )); + + Ok(()) +} + #[path = "luau/require.rs"] mod require; From 2e7c654cfec763c80b7d23b6738ec9ad97743368 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 26 May 2025 23:29:59 +0100 Subject: [PATCH 407/635] Make `AsChunk` trait dyn-friendly --- src/chunk.rs | 45 +++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index 022fa517..375f15fb 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -38,28 +38,28 @@ pub trait AsChunk { } /// Returns chunk data (can be text or binary) - fn source<'a>(self) -> IoResult> + fn source<'a>(&self) -> IoResult> where Self: 'a; } impl AsChunk for &str { - fn source<'a>(self) -> IoResult> + fn source<'a>(&self) -> IoResult> where Self: 'a, { - Ok(Cow::Borrowed(self.as_ref())) + Ok(Cow::Borrowed(self.as_bytes())) } } impl AsChunk for StdString { - fn source<'a>(self) -> IoResult> { - Ok(Cow::Owned(self.into_bytes())) + fn source<'a>(&self) -> IoResult> { + Ok(Cow::Owned(self.clone().into_bytes())) } } impl AsChunk for &StdString { - fn source<'a>(self) -> IoResult> + fn source<'a>(&self) -> IoResult> where Self: 'a, { @@ -68,7 +68,7 @@ impl AsChunk for &StdString { } impl AsChunk for &[u8] { - fn source<'a>(self) -> IoResult> + fn source<'a>(&self) -> IoResult> where Self: 'a, { @@ -77,13 +77,13 @@ impl AsChunk for &[u8] { } impl AsChunk for Vec { - fn source<'a>(self) -> IoResult> { - Ok(Cow::Owned(self)) + fn source<'a>(&self) -> IoResult> { + Ok(Cow::Owned(self.clone())) } } impl AsChunk for &Vec { - fn source<'a>(self) -> IoResult> + fn source<'a>(&self) -> IoResult> where Self: 'a, { @@ -96,7 +96,7 @@ impl AsChunk for &Path { Some(format!("@{}", self.display())) } - fn source<'a>(self) -> IoResult> { + fn source<'a>(&self) -> IoResult> { std::fs::read(self).map(Cow::Owned) } } @@ -106,11 +106,32 @@ impl AsChunk for PathBuf { Some(format!("@{}", self.display())) } - fn source<'a>(self) -> IoResult> { + fn source<'a>(&self) -> IoResult> { std::fs::read(self).map(Cow::Owned) } } +impl AsChunk for Box { + fn name(&self) -> Option { + (**self).name() + } + + fn environment(&self, lua: &Lua) -> Result> { + (**self).environment(lua) + } + + fn mode(&self) -> Option { + (**self).mode() + } + + fn source<'a>(&self) -> IoResult> + where + Self: 'a, + { + (**self).source() + } +} + /// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks. #[must_use = "`Chunk`s do nothing unless one of `exec`, `eval`, `call`, or `into_function` are called on them"] pub struct Chunk<'a> { From c61219dd939228e2ede1a767f3c069a0507903de Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 27 May 2025 00:12:45 +0100 Subject: [PATCH 408/635] Update Luau Require trait (sync with 0.674) --- mlua-sys/Cargo.toml | 2 +- mlua-sys/src/luau/luarequire.rs | 51 ++++-- src/luau/require.rs | 296 ++++++++++++++++---------------- 3 files changed, 182 insertions(+), 167 deletions(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 4c1f8a00..d470b9ce 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -40,7 +40,7 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 547.1.0, < 547.2.0", optional = true } luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true } -luau0-src = { version = "0.14.2", optional = true } +luau0-src = { git = "https://github.com/mlua-rs/luau-src-rs", version = "0.15.0", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/mlua-sys/src/luau/luarequire.rs b/mlua-sys/src/luau/luarequire.rs index 7225cb9b..ff8343a9 100644 --- a/mlua-sys/src/luau/luarequire.rs +++ b/mlua-sys/src/luau/luarequire.rs @@ -56,9 +56,9 @@ pub struct luarequire_Configuration { // Returns whether the context is currently pointing at a module. pub is_module_present: unsafe extern "C" fn(L: *mut lua_State, ctx: *mut c_void) -> bool, - // Provides the contents of the current module. This function is only called if is_module_present returns - // true. - pub get_contents: unsafe extern "C" fn( + // Provides a chunkname for the current module. This will be accessible through the debug library. This + // function is only called if is_module_present returns true. + pub get_chunkname: unsafe extern "C" fn( L: *mut lua_State, ctx: *mut c_void, buffer: *mut c_char, @@ -66,9 +66,9 @@ pub struct luarequire_Configuration { size_out: *mut usize, ) -> luarequire_WriteResult, - // Provides a chunkname for the current module. This will be accessible through the debug library. This - // function is only called if is_module_present returns true. - pub get_chunkname: unsafe extern "C" fn( + // Provides a loadname that identifies the current module and is passed to load. This function + // is only called if is_module_present returns true. + pub get_loadname: unsafe extern "C" fn( L: *mut lua_State, ctx: *mut c_void, buffer: *mut c_char, @@ -91,15 +91,34 @@ pub struct luarequire_Configuration { // NAVIGATE_FAILURE is returned (at root). pub is_config_present: unsafe extern "C" fn(L: *mut lua_State, ctx: *mut c_void) -> bool, - // Provides the contents of the configuration file in the current context. - // This function is only called if is_config_present returns true. - pub get_config: unsafe extern "C" fn( - L: *mut lua_State, - ctx: *mut c_void, - buffer: *mut c_char, - buffer_size: usize, - size_out: *mut usize, - ) -> luarequire_WriteResult, + // Parses the configuration file in the current context for the given alias and returns its + // value or WRITE_FAILURE if not found. This function is only called if is_config_present + // returns true. If this function pointer is set, get_config must not be set. Opting in to this + // function pointer disables parsing configuration files internally and can be used for finer + // control over the configuration file parsing process. + pub get_alias: Option< + unsafe extern "C" fn( + L: *mut lua_State, + ctx: *mut c_void, + alias: *const c_char, + buffer: *mut c_char, + buffer_size: usize, + size_out: *mut usize, + ) -> luarequire_WriteResult, + >, + + // Provides the contents of the configuration file in the current context. This function is only called + // if is_config_present returns true. If this function pointer is set, get_alias must not be set. Opting + // in to this function pointer enables parsing configuration files internally. + pub get_config: Option< + unsafe extern "C" fn( + L: *mut lua_State, + ctx: *mut c_void, + buffer: *mut c_char, + buffer_size: usize, + size_out: *mut usize, + ) -> luarequire_WriteResult, + >, // Executes the module and places the result on the stack. Returns the number of results placed on the // stack. @@ -110,7 +129,7 @@ pub struct luarequire_Configuration { ctx: *mut c_void, path: *const c_char, chunkname: *const c_char, - contents: *const c_char, + loadname: *const c_char, ) -> c_int, } diff --git a/src/luau/require.rs b/src/luau/require.rs index 152114de..abf56df2 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -2,6 +2,7 @@ use std::cell::RefCell; use std::collections::VecDeque; use std::ffi::CStr; use std::io::Result as IoResult; +use std::ops::{Deref, DerefMut}; use std::os::raw::{c_char, c_int, c_void}; use std::path::{Component, Path, PathBuf}; use std::result::Result as StdResult; @@ -44,55 +45,43 @@ pub trait Require: MaybeSend { fn is_require_allowed(&self, chunk_name: &str) -> bool; /// Resets the internal state to point at the requirer module. - fn reset(&self, chunk_name: &str) -> StdResult<(), NavigateError>; + fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError>; /// Resets the internal state to point at an aliased module. /// /// This function received an exact path from a configuration file. /// It's only called when an alias's path cannot be resolved relative to its /// configuration file. - fn jump_to_alias(&self, path: &str) -> StdResult<(), NavigateError>; + fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError>; // Navigate to parent directory - fn to_parent(&self) -> StdResult<(), NavigateError>; + fn to_parent(&mut self) -> StdResult<(), NavigateError>; /// Navigate to the given child directory. - fn to_child(&self, name: &str) -> StdResult<(), NavigateError>; + fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError>; /// Returns whether the context is currently pointing at a module - fn is_module_present(&self) -> bool; - - /// Returns the contents of the current module - /// - /// This function is only called if `is_module_present` returns true. - fn contents(&self) -> IoResult>; - - /// Returns a chunk name for the current module. - /// - /// This function is only called if `is_module_present` returns true. - /// The chunk name is used to identify the module using the debug library. - fn chunk_name(&self) -> String; + fn has_module(&self) -> bool; /// Provides a cache key representing the current module. /// - /// This function is only called if `is_module_present` returns true. - fn cache_key(&self) -> Vec; + /// This function is only called if `has_module` returns true. + fn cache_key(&self) -> String; - /// Returns whether a configuration file is present in the current context. - fn is_config_present(&self) -> bool; + /// Returns whether a configuration is present in the current context. + fn has_config(&self) -> bool; /// Returns the contents of the configuration file in the current context. /// - /// This function is only called if `is_config_present` returns true. + /// This function is only called if `has_config` returns true. fn config(&self) -> IoResult>; - /// Returns a loader that when called, loads the module and returns the result. + /// Returns a loader function for the current module, that when called, loads the module + /// and returns the result. /// /// Loader can be sync or async. - fn loader(&self, lua: &Lua, path: &str, chunk_name: &str, content: &[u8]) -> Result { - let _ = path; - lua.load(content).set_name(chunk_name).into_function() - } + /// This function is only called if `has_module` returns true. + fn loader(&self, lua: &Lua) -> Result; } impl fmt::Debug for dyn Require { @@ -104,9 +93,9 @@ impl fmt::Debug for dyn Require { /// The standard implementation of Luau `require` navigation. #[derive(Default)] pub(super) struct TextRequirer { - abs_path: RefCell, - rel_path: RefCell, - module_path: RefCell, + abs_path: PathBuf, + rel_path: PathBuf, + module_path: PathBuf, } impl TextRequirer { @@ -196,7 +185,7 @@ impl Require for TextRequirer { chunk_name.starts_with('@') } - fn reset(&self, chunk_name: &str) -> StdResult<(), NavigateError> { + fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError> { if !chunk_name.starts_with('@') { return Err(NavigateError::NotFound); } @@ -208,18 +197,18 @@ impl Require for TextRequirer { Ok(cwd) => cwd, Err(_) => return Err(NavigateError::NotFound), }; - self.abs_path.replace(Self::normalize_path(&cwd.join(&path))); - self.rel_path.replace(path); - self.module_path.replace(PathBuf::new()); + self.abs_path = Self::normalize_path(&cwd.join(&path)); + self.rel_path = path; + self.module_path = PathBuf::new(); return Ok(()); } if path.is_absolute() { let module_path = Self::find_module_path(&path)?; - self.abs_path.replace(path.clone()); - self.rel_path.replace(path); - self.module_path.replace(module_path); + self.abs_path = path.clone(); + self.rel_path = path; + self.module_path = module_path; } else { // Relative path let cwd = match env::current_dir() { @@ -228,78 +217,111 @@ impl Require for TextRequirer { }; let abs_path = cwd.join(&path); let module_path = Self::find_module_path(&abs_path)?; - self.abs_path.replace(Self::normalize_path(&abs_path)); - self.rel_path.replace(path); - self.module_path.replace(module_path); + self.abs_path = Self::normalize_path(&abs_path); + self.rel_path = path; + self.module_path = module_path; } Ok(()) } - fn jump_to_alias(&self, path: &str) -> StdResult<(), NavigateError> { + fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> { let path = Self::normalize_path(path.as_ref()); let module_path = Self::find_module_path(&path)?; - self.abs_path.replace(path.clone()); - self.rel_path.replace(path); - self.module_path.replace(module_path); + self.abs_path = path.clone(); + self.rel_path = path; + self.module_path = module_path; Ok(()) } - fn to_parent(&self) -> StdResult<(), NavigateError> { - let mut abs_path = self.abs_path.borrow().clone(); + fn to_parent(&mut self) -> StdResult<(), NavigateError> { + let mut abs_path = self.abs_path.clone(); if !abs_path.pop() { return Err(NavigateError::NotFound); } - let mut rel_parent = self.rel_path.borrow().clone(); + let mut rel_parent = self.rel_path.clone(); rel_parent.pop(); let module_path = Self::find_module_path(&abs_path)?; - self.abs_path.replace(abs_path); - self.rel_path.replace(Self::normalize_path(&rel_parent)); - self.module_path.replace(module_path); + self.abs_path = abs_path; + self.rel_path = Self::normalize_path(&rel_parent); + self.module_path = module_path; Ok(()) } - fn to_child(&self, name: &str) -> StdResult<(), NavigateError> { - let abs_path = self.abs_path.borrow().join(name); - let rel_path = self.rel_path.borrow().join(name); + fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> { + let abs_path = self.abs_path.join(name); + let rel_path = self.rel_path.join(name); let module_path = Self::find_module_path(&abs_path)?; - self.abs_path.replace(abs_path); - self.rel_path.replace(rel_path); - self.module_path.replace(module_path); + self.abs_path = abs_path; + self.rel_path = rel_path; + self.module_path = module_path; Ok(()) } - fn is_module_present(&self) -> bool { - self.module_path.borrow().is_file() + fn has_module(&self) -> bool { + self.module_path.is_file() } - fn contents(&self) -> IoResult> { - fs::read(&*self.module_path.borrow()) + fn cache_key(&self) -> String { + self.module_path.display().to_string() } - fn chunk_name(&self) -> String { - format!("@{}", self.rel_path.borrow().display()) + fn has_config(&self) -> bool { + self.abs_path.join(".luaurc").is_file() } - fn cache_key(&self) -> Vec { - self.module_path.borrow().display().to_string().into_bytes() + fn config(&self) -> IoResult> { + fs::read(self.abs_path.join(".luaurc")) } - fn is_config_present(&self) -> bool { - self.abs_path.borrow().join(".luaurc").is_file() + fn loader(&self, lua: &Lua) -> Result { + let name = format!("@{}", self.rel_path.display()); + lua.load(self.module_path.as_path()) + .set_name(name) + .into_function() } +} - fn config(&self) -> IoResult> { - fs::read(self.abs_path.borrow().join(".luaurc")) +struct Context(Box); + +impl Deref for Context { + type Target = dyn Require; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +impl DerefMut for Context { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut *self.0 } } +macro_rules! try_borrow { + ($state:expr, $ctx:expr) => { + match (*($ctx as *const RefCell)).try_borrow() { + Ok(ctx) => ctx, + Err(_) => ffi::luaL_error($state, cstr!("require context is already borrowed")), + } + }; +} + +macro_rules! try_borrow_mut { + ($state:expr, $ctx:expr) => { + match (*($ctx as *const RefCell)).try_borrow_mut() { + Ok(ctx) => ctx, + Err(_) => ffi::luaL_error($state, cstr!("require context is already borrowed")), + } + }; +} + #[cfg(feature = "luau")] pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configuration) { if config.is_null() { @@ -307,7 +329,7 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu } unsafe extern "C" fn is_require_allowed( - _state: *mut ffi::lua_State, + state: *mut ffi::lua_State, ctx: *mut c_void, requirer_chunkname: *const c_char, ) -> bool { @@ -315,76 +337,72 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu return false; } - let this = &*(ctx as *const Box); + let this = try_borrow!(state, ctx); let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy(); this.is_require_allowed(&chunk_name) } unsafe extern "C" fn reset( - _state: *mut ffi::lua_State, + state: *mut ffi::lua_State, ctx: *mut c_void, requirer_chunkname: *const c_char, ) -> ffi::luarequire_NavigateResult { - let this = &*(ctx as *const Box); + let mut this = try_borrow_mut!(state, ctx); let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy(); this.reset(&chunk_name).into_nav_result() } unsafe extern "C" fn jump_to_alias( - _state: *mut ffi::lua_State, + state: *mut ffi::lua_State, ctx: *mut c_void, path: *const c_char, ) -> ffi::luarequire_NavigateResult { - let this = &*(ctx as *const Box); + let mut this = try_borrow_mut!(state, ctx); let path = CStr::from_ptr(path).to_string_lossy(); this.jump_to_alias(&path).into_nav_result() } unsafe extern "C" fn to_parent( - _state: *mut ffi::lua_State, + state: *mut ffi::lua_State, ctx: *mut c_void, ) -> ffi::luarequire_NavigateResult { - let this = &*(ctx as *const Box); + let mut this = try_borrow_mut!(state, ctx); this.to_parent().into_nav_result() } unsafe extern "C" fn to_child( - _state: *mut ffi::lua_State, + state: *mut ffi::lua_State, ctx: *mut c_void, name: *const c_char, ) -> ffi::luarequire_NavigateResult { - let this = &*(ctx as *const Box); + let mut this = try_borrow_mut!(state, ctx); let name = CStr::from_ptr(name).to_string_lossy(); this.to_child(&name).into_nav_result() } - unsafe extern "C" fn is_module_present(_state: *mut ffi::lua_State, ctx: *mut c_void) -> bool { - let this = &*(ctx as *const Box); - this.is_module_present() + unsafe extern "C" fn is_module_present(state: *mut ffi::lua_State, ctx: *mut c_void) -> bool { + let this = try_borrow!(state, ctx); + this.has_module() } - unsafe extern "C" fn get_contents( - state: *mut ffi::lua_State, - ctx: *mut c_void, + unsafe extern "C" fn get_chunkname( + _state: *mut ffi::lua_State, + _ctx: *mut c_void, buffer: *mut c_char, buffer_size: usize, size_out: *mut usize, ) -> WriteResult { - let this = &*(ctx as *const Box); - write_to_buffer(state, buffer, buffer_size, size_out, || this.contents()) + write_to_buffer(buffer, buffer_size, size_out, &[]) } - unsafe extern "C" fn get_chunkname( - state: *mut ffi::lua_State, - ctx: *mut c_void, + unsafe extern "C" fn get_loadname( + _state: *mut ffi::lua_State, + _ctx: *mut c_void, buffer: *mut c_char, buffer_size: usize, size_out: *mut usize, ) -> WriteResult { - let this = &*(ctx as *const Box); - write_to_buffer(state, buffer, buffer_size, size_out, || { - Ok(this.chunk_name().into_bytes()) - }) + write_to_buffer(buffer, buffer_size, size_out, &[]) } unsafe extern "C" fn get_cache_key( @@ -394,13 +412,14 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu buffer_size: usize, size_out: *mut usize, ) -> WriteResult { - let this = &*(ctx as *const Box); - write_to_buffer(state, buffer, buffer_size, size_out, || Ok(this.cache_key())) + let this = try_borrow!(state, ctx); + let cache_key = this.cache_key(); + write_to_buffer(buffer, buffer_size, size_out, cache_key.as_bytes()) } - unsafe extern "C" fn is_config_present(_state: *mut ffi::lua_State, ctx: *mut c_void) -> bool { - let this = &*(ctx as *const Box); - this.is_config_present() + unsafe extern "C" fn is_config_present(state: *mut ffi::lua_State, ctx: *mut c_void) -> bool { + let this = try_borrow!(state, ctx); + this.has_config() } unsafe extern "C" fn get_config( @@ -410,24 +429,25 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu buffer_size: usize, size_out: *mut usize, ) -> WriteResult { - let this = &*(ctx as *const Box); - write_to_buffer(state, buffer, buffer_size, size_out, || this.config()) + let this = try_borrow!(state, ctx); + let Ok(config) = this.config() else { + return WriteResult::Failure; + }; + write_to_buffer(buffer, buffer_size, size_out, &config) } unsafe extern "C-unwind" fn load( state: *mut ffi::lua_State, ctx: *mut c_void, - path: *const c_char, - chunk_name: *const c_char, - contents: *const c_char, + _path: *const c_char, + _chunkname: *const c_char, + _loadname: *const c_char, ) -> c_int { - let this = &*(ctx as *const Box); - let path = CStr::from_ptr(path).to_string_lossy(); - let chunk_name = CStr::from_ptr(chunk_name).to_string_lossy(); - let contents = CStr::from_ptr(contents).to_bytes(); + let this = try_borrow!(state, ctx); callback_error_ext(state, ptr::null_mut(), false, move |extra, _| { let rawlua = (*extra).raw_lua(); - rawlua.push(this.loader(rawlua.lua(), &path, &chunk_name, contents)?)?; + let loader = this.loader(rawlua.lua())?; + rawlua.push(loader)?; Ok(1) }) } @@ -438,60 +458,34 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu (*config).to_parent = to_parent; (*config).to_child = to_child; (*config).is_module_present = is_module_present; - (*config).get_contents = get_contents; (*config).get_chunkname = get_chunkname; + (*config).get_loadname = get_loadname; (*config).get_cache_key = get_cache_key; (*config).is_config_present = is_config_present; - (*config).get_config = get_config; + (*config).get_alias = None; + (*config).get_config = Some(get_config); (*config).load = load; } /// Helper function to write data to a buffer #[cfg(feature = "luau")] unsafe fn write_to_buffer( - state: *mut ffi::lua_State, buffer: *mut c_char, buffer_size: usize, size_out: *mut usize, - data_fetcher: impl Fn() -> IoResult>, + data: &[u8], ) -> WriteResult { - struct DataCache(Option>); - - // The initial buffer size can be too small, to avoid making a second data fetch call, - // we cache the content in the first call, and then re-use it. - - let lua = Lua::get_or_init_from_ptr(state); - match lua.try_app_data_mut::() { - Ok(Some(mut data_cache)) => { - if let Some(data) = data_cache.0.take() { - mlua_assert!(data.len() <= buffer_size, "buffer is too small"); - *size_out = data.len(); - ptr::copy_nonoverlapping(data.as_ptr(), buffer as *mut _, data.len()); - return WriteResult::Success; - } - } - Ok(None) => { - // Init the cache - _ = lua.try_set_app_data(DataCache(None)); - } - Err(_) => {} + // the buffer must be null terminated as it's a c++ `std::string` data() buffer + let is_null_terminated = data.last() == Some(&0); + *size_out = data.len() + if is_null_terminated { 0 } else { 1 }; + if *size_out > buffer_size { + return WriteResult::BufferTooSmall; } - - match data_fetcher() { - Ok(data) => { - *size_out = data.len(); - if *size_out > buffer_size { - // Cache the data for the next call to avoid getting the contents again - if let Ok(Some(mut data_cache)) = lua.try_app_data_mut::() { - data_cache.0 = Some(data); - } - return WriteResult::BufferTooSmall; - } - ptr::copy_nonoverlapping(data.as_ptr(), buffer as *mut _, data.len()); - WriteResult::Success - } - Err(_) => WriteResult::Failure, + ptr::copy_nonoverlapping(data.as_ptr(), buffer as *mut _, data.len()); + if !is_null_terminated { + *buffer.add(data.len()) = 0; } + WriteResult::Success } #[cfg(feature = "luau")] @@ -511,18 +505,20 @@ pub fn create_require_function(lua: &Lua, require: R) -> R } unsafe extern "C-unwind" fn get_cache_key(state: *mut ffi::lua_State) -> c_int { - let requirer = ffi::lua_touserdata(state, ffi::lua_upvalueindex(1)) as *const Box; - let cache_key = (*requirer).cache_key(); + let ctx = ffi::lua_touserdata(state, ffi::lua_upvalueindex(1)); + let ctx = try_borrow!(state, ctx); + let cache_key = ctx.cache_key(); ffi::lua_pushlstring(state, cache_key.as_ptr() as *const _, cache_key.len()); 1 } let (get_cache_key, find_current_file, proxyrequire, registered_modules, loader_cache) = unsafe { lua.exec_raw::<(Function, Function, Function, Table, Table)>((), move |state| { - let requirer_ptr = ffi::lua_newuserdata_t::>(state, Box::new(require)); + let context = Context(Box::new(require)); + let context_ptr = ffi::lua_newuserdata_t(state, RefCell::new(context)); ffi::lua_pushcclosured(state, get_cache_key, cstr!("get_cache_key"), 1); ffi::lua_pushcfunctiond(state, find_current_file, cstr!("find_current_file")); - ffi::luarequire_pushproxyrequire(state, init_config, requirer_ptr as *mut _); + ffi::luarequire_pushproxyrequire(state, init_config, context_ptr as *mut _); ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_REGISTERED_MODULES_TABLE); ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("__MLUA_LOADER_CACHE")); }) From 13395e9c3dcb34943b987bf8628d6375c8894b77 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 27 May 2025 00:54:24 +0100 Subject: [PATCH 409/635] Sync mlua_derive with `AsChunk` trait --- mlua_derive/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlua_derive/src/lib.rs b/mlua_derive/src/lib.rs index 54815cfa..eca34741 100644 --- a/mlua_derive/src/lib.rs +++ b/mlua_derive/src/lib.rs @@ -120,7 +120,7 @@ pub fn chunk(input: TokenStream) -> TokenStream { Some(ChunkMode::Text) } - fn source<'a>(self) -> IoResult> { + fn source<'a>(&self) -> IoResult> { Ok(Cow::Borrowed((#source).as_bytes())) } } From 76a8f8cc71082c6195f7cfaf15d16612cf123b3d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 27 May 2025 01:31:54 +0100 Subject: [PATCH 410/635] Add `__type` to Error's userdata metatable. Close #585 --- src/util/error.rs | 6 +++++- src/util/userdata.rs | 10 +++++----- tests/luau.rs | 11 +++++++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/util/error.rs b/src/util/error.rs index 899522de..8629f112 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -349,7 +349,11 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<( state, Some(|state| { ffi::lua_pushcfunction(state, error_tostring); - rawset_field(state, -2, "__tostring") + ffi::lua_setfield(state, -2, cstr!("__tostring")); + + // This is mostly for Luau typeof() function + ffi::lua_pushstring(state, cstr!("error")); + ffi::lua_setfield(state, -2, cstr!("__type")); }), )?; diff --git a/src/util/userdata.rs b/src/util/userdata.rs index 2edbf0ba..5e8d6111 100644 --- a/src/util/userdata.rs +++ b/src/util/userdata.rs @@ -47,7 +47,7 @@ pub(crate) unsafe fn get_internal_metatable(state: *mut ffi::lua_Sta // Uses 6 stack spaces and calls checkstack. pub(crate) unsafe fn init_internal_metatable( state: *mut ffi::lua_State, - customize_fn: Option Result<()>>, + customize_fn: Option, ) -> Result<()> { check_stack(state, 6)?; @@ -62,11 +62,11 @@ pub(crate) unsafe fn init_internal_metatable( ffi::lua_pushboolean(state, 0); rawset_field(state, -2, "__metatable")?; - if let Some(f) = customize_fn { - f(state)?; - } - protect_lua!(state, 1, 0, |state| { + if let Some(f) = customize_fn { + f(state); + } + ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, T::type_key()); })?; diff --git a/tests/luau.rs b/tests/luau.rs index 1b2cfc12..20fbee6a 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -436,5 +436,16 @@ fn test_loadstring() -> Result<()> { Ok(()) } +#[test] +fn test_typeof_error() -> Result<()> { + let lua = Lua::new(); + + let err = Error::runtime("just a test error"); + let res = lua.load("return typeof(...)").call::(err)?; + assert_eq!(res, "error"); + + Ok(()) +} + #[path = "luau/require.rs"] mod require; From 2fefaafaa6690aca7724efc9f29a83ac05b0f8d5 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 28 May 2025 12:13:34 +0100 Subject: [PATCH 411/635] Update "AnyUserData::take" to work on ref thread without need to push into stack. --- src/scope.rs | 14 ++++---------- src/userdata.rs | 24 +++++++++--------------- src/userdata/util.rs | 4 ++-- src/util/userdata.rs | 29 ++++++++++++++++------------- 4 files changed, 31 insertions(+), 40 deletions(-) diff --git a/src/scope.rs b/src/scope.rs index 7c155710..c56647a4 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -8,9 +8,7 @@ use crate::state::{Lua, LuaGuard, RawLua}; use crate::traits::{FromLuaMulti, IntoLuaMulti}; use crate::types::{Callback, CallbackUpvalue, ScopedCallback, ValueRef}; use crate::userdata::{AnyUserData, UserData, UserDataRegistry, UserDataStorage}; -use crate::util::{ - self, assert_stack, check_stack, get_metatable_ptr, get_userdata, take_userdata, StackGuard, -}; +use crate::util::{self, check_stack, get_metatable_ptr, get_userdata, take_userdata, StackGuard}; /// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and /// callbacks that are not required to be `Send` or `'static`. @@ -284,22 +282,18 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { /// Shortens the lifetime of the userdata to the lifetime of the scope. fn seal_userdata(&self, ud: &AnyUserData) { let destructor: DestructorCallback = Box::new(|rawlua, vref| unsafe { - let state = rawlua.state(); - let _sg = StackGuard::new(state); - assert_stack(state, 2); - // Ensure that userdata is not destructed - match rawlua.push_userdata_ref(&vref) { + match rawlua.get_userdata_ref_type_id(&vref) { Ok(Some(_)) => {} Ok(None) => { // Deregister metatable - let mt_ptr = get_metatable_ptr(state, -1); + let mt_ptr = get_metatable_ptr(rawlua.ref_thread(), vref.index); rawlua.deregister_userdata_metatable(mt_ptr); } Err(_) => return vec![], } - let data = take_userdata::>(state); + let data = take_userdata::>(rawlua.ref_thread(), vref.index); vec![Box::new(move || drop(data))] }); self.destructors.0.borrow_mut().push((ud.0.clone(), destructor)); diff --git a/src/userdata.rs b/src/userdata.rs index 2db8fa56..9bdfbd81 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -683,22 +683,16 @@ impl AnyUserData { /// Keeps associated user values unchanged (they will be collected by Lua's GC). pub fn take(&self) -> Result { let lua = self.0.lua.lock(); - let state = lua.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 2)?; - - let type_id = lua.push_userdata_ref(&self.0)?; - match type_id { - Some(type_id) if type_id == TypeId::of::() => { - if (*get_userdata::>(state, -1)).has_exclusive_access() { - take_userdata::>(state).into_inner() - } else { - Err(Error::UserDataBorrowMutError) - } + match lua.get_userdata_ref_type_id(&self.0)? { + Some(type_id) if type_id == TypeId::of::() => unsafe { + let ref_thread = lua.ref_thread(); + if (*get_userdata::>(ref_thread, self.0.index)).has_exclusive_access() { + take_userdata::>(ref_thread, self.0.index).into_inner() + } else { + Err(Error::UserDataBorrowMutError) } - _ => Err(Error::UserDataTypeMismatch), - } + }, + _ => Err(Error::UserDataTypeMismatch), } } diff --git a/src/userdata/util.rs b/src/userdata/util.rs index 9305eb00..c443de47 100644 --- a/src/userdata/util.rs +++ b/src/userdata/util.rs @@ -455,9 +455,9 @@ pub(crate) unsafe extern "C" fn collect_userdata( // It checks if the userdata is safe to destroy and sets the "destroyed" metatable // to prevent further GC collection. pub(super) unsafe extern "C-unwind" fn destroy_userdata_storage(state: *mut ffi::lua_State) -> c_int { - let ud = get_userdata::>(state, -1); + let ud = get_userdata::>(state, 1); if (*ud).is_safe_to_destroy() { - take_userdata::>(state); + take_userdata::>(state, 1); ffi::lua_pushboolean(state, 1); } else { ffi::lua_pushboolean(state, 0); diff --git a/src/util/userdata.rs b/src/util/userdata.rs index 5e8d6111..5ef93d5a 100644 --- a/src/util/userdata.rs +++ b/src/util/userdata.rs @@ -141,24 +141,27 @@ pub(crate) unsafe fn get_userdata(state: *mut ffi::lua_State, index: c_int) - ud } -// Pops the userdata off of the top of the stack and returns it to rust, invalidating the lua -// userdata and gives it the special "destructed" userdata metatable. Userdata must not have been -// previously invalidated, and this method does not check for this. -// Uses 1 extra stack space and does not call checkstack. -pub(crate) unsafe fn take_userdata(state: *mut ffi::lua_State) -> T { - // We set the metatable of userdata on __gc to a special table with no __gc method and with - // metamethods that trigger an error on access. We do this so that it will not be double - // dropped, and also so that it cannot be used or identified as any particular userdata type - // after the first call to __gc. +/// Unwraps `T` from the Lua userdata and invalidating it by setting the special "destructed" +/// metatable. +/// +/// This method does not check that userdata is of type `T` and was not previously invalidated. +/// +/// Uses 1 extra stack space, does not call checkstack. +pub(crate) unsafe fn take_userdata(state: *mut ffi::lua_State, idx: c_int) -> T { + #[rustfmt::skip] + let idx = if idx < 0 { ffi::lua_absindex(state, idx) } else { idx }; + + // Update the metatable of this userdata to a special one with no `__gc` method and with + // metamethods that trigger an error on access. + // We do this so that it will not be double dropped or used after being dropped. get_destructed_userdata_metatable(state); - ffi::lua_setmetatable(state, -2); - let ud = get_userdata::(state, -1); + ffi::lua_setmetatable(state, idx); + let ud = get_userdata::(state, idx); // Update userdata tag to disable destructor and mark as destructed #[cfg(feature = "luau")] - ffi::lua_setuserdatatag(state, -1, 1); + ffi::lua_setuserdatatag(state, idx, 1); - ffi::lua_pop(state, 1); ptr::read(ud) } From 00a56b115b25ac45c941d170d857c84af6f234e6 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 28 May 2025 14:01:35 +0100 Subject: [PATCH 412/635] Add LuaBorrowedBytes/LuaBorrowedStr to prelude --- src/prelude.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/prelude.rs b/src/prelude.rs index aaa94f56..eeeaea26 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,15 +2,16 @@ #[doc(no_inline)] pub use crate::{ - AnyUserData as LuaAnyUserData, Chunk as LuaChunk, Either as LuaEither, Error as LuaError, - ErrorContext as LuaErrorContext, ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, - FromLua, FromLuaMulti, Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, - Integer as LuaInteger, IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, - LuaNativeFnMut, LuaOptions, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, - Number as LuaNumber, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, - StdLib as LuaStdLib, String as LuaString, Table as LuaTable, TablePairs as LuaTablePairs, - TableSequence as LuaTableSequence, Thread as LuaThread, ThreadStatus as LuaThreadStatus, - UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, + AnyUserData as LuaAnyUserData, BorrowedBytes as LuaBorrowedBytes, BorrowedStr as LuaBorrowedStr, + Chunk as LuaChunk, Either as LuaEither, Error as LuaError, ErrorContext as LuaErrorContext, + ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti, + Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, Integer as LuaInteger, + IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions, + MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, + ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, + String as LuaString, Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, + Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData, + UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, Variadic as LuaVariadic, VmState as LuaVmState, WeakLua, From b3854d2f1d88716f504500023b16ac6c6fd26a66 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 28 May 2025 14:03:10 +0100 Subject: [PATCH 413/635] Add '=' prefix to `__mlua_bind` name --- src/function.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/function.rs b/src/function.rs index 916bf187..9a5b291b 100644 --- a/src/function.rs +++ b/src/function.rs @@ -253,7 +253,7 @@ impl Function { "#, ) .try_cache() - .set_name("__mlua_bind") + .set_name("=__mlua_bind") .call((self, args_wrapper)) } From 38fbd08c72f1b5d28b495561d75d62d88342352b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 30 May 2025 21:56:27 +0100 Subject: [PATCH 414/635] Use luau0-src v0.15.0 (Luau 0.676) --- mlua-sys/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index d470b9ce..937031f8 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -40,7 +40,7 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 547.1.0, < 547.2.0", optional = true } luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true } -luau0-src = { git = "https://github.com/mlua-rs/luau-src-rs", version = "0.15.0", optional = true } +luau0-src = { version = "0.15.0", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } From 1a82f836446eddd2772602ad98a5892279422c16 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 30 May 2025 23:20:06 +0100 Subject: [PATCH 415/635] Move `parking_lot/send_guard` from `send` to `userdata-wrappers` feature. Related to #553 --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6d7a6b5a..420bcab3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,12 +38,12 @@ luau-vector4 = ["luau", "ffi/luau-vector4"] vendored = ["ffi/vendored"] module = ["mlua_derive", "ffi/module"] async = ["dep:futures-util"] -send = ["parking_lot/send_guard", "error-send"] +send = ["error-send"] error-send = [] serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value", "bstr/serde"] macros = ["mlua_derive/macros"] anyhow = ["dep:anyhow", "error-send"] -userdata-wrappers = [] +userdata-wrappers = ["parking_lot/send_guard"] [dependencies] mlua_derive = { version = "=0.11.0-beta.1", optional = true, path = "mlua_derive" } From 65e292dac4d4b7efcc82abd882b3954dd8e9ddfc Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 3 Jun 2025 23:01:50 +0100 Subject: [PATCH 416/635] More Luau `require` tests --- src/luau/require.rs | 20 ++++++----- tests/luau/require.rs | 34 +++++++++++++++++-- .../without_config/nested_inits/init.luau | 2 ++ .../nested_inits/init/init.luau | 1 + .../without_config/nested_inits_requirer.luau | 3 ++ 5 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 tests/luau/require/without_config/nested_inits/init.luau create mode 100644 tests/luau/require/without_config/nested_inits/init/init.luau create mode 100644 tests/luau/require/without_config/nested_inits_requirer.luau diff --git a/src/luau/require.rs b/src/luau/require.rs index abf56df2..b1986ba3 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -144,16 +144,18 @@ impl TextRequirer { fn find_module_path(path: &Path) -> StdResult { let mut found_path = None; - let current_ext = (path.extension().and_then(|s| s.to_str())) - .map(|s| format!("{s}.")) - .unwrap_or_default(); - for ext in ["luau", "lua"] { - let candidate = path.with_extension(format!("{current_ext}{ext}")); - if candidate.is_file() { - if found_path.is_some() { - return Err(NavigateError::Ambiguous); + if path.components().last() != Some(Component::Normal("init".as_ref())) { + let current_ext = (path.extension().and_then(|s| s.to_str())) + .map(|s| format!("{s}.")) + .unwrap_or_default(); + for ext in ["luau", "lua"] { + let candidate = path.with_extension(format!("{current_ext}{ext}")); + if candidate.is_file() { + if found_path.is_some() { + return Err(NavigateError::Ambiguous); + } + found_path = Some(candidate); } - found_path = Some(candidate); } } if path.is_dir() { diff --git a/tests/luau/require.rs b/tests/luau/require.rs index a38b0cd6..320e87e0 100644 --- a/tests/luau/require.rs +++ b/tests/luau/require.rs @@ -1,9 +1,13 @@ -use mlua::{IntoLua, Lua, Result, Value}; +use mlua::{IntoLua, Lua, MultiValue, Result, Value}; fn run_require(lua: &Lua, path: impl IntoLua) -> Result { lua.load(r#"return require(...)"#).call(path) } +fn run_require_pcall(lua: &Lua, path: impl IntoLua) -> Result { + lua.load(r#"return pcall(require, ...)"#).call(path) +} + #[track_caller] fn get_str(value: &Value, key: impl IntoLua) -> String { value.as_table().unwrap().get::(key).unwrap() @@ -32,6 +36,13 @@ fn test_require_errors() { assert!(res.is_err()); assert!((res.unwrap_err().to_string()) .contains("bad argument #1 to 'require' (string expected, got boolean)")); + + // Require from loadstring + let res = lua + .load(r#"return loadstring("require('./a/relative/path')")()"#) + .eval::(); + assert!(res.is_err()); + assert!((res.unwrap_err().to_string()).contains("require is not supported in this context")); } #[test] @@ -42,6 +53,11 @@ fn test_require_without_config() { let res = run_require(&lua, "./require/without_config/dependency").unwrap(); assert_eq!("result from dependency", get_str(&res, 1)); + // RequireSimpleRelativePathWithinPcall + let res = run_require_pcall(&lua, "./require/without_config/dependency").unwrap(); + assert!(res[0].as_boolean().unwrap()); + assert_eq!("result from dependency", get_str(&res[1], 1)); + // RequireRelativeToRequiringFile let res = run_require(&lua, "./require/without_config/module").unwrap(); assert_eq!("result from dependency", get_str(&res, 1)); @@ -59,10 +75,24 @@ fn test_require_without_config() { let res = run_require(&lua, "./require/without_config/lua").unwrap(); assert_eq!("result from init.lua", get_str(&res, 1)); - // RequireSubmoduleUsingSelf + // RequireSubmoduleUsingSelfIndirectly let res = run_require(&lua, "./require/without_config/nested_module_requirer").unwrap(); assert_eq!("result from submodule", get_str(&res, 1)); + // RequireSubmoduleUsingSelfDirectly + let res = run_require(&lua, "./require/without_config/nested").unwrap(); + assert_eq!("result from submodule", get_str(&res, 1)); + + // CannotRequireInitLuauDirectly + let res = run_require(&lua, "./require/without_config/nested/init"); + assert!(res.is_err()); + assert!((res.unwrap_err().to_string()).contains("could not resolve child component \"init\"")); + + // RequireNestedInits + let res = run_require(&lua, "./require/without_config/nested_inits_requirer").unwrap(); + assert_eq!("result from nested_inits/init", get_str(&res, 1)); + assert_eq!("required into module", get_str(&res, 2)); + // RequireWithFileAmbiguity let res = run_require(&lua, "./require/without_config/ambiguous_file_requirer"); assert!(res.is_err()); diff --git a/tests/luau/require/without_config/nested_inits/init.luau b/tests/luau/require/without_config/nested_inits/init.luau new file mode 100644 index 00000000..9a36b68a --- /dev/null +++ b/tests/luau/require/without_config/nested_inits/init.luau @@ -0,0 +1,2 @@ +local result = require("@self/init") +return result diff --git a/tests/luau/require/without_config/nested_inits/init/init.luau b/tests/luau/require/without_config/nested_inits/init/init.luau new file mode 100644 index 00000000..0623c941 --- /dev/null +++ b/tests/luau/require/without_config/nested_inits/init/init.luau @@ -0,0 +1 @@ +return {"result from nested_inits/init"} diff --git a/tests/luau/require/without_config/nested_inits_requirer.luau b/tests/luau/require/without_config/nested_inits_requirer.luau new file mode 100644 index 00000000..6c4a0a5f --- /dev/null +++ b/tests/luau/require/without_config/nested_inits_requirer.luau @@ -0,0 +1,3 @@ +local result = require("./nested_inits") +result[#result+1] = "required into module" +return result From 942a4435928ec9128638a3a22b175b44829e32dd Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 5 Jun 2025 15:02:47 +0100 Subject: [PATCH 417/635] Bump Lua 5.4 to 5.4.8 --- mlua-sys/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 937031f8..8e28361d 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -38,7 +38,7 @@ module = [] cc = "1.0" cfg-if = "1.0" pkg-config = "0.3.17" -lua-src = { version = ">= 547.1.0, < 547.2.0", optional = true } +lua-src = { version = ">= 548.0.0, < 548.1.0", optional = true } luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true } luau0-src = { version = "0.15.0", optional = true } From e6e1ef014f2834e247c6d74666800aba4137e9f1 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 5 Jun 2025 23:08:28 +0100 Subject: [PATCH 418/635] Some cosmetic changes (Luau "require") --- src/luau/require.rs | 75 ++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 46 deletions(-) diff --git a/src/luau/require.rs b/src/luau/require.rs index b1986ba3..84be8257 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -15,6 +15,7 @@ use crate::table::Table; use crate::types::MaybeSend; /// An error that can occur during navigation in the Luau `require` system. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum NavigateError { Ambiguous, NotFound, @@ -91,7 +92,7 @@ impl fmt::Debug for dyn Require { } /// The standard implementation of Luau `require` navigation. -#[derive(Default)] +#[derive(Default, Debug)] pub(super) struct TextRequirer { abs_path: PathBuf, rel_path: PathBuf, @@ -141,35 +142,25 @@ impl TextRequirer { components.into_iter().collect() } - fn find_module_path(path: &Path) -> StdResult { + fn find_module(path: &Path) -> StdResult { let mut found_path = None; - if path.components().last() != Some(Component::Normal("init".as_ref())) { + if path.components().next_back() != Some(Component::Normal("init".as_ref())) { let current_ext = (path.extension().and_then(|s| s.to_str())) .map(|s| format!("{s}.")) .unwrap_or_default(); for ext in ["luau", "lua"] { let candidate = path.with_extension(format!("{current_ext}{ext}")); - if candidate.is_file() { - if found_path.is_some() { - return Err(NavigateError::Ambiguous); - } - found_path = Some(candidate); + if candidate.is_file() && found_path.replace(candidate).is_some() { + return Err(NavigateError::Ambiguous); } } } if path.is_dir() { - if found_path.is_some() { - return Err(NavigateError::Ambiguous); - } - for component in ["init.luau", "init.lua"] { let candidate = path.join(component); - if candidate.is_file() { - if found_path.is_some() { - return Err(NavigateError::Ambiguous); - } - found_path = Some(candidate); + if candidate.is_file() && found_path.replace(candidate).is_some() { + return Err(NavigateError::Ambiguous); } } @@ -191,36 +182,30 @@ impl Require for TextRequirer { if !chunk_name.starts_with('@') { return Err(NavigateError::NotFound); } - let chunk_name = &Self::normalize_chunk_name(chunk_name)[1..]; - let path = Self::normalize_path(chunk_name.as_ref()); - - if path.extension() == Some("rs".as_ref()) { - let cwd = match env::current_dir() { - Ok(cwd) => cwd, - Err(_) => return Err(NavigateError::NotFound), - }; - self.abs_path = Self::normalize_path(&cwd.join(&path)); - self.rel_path = path; + let chunk_name = Self::normalize_chunk_name(&chunk_name[1..]); + let chunk_path = Self::normalize_path(chunk_name.as_ref()); + + if chunk_path.extension() == Some("rs".as_ref()) { + let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?; + self.abs_path = Self::normalize_path(&cwd.join(&chunk_path)); + self.rel_path = chunk_path; self.module_path = PathBuf::new(); return Ok(()); } - if path.is_absolute() { - let module_path = Self::find_module_path(&path)?; - self.abs_path = path.clone(); - self.rel_path = path; + if chunk_path.is_absolute() { + let module_path = Self::find_module(&chunk_path)?; + self.abs_path = chunk_path.clone(); + self.rel_path = chunk_path; self.module_path = module_path; } else { // Relative path - let cwd = match env::current_dir() { - Ok(cwd) => cwd, - Err(_) => return Err(NavigateError::NotFound), - }; - let abs_path = cwd.join(&path); - let module_path = Self::find_module_path(&abs_path)?; - self.abs_path = Self::normalize_path(&abs_path); - self.rel_path = path; + let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?; + let abs_path = Self::normalize_path(&cwd.join(&chunk_path)); + let module_path = Self::find_module(&abs_path)?; + self.abs_path = abs_path; + self.rel_path = chunk_path; self.module_path = module_path; } @@ -229,7 +214,7 @@ impl Require for TextRequirer { fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> { let path = Self::normalize_path(path.as_ref()); - let module_path = Self::find_module_path(&path)?; + let module_path = Self::find_module(&path)?; self.abs_path = path.clone(); self.rel_path = path; @@ -245,7 +230,7 @@ impl Require for TextRequirer { } let mut rel_parent = self.rel_path.clone(); rel_parent.pop(); - let module_path = Self::find_module_path(&abs_path)?; + let module_path = Self::find_module(&abs_path)?; self.abs_path = abs_path; self.rel_path = Self::normalize_path(&rel_parent); @@ -257,7 +242,7 @@ impl Require for TextRequirer { fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> { let abs_path = self.abs_path.join(name); let rel_path = self.rel_path.join(name); - let module_path = Self::find_module_path(&abs_path)?; + let module_path = Self::find_module(&abs_path)?; self.abs_path = abs_path; self.rel_path = rel_path; @@ -275,7 +260,7 @@ impl Require for TextRequirer { } fn has_config(&self) -> bool { - self.abs_path.join(".luaurc").is_file() + self.abs_path.is_dir() && self.abs_path.join(".luaurc").is_file() } fn config(&self) -> IoResult> { @@ -284,9 +269,7 @@ impl Require for TextRequirer { fn loader(&self, lua: &Lua) -> Result { let name = format!("@{}", self.rel_path.display()); - lua.load(self.module_path.as_path()) - .set_name(name) - .into_function() + lua.load(&*self.module_path).set_name(name).into_function() } } From 39cac5699a498dd7a6ce2ea03cb0312a88923c94 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 6 Jun 2025 13:45:57 +0100 Subject: [PATCH 419/635] Add unwinding support (returning an Error) to Luau `Require` implementation --- mlua-sys/src/luau/luarequire.rs | 32 ++++++++------ src/lib.rs | 2 +- src/luau/mod.rs | 2 +- src/luau/require.rs | 78 +++++++++++++++++++++------------ tests/luau/require.rs | 57 +++++++++++++++++++++++- 5 files changed, 125 insertions(+), 46 deletions(-) diff --git a/mlua-sys/src/luau/luarequire.rs b/mlua-sys/src/luau/luarequire.rs index ff8343a9..64e45e25 100644 --- a/mlua-sys/src/luau/luarequire.rs +++ b/mlua-sys/src/luau/luarequire.rs @@ -26,11 +26,14 @@ pub enum luarequire_WriteResult { #[repr(C)] pub struct luarequire_Configuration { // Returns whether requires are permitted from the given chunkname. - pub is_require_allowed: - unsafe extern "C" fn(L: *mut lua_State, ctx: *mut c_void, requirer_chunkname: *const c_char) -> bool, + pub is_require_allowed: unsafe extern "C-unwind" fn( + L: *mut lua_State, + ctx: *mut c_void, + requirer_chunkname: *const c_char, + ) -> bool, // Resets the internal state to point at the requirer module. - pub reset: unsafe extern "C" fn( + pub reset: unsafe extern "C-unwind" fn( L: *mut lua_State, ctx: *mut c_void, requirer_chunkname: *const c_char, @@ -39,26 +42,27 @@ pub struct luarequire_Configuration { // Resets the internal state to point at an aliased module, given its exact path from a configuration // file. This function is only called when an alias's path cannot be resolved relative to its // configuration file. - pub jump_to_alias: unsafe extern "C" fn( + pub jump_to_alias: unsafe extern "C-unwind" fn( L: *mut lua_State, ctx: *mut c_void, path: *const c_char, ) -> luarequire_NavigateResult, // Navigates through the context by making mutations to the internal state. - pub to_parent: unsafe extern "C" fn(L: *mut lua_State, ctx: *mut c_void) -> luarequire_NavigateResult, - pub to_child: unsafe extern "C" fn( + pub to_parent: + unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> luarequire_NavigateResult, + pub to_child: unsafe extern "C-unwind" fn( L: *mut lua_State, ctx: *mut c_void, name: *const c_char, ) -> luarequire_NavigateResult, // Returns whether the context is currently pointing at a module. - pub is_module_present: unsafe extern "C" fn(L: *mut lua_State, ctx: *mut c_void) -> bool, + pub is_module_present: unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> bool, // Provides a chunkname for the current module. This will be accessible through the debug library. This // function is only called if is_module_present returns true. - pub get_chunkname: unsafe extern "C" fn( + pub get_chunkname: unsafe extern "C-unwind" fn( L: *mut lua_State, ctx: *mut c_void, buffer: *mut c_char, @@ -68,7 +72,7 @@ pub struct luarequire_Configuration { // Provides a loadname that identifies the current module and is passed to load. This function // is only called if is_module_present returns true. - pub get_loadname: unsafe extern "C" fn( + pub get_loadname: unsafe extern "C-unwind" fn( L: *mut lua_State, ctx: *mut c_void, buffer: *mut c_char, @@ -78,7 +82,7 @@ pub struct luarequire_Configuration { // Provides a cache key representing the current module. This function is only called if // is_module_present returns true. - pub get_cache_key: unsafe extern "C" fn( + pub get_cache_key: unsafe extern "C-unwind" fn( L: *mut lua_State, ctx: *mut c_void, buffer: *mut c_char, @@ -89,7 +93,7 @@ pub struct luarequire_Configuration { // Returns whether a configuration file is present in the current context. // If not, require-by-string will call to_parent until either a configuration file is present or // NAVIGATE_FAILURE is returned (at root). - pub is_config_present: unsafe extern "C" fn(L: *mut lua_State, ctx: *mut c_void) -> bool, + pub is_config_present: unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> bool, // Parses the configuration file in the current context for the given alias and returns its // value or WRITE_FAILURE if not found. This function is only called if is_config_present @@ -97,7 +101,7 @@ pub struct luarequire_Configuration { // function pointer disables parsing configuration files internally and can be used for finer // control over the configuration file parsing process. pub get_alias: Option< - unsafe extern "C" fn( + unsafe extern "C-unwind" fn( L: *mut lua_State, ctx: *mut c_void, alias: *const c_char, @@ -111,7 +115,7 @@ pub struct luarequire_Configuration { // if is_config_present returns true. If this function pointer is set, get_alias must not be set. Opting // in to this function pointer enables parsing configuration files internally. pub get_config: Option< - unsafe extern "C" fn( + unsafe extern "C-unwind" fn( L: *mut lua_State, ctx: *mut c_void, buffer: *mut c_char, @@ -134,7 +138,7 @@ pub struct luarequire_Configuration { } // Populates function pointers in the given luarequire_Configuration. -pub type luarequire_Configuration_init = unsafe extern "C" fn(config: *mut luarequire_Configuration); +pub type luarequire_Configuration_init = unsafe extern "C-unwind" fn(config: *mut luarequire_Configuration); unsafe extern "C-unwind" { // Initializes and pushes the require closure onto the stack without registration. diff --git a/src/lib.rs b/src/lib.rs index de321205..dc15737b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -132,7 +132,7 @@ pub use crate::{ buffer::Buffer, chunk::{CompileConstant, Compiler}, function::CoverageInfo, - luau::{NavigateError, Require}, + luau::{NavigateError, Require, TextRequirer}, vector::Vector, }; diff --git a/src/luau/mod.rs b/src/luau/mod.rs index f773fce7..19eb8fa5 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -8,7 +8,7 @@ use crate::function::Function; use crate::state::{callback_error_ext, Lua}; use crate::traits::{FromLuaMulti, IntoLua}; -pub use require::{NavigateError, Require}; +pub use require::{NavigateError, Require, TextRequirer}; // Since Luau has some missing standard functions, we re-implement them here diff --git a/src/luau/require.rs b/src/luau/require.rs index 84be8257..ebb7468f 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -8,39 +8,51 @@ use std::path::{Component, Path, PathBuf}; use std::result::Result as StdResult; use std::{env, fmt, fs, mem, ptr}; -use crate::error::Result; +use crate::error::{Error, Result}; use crate::function::Function; use crate::state::{callback_error_ext, Lua}; use crate::table::Table; use crate::types::MaybeSend; /// An error that can occur during navigation in the Luau `require` system. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg(any(feature = "luau", doc))] +#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] +#[derive(Debug, Clone)] pub enum NavigateError { Ambiguous, NotFound, + Other(Error), } #[cfg(feature = "luau")] trait IntoNavigateResult { - fn into_nav_result(self) -> ffi::luarequire_NavigateResult; + fn into_nav_result(self) -> Result; } #[cfg(feature = "luau")] impl IntoNavigateResult for StdResult<(), NavigateError> { - fn into_nav_result(self) -> ffi::luarequire_NavigateResult { + fn into_nav_result(self) -> Result { match self { - Ok(()) => ffi::luarequire_NavigateResult::Success, - Err(NavigateError::Ambiguous) => ffi::luarequire_NavigateResult::Ambiguous, - Err(NavigateError::NotFound) => ffi::luarequire_NavigateResult::NotFound, + Ok(()) => Ok(ffi::luarequire_NavigateResult::Success), + Err(NavigateError::Ambiguous) => Ok(ffi::luarequire_NavigateResult::Ambiguous), + Err(NavigateError::NotFound) => Ok(ffi::luarequire_NavigateResult::NotFound), + Err(NavigateError::Other(err)) => Err(err), } } } +impl From for NavigateError { + fn from(err: Error) -> Self { + NavigateError::Other(err) + } +} + #[cfg(feature = "luau")] type WriteResult = ffi::luarequire_WriteResult; /// A trait for handling modules loading and navigation in the Luau `require` system. +#[cfg(any(feature = "luau", doc))] +#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub trait Require: MaybeSend { /// Returns `true` if "require" is permitted for the given chunk name. fn is_require_allowed(&self, chunk_name: &str) -> bool; @@ -92,15 +104,17 @@ impl fmt::Debug for dyn Require { } /// The standard implementation of Luau `require` navigation. +#[doc(hidden)] #[derive(Default, Debug)] -pub(super) struct TextRequirer { +pub struct TextRequirer { abs_path: PathBuf, rel_path: PathBuf, module_path: PathBuf, } impl TextRequirer { - pub(super) fn new() -> Self { + /// Creates a new `TextRequirer` instance. + pub fn new() -> Self { Self::default() } @@ -308,12 +322,12 @@ macro_rules! try_borrow_mut { } #[cfg(feature = "luau")] -pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configuration) { +pub(super) unsafe extern "C-unwind" fn init_config(config: *mut ffi::luarequire_Configuration) { if config.is_null() { return; } - unsafe extern "C" fn is_require_allowed( + unsafe extern "C-unwind" fn is_require_allowed( state: *mut ffi::lua_State, ctx: *mut c_void, requirer_chunkname: *const c_char, @@ -327,50 +341,58 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu this.is_require_allowed(&chunk_name) } - unsafe extern "C" fn reset( + unsafe extern "C-unwind" fn reset( state: *mut ffi::lua_State, ctx: *mut c_void, requirer_chunkname: *const c_char, ) -> ffi::luarequire_NavigateResult { let mut this = try_borrow_mut!(state, ctx); let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy(); - this.reset(&chunk_name).into_nav_result() + callback_error_ext(state, ptr::null_mut(), true, move |_, _| { + this.reset(&chunk_name).into_nav_result() + }) } - unsafe extern "C" fn jump_to_alias( + unsafe extern "C-unwind" fn jump_to_alias( state: *mut ffi::lua_State, ctx: *mut c_void, path: *const c_char, ) -> ffi::luarequire_NavigateResult { let mut this = try_borrow_mut!(state, ctx); let path = CStr::from_ptr(path).to_string_lossy(); - this.jump_to_alias(&path).into_nav_result() + callback_error_ext(state, ptr::null_mut(), true, move |_, _| { + this.jump_to_alias(&path).into_nav_result() + }) } - unsafe extern "C" fn to_parent( + unsafe extern "C-unwind" fn to_parent( state: *mut ffi::lua_State, ctx: *mut c_void, ) -> ffi::luarequire_NavigateResult { let mut this = try_borrow_mut!(state, ctx); - this.to_parent().into_nav_result() + callback_error_ext(state, ptr::null_mut(), true, move |_, _| { + this.to_parent().into_nav_result() + }) } - unsafe extern "C" fn to_child( + unsafe extern "C-unwind" fn to_child( state: *mut ffi::lua_State, ctx: *mut c_void, name: *const c_char, ) -> ffi::luarequire_NavigateResult { let mut this = try_borrow_mut!(state, ctx); let name = CStr::from_ptr(name).to_string_lossy(); - this.to_child(&name).into_nav_result() + callback_error_ext(state, ptr::null_mut(), true, move |_, _| { + this.to_child(&name).into_nav_result() + }) } - unsafe extern "C" fn is_module_present(state: *mut ffi::lua_State, ctx: *mut c_void) -> bool { + unsafe extern "C-unwind" fn is_module_present(state: *mut ffi::lua_State, ctx: *mut c_void) -> bool { let this = try_borrow!(state, ctx); this.has_module() } - unsafe extern "C" fn get_chunkname( + unsafe extern "C-unwind" fn get_chunkname( _state: *mut ffi::lua_State, _ctx: *mut c_void, buffer: *mut c_char, @@ -380,7 +402,7 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu write_to_buffer(buffer, buffer_size, size_out, &[]) } - unsafe extern "C" fn get_loadname( + unsafe extern "C-unwind" fn get_loadname( _state: *mut ffi::lua_State, _ctx: *mut c_void, buffer: *mut c_char, @@ -390,7 +412,7 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu write_to_buffer(buffer, buffer_size, size_out, &[]) } - unsafe extern "C" fn get_cache_key( + unsafe extern "C-unwind" fn get_cache_key( state: *mut ffi::lua_State, ctx: *mut c_void, buffer: *mut c_char, @@ -402,12 +424,12 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu write_to_buffer(buffer, buffer_size, size_out, cache_key.as_bytes()) } - unsafe extern "C" fn is_config_present(state: *mut ffi::lua_State, ctx: *mut c_void) -> bool { + unsafe extern "C-unwind" fn is_config_present(state: *mut ffi::lua_State, ctx: *mut c_void) -> bool { let this = try_borrow!(state, ctx); this.has_config() } - unsafe extern "C" fn get_config( + unsafe extern "C-unwind" fn get_config( state: *mut ffi::lua_State, ctx: *mut c_void, buffer: *mut c_char, @@ -415,9 +437,7 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu size_out: *mut usize, ) -> WriteResult { let this = try_borrow!(state, ctx); - let Ok(config) = this.config() else { - return WriteResult::Failure; - }; + let config = callback_error_ext(state, ptr::null_mut(), true, move |_, _| Ok(this.config()?)); write_to_buffer(buffer, buffer_size, size_out, &config) } @@ -429,7 +449,7 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu _loadname: *const c_char, ) -> c_int { let this = try_borrow!(state, ctx); - callback_error_ext(state, ptr::null_mut(), false, move |extra, _| { + callback_error_ext(state, ptr::null_mut(), true, move |extra, _| { let rawlua = (*extra).raw_lua(); let loader = this.loader(rawlua.lua())?; rawlua.push(loader)?; diff --git a/tests/luau/require.rs b/tests/luau/require.rs index 320e87e0..7d2ec582 100644 --- a/tests/luau/require.rs +++ b/tests/luau/require.rs @@ -1,4 +1,7 @@ -use mlua::{IntoLua, Lua, MultiValue, Result, Value}; +use std::io::Result as IoResult; +use std::result::Result as StdResult; + +use mlua::{Error, IntoLua, Lua, MultiValue, NavigateError, Require, Result, TextRequirer, Value}; fn run_require(lua: &Lua, path: impl IntoLua) -> Result { lua.load(r#"return require(...)"#).call(path) @@ -43,6 +46,58 @@ fn test_require_errors() { .eval::(); assert!(res.is_err()); assert!((res.unwrap_err().to_string()).contains("require is not supported in this context")); + + // Test throwing mlua::Error + struct MyRequire(TextRequirer); + + impl Require for MyRequire { + fn is_require_allowed(&self, chunk_name: &str) -> bool { + self.0.is_require_allowed(chunk_name) + } + + fn reset(&mut self, _chunk_name: &str) -> StdResult<(), NavigateError> { + Err(Error::runtime("test error"))? + } + + fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> { + self.0.jump_to_alias(path) + } + + fn to_parent(&mut self) -> StdResult<(), NavigateError> { + self.0.to_parent() + } + + fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> { + self.0.to_child(name) + } + + fn has_module(&self) -> bool { + self.0.has_module() + } + + fn cache_key(&self) -> String { + self.0.cache_key() + } + + fn has_config(&self) -> bool { + self.0.has_config() + } + + fn config(&self) -> IoResult> { + self.0.config() + } + + fn loader(&self, lua: &Lua) -> Result { + self.0.loader(lua) + } + } + + let require = lua + .create_require_function(MyRequire(TextRequirer::new())) + .unwrap(); + lua.globals().set("require", require).unwrap(); + let res = lua.load(r#"return require('./a/relative/path')"#).exec(); + assert!((res.unwrap_err().to_string()).contains("test error")); } #[test] From 6fcd18e434cada98e2141734e43d25566f39aa43 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 6 Jun 2025 16:03:14 +0100 Subject: [PATCH 420/635] Update CHANGELOG --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2c6f450..0b09d4a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## v0.11.0-beta.2 (Jun ?, 2025) + +- Lua 5.4 updated to 5.4.8 +- Terminate Rust `Future` when `AsyncThread` is dropped (without relying on Lua GC) +- Added `loadstring` function to Luau +- Make `AsChunk` trait dyn-friendly +- Luau `Require` trait synced with Luau 0.674 +- Luau `Require` trait methods now can return `Error` variant (in `NavigateError` enum) +- Added `__type` to `Error`'s userdata metatable (for `typeof` function) +- `parking_log/send_guard` is moved to `userdata-wrappers` feature flag + ## v0.11.0-beta.1 (May 7th, 2025) - New "require-by-string" for Luau (with `Require` trait and async support) From c4c9609ac6f1f23d2e3be140e7b8480ad01823c9 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 6 Jun 2025 23:09:11 +0100 Subject: [PATCH 421/635] mlua-sys: v0.8.0 --- Cargo.toml | 2 +- mlua-sys/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 420bcab3..ac7034a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ parking_lot = { version = "0.12", features = ["arc_lock"] } anyhow = { version = "1.0", optional = true } rustversion = "1.0" -ffi = { package = "mlua-sys", version = "0.7.0", path = "mlua-sys" } +ffi = { package = "mlua-sys", version = "0.8.0", path = "mlua-sys" } [dev-dependencies] trybuild = "1.0" diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 8e28361d..0ce088da 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.7.0" +version = "0.8.0" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" From 031424f6ce06c92890a1be2ddd89f8384f5acd49 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 6 Jun 2025 23:17:58 +0100 Subject: [PATCH 422/635] Update dev dependencies --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ac7034a3..3aed77b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,8 +75,8 @@ tempfile = "3" static_assertions = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] -criterion = { version = "0.5", features = ["async_tokio"] } -rustyline = "15.0" +criterion = { version = "0.6", features = ["async_tokio"] } +rustyline = "16.0" tokio = { version = "1.0", features = ["full"] } [lints.rust] From b57a6239a6544a831514fe6a23b59f55cd56779c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 6 Jun 2025 23:20:01 +0100 Subject: [PATCH 423/635] mlua_derive: v0.11.0-beta.2 --- Cargo.toml | 2 +- mlua_derive/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3aed77b2..65ac1ba8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ anyhow = ["dep:anyhow", "error-send"] userdata-wrappers = ["parking_lot/send_guard"] [dependencies] -mlua_derive = { version = "=0.11.0-beta.1", optional = true, path = "mlua_derive" } +mlua_derive = { version = "=0.11.0-beta.2", optional = true, path = "mlua_derive" } bstr = { version = "1.0", features = ["std"], default-features = false } either = "1.0" num-traits = { version = "0.2.14" } diff --git a/mlua_derive/Cargo.toml b/mlua_derive/Cargo.toml index 2f11887b..fac6e00f 100644 --- a/mlua_derive/Cargo.toml +++ b/mlua_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua_derive" -version = "0.11.0-beta.1" +version = "0.11.0-beta.2" authors = ["Aleksandr Orlenko "] edition = "2021" description = "Procedural macros for the mlua crate." From 3d5261640d293c13dbe575c4fdee3eb7746a1da3 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 6 Jun 2025 23:25:36 +0100 Subject: [PATCH 424/635] Fix doc warnings --- src/state.rs | 4 ++-- src/thread.rs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/state.rs b/src/state.rs index 4be9ba4c..978b1dc9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -377,7 +377,7 @@ impl Lua { /// /// This is similar to setting the [`package.preload[modname]`] field. /// - /// [`package.preload[modname]`]: https://www.lua.org/manual/5.4/manual.html#pdf-package.preload + /// [`package.preload[modname]`]: #[cfg(not(feature = "luau"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub fn preload_module(&self, modname: &str, func: Function) -> Result<()> { @@ -636,7 +636,7 @@ impl Lua { /// Also this can be used to implement continuous execution limits by instructing Luau VM to /// yield by returning [`VmState::Yield`]. /// - /// This is similar to [`Lua::set_hook`] but in more simplified form. + /// This is similar to `Lua::set_hook` but in more simplified form. /// /// # Example /// diff --git a/src/thread.rs b/src/thread.rs index da7962b0..520f0637 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -263,6 +263,8 @@ impl Thread { /// You can have multiple hooks for different threads. /// /// To remove a hook call [`Thread::remove_hook`]. + /// + /// [`Lua::set_hook`]: crate::Lua::set_hook #[cfg(not(feature = "luau"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub fn set_hook(&self, triggers: HookTriggers, callback: F) -> Result<()> From caeac2e9a3c0492bfad27b85d9f2d6daaa5109f9 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 11 Jun 2025 22:24:26 +0100 Subject: [PATCH 425/635] Add private app_data container for mlua internal use --- src/chunk.rs | 6 +++--- src/state/extra.rs | 4 +++- src/state/raw.rs | 19 +++++++++---------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index 375f15fb..3de61648 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -637,7 +637,7 @@ impl Chunk<'_> { if let Ok(ref source) = self.source { if self.detect_mode() == ChunkMode::Text { let lua = self.lua.lock(); - if let Some(cache) = lua.app_data_ref_unguarded::() { + if let Some(cache) = lua.priv_app_data_ref::() { if let Some(data) = cache.0.get(source.as_ref()) { self.source = Ok(Cow::Owned(data.clone())); self.mode = Some(ChunkMode::Binary); @@ -654,12 +654,12 @@ impl Chunk<'_> { if let Ok(ref binary_source) = self.source { if self.detect_mode() == ChunkMode::Binary { let lua = self.lua.lock(); - if let Some(mut cache) = lua.app_data_mut_unguarded::() { + if let Some(mut cache) = lua.priv_app_data_mut::() { cache.0.insert(text_source, binary_source.to_vec()); } else { let mut cache = ChunksCache(HashMap::new()); cache.0.insert(text_source, binary_source.to_vec()); - let _ = lua.try_set_app_data(cache); + lua.set_priv_app_data(cache); }; } } diff --git a/src/state/extra.rs b/src/state/extra.rs index 12133639..5ff74a33 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -44,8 +44,9 @@ pub(crate) struct ExtraData { // When Lua instance dropped, setting `None` would prevent collecting `RegistryKey`s pub(super) registry_unref_list: Arc>>>, - // Container to store arbitrary data (extensions) + // Containers to store arbitrary data (extensions) pub(super) app_data: AppData, + pub(super) app_data_priv: AppData, pub(super) safe: bool, pub(super) libs: StdLib, @@ -159,6 +160,7 @@ impl ExtraData { last_checked_userdata_mt: (ptr::null(), None), registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))), app_data: AppData::default(), + app_data_priv: AppData::default(), safe: false, libs: StdLib::NONE, skip_memory_check: false, diff --git a/src/state/raw.rs b/src/state/raw.rs index cc4ce1cf..d360877b 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -5,7 +5,6 @@ use std::mem; use std::os::raw::{c_char, c_int, c_void}; use std::panic::resume_unwind; use std::ptr::{self, NonNull}; -use std::result::Result as StdResult; use std::sync::Arc; use crate::chunk::ChunkMode; @@ -307,27 +306,27 @@ impl RawLua { res } - /// See [`Lua::try_set_app_data`] + /// Private version of [`Lua::try_set_app_data`] #[inline] - pub(crate) fn try_set_app_data(&self, data: T) -> StdResult, T> { + pub(crate) fn set_priv_app_data(&self, data: T) -> Option { let extra = unsafe { &*self.extra.get() }; - extra.app_data.try_insert(data) + extra.app_data_priv.insert(data) } - /// See [`Lua::app_data_ref`] + /// Private version of [`Lua::app_data_ref`] #[track_caller] #[inline] - pub(crate) fn app_data_ref_unguarded(&self) -> Option> { + pub(crate) fn priv_app_data_ref(&self) -> Option> { let extra = unsafe { &*self.extra.get() }; - extra.app_data.borrow(None) + extra.app_data_priv.borrow(None) } - /// See [`Lua::app_data_mut`] + /// Private version of [`Lua::app_data_mut`] #[track_caller] #[inline] - pub(crate) fn app_data_mut_unguarded(&self) -> Option> { + pub(crate) fn priv_app_data_mut(&self) -> Option> { let extra = unsafe { &*self.extra.get() }; - extra.app_data.borrow_mut(None) + extra.app_data_priv.borrow_mut(None) } /// See [`Lua::create_registry_value`] From a2dc662a9296567f0c6c7b163d22401cdebb08bd Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 11 Jun 2025 23:55:06 +0100 Subject: [PATCH 426/635] Add RawLua::create_table_from (internal) --- src/state.rs | 27 ++------------------------- src/state/raw.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/state.rs b/src/state.rs index 978b1dc9..dfc73836 100644 --- a/src/state.rs +++ b/src/state.rs @@ -24,9 +24,7 @@ use crate::types::{ ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak, }; use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataStorage}; -use crate::util::{ - assert_stack, check_stack, protect_lua_closure, push_string, push_table, rawset_field, StackGuard, -}; +use crate::util::{assert_stack, check_stack, protect_lua_closure, push_string, rawset_field, StackGuard}; use crate::value::{Nil, Value}; #[cfg(not(feature = "luau"))] @@ -1202,28 +1200,7 @@ impl Lua { K: IntoLua, V: IntoLua, { - let lua = self.lock(); - let state = lua.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 6)?; - - let iter = iter.into_iter(); - let lower_bound = iter.size_hint().0; - let protect = !lua.unlikely_memory_error(); - push_table(state, 0, lower_bound, protect)?; - for (k, v) in iter { - lua.push(k)?; - lua.push(v)?; - if protect { - protect_lua!(state, 3, 1, fn(state) ffi::lua_rawset(state, -3))?; - } else { - ffi::lua_rawset(state, -3); - } - } - - Ok(Table(lua.pop_ref())) - } + unsafe { self.lock().create_table_from(iter) } } /// Creates a table from an iterator of values, using `1..` as the keys. diff --git a/src/state/raw.rs b/src/state/raw.rs index d360877b..8efb7146 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -537,6 +537,34 @@ impl RawLua { Ok(Table(self.pop_ref())) } + /// See [`Lua::create_table_from`] + pub(crate) unsafe fn create_table_from(&self, iter: I) -> Result
+ where + I: IntoIterator, + K: IntoLua, + V: IntoLua, + { + let state = self.state(); + let _sg = StackGuard::new(state); + check_stack(state, 6)?; + + let iter = iter.into_iter(); + let lower_bound = iter.size_hint().0; + let protect = !self.unlikely_memory_error(); + push_table(state, 0, lower_bound, protect)?; + for (k, v) in iter { + self.push(k)?; + self.push(v)?; + if protect { + protect_lua!(state, 3, 1, fn(state) ffi::lua_rawset(state, -3))?; + } else { + ffi::lua_rawset(state, -3); + } + } + + Ok(Table(self.pop_ref())) + } + /// See [`Lua::create_sequence_from`] pub(crate) unsafe fn create_sequence_from(&self, iter: I) -> Result
where From f00208373e10305470234d9ef5be117fcb4a4e04 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 12 Jun 2025 00:17:29 +0100 Subject: [PATCH 427/635] Bump lua-src --- mlua-sys/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 0ce088da..34e785d3 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -38,7 +38,7 @@ module = [] cc = "1.0" cfg-if = "1.0" pkg-config = "0.3.17" -lua-src = { version = ">= 548.0.0, < 548.1.0", optional = true } +lua-src = { version = ">= 548.1.0, < 548.2.0", optional = true } luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true } luau0-src = { version = "0.15.0", optional = true } From 2fbbbe4238eb9c0a12e5c052de2c2f5cef548ebe Mon Sep 17 00:00:00 2001 From: Ron Tseytlin <40004112+steveRoll-git@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:57:11 +0300 Subject: [PATCH 428/635] Fix minor grammar mistakes in README.md (#591) --- README.md | 70 +++++++++++++++++++++++++++---------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index a660a19a..c0c3e26b 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,12 @@ > > See v0.10 [release notes](https://github.com/mlua-rs/mlua/blob/main/docs/release_notes/v0.10.md). -`mlua` is bindings to [Lua](https://www.lua.org) programming language for Rust with a goal to provide -_safe_ (as far as it's possible), high level, easy to use, practical and flexible API. +`mlua` is a set of bindings to the [Lua](https://www.lua.org) programming language for Rust with a goal to provide a +_safe_ (as much as possible), high level, easy to use, practical and flexible API. -Started as `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Luau] and allows to write native Lua modules in Rust as well as use Lua in a standalone mode. +Started as an `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Luau] and allows writing native Lua modules in Rust as well as using Lua in a standalone mode. -`mlua` tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platform and cross-compilation to `aarch64` (other targets are also supported). +`mlua` is tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platforms and cross-compilation to `aarch64` (other targets are also supported). WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua/Luau versions excluding JIT. @@ -39,7 +39,7 @@ WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for a ### Feature flags -`mlua` uses feature flags to reduce the amount of dependencies, compiled code and allow to choose only required set of features. +`mlua` uses feature flags to reduce the amount of dependencies and compiled code, and allow to choose only required set of features. Below is a list of the available feature flags. By default `mlua` does not enable any features. * `lua54`: enable Lua [5.4] support @@ -51,12 +51,12 @@ Below is a list of the available feature flags. By default `mlua` does not enabl * `luau`: enable [Luau] support (auto vendored mode) * `luau-jit`: enable [Luau] support with JIT backend. * `luau-vector4`: enable [Luau] support with 4-dimensional vector. -* `vendored`: build static Lua(JIT) library from sources during `mlua` compilation using [lua-src] or [luajit-src] crates +* `vendored`: build static Lua(JIT) libraries from sources during `mlua` compilation using [lua-src] or [luajit-src] * `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]) * `send`: make `mlua::Lua: Send + Sync` (adds [`Send`] requirement to `mlua::Function` and `mlua::UserData`) * `error-send`: make `mlua:Error: Send + Sync` -* `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework +* `serialize`: add serialization and deserialization support to `mlua` types using [serde] * `macros`: enable procedural macros (such as `chunk!`) * `anyhow`: enable `anyhow::Error` conversion into Lua * `userdata-wrappers`: opt into `impl UserData` for `Rc`/`Arc`/`Rc>`/`Arc>` where `T: UserData` @@ -78,7 +78,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl `mlua` supports async/await for all Lua versions including Luau. -This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6) and require running [Thread](https://docs.rs/mlua/latest/mlua/struct.Thread.html) along with enabling `feature = "async"` in `Cargo.toml`. +This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6) and requires running [Thread](https://docs.rs/mlua/latest/mlua/struct.Thread.html) along with enabling `feature = "async"` in `Cargo.toml`. **Examples**: - [HTTP Client](examples/async_http_client.rs) @@ -102,7 +102,7 @@ curl -v http://localhost:3000 ### Serialization (serde) support -With `serialize` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition `mlua` provides [`serde::Serialize`] trait implementation for it (including `UserData` support). +With the `serialize` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition, `mlua` provides the [`serde::Serialize`] trait implementation for it (including `UserData` support). [Example](examples/serialize.rs) @@ -114,24 +114,24 @@ With `serialize` feature flag enabled, `mlua` allows you to serialize/deserializ You have to enable one of the features: `lua54`, `lua53`, `lua52`, `lua51`, `luajit(52)` or `luau`, according to the chosen Lua version. -By default `mlua` uses `pkg-config` tool to find lua includes and libraries for the chosen Lua version. -In most cases it works as desired, although sometimes could be more preferable to use a custom lua library. -To achieve this, mlua supports `LUA_LIB`, `LUA_LIB_NAME` and `LUA_LINK` environment variables. +By default `mlua` uses `pkg-config` to find Lua includes and libraries for the chosen Lua version. +In most cases it works as desired, although sometimes it may be preferable to use a custom Lua library. +To achieve this, mlua supports the `LUA_LIB`, `LUA_LIB_NAME` and `LUA_LINK` environment variables. `LUA_LINK` is optional and may be `dylib` (a dynamic library) or `static` (a static library, `.a` archive). -An example how to use them: +An example of how to use them: ``` sh my_project $ LUA_LIB=$HOME/tmp/lua-5.2.4/src LUA_LIB_NAME=lua LUA_LINK=static cargo build ``` -`mlua` also supports vendored lua/luajit using the auxiliary crates [lua-src](https://crates.io/crates/lua-src) and +`mlua` also supports vendored Lua/LuaJIT using the auxiliary crates [lua-src](https://crates.io/crates/lua-src) and [luajit-src](https://crates.io/crates/luajit-src). -Just enable the `vendored` feature and cargo will automatically build and link specified lua/luajit version. This is the easiest way to get started with `mlua`. +Just enable the `vendored` feature and cargo will automatically build and link the specified Lua/LuaJIT version. This is the easiest way to get started with `mlua`. ### Standalone mode -In a standalone mode `mlua` allows to add to your application scripting support with a gently configured Lua runtime to ensure safety and soundness. +In standalone mode, `mlua` allows adding scripting support to your application with a gently configured Lua runtime to ensure safety and soundness. -Add to `Cargo.toml` : +Add to `Cargo.toml`: ``` toml [dependencies] @@ -159,11 +159,11 @@ fn main() -> LuaResult<()> { ``` ### Module mode -In a module mode `mlua` allows to create a compiled Lua module that can be loaded from Lua code using [`require`](https://www.lua.org/manual/5.4/manual.html#pdf-require). In this case `mlua` uses an external Lua runtime which could lead to potential unsafety due to unpredictability of the Lua environment and usage of libraries such as [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10). +In module mode, `mlua` allows creating a compiled Lua module that can be loaded from Lua code using [`require`](https://www.lua.org/manual/5.4/manual.html#pdf-require). In this case `mlua` uses an external Lua runtime which could lead to potential unsafety due to the unpredictability of the Lua environment and usage of libraries such as [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10). [Example](examples/module) -Add to `Cargo.toml` : +Add to `Cargo.toml`: ``` toml [lib] @@ -173,7 +173,7 @@ crate-type = ["cdylib"] mlua = { version = "0.10", features = ["lua54", "module"] } ``` -`lib.rs` : +`lib.rs`: ``` rust use mlua::prelude::*; @@ -216,14 +216,14 @@ rustflags = [ ``` On Linux you can build modules normally with `cargo build --release`. -On Windows the target module will be linked with `lua5x.dll` library (depending on your feature flags). +On Windows the target module will be linked with the `lua5x.dll` library (depending on your feature flags). Your main application should provide this library. -Module builds don't require Lua lib or headers to be installed on the system. +Module builds don't require Lua binaries or headers to be installed on the system. ### Publishing to luarocks.org -There is a LuaRocks build backend for mlua modules [`luarocks-build-rust-mlua`]. +There is a LuaRocks build backend for mlua modules: [`luarocks-build-rust-mlua`]. Modules written in Rust and published to luarocks: - [`decasify`](https://github.com/alerque/decasify) @@ -236,10 +236,10 @@ Modules written in Rust and published to luarocks: ## Safety -One of the `mlua` goals is to provide *safe* API between Rust and Lua. -Every place where the Lua C API may trigger an error longjmp in any way is protected by `lua_pcall`, -and the user of the library is protected from directly interacting with unsafe things like the Lua stack, -and there is overhead associated with this safety. +One of `mlua`'s goals is to provide a *safe* API between Rust and Lua. +Every place where the Lua C API may trigger an error longjmp is protected by `lua_pcall`, +and the user of the library is protected from directly interacting with unsafe things like the Lua stack. +There is overhead associated with this safety. Unfortunately, `mlua` does not provide absolute safety even without using `unsafe` . This library contains a huge amount of unsafe code. There are almost certainly bugs still lurking in this library! @@ -247,8 +247,8 @@ It is surprisingly, fiendishly difficult to use the Lua C API without the potent ## Panic handling -`mlua` wraps panics that are generated inside Rust callbacks in a regular Lua error. Panics could be -resumed then by returning or propagating the Lua error to Rust code. +`mlua` wraps panics that are generated inside Rust callbacks in a regular Lua error. Panics can then be +resumed by returning or propagating the Lua error to Rust code. For example: ``` rust @@ -267,12 +267,12 @@ let _ = lua.load(r#" unreachable!() ``` -Optionally `mlua` can disable Rust panics catching in Lua via `pcall`/`xpcall` and automatically resume +Optionally, `mlua` can disable Rust panic catching in Lua via `pcall`/`xpcall` and automatically resume them across the Lua API boundary. This is controlled via `LuaOptions` and done by wrapping the Lua `pcall`/`xpcall` -functions on a way to prevent catching errors that are wrapped Rust panics. +functions to prevent catching errors that are wrapped Rust panics. `mlua` should also be panic safe in another way as well, which is that any `Lua` instances or handles -remains usable after a user generated panic, and such panics should not break internal invariants or +remain usable after a user generated panic, and such panics should not break internal invariants or leak Lua stack space. This is mostly important to safely use `mlua` types in Drop impls, as you should not be using panics for general error handling. @@ -289,12 +289,12 @@ If you encounter them, a bug report would be very welcome: ## Sandboxing -Please check the [Luau Sandboxing] page if you are interested in running untrusted Lua scripts in controlled environment. +Please check the [Luau Sandboxing] page if you are interested in running untrusted Lua scripts in a controlled environment. -`mlua` provides `Lua::sandbox` method for enabling sandbox mode (Luau only). +`mlua` provides the `Lua::sandbox` method for enabling sandbox mode (Luau only). [Luau Sandboxing]: https://luau.org/sandbox ## License -This project is licensed under the [MIT license](LICENSE) +This project is licensed under the [MIT license](LICENSE). From 7bc72be7d39fa9f1ec5155049057771d684cab10 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 12 Jun 2025 13:35:22 +0100 Subject: [PATCH 429/635] Use `serde` feature flag instead of `serialize`. The old one is still supported. --- .github/workflows/main.yml | 26 +++++++++++++------------- Cargo.toml | 15 +++++++++------ README.md | 6 +++--- examples/{serialize.rs => serde.rs} | 0 src/buffer.rs | 4 ++-- src/error.rs | 16 ++++++++-------- src/lib.rs | 8 ++++---- src/prelude.rs | 2 +- src/serde/mod.rs | 2 +- src/state.rs | 10 +++++----- src/state/raw.rs | 2 +- src/string.rs | 4 ++-- src/table.rs | 12 ++++++------ src/userdata.rs | 10 +++++----- src/userdata/cell.rs | 24 ++++++++++++------------ src/value.rs | 16 ++++++++-------- src/vector.rs | 4 ++-- tarpaulin.toml | 14 +++++++------- tests/serde.rs | 2 +- tests/userdata.rs | 8 ++++---- 20 files changed, 94 insertions(+), 91 deletions(-) rename examples/{serialize.rs => serde.rs} (100%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a0be1e5f..8d8e51f5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,8 +27,8 @@ jobs: - name: Build ${{ matrix.lua }} vendored run: | cargo build --features "${{ matrix.lua }},vendored" - cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers" - cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send" + cargo build --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers" + cargo build --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers,send" shell: bash - name: Build ${{ matrix.lua }} pkg-config if: ${{ matrix.os == 'ubuntu-latest' }} @@ -51,7 +51,7 @@ jobs: toolchain: stable target: aarch64-apple-darwin - name: Cross-compile - run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" + run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers" build_aarch64_cross_ubuntu: name: Cross-compile to aarch64-unknown-linux-gnu @@ -72,7 +72,7 @@ jobs: sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross shell: bash - name: Cross-compile - run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" + run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers" shell: bash build_armv7_cross_ubuntu: @@ -94,7 +94,7 @@ jobs: sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross shell: bash - name: Cross-compile - run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" + run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers" shell: bash test: @@ -123,14 +123,14 @@ jobs: - name: Run ${{ matrix.lua }} tests run: | cargo test --features "${{ matrix.lua }},vendored" - cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers" - cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send" + cargo test --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers" + cargo test --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers,send" shell: bash - name: Run compile tests (macos lua54) if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }} run: | TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" --tests -- --ignored - TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros" --tests -- --ignored + TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serde,macros" --tests -- --ignored shell: bash test_with_sanitizer: @@ -154,8 +154,8 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Run ${{ matrix.lua }} tests with address sanitizer run: | - cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions - cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions + cargo test --tests --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions + cargo test --tests --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers,send" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions shell: bash env: RUSTFLAGS: -Z sanitizer=address @@ -181,7 +181,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Run ${{ matrix.lua }} tests with forced memory limit run: | - cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" + cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers" shell: bash env: RUSTFLAGS: --cfg=force_memory_limit @@ -254,7 +254,7 @@ jobs: - name: Run ${{ matrix.lua }} tests run: | cargo test --tests --features "${{ matrix.lua }},vendored" - cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers" + cargo test --tests --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers" rustfmt: name: Rustfmt @@ -281,4 +281,4 @@ jobs: - uses: giraffate/clippy-action@v1 with: reporter: 'github-pr-review' - clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" + clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers" diff --git a/Cargo.toml b/Cargo.toml index 65ac1ba8..1cc3ab7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ with async/await features and support of writing native Lua modules in Rust. """ [package.metadata.docs.rs] -features = ["lua54", "vendored", "async", "send", "serialize", "macros"] +features = ["lua54", "vendored", "async", "send", "serde", "macros"] rustdoc-args = ["--cfg", "docsrs"] [workspace] @@ -40,11 +40,14 @@ module = ["mlua_derive", "ffi/module"] async = ["dep:futures-util"] send = ["error-send"] error-send = [] -serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value", "bstr/serde"] +serde = ["dep:serde", "dep:erased-serde", "dep:serde-value", "bstr/serde"] macros = ["mlua_derive/macros"] anyhow = ["dep:anyhow", "error-send"] userdata-wrappers = ["parking_lot/send_guard"] +# deprecated features +serialize = ["serde"] + [dependencies] mlua_derive = { version = "=0.11.0-beta.2", optional = true, path = "mlua_derive" } bstr = { version = "1.0", features = ["std"], default-features = false } @@ -90,7 +93,7 @@ required-features = ["async"] [[bench]] name = "serde" harness = false -required-features = ["serialize"] +required-features = ["serde"] [[example]] name = "async_http_client" @@ -98,7 +101,7 @@ required-features = ["async", "macros"] [[example]] name = "async_http_reqwest" -required-features = ["async", "serialize", "macros"] +required-features = ["async", "serde", "macros"] [[example]] name = "async_http_server" @@ -113,8 +116,8 @@ name = "guided_tour" required-features = ["macros"] [[example]] -name = "serialize" -required-features = ["serialize"] +name = "serde" +required-features = ["serde"] [[example]] name = "userdata" diff --git a/README.md b/README.md index c0c3e26b..ec41d3d4 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl * `async`: enable async/await support (any executor can be used, eg. [tokio] or [async-std]) * `send`: make `mlua::Lua: Send + Sync` (adds [`Send`] requirement to `mlua::Function` and `mlua::UserData`) * `error-send`: make `mlua:Error: Send + Sync` -* `serialize`: add serialization and deserialization support to `mlua` types using [serde] +* `serde`: add serialization and deserialization support to `mlua` types using [serde] * `macros`: enable procedural macros (such as `chunk!`) * `anyhow`: enable `anyhow::Error` conversion into Lua * `userdata-wrappers`: opt into `impl UserData` for `Rc`/`Arc`/`Rc>`/`Arc>` where `T: UserData` @@ -93,7 +93,7 @@ This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6 cargo run --example async_http_client --features=lua54,async,macros # async http client (reqwest) -cargo run --example async_http_reqwest --features=lua54,async,macros,serialize +cargo run --example async_http_reqwest --features=lua54,async,macros,serde # async http server cargo run --example async_http_server --features=lua54,async,macros,send @@ -102,7 +102,7 @@ curl -v http://localhost:3000 ### Serialization (serde) support -With the `serialize` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition, `mlua` provides the [`serde::Serialize`] trait implementation for it (including `UserData` support). +With the `serde` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition, `mlua` provides the [`serde::Serialize`] trait implementation for it (including `UserData` support). [Example](examples/serialize.rs) diff --git a/examples/serialize.rs b/examples/serde.rs similarity index 100% rename from examples/serialize.rs rename to examples/serde.rs diff --git a/src/buffer.rs b/src/buffer.rs index 42b20088..0ee163ff 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] use serde::ser::{Serialize, Serializer}; use crate::types::ValueRef; @@ -73,7 +73,7 @@ impl Buffer { } } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl Serialize for Buffer { fn serialize(&self, serializer: S) -> std::result::Result { serializer.serialize_bytes(unsafe { self.as_slice() }) diff --git a/src/error.rs b/src/error.rs index 1f243967..c20a39ef 100644 --- a/src/error.rs +++ b/src/error.rs @@ -183,12 +183,12 @@ pub enum Error { /// and returned again. PreviouslyResumedPanic, /// Serialization error. - #[cfg(feature = "serialize")] - #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] + #[cfg(feature = "serde")] + #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] SerializeError(StdString), /// Deserialization error. - #[cfg(feature = "serialize")] - #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] + #[cfg(feature = "serde")] + #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] DeserializeError(StdString), /// A custom error. /// @@ -309,11 +309,11 @@ impl fmt::Display for Error { Error::PreviouslyResumedPanic => { write!(fmt, "previously resumed panic returned again") } - #[cfg(feature = "serialize")] + #[cfg(feature = "serde")] Error::SerializeError(err) => { write!(fmt, "serialize error: {err}") }, - #[cfg(feature = "serialize")] + #[cfg(feature = "serde")] Error::DeserializeError(err) => { write!(fmt, "deserialize error: {err}") }, @@ -494,14 +494,14 @@ impl From for Error { } } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl serde::ser::Error for Error { fn custom(msg: T) -> Self { Self::SerializeError(msg.to_string()) } } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl serde::de::Error for Error { fn custom(msg: T) -> Self { Self::DeserializeError(msg.to_string()) diff --git a/src/lib.rs b/src/lib.rs index dc15737b..e1589ce6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,7 @@ //! The [`Value`] enum and other types implement [`serde::Serialize`] trait to support serializing //! Lua values into Rust values. //! -//! Requires `feature = "serialize"`. +//! Requires `feature = "serde"`. //! //! # Async/await support //! @@ -140,12 +140,12 @@ pub use crate::{ #[cfg_attr(docsrs, doc(cfg(feature = "async")))] pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn}; -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] #[doc(inline)] pub use crate::serde::{de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt}; -#[cfg(feature = "serialize")] -#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] pub mod serde; #[cfg(feature = "mlua_derive")] diff --git a/src/prelude.rs b/src/prelude.rs index eeeaea26..a3a03201 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -32,7 +32,7 @@ pub use crate::{ #[doc(no_inline)] pub use crate::{AsyncThread as LuaAsyncThread, LuaNativeAsyncFn}; -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] #[doc(no_inline)] pub use crate::{ DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, SerializeOptions as LuaSerializeOptions, diff --git a/src/serde/mod.rs b/src/serde/mod.rs index f4752145..1b85a763 100644 --- a/src/serde/mod.rs +++ b/src/serde/mod.rs @@ -13,7 +13,7 @@ use crate::util::check_stack; use crate::value::Value; /// Trait for serializing/deserializing Lua values using Serde. -#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] pub trait LuaSerdeExt: Sealed { /// A special value (lightuserdata) to encode/decode optional (none) values. /// diff --git a/src/state.rs b/src/state.rs index dfc73836..dbe4c2b2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -39,7 +39,7 @@ use { std::future::{self, Future}, }; -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] use serde::Serialize; pub(crate) use extra::ExtraData; @@ -1367,8 +1367,8 @@ impl Lua { } /// Creates a Lua userdata object from a custom serializable userdata type. - #[cfg(feature = "serialize")] - #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] + #[cfg(feature = "serde")] + #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] #[inline] pub fn create_ser_userdata(&self, data: T) -> Result where @@ -1395,8 +1395,8 @@ impl Lua { /// Creates a Lua userdata object from a custom serializable Rust type. /// /// See [`Lua::create_any_userdata`] for more details. - #[cfg(feature = "serialize")] - #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] + #[cfg(feature = "serde")] + #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] #[inline] pub fn create_ser_any_userdata(&self, data: T) -> Result where diff --git a/src/state/raw.rs b/src/state/raw.rs index 8efb7146..b7de97f2 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -208,7 +208,7 @@ impl RawLua { } // Init serde metatables - #[cfg(feature = "serialize")] + #[cfg(feature = "serde")] crate::serde::init_metatables(state)?; Ok::<_, Error>(()) diff --git a/src/string.rs b/src/string.rs index 9c86102b..6304d484 100644 --- a/src/string.rs +++ b/src/string.rs @@ -11,7 +11,7 @@ use crate::traits::IntoLua; use crate::types::{LuaType, ValueRef}; use crate::value::Value; -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] use { serde::ser::{Serialize, Serializer}, std::result::Result as StdResult, @@ -211,7 +211,7 @@ impl Hash for String { } } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl Serialize for String { fn serialize(&self, serializer: S) -> StdResult where diff --git a/src/table.rs b/src/table.rs index 92228683..4b62705d 100644 --- a/src/table.rs +++ b/src/table.rs @@ -15,7 +15,7 @@ use crate::value::{Nil, Value}; #[cfg(feature = "async")] use futures_util::future::{self, Either, Future}; -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] use { rustc_hash::FxHashSet, serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}, @@ -735,7 +735,7 @@ impl Table { Ok(()) } - #[cfg(feature = "serialize")] + #[cfg(feature = "serde")] pub(crate) fn is_array(&self) -> bool { let lua = self.0.lua.lock(); let state = lua.state(); @@ -954,14 +954,14 @@ impl ObjectLike for Table { } /// A wrapped [`Table`] with customized serialization behavior. -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] pub(crate) struct SerializableTable<'a> { table: &'a Table, options: crate::serde::de::Options, visited: Rc>>, } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl Serialize for Table { #[inline] fn serialize(&self, serializer: S) -> StdResult { @@ -969,7 +969,7 @@ impl Serialize for Table { } } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl<'a> SerializableTable<'a> { #[inline] pub(crate) fn new( @@ -985,7 +985,7 @@ impl<'a> SerializableTable<'a> { } } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl Serialize for SerializableTable<'_> { fn serialize(&self, serializer: S) -> StdResult where diff --git a/src/userdata.rs b/src/userdata.rs index 9bdfbd81..a568485d 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -18,7 +18,7 @@ use crate::value::Value; #[cfg(feature = "async")] use std::future::Future; -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] use { serde::ser::{self, Serialize, Serializer}, std::result::Result as StdResult, @@ -957,7 +957,7 @@ impl AnyUserData { /// Returns `true` if this [`AnyUserData`] is serializable (e.g. was created using /// [`Lua::create_ser_userdata`]). - #[cfg(feature = "serialize")] + #[cfg(feature = "serde")] pub(crate) fn is_serializable(&self) -> bool { let lua = self.0.lua.lock(); let is_serializable = || unsafe { @@ -1041,7 +1041,7 @@ where } } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl Serialize for AnyUserData { fn serialize(&self, serializer: S) -> StdResult where @@ -1072,8 +1072,8 @@ impl AnyUserData { /// [`IntoLua`] trait. /// /// This function uses [`Lua::create_ser_any_userdata`] under the hood. - #[cfg(feature = "serialize")] - #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] + #[cfg(feature = "serde")] + #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] pub fn wrap_ser(data: T) -> impl IntoLua { WrappedUserdata(move |lua| lua.create_ser_any_userdata(data)) } diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index 538e33d7..f70399cd 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -1,6 +1,6 @@ use std::cell::{RefCell, UnsafeCell}; -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] use serde::ser::{Serialize, Serializer}; use crate::error::{Error, Result}; @@ -9,10 +9,10 @@ use crate::types::XRc; use super::lock::{RawLock, UserDataLock}; use super::r#ref::{UserDataRef, UserDataRefMut}; -#[cfg(all(feature = "serialize", not(feature = "send")))] +#[cfg(all(feature = "serde", not(feature = "send")))] type DynSerialize = dyn erased_serde::Serialize; -#[cfg(all(feature = "serialize", feature = "send"))] +#[cfg(all(feature = "serde", feature = "send"))] type DynSerialize = dyn erased_serde::Serialize + Send; pub(crate) enum UserDataStorage { @@ -24,7 +24,7 @@ pub(crate) enum UserDataStorage { // It's stored inside a Lua VM and protected by the outer `ReentrantMutex`. pub(crate) enum UserDataVariant { Default(XRc>), - #[cfg(feature = "serialize")] + #[cfg(feature = "serde")] Serializable(XRc>>, bool), // bool is `is_sync` } @@ -33,7 +33,7 @@ impl Clone for UserDataVariant { fn clone(&self) -> Self { match self { Self::Default(inner) => Self::Default(XRc::clone(inner)), - #[cfg(feature = "serialize")] + #[cfg(feature = "serde")] Self::Serializable(inner, is_sync) => Self::Serializable(XRc::clone(inner), *is_sync), } } @@ -79,7 +79,7 @@ impl UserDataVariant { } Ok(match self { Self::Default(inner) => XRc::into_inner(inner).unwrap().value.into_inner(), - #[cfg(feature = "serialize")] + #[cfg(feature = "serde")] Self::Serializable(inner, _) => unsafe { let raw = Box::into_raw(XRc::into_inner(inner).unwrap().value.into_inner()); *Box::from_raw(raw as *mut T) @@ -91,7 +91,7 @@ impl UserDataVariant { fn strong_count(&self) -> usize { match self { Self::Default(inner) => XRc::strong_count(inner), - #[cfg(feature = "serialize")] + #[cfg(feature = "serde")] Self::Serializable(inner, _) => XRc::strong_count(inner), } } @@ -100,7 +100,7 @@ impl UserDataVariant { pub(super) fn raw_lock(&self) -> &RawLock { match self { Self::Default(inner) => &inner.raw_lock, - #[cfg(feature = "serialize")] + #[cfg(feature = "serde")] Self::Serializable(inner, _) => &inner.raw_lock, } } @@ -109,13 +109,13 @@ impl UserDataVariant { pub(super) fn as_ptr(&self) -> *mut T { match self { Self::Default(inner) => inner.value.get(), - #[cfg(feature = "serialize")] + #[cfg(feature = "serde")] Self::Serializable(inner, _) => unsafe { &mut **(inner.value.get() as *mut Box) }, } } } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl Serialize for UserDataStorage<()> { fn serialize(&self, serializer: S) -> std::result::Result { match self { @@ -197,7 +197,7 @@ impl UserDataStorage { Self::Scoped(ScopedUserDataVariant::RefMut(RefCell::new(data))) } - #[cfg(feature = "serialize")] + #[cfg(feature = "serde")] #[inline(always)] pub(crate) fn new_ser(data: T) -> Self where @@ -209,7 +209,7 @@ impl UserDataStorage { Self::Owned(variant) } - #[cfg(feature = "serialize")] + #[cfg(feature = "serde")] #[inline(always)] pub(crate) fn is_serializable(&self) -> bool { matches!(self, Self::Owned(UserDataVariant::Serializable(..))) diff --git a/src/value.rs b/src/value.rs index 119f147e..cfc13251 100644 --- a/src/value.rs +++ b/src/value.rs @@ -15,7 +15,7 @@ use crate::types::{Integer, LightUserData, Number, ValueRef}; use crate::userdata::AnyUserData; use crate::util::{check_stack, StackGuard}; -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] use { crate::table::SerializableTable, rustc_hash::FxHashSet, @@ -481,8 +481,8 @@ impl Value { /// Wrap reference to this Value into [`SerializableValue`]. /// /// This allows customizing serialization behavior using serde. - #[cfg(feature = "serialize")] - #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] + #[cfg(feature = "serde")] + #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] #[doc(hidden)] pub fn to_serializable(&self) -> SerializableValue { SerializableValue::new(self, Default::default(), None) @@ -630,8 +630,8 @@ impl PartialEq for Value { } /// A wrapped [`Value`] with customized serialization behavior. -#[cfg(feature = "serialize")] -#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] pub struct SerializableValue<'a> { value: &'a Value, options: crate::serde::de::Options, @@ -639,7 +639,7 @@ pub struct SerializableValue<'a> { visited: Option>>>, } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl Serialize for Value { #[inline] fn serialize(&self, serializer: S) -> StdResult { @@ -647,7 +647,7 @@ impl Serialize for Value { } } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl<'a> SerializableValue<'a> { #[inline] pub(crate) fn new( @@ -711,7 +711,7 @@ impl<'a> SerializableValue<'a> { } } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl Serialize for SerializableValue<'_> { fn serialize(&self, serializer: S) -> StdResult where diff --git a/src/vector.rs b/src/vector.rs index 57a5a96d..462cafac 100644 --- a/src/vector.rs +++ b/src/vector.rs @@ -1,6 +1,6 @@ use std::fmt; -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] use serde::ser::{Serialize, SerializeTupleStruct, Serializer}; /// A Luau vector type. @@ -66,7 +66,7 @@ impl Vector { } } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl Serialize for Vector { fn serialize(&self, serializer: S) -> std::result::Result { let mut ts = serializer.serialize_tuple_struct("Vector", Self::SIZE)?; diff --git a/tarpaulin.toml b/tarpaulin.toml index 71c73a35..48bd33f3 100644 --- a/tarpaulin.toml +++ b/tarpaulin.toml @@ -1,23 +1,23 @@ [lua54] -features = "lua54,vendored,async,send,serialize,macros,anyhow,userdata-wrappers" +features = "lua54,vendored,async,send,serde,macros,anyhow,userdata-wrappers" [lua54_non_send] -features = "lua54,vendored,async,serialize,macros,anyhow,userdata-wrappers" +features = "lua54,vendored,async,serde,macros,anyhow,userdata-wrappers" [lua54_with_memory_limit] -features = "lua54,vendored,async,send,serialize,macros,anyhow,userdata-wrappers" +features = "lua54,vendored,async,send,serde,macros,anyhow,userdata-wrappers" rustflags = "--cfg force_memory_limit" [lua51] -features = "lua51,vendored,async,send,serialize,macros" +features = "lua51,vendored,async,send,serde,macros" [lua51_with_memory_limit] -features = "lua51,vendored,async,send,serialize,macros" +features = "lua51,vendored,async,send,serde,macros" rustflags = "--cfg force_memory_limit" [luau] -features = "luau,async,send,serialize,macros" +features = "luau,async,send,serde,macros" [luau_with_memory_limit] -features = "luau,async,send,serialize,macros" +features = "luau,async,send,serde,macros" rustflags = "--cfg force_memory_limit" diff --git a/tests/serde.rs b/tests/serde.rs index 9e3d5984..8f104eaf 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "serialize")] +#![cfg(feature = "serde")] use std::collections::HashMap; use std::error::Error as StdError; diff --git a/tests/userdata.rs b/tests/userdata.rs index 63f9e0f5..9dc93d99 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -38,7 +38,7 @@ fn test_userdata() -> Result<()> { #[test] fn test_methods() -> Result<()> { - #[cfg_attr(feature = "serialize", derive(serde::Serialize))] + #[cfg_attr(feature = "serde", derive(serde::Serialize))] struct MyUserData(i64); impl UserData for MyUserData { @@ -81,7 +81,7 @@ fn test_methods() -> Result<()> { check_methods(&lua, lua.create_userdata(MyUserData(42))?)?; // Additionally check serializable userdata - #[cfg(feature = "serialize")] + #[cfg(feature = "serde")] check_methods(&lua, lua.create_ser_userdata(MyUserData(42))?)?; Ok(()) @@ -306,7 +306,7 @@ fn test_userdata_take() -> Result<()> { } } - #[cfg(feature = "serialize")] + #[cfg(feature = "serde")] impl serde::Serialize for MyUserdata { fn serialize(&self, serializer: S) -> std::result::Result where @@ -364,7 +364,7 @@ fn test_userdata_take() -> Result<()> { check_userdata_take(&lua, userdata, rc)?; // Additionally check serializable userdata - #[cfg(feature = "serialize")] + #[cfg(feature = "serde")] { let rc = Arc::new(18); let userdata = lua.create_ser_userdata(MyUserdata(rc.clone()))?; From 62f84828f2870aa0bf8ac5a9c85172952fca32b4 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 12 Jun 2025 13:42:26 +0100 Subject: [PATCH 430/635] Open some doc(hidden) functionality --- src/buffer.rs | 1 - src/thread.rs | 1 - src/vector.rs | 1 - 3 files changed, 3 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 0ee163ff..181e3696 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -25,7 +25,6 @@ impl Buffer { } /// Returns `true` if the buffer is empty. - #[doc(hidden)] pub fn is_empty(&self) -> bool { self.len() == 0 } diff --git a/src/thread.rs b/src/thread.rs index 520f0637..13d95532 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -472,7 +472,6 @@ impl Thread { /// ``` #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - #[doc(hidden)] pub fn sandbox(&self) -> Result<()> { let lua = self.0.lua.lock(); let state = lua.state(); diff --git a/src/vector.rs b/src/vector.rs index 462cafac..c292e5d9 100644 --- a/src/vector.rs +++ b/src/vector.rs @@ -38,7 +38,6 @@ impl Vector { } /// Creates a new vector with all components set to `0.0`. - #[doc(hidden)] pub const fn zero() -> Self { Self([0.0; Self::SIZE]) } From 9c24c99cbe325fc9d3d4dd62c15a3b4aa98a64be Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 12 Jun 2025 14:12:46 +0100 Subject: [PATCH 431/635] v0.11.0-beta.2 --- CHANGELOG.md | 3 ++- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b09d4a6..915a9313 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## v0.11.0-beta.2 (Jun ?, 2025) +## v0.11.0-beta.2 (Jun 12, 2025) - Lua 5.4 updated to 5.4.8 - Terminate Rust `Future` when `AsyncThread` is dropped (without relying on Lua GC) @@ -8,6 +8,7 @@ - Luau `Require` trait methods now can return `Error` variant (in `NavigateError` enum) - Added `__type` to `Error`'s userdata metatable (for `typeof` function) - `parking_log/send_guard` is moved to `userdata-wrappers` feature flag +- New `serde` feature flag to replace `serialize` (the old one is still available) ## v0.11.0-beta.1 (May 7th, 2025) diff --git a/Cargo.toml b/Cargo.toml index 1cc3ab7e..7e0cb383 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.11.0-beta.1" # remember to update mlua_derive +version = "0.11.0-beta.2" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.79.0" edition = "2021" From 63e7cfd31bcea15cf224f55c1213577b6226769a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 12 Jun 2025 16:30:48 +0100 Subject: [PATCH 432/635] Satisfy mismatched-lifetime-syntaxes lint (nightly) See rust-lang/rust#141787 --- src/hook.rs | 4 ++-- src/state.rs | 12 ++++++------ src/state/raw.rs | 4 ++-- src/string.rs | 6 +++--- src/table.rs | 4 ++-- src/types/app_data.rs | 8 ++++---- src/userdata.rs | 2 +- src/value.rs | 4 ++-- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/hook.rs b/src/hook.rs index 4c8c2569..e3aef66a 100644 --- a/src/hook.rs +++ b/src/hook.rs @@ -86,7 +86,7 @@ impl<'a> Debug<'a> { } /// Corresponds to the `n` what mask. - pub fn names(&self) -> DebugNames { + pub fn names(&self) -> DebugNames<'_> { unsafe { #[cfg(not(feature = "luau"))] mlua_assert!( @@ -113,7 +113,7 @@ impl<'a> Debug<'a> { } /// Corresponds to the `S` what mask. - pub fn source(&self) -> DebugSource { + pub fn source(&self) -> DebugSource<'_> { unsafe { #[cfg(not(feature = "luau"))] mlua_assert!( diff --git a/src/state.rs b/src/state.rs index dbe4c2b2..df958dde 100644 --- a/src/state.rs +++ b/src/state.rs @@ -876,7 +876,7 @@ impl Lua { /// not count in the stack). /// /// [`Debug`]: crate::hook::Debug - pub fn inspect_stack(&self, level: usize) -> Option { + pub fn inspect_stack(&self, level: usize) -> Option> { let lua = self.lock(); unsafe { let mut ar: ffi::lua_Debug = mem::zeroed(); @@ -1955,7 +1955,7 @@ impl Lua { /// Panics if the data object of type `T` is currently mutably borrowed. Multiple immutable /// reads can be taken out at the same time. #[track_caller] - pub fn app_data_ref(&self) -> Option> { + pub fn app_data_ref(&self) -> Option> { let guard = self.lock_arc(); let extra = unsafe { &*guard.extra.get() }; extra.app_data.borrow(Some(guard)) @@ -1963,7 +1963,7 @@ impl Lua { /// Tries to get a reference to an application data object stored by [`Lua::set_app_data`] of /// type `T`. - pub fn try_app_data_ref(&self) -> StdResult>, BorrowError> { + pub fn try_app_data_ref(&self) -> StdResult>, BorrowError> { let guard = self.lock_arc(); let extra = unsafe { &*guard.extra.get() }; extra.app_data.try_borrow(Some(guard)) @@ -1976,7 +1976,7 @@ impl Lua { /// /// Panics if the data object of type `T` is currently borrowed. #[track_caller] - pub fn app_data_mut(&self) -> Option> { + pub fn app_data_mut(&self) -> Option> { let guard = self.lock_arc(); let extra = unsafe { &*guard.extra.get() }; extra.app_data.borrow_mut(Some(guard)) @@ -1984,7 +1984,7 @@ impl Lua { /// Tries to get a mutable reference to an application data object stored by /// [`Lua::set_app_data`] of type `T`. - pub fn try_app_data_mut(&self) -> StdResult>, BorrowMutError> { + pub fn try_app_data_mut(&self) -> StdResult>, BorrowMutError> { let guard = self.lock_arc(); let extra = unsafe { &*guard.extra.get() }; extra.app_data.try_borrow_mut(Some(guard)) @@ -2058,7 +2058,7 @@ impl Lua { } #[inline(always)] - pub(crate) fn lock(&self) -> ReentrantMutexGuard { + pub(crate) fn lock(&self) -> ReentrantMutexGuard<'_, RawLua> { let rawlua = self.raw.lock(); #[cfg(feature = "luau")] if unsafe { (*rawlua.extra.get()).running_gc } { diff --git a/src/state/raw.rs b/src/state/raw.rs index b7de97f2..5b48f492 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -316,7 +316,7 @@ impl RawLua { /// Private version of [`Lua::app_data_ref`] #[track_caller] #[inline] - pub(crate) fn priv_app_data_ref(&self) -> Option> { + pub(crate) fn priv_app_data_ref(&self) -> Option> { let extra = unsafe { &*self.extra.get() }; extra.app_data_priv.borrow(None) } @@ -324,7 +324,7 @@ impl RawLua { /// Private version of [`Lua::app_data_mut`] #[track_caller] #[inline] - pub(crate) fn priv_app_data_mut(&self) -> Option> { + pub(crate) fn priv_app_data_mut(&self) -> Option> { let extra = unsafe { &*self.extra.get() }; extra.app_data_priv.borrow_mut(None) } diff --git a/src/string.rs b/src/string.rs index 6304d484..df9bb818 100644 --- a/src/string.rs +++ b/src/string.rs @@ -43,7 +43,7 @@ impl String { /// # } /// ``` #[inline] - pub fn to_str(&self) -> Result { + pub fn to_str(&self) -> Result> { BorrowedStr::try_from(self) } @@ -102,12 +102,12 @@ impl String { /// # } /// ``` #[inline] - pub fn as_bytes(&self) -> BorrowedBytes { + pub fn as_bytes(&self) -> BorrowedBytes<'_> { BorrowedBytes::from(self) } /// Get the bytes that make up this string, including the trailing nul byte. - pub fn as_bytes_with_nul(&self) -> BorrowedBytes { + pub fn as_bytes_with_nul(&self) -> BorrowedBytes<'_> { let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self); // Include the trailing nul byte (it's always present but excluded by default) let buf = unsafe { slice::from_raw_parts((*buf).as_ptr(), (*buf).len() + 1) }; diff --git a/src/table.rs b/src/table.rs index 4b62705d..83b8cc3d 100644 --- a/src/table.rs +++ b/src/table.rs @@ -613,7 +613,7 @@ impl Table { /// ``` /// /// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next - pub fn pairs(&self) -> TablePairs { + pub fn pairs(&self) -> TablePairs<'_, K, V> { TablePairs { guard: self.0.lua.lock(), table: self, @@ -678,7 +678,7 @@ impl Table { /// # Ok(()) /// # } /// ``` - pub fn sequence_values(&self) -> TableSequence { + pub fn sequence_values(&self) -> TableSequence<'_, V> { TableSequence { guard: self.0.lua.lock(), table: self, diff --git a/src/types/app_data.rs b/src/types/app_data.rs index 0b4c4ba6..0ec7a9f2 100644 --- a/src/types/app_data.rs +++ b/src/types/app_data.rs @@ -43,7 +43,7 @@ impl AppData { #[inline] #[track_caller] - pub(crate) fn borrow(&self, guard: Option) -> Option> { + pub(crate) fn borrow(&self, guard: Option) -> Option> { match self.try_borrow(guard) { Ok(data) => data, Err(err) => panic!("already mutably borrowed: {err:?}"), @@ -53,7 +53,7 @@ impl AppData { pub(crate) fn try_borrow( &self, guard: Option, - ) -> Result>, BorrowError> { + ) -> Result>, BorrowError> { let data = unsafe { &*self.container.get() } .get(&TypeId::of::()) .map(|c| c.try_borrow()) @@ -74,7 +74,7 @@ impl AppData { #[inline] #[track_caller] - pub(crate) fn borrow_mut(&self, guard: Option) -> Option> { + pub(crate) fn borrow_mut(&self, guard: Option) -> Option> { match self.try_borrow_mut(guard) { Ok(data) => data, Err(err) => panic!("already borrowed: {err:?}"), @@ -84,7 +84,7 @@ impl AppData { pub(crate) fn try_borrow_mut( &self, guard: Option, - ) -> Result>, BorrowMutError> { + ) -> Result>, BorrowMutError> { let data = unsafe { &*self.container.get() } .get(&TypeId::of::()) .map(|c| c.try_borrow_mut()) diff --git a/src/userdata.rs b/src/userdata.rs index a568485d..797d32de 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -1008,7 +1008,7 @@ impl UserDataMetatable { /// The pairs are wrapped in a [`Result`], since they are lazily converted to `V` type. /// /// [`Result`]: crate::Result - pub fn pairs(&self) -> UserDataMetatablePairs { + pub fn pairs(&self) -> UserDataMetatablePairs<'_, V> { UserDataMetatablePairs(self.0.pairs()) } } diff --git a/src/value.rs b/src/value.rs index cfc13251..2edef359 100644 --- a/src/value.rs +++ b/src/value.rs @@ -357,7 +357,7 @@ impl Value { /// If the value is a Lua [`String`], try to convert it to [`BorrowedStr`] or return `None` /// otherwise. #[inline] - pub fn as_str(&self) -> Option { + pub fn as_str(&self) -> Option> { self.as_string().and_then(|s| s.to_str().ok()) } @@ -484,7 +484,7 @@ impl Value { #[cfg(feature = "serde")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] #[doc(hidden)] - pub fn to_serializable(&self) -> SerializableValue { + pub fn to_serializable(&self) -> SerializableValue<'_> { SerializableValue::new(self, Default::default(), None) } From 05d6c20520e5aa589d7a4fb1145d0e9f1965c62f Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 12 Jun 2025 23:44:47 +0100 Subject: [PATCH 433/635] One more mismatched-lifetime-syntaxes --- src/types/sync.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/sync.rs b/src/types/sync.rs index b6d61fb1..9aa3b4ff 100644 --- a/src/types/sync.rs +++ b/src/types/sync.rs @@ -31,7 +31,7 @@ mod inner { } #[inline(always)] - pub(crate) fn lock(&self) -> ReentrantMutexGuard { + pub(crate) fn lock(&self) -> ReentrantMutexGuard<'_, T> { ReentrantMutexGuard(&self.0) } From 634e5d45504bc29e37eafc5c9ade2a662f13465a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 13 Jun 2025 15:46:34 +0100 Subject: [PATCH 434/635] Reduce `collectgarbage` options in sandboxed mode See https://luau.org/sandbox#library --- src/luau/mod.rs | 13 +++++++------ src/state/extra.rs | 4 ++-- tests/luau.rs | 13 +++++++++++++ 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/luau/mod.rs b/src/luau/mod.rs index 19eb8fa5..c82b5323 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -5,7 +5,7 @@ use std::ptr; use crate::chunk::ChunkMode; use crate::error::Result; use crate::function::Function; -use crate::state::{callback_error_ext, Lua}; +use crate::state::{callback_error_ext, ExtraData, Lua}; use crate::traits::{FromLuaMulti, IntoLua}; pub use require::{NavigateError, Require, TextRequirer}; @@ -45,16 +45,17 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_ let option = ffi::luaL_optstring(state, 1, cstr!("collect")); let option = CStr::from_ptr(option); let arg = ffi::luaL_optinteger(state, 2, 0); + let is_sandboxed = (*ExtraData::get(state)).sandboxed; match option.to_str() { - Ok("collect") => { + Ok("collect") if !is_sandboxed => { ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0); 0 } - Ok("stop") => { + Ok("stop") if !is_sandboxed => { ffi::lua_gc(state, ffi::LUA_GCSTOP, 0); 0 } - Ok("restart") => { + Ok("restart") if !is_sandboxed => { ffi::lua_gc(state, ffi::LUA_GCRESTART, 0); 0 } @@ -64,12 +65,12 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_ ffi::lua_pushnumber(state, kbytes + kbytes_rem / 1024.0); 1 } - Ok("step") => { + Ok("step") if !is_sandboxed => { let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg as _); ffi::lua_pushboolean(state, res); 1 } - Ok("isrunning") => { + Ok("isrunning") if !is_sandboxed => { let res = ffi::lua_gc(state, ffi::LUA_GCISRUNNING, 0); ffi::lua_pushboolean(state, res); 1 diff --git a/src/state/extra.rs b/src/state/extra.rs index 5ff74a33..777c1a2a 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -89,7 +89,7 @@ pub(crate) struct ExtraData { #[cfg(feature = "luau")] pub(crate) running_gc: bool, #[cfg(feature = "luau")] - pub(super) sandboxed: bool, + pub(crate) sandboxed: bool, #[cfg(feature = "luau")] pub(super) compiler: Option, #[cfg(feature = "luau-jit")] @@ -212,7 +212,7 @@ impl ExtraData { self.weak.write(WeakLua(XRc::downgrade(raw))); } - pub(super) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { + pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { #[cfg(feature = "luau")] if cfg!(not(feature = "module")) { // In the main app we can use `lua_callbacks` to access ExtraData diff --git a/tests/luau.rs b/tests/luau.rs index 20fbee6a..48fed78c 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -196,6 +196,14 @@ fn test_sandbox() -> Result<()> { co.sandbox()?; assert_eq!(co.resume::>(())?, Some(123)); + // collectgarbage should be restricted in sandboxed mode + let collectgarbage = lua.globals().get::("collectgarbage")?; + for arg in ["collect", "stop", "restart", "step", "isrunning"] { + let err = collectgarbage.call::<()>(arg).err().unwrap().to_string(); + assert!(err.contains("collectgarbage called with invalid option")); + } + assert!(collectgarbage.call::("count").unwrap() > 0); + lua.sandbox(false)?; // Previously set variable `global` should be cleared now @@ -205,6 +213,11 @@ fn test_sandbox() -> Result<()> { let table = lua.globals().get::
("table")?; table.set("test", "test")?; + // collectgarbage should work now + for arg in ["collect", "stop", "restart", "count", "step", "isrunning"] { + collectgarbage.call::<()>(arg).unwrap(); + } + Ok(()) } From 0ac7aebb27489b388559d4c80760d51dcc38360e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 13 Jun 2025 15:51:31 +0100 Subject: [PATCH 435/635] Update `Lua::sandbox` doc --- src/state.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/state.rs b/src/state.rs index df958dde..132b6a1c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -486,6 +486,7 @@ impl Lua { /// - Set globals to read-only (and activates safeenv) /// - Setup local environment table that performs writes locally and proxies reads to the global /// environment. + /// - Allow only `count` mode in `collectgarbage` function. /// /// # Examples /// From 2e0e86dab28b15f6f63661d6b6fa0e69597ac5af Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 16 Jun 2025 11:12:53 +0100 Subject: [PATCH 436/635] Update `stack_value` helper. It uses zero stack spaces in Luau, and ref thread for `WrappedError` check in ther Lua versions. Close #597 --- src/state/raw.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index 5b48f492..0eb964ce 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -702,8 +702,6 @@ impl RawLua { } /// Returns value at given stack index without popping it. - /// - /// Uses 2 stack spaces, does not call checkstack. pub(crate) unsafe fn stack_value(&self, idx: c_int, type_hint: Option) -> Value { let state = self.state(); match type_hint.unwrap_or_else(|| ffi::lua_type(state, idx)) { @@ -759,21 +757,25 @@ impl RawLua { } ffi::LUA_TUSERDATA => { + let ref_thread = self.ref_thread(); + ffi::lua_xpush(state, ref_thread, idx); + // If the userdata is `WrappedFailure`, process it as an error or panic. let failure_mt_ptr = (*self.extra.get()).wrapped_failure_mt_ptr; - match get_internal_userdata::(state, idx, failure_mt_ptr).as_mut() { - Some(WrappedFailure::Error(err)) => Value::Error(Box::new(err.clone())), + match get_internal_userdata::(ref_thread, -1, failure_mt_ptr).as_mut() { + Some(WrappedFailure::Error(err)) => { + ffi::lua_pop(ref_thread, 1); + Value::Error(Box::new(err.clone())) + }, Some(WrappedFailure::Panic(panic)) => { + ffi::lua_pop(ref_thread, 1); if let Some(panic) = panic.take() { resume_unwind(panic); } // Previously resumed panic? Value::Nil } - _ => { - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::UserData(AnyUserData(self.pop_ref_thread())) - } + _ => Value::UserData(AnyUserData(self.pop_ref_thread())), } } From 3ea80b763d6c349bd4d05dbd044c6a39d2562cdd Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 16 Jun 2025 11:21:51 +0100 Subject: [PATCH 437/635] cargo fmt --- src/state/raw.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index 0eb964ce..7fcb3718 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -766,7 +766,7 @@ impl RawLua { Some(WrappedFailure::Error(err)) => { ffi::lua_pop(ref_thread, 1); Value::Error(Box::new(err.clone())) - }, + } Some(WrappedFailure::Panic(panic)) => { ffi::lua_pop(ref_thread, 1); if let Some(panic) = panic.take() { From ec10bf2a3973684f0da4a01ca653d3049923e7fb Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 16 Jun 2025 21:33:47 +0100 Subject: [PATCH 438/635] Revert 2e0e86dab28b15f6f63661d6b6fa0e69597ac5af (Update `stack_value` helper) --- src/state/raw.rs | 20 +++++++++----------- src/util/userdata.rs | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index 7fcb3718..8732c558 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -694,7 +694,7 @@ impl RawLua { /// Pops a value from the Lua stack. /// - /// Uses 2 stack spaces, does not call `checkstack`. + /// Uses up to 1 stack spaces, does not call `checkstack`. pub(crate) unsafe fn pop_value(&self) -> Value { let value = self.stack_value(-1, None); ffi::lua_pop(self.state(), 1); @@ -702,6 +702,8 @@ impl RawLua { } /// Returns value at given stack index without popping it. + /// + /// Uses up to 1 stack spaces, does not call `checkstack`. pub(crate) unsafe fn stack_value(&self, idx: c_int, type_hint: Option) -> Value { let state = self.state(); match type_hint.unwrap_or_else(|| ffi::lua_type(state, idx)) { @@ -757,25 +759,21 @@ impl RawLua { } ffi::LUA_TUSERDATA => { - let ref_thread = self.ref_thread(); - ffi::lua_xpush(state, ref_thread, idx); - // If the userdata is `WrappedFailure`, process it as an error or panic. let failure_mt_ptr = (*self.extra.get()).wrapped_failure_mt_ptr; - match get_internal_userdata::(ref_thread, -1, failure_mt_ptr).as_mut() { - Some(WrappedFailure::Error(err)) => { - ffi::lua_pop(ref_thread, 1); - Value::Error(Box::new(err.clone())) - } + match get_internal_userdata::(state, idx, failure_mt_ptr).as_mut() { + Some(WrappedFailure::Error(err)) => Value::Error(Box::new(err.clone())), Some(WrappedFailure::Panic(panic)) => { - ffi::lua_pop(ref_thread, 1); if let Some(panic) = panic.take() { resume_unwind(panic); } // Previously resumed panic? Value::Nil } - _ => Value::UserData(AnyUserData(self.pop_ref_thread())), + _ => { + ffi::lua_xpush(state, self.ref_thread(), idx); + Value::UserData(AnyUserData(self.pop_ref_thread())) + } } } diff --git a/src/util/userdata.rs b/src/util/userdata.rs index 5ef93d5a..e55fc8c8 100644 --- a/src/util/userdata.rs +++ b/src/util/userdata.rs @@ -73,7 +73,7 @@ pub(crate) unsafe fn init_internal_metatable( Ok(()) } -// Uses 2 stack spaces, does not call checkstack +// Uses up to 1 stack space, does not call `checkstack` pub(crate) unsafe fn get_internal_userdata( state: *mut ffi::lua_State, index: c_int, From aa187e66633c6adeb5ed0e700a65d7c875a5b358 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 16 Jun 2025 21:37:16 +0100 Subject: [PATCH 439/635] Increase REF_STACK_RESERVE to 3 slots --- src/state/extra.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state/extra.rs b/src/state/extra.rs index 777c1a2a..9379f658 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -28,7 +28,7 @@ use super::{Lua, WeakLua}; static EXTRA_REGISTRY_KEY: u8 = 0; const WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY: usize = 64; -const REF_STACK_RESERVE: c_int = 2; +const REF_STACK_RESERVE: c_int = 3; /// Data associated with the Lua state. pub(crate) struct ExtraData { From f539f609871c7c2ee23d74d106de1345d0bf96c3 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 16 Jun 2025 21:41:29 +0100 Subject: [PATCH 440/635] Fix `Function::deep_clone()` method (Luau). The `lua_clonefunction` function can fail (and trigger GC) so we need to return Result instead of allowing longjmp --- src/function.rs | 20 ++++++++++++++------ tests/function.rs | 4 ++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/function.rs b/src/function.rs index 9a5b291b..8ce78a8b 100644 --- a/src/function.rs +++ b/src/function.rs @@ -492,16 +492,24 @@ impl Function { /// This function returns shallow clone (same handle) for Rust/C functions. #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - pub fn deep_clone(&self) -> Self { + pub fn deep_clone(&self) -> Result { let lua = self.0.lua.lock(); - let ref_thread = lua.ref_thread(); + let state = lua.state(); unsafe { - if ffi::lua_iscfunction(ref_thread, self.0.index) != 0 { - return self.clone(); + let _sg = StackGuard::new(state); + check_stack(state, 2)?; + + lua.push_ref(&self.0); + if ffi::lua_iscfunction(state, -1) != 0 { + return Ok(self.clone()); } - ffi::lua_clonefunction(ref_thread, self.0.index); - Function(lua.pop_ref_thread()) + if lua.unlikely_memory_error() { + ffi::lua_clonefunction(state, -1); + } else { + protect_lua!(state, 1, 1, fn(state) ffi::lua_clonefunction(state, -1))?; + } + Ok(Function(lua.pop_ref())) } } } diff --git a/tests/function.rs b/tests/function.rs index 683eca9c..f4afa6b2 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -306,7 +306,7 @@ fn test_function_deep_clone() -> Result<()> { lua.globals().set("a", 1)?; let func1 = lua.load("a += 1; return a").into_function()?; - let func2 = func1.deep_clone(); + let func2 = func1.deep_clone()?; assert_ne!(func1.to_pointer(), func2.to_pointer()); assert_eq!(func1.call::(())?, 2); @@ -314,7 +314,7 @@ fn test_function_deep_clone() -> Result<()> { // Check that for Rust functions deep_clone is just a clone let rust_func = lua.create_function(|_, ()| Ok(42))?; - let rust_func2 = rust_func.deep_clone(); + let rust_func2 = rust_func.deep_clone()?; assert_eq!(rust_func.to_pointer(), rust_func2.to_pointer()); Ok(()) From 9da98d42c755ff13dd98d05c577324ecbcfc0707 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 16 Jun 2025 22:25:26 +0100 Subject: [PATCH 441/635] Move `ref_stack_pop` into `ExtraData` method. --- src/state/extra.rs | 28 ++++++++++++++++++++++++++++ src/state/raw.rs | 8 ++++---- src/state/util.rs | 30 +----------------------------- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/state/extra.rs b/src/state/extra.rs index 9379f658..ddbd4fdf 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -259,4 +259,32 @@ impl ExtraData { pub(super) unsafe fn weak(&self) -> &WeakLua { self.weak.assume_init_ref() } + + /// Pops a reference from top of the auxiliary stack and move it to a first free slot. + pub(super) unsafe fn ref_stack_pop(&mut self) -> c_int { + if let Some(free) = self.ref_free.pop() { + ffi::lua_replace(self.ref_thread, free); + return free; + } + + // Try to grow max stack size + if self.ref_stack_top >= self.ref_stack_size { + let mut inc = self.ref_stack_size; // Try to double stack size + while inc > 0 && ffi::lua_checkstack(self.ref_thread, inc) == 0 { + inc /= 2; + } + if inc == 0 { + // Pop item on top of the stack to avoid stack leaking and successfully run destructors + // during unwinding. + ffi::lua_pop(self.ref_thread, 1); + let top = self.ref_stack_top; + // It is a user error to create too many references to exhaust the Lua max stack size + // for the ref thread. + panic!("cannot create a Lua reference, out of auxiliary stack space (used {top} slots)"); + } + self.ref_stack_size += inc; + } + self.ref_stack_top += 1; + self.ref_stack_top + } } diff --git a/src/state/raw.rs b/src/state/raw.rs index 8732c558..cfa212e1 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -11,7 +11,7 @@ use crate::chunk::ChunkMode; use crate::error::{Error, Result}; use crate::function::Function; use crate::memory::{MemoryState, ALLOCATOR}; -use crate::state::util::{callback_error_ext, ref_stack_pop}; +use crate::state::util::callback_error_ext; use crate::stdlib::StdLib; use crate::string::String; use crate::table::Table; @@ -816,21 +816,21 @@ impl RawLua { #[inline] pub(crate) unsafe fn pop_ref(&self) -> ValueRef { ffi::lua_xmove(self.state(), self.ref_thread(), 1); - let index = ref_stack_pop(self.extra.get()); + let index = (*self.extra.get()).ref_stack_pop(); ValueRef::new(self, index) } // Same as `pop_ref` but assumes the value is already on the reference thread #[inline] pub(crate) unsafe fn pop_ref_thread(&self) -> ValueRef { - let index = ref_stack_pop(self.extra.get()); + let index = (*self.extra.get()).ref_stack_pop(); ValueRef::new(self, index) } #[inline] pub(crate) unsafe fn clone_ref(&self, vref: &ValueRef) -> ValueRef { ffi::lua_pushvalue(self.ref_thread(), vref.index); - let index = ref_stack_pop(self.extra.get()); + let index = (*self.extra.get()).ref_stack_pop(); ValueRef::new(self, index) } diff --git a/src/state/util.rs b/src/state/util.rs index c3c79302..5c8a0afb 100644 --- a/src/state/util.rs +++ b/src/state/util.rs @@ -89,7 +89,7 @@ where PreallocatedFailure::New(_) => { ffi::lua_rotate(state, 1, -1); ffi::lua_xmove(state, ref_thread, 1); - let index = ref_stack_pop(extra); + let index = (*extra).ref_stack_pop(); (*extra).wrapped_failure_pool.push(index); (*extra).wrapped_failure_top += 1; } @@ -150,31 +150,3 @@ where } } } - -pub(super) unsafe fn ref_stack_pop(extra: *mut ExtraData) -> c_int { - let extra = &mut *extra; - if let Some(free) = extra.ref_free.pop() { - ffi::lua_replace(extra.ref_thread, free); - return free; - } - - // Try to grow max stack size - if extra.ref_stack_top >= extra.ref_stack_size { - let mut inc = extra.ref_stack_size; // Try to double stack size - while inc > 0 && ffi::lua_checkstack(extra.ref_thread, inc) == 0 { - inc /= 2; - } - if inc == 0 { - // Pop item on top of the stack to avoid stack leaking and successfully run destructors - // during unwinding. - ffi::lua_pop(extra.ref_thread, 1); - let top = extra.ref_stack_top; - // It is a user error to create enough references to exhaust the Lua max stack size for - // the ref thread. - panic!("cannot create a Lua reference, out of auxiliary stack space (used {top} slots)"); - } - extra.ref_stack_size += inc; - } - extra.ref_stack_top += 1; - extra.ref_stack_top -} From 3f0c69b70b62ca01ab99e889b08e982511b35a95 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 17 Jun 2025 15:25:21 +0100 Subject: [PATCH 442/635] Fix logic to terminate futures on drop. The underlying Lua thread must stay in yielded state rather than finished. --- src/state/raw.rs | 6 +++++- src/thread.rs | 1 + tests/async.rs | 30 ++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index cfa212e1..e72bf73c 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -1262,7 +1262,7 @@ impl RawLua { if nargs == 1 && ffi::lua_tolightuserdata(state, -1) == Lua::poll_terminate().0 { // Destroy the future and terminate the Lua thread (*upvalue).data.take(); - ffi::lua_pushinteger(state, 0); + ffi::lua_pushinteger(state, -1); return Ok(1); } @@ -1347,6 +1347,10 @@ impl RawLua { return res elseif nres == 2 then return res, res2 + elseif nres < 0 then + -- Negative `nres` means that the future is terminated + -- It must stay yielded and never be resumed again + yield() else return unpack(res, nres) end diff --git a/src/thread.rs b/src/thread.rs index 13d95532..8d800073 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -529,6 +529,7 @@ impl Drop for AsyncThread { // The thread is dropped while yielded, resume it with the "terminate" signal ffi::lua_pushlightuserdata(self.thread.1, crate::Lua::poll_terminate().0); if let Ok((new_status, _)) = self.thread.resume_inner(&lua, 1) { + // `new_status` should always be `ThreadStatusInner::Yielded(0)` status = new_status; } } diff --git a/tests/async.rs b/tests/async.rs index 752abb5c..725a1dbb 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -610,6 +610,36 @@ async fn test_async_task() -> Result<()> { Ok(()) } +#[tokio::test] +async fn test_async_task_abort() -> Result<()> { + let lua = Lua::new(); + + let sleep = lua.create_async_function(move |_lua, n: u64| async move { + sleep_ms(n).await; + Ok(()) + })?; + lua.globals().set("sleep", sleep)?; + + let local = tokio::task::LocalSet::new(); + local + .run_until(async { + let lua2 = lua.clone(); + let jh = tokio::task::spawn_local(async move { + lua2.load("sleep(200) result = 'done'") + .exec_async() + .await + .unwrap(); + }); + sleep_ms(100).await; // Wait for the task to start + jh.abort(); + }) + .await; + local.await; + assert_eq!(lua.globals().get::("result")?, Value::Nil); + + Ok(()) +} + #[tokio::test] #[cfg(not(feature = "luau"))] async fn test_async_hook() -> Result<()> { From 25955893e029f74bd13749b0e95b40ccb9c32199 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 23 Jun 2025 15:49:37 +0100 Subject: [PATCH 443/635] (Luau Require) Resolve Lua file path relative to the current directory and unrelated to Rust source file location. When a Lua file is required inside a Rust file (in a chunk), we should resolve the Lua file relative to the current directory, instead of relative to the Rust chunk path. The Rust file location is an internal information that does not exist when the compiled binary runs. Fixes #605 --- src/luau/require.rs | 6 ++++-- tests/luau/require.rs | 42 ++++++++++++++++++++++++++---------------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/luau/require.rs b/src/luau/require.rs index ebb7468f..167d04a3 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -200,9 +200,11 @@ impl Require for TextRequirer { let chunk_path = Self::normalize_path(chunk_name.as_ref()); if chunk_path.extension() == Some("rs".as_ref()) { + // Special case for Rust source files, reset to the current directory + let chunk_filename = chunk_path.file_name().unwrap(); let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?; - self.abs_path = Self::normalize_path(&cwd.join(&chunk_path)); - self.rel_path = chunk_path; + self.abs_path = Self::normalize_path(&cwd.join(chunk_filename)); + self.rel_path = ([Component::CurDir, Component::Normal(chunk_filename)].into_iter()).collect(); self.module_path = PathBuf::new(); return Ok(()); diff --git a/tests/luau/require.rs b/tests/luau/require.rs index 7d2ec582..da8c4f1c 100644 --- a/tests/luau/require.rs +++ b/tests/luau/require.rs @@ -105,63 +105,69 @@ fn test_require_without_config() { let lua = Lua::new(); // RequireSimpleRelativePath - let res = run_require(&lua, "./require/without_config/dependency").unwrap(); + let res = run_require(&lua, "./tests/luau/require/without_config/dependency").unwrap(); assert_eq!("result from dependency", get_str(&res, 1)); // RequireSimpleRelativePathWithinPcall - let res = run_require_pcall(&lua, "./require/without_config/dependency").unwrap(); + let res = run_require_pcall(&lua, "./tests/luau/require/without_config/dependency").unwrap(); assert!(res[0].as_boolean().unwrap()); assert_eq!("result from dependency", get_str(&res[1], 1)); // RequireRelativeToRequiringFile - let res = run_require(&lua, "./require/without_config/module").unwrap(); + let res = run_require(&lua, "./tests/luau/require/without_config/module").unwrap(); assert_eq!("result from dependency", get_str(&res, 1)); assert_eq!("required into module", get_str(&res, 2)); // RequireLua - let res = run_require(&lua, "./require/without_config/lua_dependency").unwrap(); + let res = run_require(&lua, "./tests/luau/require/without_config/lua_dependency").unwrap(); assert_eq!("result from lua_dependency", get_str(&res, 1)); // RequireInitLuau - let res = run_require(&lua, "./require/without_config/luau").unwrap(); + let res = run_require(&lua, "./tests/luau/require/without_config/luau").unwrap(); assert_eq!("result from init.luau", get_str(&res, 1)); // RequireInitLua - let res = run_require(&lua, "./require/without_config/lua").unwrap(); + let res = run_require(&lua, "./tests/luau/require/without_config/lua").unwrap(); assert_eq!("result from init.lua", get_str(&res, 1)); // RequireSubmoduleUsingSelfIndirectly - let res = run_require(&lua, "./require/without_config/nested_module_requirer").unwrap(); + let res = run_require(&lua, "./tests/luau/require/without_config/nested_module_requirer").unwrap(); assert_eq!("result from submodule", get_str(&res, 1)); // RequireSubmoduleUsingSelfDirectly - let res = run_require(&lua, "./require/without_config/nested").unwrap(); + let res = run_require(&lua, "./tests/luau/require/without_config/nested").unwrap(); assert_eq!("result from submodule", get_str(&res, 1)); // CannotRequireInitLuauDirectly - let res = run_require(&lua, "./require/without_config/nested/init"); + let res = run_require(&lua, "./tests/luau/require/without_config/nested/init"); assert!(res.is_err()); assert!((res.unwrap_err().to_string()).contains("could not resolve child component \"init\"")); // RequireNestedInits - let res = run_require(&lua, "./require/without_config/nested_inits_requirer").unwrap(); + let res = run_require(&lua, "./tests/luau/require/without_config/nested_inits_requirer").unwrap(); assert_eq!("result from nested_inits/init", get_str(&res, 1)); assert_eq!("required into module", get_str(&res, 2)); // RequireWithFileAmbiguity - let res = run_require(&lua, "./require/without_config/ambiguous_file_requirer"); + let res = run_require( + &lua, + "./tests/luau/require/without_config/ambiguous_file_requirer", + ); assert!(res.is_err()); assert!((res.unwrap_err().to_string()) .contains("could not resolve child component \"dependency\" (ambiguous)")); // RequireWithDirectoryAmbiguity - let res = run_require(&lua, "./require/without_config/ambiguous_directory_requirer"); + let res = run_require( + &lua, + "./tests/luau/require/without_config/ambiguous_directory_requirer", + ); assert!(res.is_err()); assert!((res.unwrap_err().to_string()) .contains("could not resolve child component \"dependency\" (ambiguous)")); // CheckCachedResult - let res = run_require(&lua, "./require/without_config/validate_cache").unwrap(); + let res = run_require(&lua, "./tests/luau/require/without_config/validate_cache").unwrap(); assert!(res.is_table()); } @@ -170,15 +176,19 @@ fn test_require_with_config() { let lua = Lua::new(); // RequirePathWithAlias - let res = run_require(&lua, "./require/with_config/src/alias_requirer").unwrap(); + let res = run_require(&lua, "./tests/luau/require/with_config/src/alias_requirer").unwrap(); assert_eq!("result from dependency", get_str(&res, 1)); // RequirePathWithParentAlias - let res = run_require(&lua, "./require/with_config/src/parent_alias_requirer").unwrap(); + let res = run_require(&lua, "./tests/luau/require/with_config/src/parent_alias_requirer").unwrap(); assert_eq!("result from other_dependency", get_str(&res, 1)); // RequirePathWithAliasPointingToDirectory - let res = run_require(&lua, "./require/with_config/src/directory_alias_requirer").unwrap(); + let res = run_require( + &lua, + "./tests/luau/require/with_config/src/directory_alias_requirer", + ) + .unwrap(); assert_eq!("result from subdirectory_dependency", get_str(&res, 1)); // RequireAliasThatDoesNotExist From f8ed33a2aa5c24c88b8ad49c8251c574707050ba Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 23 Jun 2025 16:23:53 +0100 Subject: [PATCH 444/635] Fix tests --- tests/luau/require.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/luau/require.rs b/tests/luau/require.rs index da8c4f1c..323b2d65 100644 --- a/tests/luau/require.rs +++ b/tests/luau/require.rs @@ -225,14 +225,19 @@ async fn test_async_require() -> Result<()> { Ok(()) })?, )?; + lua.globals().set("tmp_dir", temp_dir.path().to_str().unwrap())?; + lua.globals().set( + "curr_dir_components", + std::env::current_dir().unwrap().components().count(), + )?; lua.load( r#" - local result = require("./async_chunk") + local path_to_root = string.rep("/..", curr_dir_components - 1) + local result = require(`.{path_to_root}{tmp_dir}/async_chunk`) assert(result == "result_after_async_sleep") "#, ) - .set_name(format!("@{}", temp_dir.path().join("require.rs").display())) .exec_async() .await } From 1cd2bdc80863096e1135e03626b79d2068dad101 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 23 Jun 2025 16:37:46 +0100 Subject: [PATCH 445/635] Ignore test_async_require on windows --- tests/luau/require.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/luau/require.rs b/tests/luau/require.rs index 323b2d65..05c2c684 100644 --- a/tests/luau/require.rs +++ b/tests/luau/require.rs @@ -202,7 +202,7 @@ fn test_require_with_config() { assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias")); } -#[cfg(feature = "async")] +#[cfg(all(feature = "async", not(windows)))] #[tokio::test] async fn test_async_require() -> Result<()> { let lua = Lua::new(); From 2445230759542e5b8e61a482d9fdbc55830e2863 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 23 Jun 2025 22:44:36 +0100 Subject: [PATCH 446/635] Update CHANGELOG --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 915a9313..405045eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## v0.11.0-beta.3 (Jun 23, 2025) + +- Luau in sandboxed mode has reduced options in `collectgarbage` function (to follow the official doc) +- `Function::deep_clone` now returns `Result` as this operation can trigger memory errors +- Luau "Require" resolves included Lua files relative to the current directory (#605) +- Fixed bug when finalizing `AsyncThread` on drop (`call_async` methods family) + ## v0.11.0-beta.2 (Jun 12, 2025) - Lua 5.4 updated to 5.4.8 From 727096dd3b08ec5ec331b3f346022dd717e7aeac Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 23 Jun 2025 23:22:36 +0100 Subject: [PATCH 447/635] Handle OOM error during luau_load (Luau >= 0.679) --- mlua-sys/src/luau/compat.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index f4c39ea6..e0c7e10d 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -388,7 +388,7 @@ pub unsafe fn luaL_loadbufferenv( } } - if chunk_is_text { + let status = if chunk_is_text { if env < 0 { env -= 1; } @@ -397,14 +397,21 @@ pub unsafe fn luaL_loadbufferenv( ptr::write(data_ud, data); // By deferring the `free(data)` to the userdata destructor, we ensure that // even if `luau_load` throws an error, the `data` is still released. - let ok = luau_load(L, name, data, size, env) == 0; + let status = luau_load(L, name, data, size, env); lua_replace(L, -2); // replace data with the result - if !ok { - return LUA_ERRSYNTAX; + status + } else { + luau_load(L, name, data, size, env) + }; + + if status != 0 { + if lua_isstring(L, -1) != 0 && CStr::from_ptr(lua_tostring(L, -1)) == c"not enough memory" { + // A case for Luau >= 0.679 + return LUA_ERRMEM; } - } else if luau_load(L, name, data, size, env) != 0 { return LUA_ERRSYNTAX; } + LUA_OK } From 6406de405db0678ed45d02a4961cd7d6317c7241 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 23 Jun 2025 23:22:53 +0100 Subject: [PATCH 448/635] mlua-sys: v0.8.1 --- mlua-sys/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 34e785d3..7fbf0076 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.8.0" +version = "0.8.1" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" From 58953e563560f95b8837585d91f20271cd9a8880 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 23 Jun 2025 22:47:09 +0100 Subject: [PATCH 449/635] v0.11.0-beta.3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7e0cb383..d24d006f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.11.0-beta.2" # remember to update mlua_derive +version = "0.11.0-beta.3" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.79.0" edition = "2021" From 0de7cd1c7d51b4173f5f5b52f47e3d7ae1fbca70 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 29 Jun 2025 11:49:34 +0100 Subject: [PATCH 450/635] Don't move or wrap `ffi::lua_Debug` struct when inspecting stack This can cause a crash if `ffi::lua_Debug` changed between `lua_getstack` and `lua_getinfo` calls. Fixes #610 --- src/hook.rs | 29 ++++++++++++++++------------- src/state.rs | 6 +++--- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/hook.rs b/src/hook.rs index e3aef66a..ce18d423 100644 --- a/src/hook.rs +++ b/src/hook.rs @@ -1,5 +1,4 @@ use std::borrow::Cow; -use std::cell::UnsafeCell; use std::ops::Deref; #[cfg(not(feature = "luau"))] use std::ops::{BitOr, BitOrAssign}; @@ -51,14 +50,18 @@ impl<'a> Debug<'a> { pub(crate) fn new(lua: &'a RawLua, ar: *mut lua_Debug) -> Self { Debug { lua: EitherLua::Borrowed(lua), - ar: ActivationRecord::Borrowed(ar), + ar: ActivationRecord(ar, false), } } - pub(crate) fn new_owned(guard: ReentrantMutexGuard<'a, RawLua>, _level: c_int, ar: lua_Debug) -> Self { + pub(crate) fn new_owned( + guard: ReentrantMutexGuard<'a, RawLua>, + _level: c_int, + ar: Box, + ) -> Self { Debug { lua: EitherLua::Owned(guard), - ar: ActivationRecord::Owned(UnsafeCell::new(ar)), + ar: ActivationRecord(Box::into_raw(ar), true), #[cfg(feature = "luau")] level: _level, } @@ -207,19 +210,19 @@ impl<'a> Debug<'a> { } } -enum ActivationRecord { - #[cfg(not(feature = "luau"))] - Borrowed(*mut lua_Debug), - Owned(UnsafeCell), -} +struct ActivationRecord(*mut lua_Debug, bool); impl ActivationRecord { #[inline] fn get(&self) -> *mut lua_Debug { - match self { - #[cfg(not(feature = "luau"))] - ActivationRecord::Borrowed(x) => *x, - ActivationRecord::Owned(x) => x.get(), + self.0 + } +} + +impl Drop for ActivationRecord { + fn drop(&mut self) { + if self.1 { + drop(unsafe { Box::from_raw(self.0) }); } } } diff --git a/src/state.rs b/src/state.rs index 132b6a1c..a3de4269 100644 --- a/src/state.rs +++ b/src/state.rs @@ -880,14 +880,14 @@ impl Lua { pub fn inspect_stack(&self, level: usize) -> Option> { let lua = self.lock(); unsafe { - let mut ar: ffi::lua_Debug = mem::zeroed(); + let mut ar = Box::new(mem::zeroed::()); let level = level as c_int; #[cfg(not(feature = "luau"))] - if ffi::lua_getstack(lua.state(), level, &mut ar) == 0 { + if ffi::lua_getstack(lua.state(), level, &mut *ar) == 0 { return None; } #[cfg(feature = "luau")] - if ffi::lua_getinfo(lua.state(), level, cstr!(""), &mut ar) == 0 { + if ffi::lua_getinfo(lua.state(), level, cstr!(""), &mut *ar) == 0 { return None; } Some(Debug::new_owned(lua, level, ar)) From faf547c154aaba28a1fb0d33fd42d3f350f6d84c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 30 Jun 2025 12:15:08 +0100 Subject: [PATCH 451/635] Refactor `Lua::inspect_stack` and debug interface. It was possible to cause a crash when getting a `Debug` instance and keeping it while deallocating the Lua stack frames. --- src/{hook.rs => debug.rs} | 123 ++++++++++---------------------------- src/lib.rs | 6 +- src/state.rs | 27 ++++----- src/state/extra.rs | 2 +- src/state/raw.rs | 6 +- src/thread.rs | 2 +- src/types.rs | 4 +- tests/tests.rs | 19 +++--- 8 files changed, 67 insertions(+), 122 deletions(-) rename src/{hook.rs => debug.rs} (79%) diff --git a/src/hook.rs b/src/debug.rs similarity index 79% rename from src/hook.rs rename to src/debug.rs index ce18d423..f3077256 100644 --- a/src/hook.rs +++ b/src/debug.rs @@ -1,13 +1,9 @@ use std::borrow::Cow; -use std::ops::Deref; -#[cfg(not(feature = "luau"))] -use std::ops::{BitOr, BitOrAssign}; use std::os::raw::c_int; use ffi::lua_Debug; use crate::state::RawLua; -use crate::types::ReentrantMutexGuard; use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str}; /// Contains information about currently executing Lua code. @@ -20,51 +16,15 @@ use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str}; /// [documentation]: https://www.lua.org/manual/5.4/manual.html#lua_Debug /// [`Lua::set_hook`]: crate::Lua::set_hook pub struct Debug<'a> { - lua: EitherLua<'a>, - ar: ActivationRecord, - #[cfg(feature = "luau")] + lua: &'a RawLua, + #[cfg_attr(not(feature = "luau"), allow(unused))] level: c_int, -} - -enum EitherLua<'a> { - Owned(ReentrantMutexGuard<'a, RawLua>), - #[cfg(not(feature = "luau"))] - Borrowed(&'a RawLua), -} - -impl Deref for EitherLua<'_> { - type Target = RawLua; - - fn deref(&self) -> &Self::Target { - match self { - EitherLua::Owned(guard) => guard, - #[cfg(not(feature = "luau"))] - EitherLua::Borrowed(lua) => lua, - } - } + ar: *mut lua_Debug, } impl<'a> Debug<'a> { - // We assume the lock is held when this function is called. - #[cfg(not(feature = "luau"))] - pub(crate) fn new(lua: &'a RawLua, ar: *mut lua_Debug) -> Self { - Debug { - lua: EitherLua::Borrowed(lua), - ar: ActivationRecord(ar, false), - } - } - - pub(crate) fn new_owned( - guard: ReentrantMutexGuard<'a, RawLua>, - _level: c_int, - ar: Box, - ) -> Self { - Debug { - lua: EitherLua::Owned(guard), - ar: ActivationRecord(Box::into_raw(ar), true), - #[cfg(feature = "luau")] - level: _level, - } + pub(crate) fn new(lua: &'a RawLua, level: c_int, ar: *mut lua_Debug) -> Self { + Debug { lua, ar, level } } /// Returns the specific event that triggered the hook. @@ -77,7 +37,7 @@ impl<'a> Debug<'a> { #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub fn event(&self) -> DebugEvent { unsafe { - match (*self.ar.get()).event { + match (*self.ar).event { ffi::LUA_HOOKCALL => DebugEvent::Call, ffi::LUA_HOOKRET => DebugEvent::Ret, ffi::LUA_HOOKTAILCALL => DebugEvent::TailCall, @@ -93,19 +53,19 @@ impl<'a> Debug<'a> { unsafe { #[cfg(not(feature = "luau"))] mlua_assert!( - ffi::lua_getinfo(self.lua.state(), cstr!("n"), self.ar.get()) != 0, + ffi::lua_getinfo(self.lua.state(), cstr!("n"), self.ar) != 0, "lua_getinfo failed with `n`" ); #[cfg(feature = "luau")] mlua_assert!( - ffi::lua_getinfo(self.lua.state(), self.level, cstr!("n"), self.ar.get()) != 0, + ffi::lua_getinfo(self.lua.state(), self.level, cstr!("n"), self.ar) != 0, "lua_getinfo failed with `n`" ); DebugNames { - name: ptr_to_lossy_str((*self.ar.get()).name), + name: ptr_to_lossy_str((*self.ar).name), #[cfg(not(feature = "luau"))] - name_what: match ptr_to_str((*self.ar.get()).namewhat) { + name_what: match ptr_to_str((*self.ar).namewhat) { Some("") => None, val => val, }, @@ -120,27 +80,27 @@ impl<'a> Debug<'a> { unsafe { #[cfg(not(feature = "luau"))] mlua_assert!( - ffi::lua_getinfo(self.lua.state(), cstr!("S"), self.ar.get()) != 0, + ffi::lua_getinfo(self.lua.state(), cstr!("S"), self.ar) != 0, "lua_getinfo failed with `S`" ); #[cfg(feature = "luau")] mlua_assert!( - ffi::lua_getinfo(self.lua.state(), self.level, cstr!("s"), self.ar.get()) != 0, + ffi::lua_getinfo(self.lua.state(), self.level, cstr!("s"), self.ar) != 0, "lua_getinfo failed with `s`" ); DebugSource { - source: ptr_to_lossy_str((*self.ar.get()).source), + source: ptr_to_lossy_str((*self.ar).source), #[cfg(not(feature = "luau"))] - short_src: ptr_to_lossy_str((*self.ar.get()).short_src.as_ptr()), + short_src: ptr_to_lossy_str((*self.ar).short_src.as_ptr()), #[cfg(feature = "luau")] - short_src: ptr_to_lossy_str((*self.ar.get()).short_src), - line_defined: linenumber_to_usize((*self.ar.get()).linedefined), + short_src: ptr_to_lossy_str((*self.ar).short_src), + line_defined: linenumber_to_usize((*self.ar).linedefined), #[cfg(not(feature = "luau"))] - last_line_defined: linenumber_to_usize((*self.ar.get()).lastlinedefined), + last_line_defined: linenumber_to_usize((*self.ar).lastlinedefined), #[cfg(feature = "luau")] last_line_defined: None, - what: ptr_to_str((*self.ar.get()).what).unwrap_or("main"), + what: ptr_to_str((*self.ar).what).unwrap_or("main"), } } } @@ -150,16 +110,16 @@ impl<'a> Debug<'a> { unsafe { #[cfg(not(feature = "luau"))] mlua_assert!( - ffi::lua_getinfo(self.lua.state(), cstr!("l"), self.ar.get()) != 0, + ffi::lua_getinfo(self.lua.state(), cstr!("l"), self.ar) != 0, "lua_getinfo failed with `l`" ); #[cfg(feature = "luau")] mlua_assert!( - ffi::lua_getinfo(self.lua.state(), self.level, cstr!("l"), self.ar.get()) != 0, + ffi::lua_getinfo(self.lua.state(), self.level, cstr!("l"), self.ar) != 0, "lua_getinfo failed with `l`" ); - (*self.ar.get()).currentline + (*self.ar).currentline } } @@ -170,10 +130,10 @@ impl<'a> Debug<'a> { pub fn is_tail_call(&self) -> bool { unsafe { mlua_assert!( - ffi::lua_getinfo(self.lua.state(), cstr!("t"), self.ar.get()) != 0, + ffi::lua_getinfo(self.lua.state(), cstr!("t"), self.ar) != 0, "lua_getinfo failed with `t`" ); - (*self.ar.get()).currentline != 0 + (*self.ar).currentline != 0 } } @@ -182,51 +142,34 @@ impl<'a> Debug<'a> { unsafe { #[cfg(not(feature = "luau"))] mlua_assert!( - ffi::lua_getinfo(self.lua.state(), cstr!("u"), self.ar.get()) != 0, + ffi::lua_getinfo(self.lua.state(), cstr!("u"), self.ar) != 0, "lua_getinfo failed with `u`" ); #[cfg(feature = "luau")] mlua_assert!( - ffi::lua_getinfo(self.lua.state(), self.level, cstr!("au"), self.ar.get()) != 0, + ffi::lua_getinfo(self.lua.state(), self.level, cstr!("au"), self.ar) != 0, "lua_getinfo failed with `au`" ); #[cfg(not(feature = "luau"))] let stack = DebugStack { - num_ups: (*self.ar.get()).nups as _, + num_ups: (*self.ar).nups as _, #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - num_params: (*self.ar.get()).nparams as _, + num_params: (*self.ar).nparams as _, #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - is_vararg: (*self.ar.get()).isvararg != 0, + is_vararg: (*self.ar).isvararg != 0, }; #[cfg(feature = "luau")] let stack = DebugStack { - num_ups: (*self.ar.get()).nupvals, - num_params: (*self.ar.get()).nparams, - is_vararg: (*self.ar.get()).isvararg != 0, + num_ups: (*self.ar).nupvals, + num_params: (*self.ar).nparams, + is_vararg: (*self.ar).isvararg != 0, }; stack } } } -struct ActivationRecord(*mut lua_Debug, bool); - -impl ActivationRecord { - #[inline] - fn get(&self) -> *mut lua_Debug { - self.0 - } -} - -impl Drop for ActivationRecord { - fn drop(&mut self) { - if self.1 { - drop(unsafe { Box::from_raw(self.0) }); - } - } -} - /// Represents a specific event that triggered the hook. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum DebugEvent { @@ -385,7 +328,7 @@ impl HookTriggers { } #[cfg(not(feature = "luau"))] -impl BitOr for HookTriggers { +impl std::ops::BitOr for HookTriggers { type Output = Self; fn bitor(mut self, rhs: Self) -> Self::Output { @@ -400,7 +343,7 @@ impl BitOr for HookTriggers { } #[cfg(not(feature = "luau"))] -impl BitOrAssign for HookTriggers { +impl std::ops::BitOrAssign for HookTriggers { fn bitor_assign(&mut self, rhs: Self) { *self = *self | rhs; } diff --git a/src/lib.rs b/src/lib.rs index e1589ce6..83247f02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,9 +75,9 @@ mod macros; mod buffer; mod chunk; mod conversion; +mod debug; mod error; mod function; -mod hook; #[cfg(any(feature = "luau", doc))] mod luau; mod memory; @@ -101,9 +101,9 @@ pub use bstr::BString; pub use ffi::{self, lua_CFunction, lua_State}; pub use crate::chunk::{AsChunk, Chunk, ChunkMode}; +pub use crate::debug::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack}; pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result}; pub use crate::function::{Function, FunctionInfo}; -pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack}; pub use crate::multi::{MultiValue, Variadic}; pub use crate::scope::Scope; pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua}; @@ -124,7 +124,7 @@ pub use crate::userdata::{ pub use crate::value::{Nil, Value}; #[cfg(not(feature = "luau"))] -pub use crate::hook::HookTriggers; +pub use crate::debug::HookTriggers; #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] diff --git a/src/state.rs b/src/state.rs index a3de4269..17fc6c07 100644 --- a/src/state.rs +++ b/src/state.rs @@ -8,9 +8,9 @@ use std::result::Result as StdResult; use std::{fmt, mem, ptr}; use crate::chunk::{AsChunk, Chunk}; +use crate::debug::Debug; use crate::error::{Error, Result}; use crate::function::Function; -use crate::hook::Debug; use crate::memory::MemoryState; use crate::multi::MultiValue; use crate::scope::Scope; @@ -28,7 +28,7 @@ use crate::util::{assert_stack, check_stack, protect_lua_closure, push_string, r use crate::value::{Nil, Value}; #[cfg(not(feature = "luau"))] -use crate::{hook::HookTriggers, types::HookKind}; +use crate::{debug::HookTriggers, types::HookKind}; #[cfg(any(feature = "luau", doc))] use crate::{buffer::Buffer, chunk::Compiler}; @@ -869,28 +869,27 @@ impl Lua { } } - /// Gets information about the interpreter runtime stack. + /// Gets information about the interpreter runtime stack at a given level. /// - /// This function returns [`Debug`] structure that can be used to get information about the - /// function executing at a given level. Level `0` is the current running function, whereas - /// level `n+1` is the function that has called level `n` (except for tail calls, which do - /// not count in the stack). - /// - /// [`Debug`]: crate::hook::Debug - pub fn inspect_stack(&self, level: usize) -> Option> { + /// This function calls callback `f`, passing the [`Debug`] structure that can be used to get + /// information about the function executing at a given level. + /// Level `0` is the current running function, whereas level `n+1` is the function that has + /// called level `n` (except for tail calls, which do not count in the stack). + pub fn inspect_stack(&self, level: usize, f: impl FnOnce(Debug) -> R) -> Option { let lua = self.lock(); unsafe { - let mut ar = Box::new(mem::zeroed::()); + let mut ar = mem::zeroed::(); let level = level as c_int; #[cfg(not(feature = "luau"))] - if ffi::lua_getstack(lua.state(), level, &mut *ar) == 0 { + if ffi::lua_getstack(lua.state(), level, &mut ar) == 0 { return None; } #[cfg(feature = "luau")] - if ffi::lua_getinfo(lua.state(), level, cstr!(""), &mut *ar) == 0 { + if ffi::lua_getinfo(lua.state(), level, cstr!(""), &mut ar) == 0 { return None; } - Some(Debug::new_owned(lua, level, ar)) + + Some(f(Debug::new(&lua, level, &mut ar))) } } diff --git a/src/state/extra.rs b/src/state/extra.rs index ddbd4fdf..588aa08b 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -76,7 +76,7 @@ pub(crate) struct ExtraData { #[cfg(not(feature = "luau"))] pub(super) hook_callback: Option, #[cfg(not(feature = "luau"))] - pub(super) hook_triggers: crate::hook::HookTriggers, + pub(super) hook_triggers: crate::debug::HookTriggers, #[cfg(feature = "lua54")] pub(super) warn_callback: Option, #[cfg(feature = "luau")] diff --git a/src/state/raw.rs b/src/state/raw.rs index e72bf73c..07eb9539 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -38,7 +38,7 @@ use super::{Lua, LuaOptions, WeakLua}; #[cfg(not(feature = "luau"))] use crate::{ - hook::Debug, + debug::Debug, types::{HookCallback, HookKind, VmState}, }; @@ -435,7 +435,7 @@ impl RawLua { match (*extra).hook_callback.clone() { Some(hook_callback) => { let rawlua = (*extra).raw_lua(); - let debug = Debug::new(rawlua, ar); + let debug = Debug::new(rawlua, 0, ar); hook_callback((*extra).lua(), debug) } None => { @@ -465,7 +465,7 @@ impl RawLua { let status = callback_error_ext(state, ptr::null_mut(), false, |extra, _| { let rawlua = (*extra).raw_lua(); - let debug = Debug::new(rawlua, ar); + let debug = Debug::new(rawlua, 0, ar); let hook_callback = (*hook_callback_ptr).clone(); hook_callback((*extra).lua(), debug) }); diff --git a/src/thread.rs b/src/thread.rs index 8d800073..eb32d61d 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -10,7 +10,7 @@ use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard}; #[cfg(not(feature = "luau"))] use crate::{ - hook::{Debug, HookTriggers}, + debug::{Debug, HookTriggers}, types::HookKind, }; diff --git a/src/types.rs b/src/types.rs index 2589ea6e..6fde0c47 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,9 +1,9 @@ use std::cell::UnsafeCell; use std::os::raw::{c_int, c_void}; -use crate::error::Result; #[cfg(not(feature = "luau"))] -use crate::hook::{Debug, HookTriggers}; +use crate::debug::{Debug, HookTriggers}; +use crate::error::Result; use crate::state::{ExtraData, Lua, RawLua}; // Re-export mutex wrappers diff --git a/tests/tests.rs b/tests/tests.rs index 7bf40af5..b6c90ebc 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1251,14 +1251,18 @@ fn test_inspect_stack() -> Result<()> { let lua = Lua::new(); // Not inside any function - assert!(lua.inspect_stack(0).is_none()); + assert!(lua.inspect_stack(0, |_| ()).is_none()); let logline = lua.create_function(|lua, msg: StdString| { - let debug = lua.inspect_stack(1).unwrap(); // caller - let source = debug.source().short_src; - let source = source.as_deref().unwrap_or("?"); - let line = debug.curr_line(); - Ok(format!("{}:{} {}", source, line, msg)) + let r = lua + .inspect_stack(1, |debug| { + let source = debug.source().short_src; + let source = source.as_deref().unwrap_or("?"); + let line = debug.curr_line(); + format!("{}:{} {}", source, line, msg) + }) + .unwrap(); + Ok(r) })?; lua.globals().set("logline", logline)?; @@ -1281,8 +1285,7 @@ fn test_inspect_stack() -> Result<()> { .exec()?; let stack_info = lua.create_function(|lua, ()| { - let debug = lua.inspect_stack(1).unwrap(); // caller - let stack_info = debug.stack(); + let stack_info = lua.inspect_stack(1, |debug| debug.stack()).unwrap(); Ok(format!("{stack_info:?}")) })?; lua.globals().set("stack_info", stack_info)?; From 052740db1557950822f99f73834db5a3cea7090d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 30 Jun 2025 22:38:46 +0100 Subject: [PATCH 452/635] Save `lua_State` at the moment of constructing `Debug` instead of resolving it dynamically --- src/debug.rs | 31 ++++++++++++++++--------------- src/state.rs | 4 ++-- src/state/raw.rs | 4 ++-- src/thread.rs | 2 +- src/types.rs | 4 ++-- 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/debug.rs b/src/debug.rs index f3077256..8df98b94 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use std::os::raw::c_int; -use ffi::lua_Debug; +use ffi::{lua_Debug, lua_State}; use crate::state::RawLua; use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str}; @@ -15,16 +15,17 @@ use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str}; /// /// [documentation]: https://www.lua.org/manual/5.4/manual.html#lua_Debug /// [`Lua::set_hook`]: crate::Lua::set_hook -pub struct Debug<'a> { - lua: &'a RawLua, +pub struct Debug { + state: *mut lua_State, #[cfg_attr(not(feature = "luau"), allow(unused))] level: c_int, ar: *mut lua_Debug, } -impl<'a> Debug<'a> { - pub(crate) fn new(lua: &'a RawLua, level: c_int, ar: *mut lua_Debug) -> Self { - Debug { lua, ar, level } +impl Debug { + pub(crate) fn new(lua: &RawLua, level: c_int, ar: *mut lua_Debug) -> Self { + let state = lua.state(); + Debug { state, ar, level } } /// Returns the specific event that triggered the hook. @@ -53,12 +54,12 @@ impl<'a> Debug<'a> { unsafe { #[cfg(not(feature = "luau"))] mlua_assert!( - ffi::lua_getinfo(self.lua.state(), cstr!("n"), self.ar) != 0, + ffi::lua_getinfo(self.state, cstr!("n"), self.ar) != 0, "lua_getinfo failed with `n`" ); #[cfg(feature = "luau")] mlua_assert!( - ffi::lua_getinfo(self.lua.state(), self.level, cstr!("n"), self.ar) != 0, + ffi::lua_getinfo(self.state, self.level, cstr!("n"), self.ar) != 0, "lua_getinfo failed with `n`" ); @@ -80,12 +81,12 @@ impl<'a> Debug<'a> { unsafe { #[cfg(not(feature = "luau"))] mlua_assert!( - ffi::lua_getinfo(self.lua.state(), cstr!("S"), self.ar) != 0, + ffi::lua_getinfo(self.state, cstr!("S"), self.ar) != 0, "lua_getinfo failed with `S`" ); #[cfg(feature = "luau")] mlua_assert!( - ffi::lua_getinfo(self.lua.state(), self.level, cstr!("s"), self.ar) != 0, + ffi::lua_getinfo(self.state, self.level, cstr!("s"), self.ar) != 0, "lua_getinfo failed with `s`" ); @@ -110,12 +111,12 @@ impl<'a> Debug<'a> { unsafe { #[cfg(not(feature = "luau"))] mlua_assert!( - ffi::lua_getinfo(self.lua.state(), cstr!("l"), self.ar) != 0, + ffi::lua_getinfo(self.state, cstr!("l"), self.ar) != 0, "lua_getinfo failed with `l`" ); #[cfg(feature = "luau")] mlua_assert!( - ffi::lua_getinfo(self.lua.state(), self.level, cstr!("l"), self.ar) != 0, + ffi::lua_getinfo(self.state, self.level, cstr!("l"), self.ar) != 0, "lua_getinfo failed with `l`" ); @@ -130,7 +131,7 @@ impl<'a> Debug<'a> { pub fn is_tail_call(&self) -> bool { unsafe { mlua_assert!( - ffi::lua_getinfo(self.lua.state(), cstr!("t"), self.ar) != 0, + ffi::lua_getinfo(self.state, cstr!("t"), self.ar) != 0, "lua_getinfo failed with `t`" ); (*self.ar).currentline != 0 @@ -142,12 +143,12 @@ impl<'a> Debug<'a> { unsafe { #[cfg(not(feature = "luau"))] mlua_assert!( - ffi::lua_getinfo(self.lua.state(), cstr!("u"), self.ar) != 0, + ffi::lua_getinfo(self.state, cstr!("u"), self.ar) != 0, "lua_getinfo failed with `u`" ); #[cfg(feature = "luau")] mlua_assert!( - ffi::lua_getinfo(self.lua.state(), self.level, cstr!("au"), self.ar) != 0, + ffi::lua_getinfo(self.state, self.level, cstr!("au"), self.ar) != 0, "lua_getinfo failed with `au`" ); diff --git a/src/state.rs b/src/state.rs index 17fc6c07..7e4bcb79 100644 --- a/src/state.rs +++ b/src/state.rs @@ -544,7 +544,7 @@ impl Lua { #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub fn set_global_hook(&self, triggers: HookTriggers, callback: F) -> Result<()> where - F: Fn(&Lua, Debug) -> Result + MaybeSend + 'static, + F: Fn(&Lua, &Debug) -> Result + MaybeSend + 'static, { let lua = self.lock(); unsafe { @@ -594,7 +594,7 @@ impl Lua { #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub fn set_hook(&self, triggers: HookTriggers, callback: F) -> Result<()> where - F: Fn(&Lua, Debug) -> Result + MaybeSend + 'static, + F: Fn(&Lua, &Debug) -> Result + MaybeSend + 'static, { let lua = self.lock(); unsafe { lua.set_thread_hook(lua.state(), HookKind::Thread(triggers, XRc::new(callback))) } diff --git a/src/state/raw.rs b/src/state/raw.rs index 07eb9539..a91517e7 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -436,7 +436,7 @@ impl RawLua { Some(hook_callback) => { let rawlua = (*extra).raw_lua(); let debug = Debug::new(rawlua, 0, ar); - hook_callback((*extra).lua(), debug) + hook_callback((*extra).lua(), &debug) } None => { ffi::lua_sethook(state, None, 0, 0); @@ -467,7 +467,7 @@ impl RawLua { let rawlua = (*extra).raw_lua(); let debug = Debug::new(rawlua, 0, ar); let hook_callback = (*hook_callback_ptr).clone(); - hook_callback((*extra).lua(), debug) + hook_callback((*extra).lua(), &debug) }); process_status(state, (*ar).event, status) } diff --git a/src/thread.rs b/src/thread.rs index eb32d61d..20848a6b 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -269,7 +269,7 @@ impl Thread { #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub fn set_hook(&self, triggers: HookTriggers, callback: F) -> Result<()> where - F: Fn(&crate::Lua, Debug) -> Result + crate::MaybeSend + 'static, + F: Fn(&crate::Lua, &Debug) -> Result + crate::MaybeSend + 'static, { let lua = self.0.lua.lock(); unsafe { diff --git a/src/types.rs b/src/types.rs index 6fde0c47..84cd02e7 100644 --- a/src/types.rs +++ b/src/types.rs @@ -79,10 +79,10 @@ pub(crate) enum HookKind { } #[cfg(all(feature = "send", not(feature = "luau")))] -pub(crate) type HookCallback = XRc Result + Send>; +pub(crate) type HookCallback = XRc Result + Send>; #[cfg(all(not(feature = "send"), not(feature = "luau")))] -pub(crate) type HookCallback = XRc Result>; +pub(crate) type HookCallback = XRc Result>; #[cfg(all(feature = "send", feature = "luau"))] pub(crate) type InterruptCallback = XRc Result + Send>; From a3697ab1db4b8c40bcfd9e0d35882fac2f26bb83 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 30 Jun 2025 23:25:36 +0100 Subject: [PATCH 453/635] Add `Debug::function` method to get function running at a given level. Close #607 --- src/debug.rs | 61 ++++++++++++++++++++++++++++++++++++-------------- tests/tests.rs | 14 ++++++++++++ 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/debug.rs b/src/debug.rs index 8df98b94..023296a4 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -3,29 +3,32 @@ use std::os::raw::c_int; use ffi::{lua_Debug, lua_State}; +use crate::function::Function; use crate::state::RawLua; -use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str}; +use crate::util::{assert_stack, linenumber_to_usize, ptr_to_lossy_str, ptr_to_str, StackGuard}; /// Contains information about currently executing Lua code. /// -/// The `Debug` structure is provided as a parameter to the hook function set with -/// [`Lua::set_hook`]. You may call the methods on this structure to retrieve information about the -/// Lua code executing at the time that the hook function was called. Further information can be -/// found in the Lua [documentation]. +/// You may call the methods on this structure to retrieve information about the Lua code executing +/// at the specific level. Further information can be found in the Lua [documentation]. /// /// [documentation]: https://www.lua.org/manual/5.4/manual.html#lua_Debug -/// [`Lua::set_hook`]: crate::Lua::set_hook -pub struct Debug { +pub struct Debug<'a> { state: *mut lua_State, + lua: &'a RawLua, #[cfg_attr(not(feature = "luau"), allow(unused))] level: c_int, ar: *mut lua_Debug, } -impl Debug { - pub(crate) fn new(lua: &RawLua, level: c_int, ar: *mut lua_Debug) -> Self { - let state = lua.state(); - Debug { state, ar, level } +impl<'a> Debug<'a> { + pub(crate) fn new(lua: &'a RawLua, level: c_int, ar: *mut lua_Debug) -> Self { + Debug { + state: lua.state(), + lua, + ar, + level, + } } /// Returns the specific event that triggered the hook. @@ -49,7 +52,31 @@ impl Debug { } } - /// Corresponds to the `n` what mask. + /// Returns the function that is running at the given level. + /// + /// Corresponds to the `f` "what" mask. + pub fn function(&self) -> Function { + unsafe { + let _sg = StackGuard::new(self.state); + assert_stack(self.state, 1); + + #[cfg(not(feature = "luau"))] + mlua_assert!( + ffi::lua_getinfo(self.state, cstr!("f"), self.ar) != 0, + "lua_getinfo failed with `f`" + ); + #[cfg(feature = "luau")] + mlua_assert!( + ffi::lua_getinfo(self.state, self.level, cstr!("f"), self.ar) != 0, + "lua_getinfo failed with `f`" + ); + + ffi::lua_xmove(self.state, self.lua.ref_thread(), 1); + Function(self.lua.pop_ref_thread()) + } + } + + /// Corresponds to the `n` "what" mask. pub fn names(&self) -> DebugNames<'_> { unsafe { #[cfg(not(feature = "luau"))] @@ -76,7 +103,7 @@ impl Debug { } } - /// Corresponds to the `S` what mask. + /// Corresponds to the `S` "what" mask. pub fn source(&self) -> DebugSource<'_> { unsafe { #[cfg(not(feature = "luau"))] @@ -106,7 +133,7 @@ impl Debug { } } - /// Corresponds to the `l` what mask. Returns the current line. + /// Corresponds to the `l` "what" mask. Returns the current line. pub fn curr_line(&self) -> i32 { unsafe { #[cfg(not(feature = "luau"))] @@ -124,8 +151,8 @@ impl Debug { } } - /// Corresponds to the `t` what mask. Returns true if the hook is in a function tail call, false - /// otherwise. + /// Corresponds to the `t` "what" mask. Returns true if the hook is in a function tail call, + /// false otherwise. #[cfg(not(feature = "luau"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub fn is_tail_call(&self) -> bool { @@ -138,7 +165,7 @@ impl Debug { } } - /// Corresponds to the `u` what mask. + /// Corresponds to the `u` "what" mask. pub fn stack(&self) -> DebugStack { unsafe { #[cfg(not(feature = "luau"))] diff --git a/tests/tests.rs b/tests/tests.rs index b6c90ebc..8fee73b7 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1315,6 +1315,20 @@ fn test_inspect_stack() -> Result<()> { ) .exec()?; + // Test retrieving currently running function + let running_function = + lua.create_function(|lua, ()| Ok(lua.inspect_stack(1, |debug| debug.function())))?; + lua.globals().set("running_function", running_function)?; + lua.load( + r#" + local function baz() + return running_function() + end + assert(baz() == baz) + "#, + ) + .exec()?; + Ok(()) } From 92db0f6d3ab0c3cacbfd9597ea0126ab5621a5df Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 30 Jun 2025 23:27:55 +0100 Subject: [PATCH 454/635] Fix `Lua::inspect_stack` callback proto --- src/state.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/state.rs b/src/state.rs index 7e4bcb79..fd34ee96 100644 --- a/src/state.rs +++ b/src/state.rs @@ -875,7 +875,7 @@ impl Lua { /// information about the function executing at a given level. /// Level `0` is the current running function, whereas level `n+1` is the function that has /// called level `n` (except for tail calls, which do not count in the stack). - pub fn inspect_stack(&self, level: usize, f: impl FnOnce(Debug) -> R) -> Option { + pub fn inspect_stack(&self, level: usize, f: impl FnOnce(&Debug) -> R) -> Option { let lua = self.lock(); unsafe { let mut ar = mem::zeroed::(); @@ -889,7 +889,7 @@ impl Lua { return None; } - Some(f(Debug::new(&lua, level, &mut ar))) + Some(f(&Debug::new(&lua, level, &mut ar))) } } From dfb4e9a6683947ce1fca3cea170d3451c47b5e17 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 1 Jul 2025 21:40:06 +0100 Subject: [PATCH 455/635] Fix LuaJIT stack inspection tests --- tests/tests.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/tests.rs b/tests/tests.rs index 8fee73b7..f0cad949 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1324,7 +1324,12 @@ fn test_inspect_stack() -> Result<()> { local function baz() return running_function() end - assert(baz() == baz) + if jit == nil then + assert(baz() == baz) + else + -- luajit inline the "baz" function and returns the chunk itself + assert(baz() == running_function()) + end "#, ) .exec()?; From a3302afdc1379bee910100bbaca43b57f1a9f457 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 1 Jul 2025 22:32:50 +0100 Subject: [PATCH 456/635] Deprecate `Value::as_str` and `Value::as_string_lossy` These methods don't follow Rust naming convention, see https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv --- src/string.rs | 8 ++++---- src/value.rs | 8 ++++++++ tests/conversion.rs | 12 ++++++------ tests/multi.rs | 4 ++-- tests/value.rs | 11 +---------- 5 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/string.rs b/src/string.rs index df9bb818..eeb08ed4 100644 --- a/src/string.rs +++ b/src/string.rs @@ -86,7 +86,7 @@ impl String { /// Get the bytes that make up this string. /// - /// The returned slice will not contain the terminating nul byte, but will contain any nul + /// The returned slice will not contain the terminating null byte, but will contain any null /// bytes embedded into the Lua string. /// /// # Examples @@ -106,15 +106,15 @@ impl String { BorrowedBytes::from(self) } - /// Get the bytes that make up this string, including the trailing nul byte. + /// Get the bytes that make up this string, including the trailing null byte. pub fn as_bytes_with_nul(&self) -> BorrowedBytes<'_> { let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self); - // Include the trailing nul byte (it's always present but excluded by default) + // Include the trailing null byte (it's always present but excluded by default) let buf = unsafe { slice::from_raw_parts((*buf).as_ptr(), (*buf).len() + 1) }; BorrowedBytes { buf, borrow, _lua } } - // Does not return the terminating nul byte + // Does not return the terminating null byte unsafe fn to_slice(&self) -> (&[u8], Lua) { let lua = self.0.lua.upgrade(); let slice = { diff --git a/src/value.rs b/src/value.rs index 2edef359..662ea6ae 100644 --- a/src/value.rs +++ b/src/value.rs @@ -356,6 +356,10 @@ impl Value { /// /// If the value is a Lua [`String`], try to convert it to [`BorrowedStr`] or return `None` /// otherwise. + #[deprecated( + since = "0.11.0", + note = "This method does not follow Rust naming convention. Use `as_string().and_then(|s| s.to_str().ok())` instead." + )] #[inline] pub fn as_str(&self) -> Option> { self.as_string().and_then(|s| s.to_str().ok()) @@ -364,6 +368,10 @@ impl Value { /// Cast the value to [`StdString`]. /// /// If the value is a Lua [`String`], converts it to [`StdString`] or returns `None` otherwise. + #[deprecated( + since = "0.11.0", + note = "This method does not follow Rust naming convention. Use `as_string().map(|s| s.to_string_lossy())` instead." + )] #[inline] pub fn as_string_lossy(&self) -> Option { self.as_string().map(|s| s.to_string_lossy()) diff --git a/tests/conversion.rs b/tests/conversion.rs index d724fa81..73228532 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -267,7 +267,7 @@ fn test_registry_value_into_lua() -> Result<()> { let r = lua.create_registry_value(&s)?; let value1 = lua.pack(&r)?; let value2 = lua.pack(r)?; - assert_eq!(value1.as_str().as_deref(), Some("hello, world")); + assert_eq!(value1.to_string()?, "hello, world"); assert_eq!(value1.to_pointer(), value2.to_pointer()); // Push into stack @@ -560,11 +560,11 @@ fn test_osstring_into_from_lua() -> Result<()> { let v = lua.pack(s.as_os_str())?; assert!(v.is_string()); - assert_eq!(v.as_str().unwrap(), "hello, world"); + assert_eq!(v.as_string().unwrap(), "hello, world"); let v = lua.pack(s)?; assert!(v.is_string()); - assert_eq!(v.as_str().unwrap(), "hello, world"); + assert_eq!(v.as_string().unwrap(), "hello, world"); let s = lua.create_string("hello, world")?; let bstr = lua.unpack::(Value::String(s))?; @@ -588,11 +588,11 @@ fn test_pathbuf_into_from_lua() -> Result<()> { let v = lua.pack(pb.as_path())?; assert!(v.is_string()); - assert_eq!(v.as_str().unwrap(), pb_str); + assert_eq!(v.to_string().unwrap(), pb_str); let v = lua.pack(pb.clone())?; assert!(v.is_string()); - assert_eq!(v.as_str().unwrap(), pb_str); + assert_eq!(v.to_string().unwrap(), pb_str); let s = lua.create_string(pb_str)?; let bstr = lua.unpack::(Value::String(s))?; @@ -724,7 +724,7 @@ fn test_char_into_lua() -> Result<()> { let v = '🦀'; let v2 = v.into_lua(&lua)?; - assert_eq!(Some(v.to_string()), v2.as_string_lossy()); + assert_eq!(*v2.as_string().unwrap(), v.to_string()); Ok(()) } diff --git a/tests/multi.rs b/tests/multi.rs index 13ce6875..32085557 100644 --- a/tests/multi.rs +++ b/tests/multi.rs @@ -43,12 +43,12 @@ fn test_result_conversions() -> Result<()> { let multi_err1 = err1.into_lua_multi(&lua)?; assert_eq!(multi_err1.len(), 2); assert_eq!(multi_err1[0], Value::Nil); - assert_eq!(multi_err1[1].as_str().unwrap(), "failure1"); + assert_eq!(multi_err1[1].as_string().unwrap(), "failure1"); let ok2 = Ok::<_, Error>("!"); let multi_ok2 = ok2.into_lua_multi(&lua)?; assert_eq!(multi_ok2.len(), 1); - assert_eq!(multi_ok2[0].as_str().unwrap(), "!"); + assert_eq!(multi_ok2[0].as_string().unwrap(), "!"); let err2 = Err::("failure2".into_lua_err()); let multi_err2 = err2.into_lua_multi(&lua)?; assert_eq!(multi_err2.len(), 2); diff --git a/tests/value.rs b/tests/value.rs index 4185442e..0ff48448 100644 --- a/tests/value.rs +++ b/tests/value.rs @@ -255,16 +255,7 @@ fn test_value_conversions() -> Result<()> { Value::String(lua.create_string("hello")?).as_string().unwrap(), "hello" ); - assert_eq!( - Value::String(lua.create_string("hello")?).as_str().unwrap(), - "hello" - ); - assert_eq!( - Value::String(lua.create_string("hello")?) - .as_string_lossy() - .unwrap(), - "hello" - ); + assert_eq!(Value::String(lua.create_string("hello")?).to_string()?, "hello"); assert!(Value::Table(lua.create_table()?).is_table()); assert!(Value::Table(lua.create_table()?).as_table().is_some()); assert!(Value::Function(lua.create_function(|_, ()| Ok(())).unwrap()).is_function()); From ef4eabd3270e4691bc74db29ea05bb264f9fee68 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 1 Jul 2025 22:51:16 +0100 Subject: [PATCH 457/635] Don't use `Value::as_str()` internally --- src/serde/ser.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 81b24682..1d717a79 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -529,8 +529,8 @@ impl ser::SerializeStruct for SerializeStruct<'_> { fn end(self) -> Result { match self.inner { Some(table @ Value::Table(_)) => Ok(table), - Some(value) if self.options.detect_serde_json_arbitrary_precision => { - let number_s = value.as_str().expect("not an arbitrary precision number"); + Some(value @ Value::String(_)) if self.options.detect_serde_json_arbitrary_precision => { + let number_s = value.to_string()?; if number_s.contains(['.', 'e', 'E']) { if let Ok(number) = number_s.parse().map(Value::Number) { return Ok(number); From 55c07f3b28b99ab4280060bec526f254beea9499 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 3 Jul 2025 14:42:24 +0100 Subject: [PATCH 458/635] Some minor changes in Luau TextRequirer (comments, naming, etc) --- src/luau/require.rs | 75 +++++++++++++++++++++++++++++---------------- src/prelude.rs | 3 +- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/src/luau/require.rs b/src/luau/require.rs index 167d04a3..13d09f02 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -14,7 +14,7 @@ use crate::state::{callback_error_ext, Lua}; use crate::table::Table; use crate::types::MaybeSend; -/// An error that can occur during navigation in the Luau `require` system. +/// An error that can occur during navigation in the Luau `require-by-string` system. #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[derive(Debug, Clone)] @@ -50,7 +50,7 @@ impl From for NavigateError { #[cfg(feature = "luau")] type WriteResult = ffi::luarequire_WriteResult; -/// A trait for handling modules loading and navigation in the Luau `require` system. +/// A trait for handling modules loading and navigation in the Luau `require-by-string` system. #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub trait Require: MaybeSend { @@ -103,16 +103,26 @@ impl fmt::Debug for dyn Require { } } -/// The standard implementation of Luau `require` navigation. -#[doc(hidden)] +/// The standard implementation of Luau `require-by-string` navigation. #[derive(Default, Debug)] pub struct TextRequirer { + /// An absolute path to the current Luau module (not mapped to a physical file) abs_path: PathBuf, + /// A relative path to the current Luau module (not mapped to a physical file) rel_path: PathBuf, - module_path: PathBuf, + /// A physical path to the current Luau module, which is a file or a directory with an + /// `init.lua(u)` file + resolved_path: Option, } impl TextRequirer { + /// The prefix used for chunk names in the require system. + /// Only chunk names starting with this prefix are allowed to be used in `require`. + const CHUNK_PREFIX: &str = "@"; + + /// The file extensions that are considered valid for Luau modules. + const FILE_EXTENSIONS: &[&str] = &["luau", "lua"]; + /// Creates a new `TextRequirer` instance. pub fn new() -> Self { Self::default() @@ -156,14 +166,17 @@ impl TextRequirer { components.into_iter().collect() } - fn find_module(path: &Path) -> StdResult { + /// Resolve a Luau module path to a physical file or directory. + /// + /// Empty directories without init files are considered valid as "intermediate" directories. + fn resolve_module(path: &Path) -> StdResult, NavigateError> { let mut found_path = None; if path.components().next_back() != Some(Component::Normal("init".as_ref())) { let current_ext = (path.extension().and_then(|s| s.to_str())) .map(|s| format!("{s}.")) .unwrap_or_default(); - for ext in ["luau", "lua"] { + for ext in Self::FILE_EXTENSIONS { let candidate = path.with_extension(format!("{current_ext}{ext}")); if candidate.is_file() && found_path.replace(candidate).is_some() { return Err(NavigateError::Ambiguous); @@ -171,7 +184,7 @@ impl TextRequirer { } } if path.is_dir() { - for component in ["init.luau", "init.lua"] { + for component in Self::FILE_EXTENSIONS.iter().map(|ext| format!("init.{ext}")) { let candidate = path.join(component); if candidate.is_file() && found_path.replace(candidate).is_some() { return Err(NavigateError::Ambiguous); @@ -179,21 +192,22 @@ impl TextRequirer { } if found_path.is_none() { - found_path = Some(PathBuf::new()); + // Directories without init files are considered valid "intermediate" path + return Ok(None); } } - found_path.ok_or(NavigateError::NotFound) + Ok(Some(found_path.ok_or(NavigateError::NotFound)?)) } } impl Require for TextRequirer { fn is_require_allowed(&self, chunk_name: &str) -> bool { - chunk_name.starts_with('@') + chunk_name.starts_with(Self::CHUNK_PREFIX) } fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError> { - if !chunk_name.starts_with('@') { + if !chunk_name.starts_with(Self::CHUNK_PREFIX) { return Err(NavigateError::NotFound); } let chunk_name = Self::normalize_chunk_name(&chunk_name[1..]); @@ -205,24 +219,24 @@ impl Require for TextRequirer { let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?; self.abs_path = Self::normalize_path(&cwd.join(chunk_filename)); self.rel_path = ([Component::CurDir, Component::Normal(chunk_filename)].into_iter()).collect(); - self.module_path = PathBuf::new(); + self.resolved_path = None; return Ok(()); } if chunk_path.is_absolute() { - let module_path = Self::find_module(&chunk_path)?; + let resolved_path = Self::resolve_module(&chunk_path)?; self.abs_path = chunk_path.clone(); self.rel_path = chunk_path; - self.module_path = module_path; + self.resolved_path = resolved_path; } else { // Relative path let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?; let abs_path = Self::normalize_path(&cwd.join(&chunk_path)); - let module_path = Self::find_module(&abs_path)?; + let resolved_path = Self::resolve_module(&abs_path)?; self.abs_path = abs_path; self.rel_path = chunk_path; - self.module_path = module_path; + self.resolved_path = resolved_path; } Ok(()) @@ -230,11 +244,11 @@ impl Require for TextRequirer { fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> { let path = Self::normalize_path(path.as_ref()); - let module_path = Self::find_module(&path)?; + let resolved_path = Self::resolve_module(&path)?; self.abs_path = path.clone(); self.rel_path = path; - self.module_path = module_path; + self.resolved_path = resolved_path; Ok(()) } @@ -242,15 +256,18 @@ impl Require for TextRequirer { fn to_parent(&mut self) -> StdResult<(), NavigateError> { let mut abs_path = self.abs_path.clone(); if !abs_path.pop() { + // It's important to return `NotFound` if we reached the root, as it's a "recoverable" error if we + // cannot go beyond the root directory. + // Luau "require-by-string` has a special logic to search for config file to resolve aliases. return Err(NavigateError::NotFound); } let mut rel_parent = self.rel_path.clone(); rel_parent.pop(); - let module_path = Self::find_module(&abs_path)?; + let resolved_path = Self::resolve_module(&abs_path)?; self.abs_path = abs_path; self.rel_path = Self::normalize_path(&rel_parent); - self.module_path = module_path; + self.resolved_path = resolved_path; Ok(()) } @@ -258,21 +275,23 @@ impl Require for TextRequirer { fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> { let abs_path = self.abs_path.join(name); let rel_path = self.rel_path.join(name); - let module_path = Self::find_module(&abs_path)?; + let resolved_path = Self::resolve_module(&abs_path)?; self.abs_path = abs_path; self.rel_path = rel_path; - self.module_path = module_path; + self.resolved_path = resolved_path; Ok(()) } fn has_module(&self) -> bool { - self.module_path.is_file() + (self.resolved_path.as_deref()) + .map(Path::is_file) + .unwrap_or(false) } fn cache_key(&self) -> String { - self.module_path.display().to_string() + self.resolved_path.as_deref().unwrap().display().to_string() } fn has_config(&self) -> bool { @@ -285,7 +304,9 @@ impl Require for TextRequirer { fn loader(&self, lua: &Lua) -> Result { let name = format!("@{}", self.rel_path.display()); - lua.load(&*self.module_path).set_name(name).into_function() + lua.load(self.resolved_path.as_deref().unwrap()) + .set_name(name) + .into_function() } } @@ -496,7 +517,7 @@ unsafe fn write_to_buffer( } #[cfg(feature = "luau")] -pub fn create_require_function(lua: &Lua, require: R) -> Result { +pub(super) fn create_require_function(lua: &Lua, require: R) -> Result { unsafe extern "C-unwind" fn find_current_file(state: *mut ffi::lua_State) -> c_int { let mut ar: ffi::lua_Debug = mem::zeroed(); for level in 2.. { diff --git a/src/prelude.rs b/src/prelude.rs index a3a03201..0e4cd0bd 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -25,7 +25,8 @@ pub use crate::HookTriggers as LuaHookTriggers; #[doc(no_inline)] pub use crate::{ CompileConstant as LuaCompileConstant, CoverageInfo as LuaCoverageInfo, - NavigateError as LuaNavigateError, Require as LuaRequire, Vector as LuaVector, + NavigateError as LuaNavigateError, Require as LuaRequire, TextRequirer as LuaTextRequirer, + Vector as LuaVector, }; #[cfg(feature = "async")] From 4b9d1cf27186131990be49d3b9c982f185956195 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 5 Jul 2025 11:21:18 +0100 Subject: [PATCH 459/635] Replace `impl ToString` with `Into` This is a more canonical way to accept any types of stirng but not arbitrary types that implement `Display` --- src/userdata.rs | 49 +++++++++++++---------- src/userdata/registry.rs | 84 ++++++++++++++++++++-------------------- 2 files changed, 70 insertions(+), 63 deletions(-) diff --git a/src/userdata.rs b/src/userdata.rs index 797d32de..ae04f092 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -240,6 +240,13 @@ impl AsRef for MetaMethod { } } +impl From for StdString { + #[inline] + fn from(method: MetaMethod) -> Self { + method.name().to_owned() + } +} + /// Method registry for [`UserData`] implementors. pub trait UserDataMethods { /// Add a regular method which accepts a `&T` as the first parameter. @@ -249,7 +256,7 @@ pub trait UserDataMethods { /// /// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will /// be used as a fall-back if no regular method is found. - fn add_method(&mut self, name: impl ToString, method: M) + fn add_method(&mut self, name: impl Into, method: M) where M: Fn(&Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -260,7 +267,7 @@ pub trait UserDataMethods { /// Refer to [`add_method`] for more information about the implementation. /// /// [`add_method`]: UserDataMethods::add_method - fn add_method_mut(&mut self, name: impl ToString, method: M) + fn add_method_mut(&mut self, name: impl Into, method: M) where M: FnMut(&Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -273,7 +280,7 @@ pub trait UserDataMethods { /// [`add_method`]: UserDataMethods::add_method #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn add_async_method(&mut self, name: impl ToString, method: M) + fn add_async_method(&mut self, name: impl Into, method: M) where T: 'static, M: Fn(Lua, UserDataRef, A) -> MR + MaybeSend + 'static, @@ -288,7 +295,7 @@ pub trait UserDataMethods { /// [`add_method`]: UserDataMethods::add_method #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn add_async_method_mut(&mut self, name: impl ToString, method: M) + fn add_async_method_mut(&mut self, name: impl Into, method: M) where T: 'static, M: Fn(Lua, UserDataRefMut, A) -> MR + MaybeSend + 'static, @@ -301,7 +308,7 @@ pub trait UserDataMethods { /// The first argument will be a [`AnyUserData`] of type `T` if the method is called with Lua /// method syntax: `my_userdata:my_method(arg1, arg2)`, or it is passed in as the first /// argument: `my_userdata.my_method(my_userdata, arg1, arg2)`. - fn add_function(&mut self, name: impl ToString, function: F) + fn add_function(&mut self, name: impl Into, function: F) where F: Fn(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -312,7 +319,7 @@ pub trait UserDataMethods { /// This is a version of [`add_function`] that accepts a `FnMut` argument. /// /// [`add_function`]: UserDataMethods::add_function - fn add_function_mut(&mut self, name: impl ToString, function: F) + fn add_function_mut(&mut self, name: impl Into, function: F) where F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -326,7 +333,7 @@ pub trait UserDataMethods { /// [`add_function`]: UserDataMethods::add_function #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn add_async_function(&mut self, name: impl ToString, function: F) + fn add_async_function(&mut self, name: impl Into, function: F) where F: Fn(Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, @@ -341,7 +348,7 @@ pub trait UserDataMethods { /// side has a metatable. To prevent this, use [`add_meta_function`]. /// /// [`add_meta_function`]: UserDataMethods::add_meta_function - fn add_meta_method(&mut self, name: impl ToString, method: M) + fn add_meta_method(&mut self, name: impl Into, method: M) where M: Fn(&Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -355,7 +362,7 @@ pub trait UserDataMethods { /// side has a metatable. To prevent this, use [`add_meta_function`]. /// /// [`add_meta_function`]: UserDataMethods::add_meta_function - fn add_meta_method_mut(&mut self, name: impl ToString, method: M) + fn add_meta_method_mut(&mut self, name: impl Into, method: M) where M: FnMut(&Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -371,7 +378,7 @@ pub trait UserDataMethods { docsrs, doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))) )] - fn add_async_meta_method(&mut self, name: impl ToString, method: M) + fn add_async_meta_method(&mut self, name: impl Into, method: M) where T: 'static, M: Fn(Lua, UserDataRef, A) -> MR + MaybeSend + 'static, @@ -387,7 +394,7 @@ pub trait UserDataMethods { /// [`add_meta_method_mut`]: UserDataMethods::add_meta_method_mut #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn add_async_meta_method_mut(&mut self, name: impl ToString, method: M) + fn add_async_meta_method_mut(&mut self, name: impl Into, method: M) where T: 'static, M: Fn(Lua, UserDataRefMut, A) -> MR + MaybeSend + 'static, @@ -400,7 +407,7 @@ pub trait UserDataMethods { /// Metamethods for binary operators can be triggered if either the left or right argument to /// the binary operator has a metatable, so the first argument here is not necessarily a /// userdata of type `T`. - fn add_meta_function(&mut self, name: impl ToString, function: F) + fn add_meta_function(&mut self, name: impl Into, function: F) where F: Fn(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -411,7 +418,7 @@ pub trait UserDataMethods { /// This is a version of [`add_meta_function`] that accepts a `FnMut` argument. /// /// [`add_meta_function`]: UserDataMethods::add_meta_function - fn add_meta_function_mut(&mut self, name: impl ToString, function: F) + fn add_meta_function_mut(&mut self, name: impl Into, function: F) where F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -427,7 +434,7 @@ pub trait UserDataMethods { docsrs, doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))) )] - fn add_async_meta_function(&mut self, name: impl ToString, function: F) + fn add_async_meta_function(&mut self, name: impl Into, function: F) where F: Fn(Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, @@ -446,7 +453,7 @@ pub trait UserDataFields { /// /// If `add_meta_method` is used to set the `__index` metamethod, it will /// be used as a fall-back if no regular field or method are found. - fn add_field(&mut self, name: impl ToString, value: V) + fn add_field(&mut self, name: impl Into, value: V) where V: IntoLua + 'static; @@ -457,7 +464,7 @@ pub trait UserDataFields { /// /// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will /// be used as a fall-back if no regular field or method are found. - fn add_field_method_get(&mut self, name: impl ToString, method: M) + fn add_field_method_get(&mut self, name: impl Into, method: M) where M: Fn(&Lua, &T) -> Result + MaybeSend + 'static, R: IntoLua; @@ -470,21 +477,21 @@ pub trait UserDataFields { /// /// If `add_meta_method` is used to set the `__newindex` metamethod, the `__newindex` metamethod /// will be used as a fall-back if no regular field is found. - fn add_field_method_set(&mut self, name: impl ToString, method: M) + fn add_field_method_set(&mut self, name: impl Into, method: M) where M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static, A: FromLua; /// Add a regular field getter as a function which accepts a generic [`AnyUserData`] of type `T` /// argument. - fn add_field_function_get(&mut self, name: impl ToString, function: F) + fn add_field_function_get(&mut self, name: impl Into, function: F) where F: Fn(&Lua, AnyUserData) -> Result + MaybeSend + 'static, R: IntoLua; /// Add a regular field setter as a function which accepts a generic [`AnyUserData`] of type `T` /// first argument. - fn add_field_function_set(&mut self, name: impl ToString, function: F) + fn add_field_function_set(&mut self, name: impl Into, function: F) where F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static, A: FromLua; @@ -497,7 +504,7 @@ pub trait UserDataFields { /// /// `mlua` will trigger an error on an attempt to define a protected metamethod, /// like `__gc` or `__metatable`. - fn add_meta_field(&mut self, name: impl ToString, value: V) + fn add_meta_field(&mut self, name: impl Into, value: V) where V: IntoLua + 'static; @@ -509,7 +516,7 @@ pub trait UserDataFields { /// /// `mlua` will trigger an error on an attempt to define a protected metamethod, /// like `__gc` or `__metatable`. - fn add_meta_field_with(&mut self, name: impl ToString, f: F) + fn add_meta_field_with(&mut self, name: impl Into, f: F) where F: FnOnce(&Lua) -> Result + 'static, R: IntoLua; diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index 74e36e7d..d6d6827b 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -365,101 +365,101 @@ fn get_function_name(name: &str) -> StdString { } impl UserDataFields for UserDataRegistry { - fn add_field(&mut self, name: impl ToString, value: V) + fn add_field(&mut self, name: impl Into, value: V) where V: IntoLua + 'static, { - let name = name.to_string(); + let name = name.into(); self.raw.fields.push((name, value.into_lua(self.lua.lua()))); } - fn add_field_method_get(&mut self, name: impl ToString, method: M) + fn add_field_method_get(&mut self, name: impl Into, method: M) where M: Fn(&Lua, &T) -> Result + MaybeSend + 'static, R: IntoLua, { - let name = name.to_string(); + let name = name.into(); let callback = self.box_method(&name, move |lua, data, ()| method(lua, data)); self.raw.field_getters.push((name, callback)); } - fn add_field_method_set(&mut self, name: impl ToString, method: M) + fn add_field_method_set(&mut self, name: impl Into, method: M) where M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static, A: FromLua, { - let name = name.to_string(); + let name = name.into(); let callback = self.box_method_mut(&name, method); self.raw.field_setters.push((name, callback)); } - fn add_field_function_get(&mut self, name: impl ToString, function: F) + fn add_field_function_get(&mut self, name: impl Into, function: F) where F: Fn(&Lua, AnyUserData) -> Result + MaybeSend + 'static, R: IntoLua, { - let name = name.to_string(); + let name = name.into(); let callback = self.box_function(&name, function); self.raw.field_getters.push((name, callback)); } - fn add_field_function_set(&mut self, name: impl ToString, mut function: F) + fn add_field_function_set(&mut self, name: impl Into, mut function: F) where F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static, A: FromLua, { - let name = name.to_string(); + let name = name.into(); let callback = self.box_function_mut(&name, move |lua, (data, val)| function(lua, data, val)); self.raw.field_setters.push((name, callback)); } - fn add_meta_field(&mut self, name: impl ToString, value: V) + fn add_meta_field(&mut self, name: impl Into, value: V) where V: IntoLua + 'static, { let lua = self.lua.lua(); - let name = name.to_string(); + let name = name.into(); let field = Self::check_meta_field(lua, &name, value).and_then(|v| v.into_lua(lua)); self.raw.meta_fields.push((name, field)); } - fn add_meta_field_with(&mut self, name: impl ToString, f: F) + fn add_meta_field_with(&mut self, name: impl Into, f: F) where F: FnOnce(&Lua) -> Result + 'static, R: IntoLua, { let lua = self.lua.lua(); - let name = name.to_string(); + let name = name.into(); let field = f(lua).and_then(|v| Self::check_meta_field(lua, &name, v).and_then(|v| v.into_lua(lua))); self.raw.meta_fields.push((name, field)); } } impl UserDataMethods for UserDataRegistry { - fn add_method(&mut self, name: impl ToString, method: M) + fn add_method(&mut self, name: impl Into, method: M) where M: Fn(&Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { - let name = name.to_string(); + let name = name.into(); let callback = self.box_method(&name, method); self.raw.methods.push((name, callback)); } - fn add_method_mut(&mut self, name: impl ToString, method: M) + fn add_method_mut(&mut self, name: impl Into, method: M) where M: FnMut(&Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { - let name = name.to_string(); + let name = name.into(); let callback = self.box_method_mut(&name, method); self.raw.methods.push((name, callback)); } #[cfg(feature = "async")] - fn add_async_method(&mut self, name: impl ToString, method: M) + fn add_async_method(&mut self, name: impl Into, method: M) where T: 'static, M: Fn(Lua, UserDataRef, A) -> MR + MaybeSend + 'static, @@ -467,13 +467,13 @@ impl UserDataMethods for UserDataRegistry { MR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { - let name = name.to_string(); + let name = name.into(); let callback = self.box_async_method(&name, method); self.raw.async_methods.push((name, callback)); } #[cfg(feature = "async")] - fn add_async_method_mut(&mut self, name: impl ToString, method: M) + fn add_async_method_mut(&mut self, name: impl Into, method: M) where T: 'static, M: Fn(Lua, UserDataRefMut, A) -> MR + MaybeSend + 'static, @@ -481,70 +481,70 @@ impl UserDataMethods for UserDataRegistry { MR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { - let name = name.to_string(); + let name = name.into(); let callback = self.box_async_method_mut(&name, method); self.raw.async_methods.push((name, callback)); } - fn add_function(&mut self, name: impl ToString, function: F) + fn add_function(&mut self, name: impl Into, function: F) where F: Fn(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { - let name = name.to_string(); + let name = name.into(); let callback = self.box_function(&name, function); self.raw.methods.push((name, callback)); } - fn add_function_mut(&mut self, name: impl ToString, function: F) + fn add_function_mut(&mut self, name: impl Into, function: F) where F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { - let name = name.to_string(); + let name = name.into(); let callback = self.box_function_mut(&name, function); self.raw.methods.push((name, callback)); } #[cfg(feature = "async")] - fn add_async_function(&mut self, name: impl ToString, function: F) + fn add_async_function(&mut self, name: impl Into, function: F) where F: Fn(Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, FR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { - let name = name.to_string(); + let name = name.into(); let callback = self.box_async_function(&name, function); self.raw.async_methods.push((name, callback)); } - fn add_meta_method(&mut self, name: impl ToString, method: M) + fn add_meta_method(&mut self, name: impl Into, method: M) where M: Fn(&Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { - let name = name.to_string(); + let name = name.into(); let callback = self.box_method(&name, method); self.raw.meta_methods.push((name, callback)); } - fn add_meta_method_mut(&mut self, name: impl ToString, method: M) + fn add_meta_method_mut(&mut self, name: impl Into, method: M) where M: FnMut(&Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { - let name = name.to_string(); + let name = name.into(); let callback = self.box_method_mut(&name, method); self.raw.meta_methods.push((name, callback)); } #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] - fn add_async_meta_method(&mut self, name: impl ToString, method: M) + fn add_async_meta_method(&mut self, name: impl Into, method: M) where T: 'static, M: Fn(Lua, UserDataRef, A) -> MR + MaybeSend + 'static, @@ -552,13 +552,13 @@ impl UserDataMethods for UserDataRegistry { MR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { - let name = name.to_string(); + let name = name.into(); let callback = self.box_async_method(&name, method); self.raw.async_meta_methods.push((name, callback)); } #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] - fn add_async_meta_method_mut(&mut self, name: impl ToString, method: M) + fn add_async_meta_method_mut(&mut self, name: impl Into, method: M) where T: 'static, M: Fn(Lua, UserDataRefMut, A) -> MR + MaybeSend + 'static, @@ -566,42 +566,42 @@ impl UserDataMethods for UserDataRegistry { MR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { - let name = name.to_string(); + let name = name.into(); let callback = self.box_async_method_mut(&name, method); self.raw.async_meta_methods.push((name, callback)); } - fn add_meta_function(&mut self, name: impl ToString, function: F) + fn add_meta_function(&mut self, name: impl Into, function: F) where F: Fn(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { - let name = name.to_string(); + let name = name.into(); let callback = self.box_function(&name, function); self.raw.meta_methods.push((name, callback)); } - fn add_meta_function_mut(&mut self, name: impl ToString, function: F) + fn add_meta_function_mut(&mut self, name: impl Into, function: F) where F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, { - let name = name.to_string(); + let name = name.into(); let callback = self.box_function_mut(&name, function); self.raw.meta_methods.push((name, callback)); } #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] - fn add_async_meta_function(&mut self, name: impl ToString, function: F) + fn add_async_meta_function(&mut self, name: impl Into, function: F) where F: Fn(Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, FR: Future> + MaybeSend + 'static, R: IntoLuaMulti, { - let name = name.to_string(); + let name = name.into(); let callback = self.box_async_function(&name, function); self.raw.async_meta_methods.push((name, callback)); } From 80471c6dada5185dc0eb088b52336d4fa59041c9 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 5 Jul 2025 11:33:11 +0100 Subject: [PATCH 460/635] Optimize `AnyUserData::metatable` --- src/userdata.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/userdata.rs b/src/userdata.rs index ae04f092..f82e66d9 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -888,16 +888,17 @@ impl AnyUserData { self.raw_metatable().map(UserDataMetatable) } + /// Returns a raw metatable of this [`AnyUserData`]. fn raw_metatable(&self) -> Result
{ let lua = self.0.lua.lock(); - let state = lua.state(); + let ref_thread = lua.ref_thread(); unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 3)?; + // Check that userdata is registered and not destructed + // All registered userdata types have a non-empty metatable + let _type_id = lua.get_userdata_ref_type_id(&self.0)?; - lua.push_userdata_ref(&self.0)?; - ffi::lua_getmetatable(state, -1); // Checked that non-empty on the previous call - Ok(Table(lua.pop_ref())) + ffi::lua_getmetatable(ref_thread, self.0.index); + Ok(Table(lua.pop_ref_thread())) } } From c0d839d8d2f248f00535f2bcaef9c93d7f93c9ff Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 5 Jul 2025 22:36:47 +0100 Subject: [PATCH 461/635] Make `Thread::state` pub (hidden) --- src/thread.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/thread.rs b/src/thread.rs index 20848a6b..b524582f 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -91,8 +91,10 @@ pub struct AsyncThread { } impl Thread { + /// Returns reference to the Lua state that this thread is associated with. + #[doc(hidden)] #[inline(always)] - fn state(&self) -> *mut ffi::lua_State { + pub fn state(&self) -> *mut ffi::lua_State { self.1 } From c90cac5189943563de7bfe1d55e85b1da2ef0dad Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 6 Jul 2025 10:35:07 +0100 Subject: [PATCH 462/635] Add `Lua::set_globals` method to replace global environment. Closes #611 --- src/state.rs | 33 +++++++++++++++++++++++++++++++++ tests/tests.rs | 25 +++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/state.rs b/src/state.rs index fd34ee96..10701125 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1550,6 +1550,39 @@ impl Lua { } } + /// Sets the global environment. + /// + /// This will replace the current global environment with the provided `globals` table. + /// + /// For Lua 5.2+ the globals table is stored in the registry and shared between all threads. + /// For Lua 5.1 and Luau the globals table is stored in each thread. + /// + /// Please note that any existing Lua functions have cached global environment and will not + /// see the changes made by this method. + /// To update the environment for existing Lua functions, use [`Function::set_environment`]. + pub fn set_globals(&self, globals: Table) -> Result<()> { + let lua = self.lock(); + let state = lua.state(); + unsafe { + #[cfg(feature = "luau")] + if (*lua.extra.get()).sandboxed { + return Err(Error::runtime("cannot change globals in a sandboxed Lua state")); + } + + let _sg = StackGuard::new(state); + check_stack(state, 1)?; + + lua.push_ref(&globals.0); + + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX); + } + + Ok(()) + } + /// Returns a handle to the active `Thread`. /// /// For calls to `Lua` this will be the main Lua thread, for parameters given to a callback, diff --git a/tests/tests.rs b/tests/tests.rs index f0cad949..25dfb494 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -147,6 +147,31 @@ fn test_eval() -> Result<()> { Ok(()) } +#[test] +fn test_replace_globals() -> Result<()> { + let lua = Lua::new(); + + let globals = lua.create_table()?; + globals.set("foo", "bar")?; + + lua.set_globals(globals.clone())?; + let val = lua.load("return foo").eval::()?; + assert_eq!(val, "bar"); + + // Updating globals in sandboxed Lua state is not allowed + #[cfg(feature = "luau")] + { + lua.sandbox(true)?; + match lua.set_globals(globals) { + Err(Error::RuntimeError(msg)) + if msg.contains("cannot change globals in a sandboxed Lua state") => {} + r => panic!("expected RuntimeError(...) with a specific error message, got {r:?}"), + } + } + + Ok(()) +} + #[test] fn test_load_mode() -> Result<()> { let lua = unsafe { Lua::unsafe_new() }; From 1882931cd9a144c3e36668ee37c2f9e652635684 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 6 Jul 2025 10:44:46 +0100 Subject: [PATCH 463/635] Optimize `Table::metatable` --- src/table.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/table.rs b/src/table.rs index 83b8cc3d..c4fd3ca6 100644 --- a/src/table.rs +++ b/src/table.rs @@ -487,16 +487,12 @@ impl Table { /// [`getmetatable`]: https://www.lua.org/manual/5.4/manual.html#pdf-getmetatable pub fn metatable(&self) -> Option
{ let lua = self.0.lua.lock(); - let state = lua.state(); + let ref_thread = lua.ref_thread(); unsafe { - let _sg = StackGuard::new(state); - assert_stack(state, 2); - - lua.push_ref(&self.0); - if ffi::lua_getmetatable(state, -1) == 0 { + if ffi::lua_getmetatable(ref_thread, self.0.index) == 0 { None } else { - Some(Table(lua.pop_ref())) + Some(Table(lua.pop_ref_thread())) } } } From 646827a6bb2aa218d3b6c1f2613f14fff8c3185a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 6 Jul 2025 11:37:02 +0100 Subject: [PATCH 464/635] Update `Table::set_metatable` - Return Err (instead of panic) when trying to change readonly table (Luau) - Slightly optimize performance --- mlua_derive/src/lib.rs | 6 +++--- src/serde/ser.rs | 2 +- src/table.rs | 20 ++++++++------------ tests/async.rs | 2 +- tests/luau.rs | 9 ++++----- tests/serde.rs | 4 ++-- tests/table.rs | 4 ++-- 7 files changed, 21 insertions(+), 26 deletions(-) diff --git a/mlua_derive/src/lib.rs b/mlua_derive/src/lib.rs index eca34741..f7d04803 100644 --- a/mlua_derive/src/lib.rs +++ b/mlua_derive/src/lib.rs @@ -129,13 +129,13 @@ pub fn chunk(input: TokenStream) -> TokenStream { let globals = lua.globals(); let env = lua.create_table()?; let meta = lua.create_table()?; - meta.raw_set("__index", globals.clone())?; - meta.raw_set("__newindex", globals)?; + meta.raw_set("__index", &globals)?; + meta.raw_set("__newindex", &globals)?; // Add captured variables #(#caps)* - env.set_metatable(Some(meta)); + env.set_metatable(Some(meta))?; Ok(env) }; diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 1d717a79..7b40dfc3 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -256,7 +256,7 @@ impl<'a> ser::Serializer for Serializer<'a> { fn serialize_seq(self, len: Option) -> Result { let table = self.lua.create_table_with_capacity(len.unwrap_or(0), 0)?; if self.options.set_array_metatable { - table.set_metatable(Some(self.lua.array_metatable())); + table.set_metatable(Some(self.lua.array_metatable()))?; } Ok(SerializeSeq::new(self.lua, table, self.options)) } diff --git a/src/table.rs b/src/table.rs index c4fd3ca6..1147b34e 100644 --- a/src/table.rs +++ b/src/table.rs @@ -211,7 +211,7 @@ impl Table { /// /// let always_equals_mt = lua.create_table()?; /// always_equals_mt.set("__eq", lua.create_function(|_, (_t1, _t2): (Table, Table)| Ok(true))?)?; - /// table2.set_metatable(Some(always_equals_mt)); + /// table2.set_metatable(Some(always_equals_mt))?; /// /// assert!(table1.equals(&table1.clone())?); /// assert!(table1.equals(&table2)?); @@ -501,27 +501,23 @@ impl Table { /// /// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does /// nothing). - pub fn set_metatable(&self, metatable: Option
) { - // Workaround to throw readonly error without returning Result + pub fn set_metatable(&self, metatable: Option
) -> Result<()> { #[cfg(feature = "luau")] if self.is_readonly() { - panic!("attempt to modify a readonly table"); + return Err(Error::runtime("attempt to modify a readonly table")); } let lua = self.0.lua.lock(); - let state = lua.state(); + let ref_thread = lua.ref_thread(); unsafe { - let _sg = StackGuard::new(state); - assert_stack(state, 2); - - lua.push_ref(&self.0); if let Some(metatable) = metatable { - lua.push_ref(&metatable.0); + ffi::lua_pushvalue(ref_thread, metatable.0.index); } else { - ffi::lua_pushnil(state); + ffi::lua_pushnil(ref_thread); } - ffi::lua_setmetatable(state, -2); + ffi::lua_setmetatable(ref_thread, self.0.index); } + Ok(()) } /// Returns true if the table has metatable attached. diff --git a/tests/async.rs b/tests/async.rs index 725a1dbb..4a8a5b54 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -386,7 +386,7 @@ async fn test_async_table_object_like() -> Result<()> { table.get::("val") })?, )?; - table.set_metatable(Some(metatable)); + table.set_metatable(Some(metatable))?; assert_eq!(table.call_async::(()).await.unwrap(), 15); match table.call_async_method::<()>("non_existent", ()).await { diff --git a/tests/luau.rs b/tests/luau.rs index 48fed78c..4c4d7aea 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -3,7 +3,6 @@ use std::cell::Cell; use std::fmt::Debug; use std::os::raw::c_void; -use std::panic::{catch_unwind, AssertUnwindSafe}; use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering}; use std::sync::Arc; @@ -120,7 +119,7 @@ fn test_vector_metatable() -> Result<()> { "#, ) .eval::
()?; - vector_mt.set_metatable(Some(vector_mt.clone())); + vector_mt.set_metatable(Some(vector_mt.clone()))?; lua.set_type_metatable::(Some(vector_mt.clone())); lua.globals().set("Vector3", vector_mt)?; @@ -167,9 +166,9 @@ fn test_readonly_table() -> Result<()> { check_readonly_error(t.raw_pop::()); // Special case - match catch_unwind(AssertUnwindSafe(|| t.set_metatable(None))) { - Ok(_) => panic!("expected panic, got nothing"), - Err(_) => {} + match t.set_metatable(None) { + Err(Error::RuntimeError(e)) if e.contains("attempt to modify a readonly table") => {} + r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"), } Ok(()) diff --git a/tests/serde.rs b/tests/serde.rs index 8f104eaf..f3c965e2 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -25,7 +25,7 @@ fn test_serialize() -> Result<(), Box> { globals.set("null", lua.null())?; let empty_array = lua.create_table()?; - empty_array.set_metatable(Some(lua.array_metatable())); + empty_array.set_metatable(Some(lua.array_metatable()))?; globals.set("empty_array", empty_array)?; let val = lua @@ -173,7 +173,7 @@ fn test_serialize_sorted() -> LuaResult<()> { globals.set("null", lua.null())?; let empty_array = lua.create_table()?; - empty_array.set_metatable(Some(lua.array_metatable())); + empty_array.set_metatable(Some(lua.array_metatable()))?; globals.set("empty_array", empty_array)?; let value = lua diff --git a/tests/table.rs b/tests/table.rs index d76f3a5f..3d4f5efa 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -298,10 +298,10 @@ fn test_metatable() -> Result<()> { let table = lua.create_table()?; let metatable = lua.create_table()?; metatable.set("__index", lua.create_function(|_, ()| Ok("index_value"))?)?; - table.set_metatable(Some(metatable)); + table.set_metatable(Some(metatable))?; assert_eq!(table.get::("any_key")?, "index_value"); assert_eq!(table.raw_get::("any_key")?, Value::Nil); - table.set_metatable(None); + table.set_metatable(None)?; assert_eq!(table.get::("any_key")?, Value::Nil); Ok(()) From 72f6536efb4e56de6c201f84f770c6c34df985cc Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 7 Jul 2025 22:27:53 +0100 Subject: [PATCH 465/635] Check table requested capacity limits before enabling unprotected mode. Lua tables have limits and can overflow, which must be captured in protected mode. --- src/util/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/mod.rs b/src/util/mod.rs index f5fbae52..b69a8675 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -88,7 +88,7 @@ impl Drop for StackGuard { #[inline(always)] pub(crate) unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: bool) -> Result<()> { // Always use protected mode if the string is too long - if protect || s.len() > (1 << 30) { + if protect || s.len() >= const { 1 << 30 } { protect_lua!(state, 0, 1, |state| { ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len()); }) @@ -122,7 +122,7 @@ pub(crate) unsafe fn push_table( ) -> Result<()> { let narr: c_int = narr.try_into().unwrap_or(c_int::MAX); let nrec: c_int = nrec.try_into().unwrap_or(c_int::MAX); - if protect { + if protect || narr >= const { 1 << 30 } || nrec >= const { 1 << 27 } { protect_lua!(state, 0, 1, |state| ffi::lua_createtable(state, narr, nrec)) } else { ffi::lua_createtable(state, narr, nrec); From d3b2999d2f749bb6ebf0217bfb606e0fb05b7ff8 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 7 Jul 2025 22:37:29 +0100 Subject: [PATCH 466/635] Remove `MaybeSend` requirement from `Require` trait and add to `Lua::create_require_function` instead --- src/luau/mod.rs | 3 ++- src/luau/require.rs | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/luau/mod.rs b/src/luau/mod.rs index c82b5323..042e751a 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -7,6 +7,7 @@ use crate::error::Result; use crate::function::Function; use crate::state::{callback_error_ext, ExtraData, Lua}; use crate::traits::{FromLuaMulti, IntoLua}; +use crate::types::MaybeSend; pub use require::{NavigateError, Require, TextRequirer}; @@ -17,7 +18,7 @@ impl Lua { /// and load modules. #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - pub fn create_require_function(&self, require: R) -> Result { + pub fn create_require_function(&self, require: R) -> Result { require::create_require_function(self, require) } diff --git a/src/luau/require.rs b/src/luau/require.rs index 13d09f02..82451f99 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -53,7 +53,7 @@ type WriteResult = ffi::luarequire_WriteResult; /// A trait for handling modules loading and navigation in the Luau `require-by-string` system. #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] -pub trait Require: MaybeSend { +pub trait Require { /// Returns `true` if "require" is permitted for the given chunk name. fn is_require_allowed(&self, chunk_name: &str) -> bool; @@ -517,7 +517,10 @@ unsafe fn write_to_buffer( } #[cfg(feature = "luau")] -pub(super) fn create_require_function(lua: &Lua, require: R) -> Result { +pub(super) fn create_require_function( + lua: &Lua, + require: R, +) -> Result { unsafe extern "C-unwind" fn find_current_file(state: *mut ffi::lua_State) -> c_int { let mut ar: ffi::lua_Debug = mem::zeroed(); for level in 2.. { From 1ec4661bf970046698d4e7015a2ead8fb627ce12 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 7 Jul 2025 22:47:40 +0100 Subject: [PATCH 467/635] mlua_derive: v0.11.0 --- Cargo.toml | 2 +- mlua_derive/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d24d006f..5631efa9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,7 @@ userdata-wrappers = ["parking_lot/send_guard"] serialize = ["serde"] [dependencies] -mlua_derive = { version = "=0.11.0-beta.2", optional = true, path = "mlua_derive" } +mlua_derive = { version = "=0.11.0", optional = true, path = "mlua_derive" } bstr = { version = "1.0", features = ["std"], default-features = false } either = "1.0" num-traits = { version = "0.2.14" } diff --git a/mlua_derive/Cargo.toml b/mlua_derive/Cargo.toml index fac6e00f..74d3c1ad 100644 --- a/mlua_derive/Cargo.toml +++ b/mlua_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua_derive" -version = "0.11.0-beta.2" +version = "0.11.0" authors = ["Aleksandr Orlenko "] edition = "2021" description = "Procedural macros for the mlua crate." From d011a1f851f78d78839d31d8dadad08a61422029 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 7 Jul 2025 23:15:46 +0100 Subject: [PATCH 468/635] Update CHANGELOG --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 405045eb..539074f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## v0.11.0 (Jul 8, 2025) + +Changes since v0.11.0-beta.3 +- `Lua::inspect_stack` takes a callback passing `&Debug` arguments, instead of returning `Debug` directly +- Added `Debug::function` method to get function running at a given level +- Added `Lua::set_globals` method to replace global environment +- `Table::set_metatable` now returns `Result<()>` (this operation can fail in sandboxed Luau mode) +- `impl ToString` replaced with `Into` in `UserData` registration +- `Value::as_str` and `Value::as_string_lossy` methods are deprecated (as they are non-idiomatic) +- Bugfixes and improvements + ## v0.11.0-beta.3 (Jun 23, 2025) - Luau in sandboxed mode has reduced options in `collectgarbage` function (to follow the official doc) From cf05593d66f48d0600118e119bcaef96a9d4910a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 8 Jul 2025 09:54:52 +0100 Subject: [PATCH 469/635] Fix `Debug::is_tail_call` --- src/debug.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/debug.rs b/src/debug.rs index 023296a4..ed57f1a0 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -153,15 +153,18 @@ impl<'a> Debug<'a> { /// Corresponds to the `t` "what" mask. Returns true if the hook is in a function tail call, /// false otherwise. - #[cfg(not(feature = "luau"))] - #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg_attr( + docsrs, + doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))) + )] pub fn is_tail_call(&self) -> bool { unsafe { mlua_assert!( ffi::lua_getinfo(self.state, cstr!("t"), self.ar) != 0, "lua_getinfo failed with `t`" ); - (*self.ar).currentline != 0 + (*self.ar).istailcall != 0 } } From ca22ea3be70808f1724d4ac5594b4c78cfd1e473 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 8 Jul 2025 10:04:32 +0100 Subject: [PATCH 470/635] Deprecate `Debug::curr_line()` in favour of `Debug::current_line()` that returns `Option` --- src/debug.rs | 10 ++++++++-- src/state.rs | 2 +- tests/hooks.rs | 4 ++-- tests/tests.rs | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/debug.rs b/src/debug.rs index ed57f1a0..ace8f0e7 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -133,8 +133,14 @@ impl<'a> Debug<'a> { } } - /// Corresponds to the `l` "what" mask. Returns the current line. + #[doc(hidden)] + #[deprecated(note = "Use `current_line` instead")] pub fn curr_line(&self) -> i32 { + self.current_line().map(|n| n as i32).unwrap_or(-1) + } + + /// Corresponds to the `l` "what" mask. Returns the current line. + pub fn current_line(&self) -> Option { unsafe { #[cfg(not(feature = "luau"))] mlua_assert!( @@ -147,7 +153,7 @@ impl<'a> Debug<'a> { "lua_getinfo failed with `l`" ); - (*self.ar).currentline + linenumber_to_usize((*self.ar).currentline) } } diff --git a/src/state.rs b/src/state.rs index 10701125..13bed6ce 100644 --- a/src/state.rs +++ b/src/state.rs @@ -577,7 +577,7 @@ impl Lua { /// # fn main() -> Result<()> { /// let lua = Lua::new(); /// lua.set_hook(HookTriggers::EVERY_LINE, |_lua, debug| { - /// println!("line {}", debug.curr_line()); + /// println!("line {:?}", debug.current_line()); /// Ok(VmState::Continue) /// }); /// diff --git a/tests/hooks.rs b/tests/hooks.rs index f0094b29..4f635052 100644 --- a/tests/hooks.rs +++ b/tests/hooks.rs @@ -24,7 +24,7 @@ fn test_line_counts() -> Result<()> { let lua = Lua::new(); lua.set_hook(HookTriggers::EVERY_LINE, move |_lua, debug| { assert_eq!(debug.event(), DebugEvent::Line); - hook_output.lock().unwrap().push(debug.curr_line()); + hook_output.lock().unwrap().push(debug.current_line().unwrap()); Ok(VmState::Continue) })?; lua.load( @@ -240,7 +240,7 @@ fn test_hook_threads() -> Result<()> { let hook_output = output.clone(); co.set_hook(HookTriggers::EVERY_LINE, move |_lua, debug| { assert_eq!(debug.event(), DebugEvent::Line); - hook_output.lock().unwrap().push(debug.curr_line()); + hook_output.lock().unwrap().push(debug.current_line().unwrap()); Ok(VmState::Continue) })?; diff --git a/tests/tests.rs b/tests/tests.rs index 25dfb494..94d682d6 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1283,7 +1283,7 @@ fn test_inspect_stack() -> Result<()> { .inspect_stack(1, |debug| { let source = debug.source().short_src; let source = source.as_deref().unwrap_or("?"); - let line = debug.curr_line(); + let line = debug.current_line().unwrap(); format!("{}:{} {}", source, line, msg) }) .unwrap(); From d8455c038ae711a63d336b797da53359d9dc0832 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 8 Jul 2025 10:12:03 +0100 Subject: [PATCH 471/635] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 539074f4..91dde2cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Changes since v0.11.0-beta.3 - `Lua::inspect_stack` takes a callback passing `&Debug` arguments, instead of returning `Debug` directly - Added `Debug::function` method to get function running at a given level +- `Debug::curr_line` is deprecated in favour of `Debug::current_line` that returns `Option` - Added `Lua::set_globals` method to replace global environment - `Table::set_metatable` now returns `Result<()>` (this operation can fail in sandboxed Luau mode) - `impl ToString` replaced with `Into` in `UserData` registration From 04aaa18dc89a04789fe8bf819014ecd55b8f5a83 Mon Sep 17 00:00:00 2001 From: Sculas Date: Tue, 8 Jul 2025 11:30:26 +0200 Subject: [PATCH 472/635] feat: Allow external build scripts to link Lua libraries (#529) Allow external build scripts to link Lua libraries --- mlua-sys/Cargo.toml | 1 + mlua-sys/build/main_inner.rs | 31 +++++++++++++++++++------------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 7fbf0076..4cb6ddd5 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -30,6 +30,7 @@ luau = ["luau0-src"] luau-codegen = ["luau"] luau-vector4 = ["luau"] vendored = ["lua-src", "luajit-src"] +external = [] module = [] [dependencies] diff --git a/mlua-sys/build/main_inner.rs b/mlua-sys/build/main_inner.rs index 05ac53b5..dc97f38d 100644 --- a/mlua-sys/build/main_inner.rs +++ b/mlua-sys/build/main_inner.rs @@ -14,22 +14,29 @@ fn main() { #[cfg(all(feature = "luau", feature = "module", windows))] compile_error!("Luau does not support `module` mode on Windows"); - #[cfg(all(feature = "module", feature = "vendored"))] - compile_error!("`vendored` and `module` features are mutually exclusive"); + #[cfg(any( + all(feature = "vendored", any(feature = "external", feature = "module")), + all(feature = "external", any(feature = "vendored", feature = "module")), + all(feature = "module", any(feature = "vendored", feature = "external")) + ))] + compile_error!("`vendored`, `external` and `module` features are mutually exclusive"); println!("cargo:rerun-if-changed=build"); - let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); - if target_os == "windows" && cfg!(feature = "module") { - if !std::env::var("LUA_LIB_NAME").unwrap_or_default().is_empty() { - // Don't use raw-dylib linking - find::probe_lua(); - return; + // Check if compilation and linking is handled by external crate + if !cfg!(feature = "external") { + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); + if target_os == "windows" && cfg!(feature = "module") { + if !std::env::var("LUA_LIB_NAME").unwrap_or_default().is_empty() { + // Don't use raw-dylib linking + find::probe_lua(); + return; + } + + println!("cargo:rustc-cfg=raw_dylib"); } - println!("cargo:rustc-cfg=raw_dylib"); + #[cfg(not(feature = "module"))] + find::probe_lua(); } - - #[cfg(not(feature = "module"))] - find::probe_lua(); } From dea38f27a526bee4dc92fefd7fe235d65c769f98 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 8 Jul 2025 10:42:15 +0100 Subject: [PATCH 473/635] Change `!cfg!(..)` to `cfg!(not(..))` for better readability --- mlua-sys/build/main_inner.rs | 2 +- src/userdata/ref.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mlua-sys/build/main_inner.rs b/mlua-sys/build/main_inner.rs index dc97f38d..5d6c04ed 100644 --- a/mlua-sys/build/main_inner.rs +++ b/mlua-sys/build/main_inner.rs @@ -24,7 +24,7 @@ fn main() { println!("cargo:rerun-if-changed=build"); // Check if compilation and linking is handled by external crate - if !cfg!(feature = "external") { + if cfg!(not(feature = "external")) { let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); if target_os == "windows" && cfg!(feature = "module") { if !std::env::var("LUA_LIB_NAME").unwrap_or_default().is_empty() { diff --git a/src/userdata/ref.rs b/src/userdata/ref.rs index 750443a9..dde89c2b 100644 --- a/src/userdata/ref.rs +++ b/src/userdata/ref.rs @@ -63,7 +63,7 @@ impl TryFrom> for UserDataRef { #[inline] fn try_from(variant: UserDataVariant) -> Result { - let guard = if !cfg!(feature = "send") || is_sync::() { + let guard = if cfg!(not(feature = "send")) || is_sync::() { variant.raw_lock().try_lock_shared_guarded() } else { variant.raw_lock().try_lock_exclusive_guarded() From 61a2141151fa959a83bfa3d6971228d12bdb8c39 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 8 Jul 2025 13:33:44 +0100 Subject: [PATCH 474/635] Don't panic when fourth library searcher does not exists. When disabling C modules, we remove the last two searchers (C & C all-in-one). In Pluto the C searches may not exist by design, in this case check that 4th searcher is present before removing it. Closes #530 --- src/state.rs | 5 +++-- src/state/raw.rs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/state.rs b/src/state.rs index 13bed6ce..4d762571 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2062,7 +2062,6 @@ impl Lua { WeakLua(XRc::downgrade(&self.raw)) } - // Luau version located in `luau/mod.rs` #[cfg(not(feature = "luau"))] fn disable_c_modules(&self) -> Result<()> { let package: Table = self.globals().get("package")?; @@ -2085,7 +2084,9 @@ impl Lua { // The third and fourth searchers looks for a loader as a C library searchers.raw_set(3, loader)?; - searchers.raw_remove(4)?; + if searchers.raw_len() >= 4 { + searchers.raw_remove(4)?; + } Ok(()) } diff --git a/src/state/raw.rs b/src/state/raw.rs index a91517e7..74445ef8 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -296,7 +296,7 @@ impl RawLua { if is_safe { let curr_libs = (*self.extra.get()).libs; if (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) { - mlua_expect!(self.lua().disable_c_modules(), "Error during disabling C modules"); + mlua_expect!(self.lua().disable_c_modules(), "Error disabling C modules"); } } #[cfg(feature = "luau")] From b1f73ec29d8fbd1dfd587d8a2f63d81e8bee8966 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 8 Jul 2025 22:09:24 +0100 Subject: [PATCH 475/635] Update Luau `Compiler` methods to better control extra options: - Add `add_mutable_global` - Add `add_userdata_type` - Replace `set_library_constants` with `add_library_constant` - Add `add_disabled_builtin` --- src/chunk.rs | 91 ++++++++++++++++++++++++++++++++++++++------------ tests/chunk.rs | 18 +++++----- 2 files changed, 77 insertions(+), 32 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index 3de61648..216ee1c9 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -163,15 +163,36 @@ pub enum CompileConstant { String(String), } -#[cfg(feature = "luau")] -impl From<&'static str> for CompileConstant { - fn from(s: &'static str) -> Self { - CompileConstant::String(s.to_string()) +#[cfg(any(feature = "luau", doc))] +impl From for CompileConstant { + fn from(b: bool) -> Self { + CompileConstant::Boolean(b) + } +} + +#[cfg(any(feature = "luau", doc))] +impl From for CompileConstant { + fn from(n: crate::Number) -> Self { + CompileConstant::Number(n) } } #[cfg(any(feature = "luau", doc))] -type LibraryMemberConstantMap = std::sync::Arc>; +impl From for CompileConstant { + fn from(v: crate::Vector) -> Self { + CompileConstant::Vector(v) + } +} + +#[cfg(any(feature = "luau", doc))] +impl From<&str> for CompileConstant { + fn from(s: &str) -> Self { + CompileConstant::String(s.to_owned()) + } +} + +#[cfg(any(feature = "luau", doc))] +type LibraryMemberConstantMap = HashMap<(String, String), CompileConstant>; /// Luau compiler #[cfg(any(feature = "luau", doc))] @@ -288,23 +309,39 @@ impl Compiler { self } + /// Adds a mutable global. + /// + /// It disables the import optimization for fields accessed through it. + #[must_use] + pub fn add_mutable_global(mut self, global: impl Into) -> Self { + self.mutable_globals.push(global.into()); + self + } + /// Sets a list of globals that are mutable. /// /// It disables the import optimization for fields accessed through these. #[must_use] - pub fn set_mutable_globals>(mut self, globals: Vec) -> Self { + pub fn set_mutable_globals>(mut self, globals: impl IntoIterator) -> Self { self.mutable_globals = globals.into_iter().map(|s| s.into()).collect(); self } + /// Adds a userdata type to the list that will be included in the type information. + #[must_use] + pub fn add_userdata_type(mut self, r#type: impl Into) -> Self { + self.userdata_types.push(r#type.into()); + self + } + /// Sets a list of userdata types that will be included in the type information. #[must_use] - pub fn set_userdata_types>(mut self, types: Vec) -> Self { + pub fn set_userdata_types>(mut self, types: impl IntoIterator) -> Self { self.userdata_types = types.into_iter().map(|s| s.into()).collect(); self } - /// Sets constants for known library members. + /// Adds a constant for a known library member. /// /// The constants are used by the compiler to optimize the generated bytecode. /// Optimization level must be at least 2 for this to have any effect. @@ -312,25 +349,35 @@ impl Compiler { /// The first element of the tuple is the library name,the second is the member name, and the /// third is the constant value. #[must_use] - pub fn set_library_constants(mut self, constants: Vec<(L, M, CompileConstant)>) -> Self - where - L: Into, - M: Into, - { - let map = constants - .into_iter() - .map(|(lib, member, cons)| ((lib.into(), member.into()), cons)) - .collect::>(); - self.library_constants = Some(std::sync::Arc::new(map)); - self.libraries_with_known_members = (self.library_constants.clone()) - .map(|map| map.keys().map(|(lib, _)| lib.clone()).collect()) - .unwrap_or_default(); + pub fn add_library_constant( + mut self, + lib: impl Into, + member: impl Into, + r#const: impl Into, + ) -> Self { + let (lib, member) = (lib.into(), member.into()); + if !self.libraries_with_known_members.contains(&lib) { + self.libraries_with_known_members.push(lib.clone()); + } + self.library_constants + .get_or_insert_with(HashMap::new) + .insert((lib, member), r#const.into()); + self + } + + /// Adds a builtin that should be disabled. + #[must_use] + pub fn add_disabled_builtin(mut self, builtin: impl Into) -> Self { + self.disabled_builtins.push(builtin.into()); self } /// Sets a list of builtins that should be disabled. #[must_use] - pub fn set_disabled_builtins>(mut self, builtins: Vec) -> Self { + pub fn set_disabled_builtins>( + mut self, + builtins: impl IntoIterator, + ) -> Self { self.disabled_builtins = builtins.into_iter().map(|s| s.into()).collect(); self } diff --git a/tests/chunk.rs b/tests/chunk.rs index 45ad95ec..39b18b70 100644 --- a/tests/chunk.rs +++ b/tests/chunk.rs @@ -122,9 +122,9 @@ fn test_compiler() -> Result<()> { .set_vector_lib("vector") .set_vector_ctor("new") .set_vector_type("vector") - .set_mutable_globals(vec!["mutable_global"]) - .set_userdata_types(vec!["MyUserdata"]) - .set_disabled_builtins(vec!["tostring"]); + .set_mutable_globals(["mutable_global"]) + .set_userdata_types(["MyUserdata"]) + .set_disabled_builtins(["tostring"]); assert!(compiler.compile("return tostring(vector.new(1, 2, 3))").is_ok()); @@ -142,16 +142,14 @@ fn test_compiler() -> Result<()> { #[cfg(feature = "luau")] #[test] fn test_compiler_library_constants() { - use mlua::{CompileConstant, Compiler, Vector}; + use mlua::{Compiler, Vector}; let compiler = Compiler::new() .set_optimization_level(2) - .set_library_constants(vec![ - ("mylib", "const_bool", CompileConstant::Boolean(true)), - ("mylib", "const_num", CompileConstant::Number(123.0)), - ("mylib", "const_vec", CompileConstant::Vector(Vector::zero())), - ("mylib", "const_str", "value1".into()), - ]); + .add_library_constant("mylib", "const_bool", true) + .add_library_constant("mylib", "const_num", 123.0) + .add_library_constant("mylib", "const_vec", Vector::zero()) + .add_library_constant("mylib", "const_str", "value1"); let lua = Lua::new(); lua.set_compiler(compiler); From a9a4814c3c6faf22c09572800bf0849ba53a1123 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 8 Jul 2025 22:25:23 +0100 Subject: [PATCH 476/635] Use StdString for consistency in `chunk.rs` --- src/chunk.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index 216ee1c9..1cfbc14b 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -160,7 +160,7 @@ pub enum CompileConstant { Boolean(bool), Number(crate::Number), Vector(crate::Vector), - String(String), + String(StdString), } #[cfg(any(feature = "luau", doc))] @@ -192,7 +192,7 @@ impl From<&str> for CompileConstant { } #[cfg(any(feature = "luau", doc))] -type LibraryMemberConstantMap = HashMap<(String, String), CompileConstant>; +type LibraryMemberConstantMap = HashMap<(StdString, StdString), CompileConstant>; /// Luau compiler #[cfg(any(feature = "luau", doc))] @@ -203,14 +203,14 @@ pub struct Compiler { debug_level: u8, type_info_level: u8, coverage_level: u8, - vector_lib: Option, - vector_ctor: Option, - vector_type: Option, - mutable_globals: Vec, - userdata_types: Vec, - libraries_with_known_members: Vec, + vector_lib: Option, + vector_ctor: Option, + vector_type: Option, + mutable_globals: Vec, + userdata_types: Vec, + libraries_with_known_members: Vec, library_constants: Option, - disabled_builtins: Vec, + disabled_builtins: Vec, } #[cfg(any(feature = "luau", doc))] @@ -290,21 +290,21 @@ impl Compiler { #[doc(hidden)] #[must_use] - pub fn set_vector_lib(mut self, lib: impl Into) -> Self { + pub fn set_vector_lib(mut self, lib: impl Into) -> Self { self.vector_lib = Some(lib.into()); self } #[doc(hidden)] #[must_use] - pub fn set_vector_ctor(mut self, ctor: impl Into) -> Self { + pub fn set_vector_ctor(mut self, ctor: impl Into) -> Self { self.vector_ctor = Some(ctor.into()); self } #[doc(hidden)] #[must_use] - pub fn set_vector_type(mut self, r#type: impl Into) -> Self { + pub fn set_vector_type(mut self, r#type: impl Into) -> Self { self.vector_type = Some(r#type.into()); self } @@ -484,7 +484,7 @@ impl Compiler { if bytecode.first() == Some(&0) { // The rest of the bytecode is the error message starting with `:` // See https://github.com/luau-lang/luau/blob/0.640/Compiler/src/Compiler.cpp#L4336 - let message = String::from_utf8_lossy(&bytecode[2..]).to_string(); + let message = StdString::from_utf8_lossy(&bytecode[2..]).into_owned(); return Err(Error::SyntaxError { incomplete_input: message.ends_with(""), message, @@ -507,7 +507,7 @@ impl Chunk<'_> { /// - `@` - file path (when truncation is needed, the end of the file path is kept, as this is /// more useful for identifying the file) /// - `=` - custom chunk name (when truncation is needed, the beginning of the name is kept) - pub fn set_name(mut self, name: impl Into) -> Self { + pub fn set_name(mut self, name: impl Into) -> Self { self.name = name.into(); self } @@ -755,7 +755,7 @@ impl Chunk<'_> { ChunkMode::Text } - fn convert_name(name: String) -> Result { + fn convert_name(name: StdString) -> Result { CString::new(name).map_err(|err| Error::runtime(format!("invalid name: {err}"))) } From 4cfe0be9451efb935456e9ee881e6c11afe4a8c7 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 8 Jul 2025 22:44:26 +0100 Subject: [PATCH 477/635] Update CHANGELOG --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91dde2cc..292cd501 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ -## v0.11.0 (Jul 8, 2025) +## v0.11.0 (Jul 9, 2025) Changes since v0.11.0-beta.3 -- `Lua::inspect_stack` takes a callback passing `&Debug` arguments, instead of returning `Debug` directly + +- Allow linking external Lua libraries in a build script (e.g. pluto) using `external` mlua-sys feature flag +- `Lua::inspect_stack` takes a callback with `&Debug` argument, instead of returning `Debug` directly - Added `Debug::function` method to get function running at a given level - `Debug::curr_line` is deprecated in favour of `Debug::current_line` that returns `Option` - Added `Lua::set_globals` method to replace global environment From 2b6b0144a1853fce3f589c8f4b6f6bd1fd54c44d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 8 Jul 2025 23:54:27 +0100 Subject: [PATCH 478/635] Merge `Compiler::set_vector_lib` into `set_vector_ctor` --- src/chunk.rs | 19 +++++++++++-------- tests/chunk.rs | 3 +-- tests/luau.rs | 4 +++- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index 1cfbc14b..c06b397c 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -288,20 +288,23 @@ impl Compiler { self } - #[doc(hidden)] - #[must_use] - pub fn set_vector_lib(mut self, lib: impl Into) -> Self { - self.vector_lib = Some(lib.into()); - self - } - + /// Sets alternative global builtin to construct vectors, in addition to default builtin + /// `vector.create`. + /// + /// To set the library and method name, use the `lib.ctor` format. #[doc(hidden)] #[must_use] pub fn set_vector_ctor(mut self, ctor: impl Into) -> Self { - self.vector_ctor = Some(ctor.into()); + let ctor = ctor.into(); + let lib_ctor = ctor.split_once('.'); + self.vector_lib = lib_ctor.as_ref().map(|&(lib, _)| lib.to_owned()); + self.vector_ctor = (lib_ctor.as_ref()) + .map(|&(_, ctor)| ctor.to_owned()) + .or(Some(ctor)); self } + /// Sets alternative vector type name for type tables, in addition to default type `vector`. #[doc(hidden)] #[must_use] pub fn set_vector_type(mut self, r#type: impl Into) -> Self { diff --git a/tests/chunk.rs b/tests/chunk.rs index 39b18b70..d8df6d1d 100644 --- a/tests/chunk.rs +++ b/tests/chunk.rs @@ -119,8 +119,7 @@ fn test_compiler() -> Result<()> { .set_debug_level(2) .set_type_info_level(1) .set_coverage_level(2) - .set_vector_lib("vector") - .set_vector_ctor("new") + .set_vector_ctor("vector.new") .set_vector_type("vector") .set_mutable_globals(["mutable_global"]) .set_userdata_types(["MyUserdata"]) diff --git a/tests/luau.rs b/tests/luau.rs index 4c4d7aea..f073f068 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -123,7 +123,9 @@ fn test_vector_metatable() -> Result<()> { lua.set_type_metatable::(Some(vector_mt.clone())); lua.globals().set("Vector3", vector_mt)?; - let compiler = Compiler::new().set_vector_lib("Vector3").set_vector_ctor("new"); + let compiler = Compiler::new() + .set_vector_ctor("Vector3.new") + .set_vector_type("Vector3"); // Test vector methods (fastcall) lua.load( From a653d0876862469c140616e63b8503888261ee41 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 9 Jul 2025 00:11:46 +0100 Subject: [PATCH 479/635] Simplify `Compiler::add_library_constant` (combine `lib` and `member`) --- src/chunk.rs | 13 ++++++++----- tests/chunk.rs | 8 ++++---- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index c06b397c..5dd92640 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -349,16 +349,19 @@ impl Compiler { /// The constants are used by the compiler to optimize the generated bytecode. /// Optimization level must be at least 2 for this to have any effect. /// - /// The first element of the tuple is the library name,the second is the member name, and the - /// third is the constant value. + /// The `name` is a string in the format `lib.member`, where `lib` is the library name + /// and `member` is the member (constant) name. #[must_use] pub fn add_library_constant( mut self, - lib: impl Into, - member: impl Into, + name: impl AsRef, r#const: impl Into, ) -> Self { - let (lib, member) = (lib.into(), member.into()); + let Some((lib, member)) = name.as_ref().split_once('.') else { + return self; + }; + let (lib, member) = (lib.to_owned(), member.to_owned()); + if !self.libraries_with_known_members.contains(&lib) { self.libraries_with_known_members.push(lib.clone()); } diff --git a/tests/chunk.rs b/tests/chunk.rs index d8df6d1d..ffd7ac0c 100644 --- a/tests/chunk.rs +++ b/tests/chunk.rs @@ -145,10 +145,10 @@ fn test_compiler_library_constants() { let compiler = Compiler::new() .set_optimization_level(2) - .add_library_constant("mylib", "const_bool", true) - .add_library_constant("mylib", "const_num", 123.0) - .add_library_constant("mylib", "const_vec", Vector::zero()) - .add_library_constant("mylib", "const_str", "value1"); + .add_library_constant("mylib.const_bool", true) + .add_library_constant("mylib.const_num", 123.0) + .add_library_constant("mylib.const_vec", Vector::zero()) + .add_library_constant("mylib.const_str", "value1"); let lua = Lua::new(); lua.set_compiler(compiler); From 1ddaea60ce15d36f25867284c342fed7c45c42d5 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 12 Jul 2025 11:27:41 +0100 Subject: [PATCH 480/635] Bump luau-src to 0.15.4+luau682 --- mlua-sys/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 4cb6ddd5..de16a990 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -41,7 +41,7 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 548.1.0, < 548.2.0", optional = true } luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true } -luau0-src = { version = "0.15.0", optional = true } +luau0-src = { version = "0.15.4", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } From 06c3bd9d6972ae6a79e02f5ed82ad746e8c58c9b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 12 Jul 2025 11:50:11 +0100 Subject: [PATCH 481/635] Fix serde README section (close #613) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ec41d3d4..c417b3c1 100644 --- a/README.md +++ b/README.md @@ -100,11 +100,11 @@ cargo run --example async_http_server --features=lua54,async,macros,send curl -v http://localhost:3000 ``` -### Serialization (serde) support +### Serde support -With the `serde` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition, `mlua` provides the [`serde::Serialize`] trait implementation for it (including `UserData` support). +With the `serde` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition, `mlua` provides the [`serde::Serialize`] trait implementation for `mlua::Value` (including `UserData` support). -[Example](examples/serialize.rs) +[Example](examples/serde.rs) [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html From 7afbf74128483ddac05376b8d763e48be544c162 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 12 Jul 2025 12:45:36 +0100 Subject: [PATCH 482/635] Add `MaybeSend` bound to async methods on `ObjectLike` trait (sealed) --- src/table.rs | 25 ++++++++++++++++++------- src/traits.rs | 20 ++++++++++++++------ src/userdata/object.rs | 25 ++++++++++++++++++------- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/src/table.rs b/src/table.rs index 1147b34e..ea3260a6 100644 --- a/src/table.rs +++ b/src/table.rs @@ -13,7 +13,10 @@ use crate::util::{assert_stack, check_stack, get_metatable_ptr, StackGuard}; use crate::value::{Nil, Value}; #[cfg(feature = "async")] -use futures_util::future::{self, Either, Future}; +use { + crate::types::MaybeSend, + futures_util::future::{self, Either, Future}, +}; #[cfg(feature = "serde")] use { @@ -889,9 +892,9 @@ impl ObjectLike for Table { #[cfg(feature = "async")] #[inline] - fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> + fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> + MaybeSend where - R: FromLuaMulti, + R: FromLuaMulti + MaybeSend, { Function(self.0.copy()).call_async(args) } @@ -905,9 +908,13 @@ impl ObjectLike for Table { } #[cfg(feature = "async")] - fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> + fn call_async_method( + &self, + name: &str, + args: impl IntoLuaMulti, + ) -> impl Future> + MaybeSend where - R: FromLuaMulti, + R: FromLuaMulti + MaybeSend, { self.call_async_function(name, (self, args)) } @@ -925,9 +932,13 @@ impl ObjectLike for Table { #[cfg(feature = "async")] #[inline] - fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> + fn call_async_function( + &self, + name: &str, + args: impl IntoLuaMulti, + ) -> impl Future> + MaybeSend where - R: FromLuaMulti, + R: FromLuaMulti + MaybeSend, { match self.get(name) { Ok(Value::Function(func)) => Either::Left(func.call_async(args)), diff --git a/src/traits.rs b/src/traits.rs index 02373e2f..0d11dceb 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -162,9 +162,9 @@ pub trait ObjectLike: Sealed { /// arguments. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> + fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> + MaybeSend where - R: FromLuaMulti; + R: FromLuaMulti + MaybeSend; /// Gets the function associated to key `name` from the object and calls it, /// passing the object itself along with `args` as function arguments. @@ -178,9 +178,13 @@ pub trait ObjectLike: Sealed { /// This might invoke the `__index` metamethod. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> + fn call_async_method( + &self, + name: &str, + args: impl IntoLuaMulti, + ) -> impl Future> + MaybeSend where - R: FromLuaMulti; + R: FromLuaMulti + MaybeSend; /// Gets the function associated to key `name` from the object and calls it, /// passing `args` as function arguments. @@ -196,9 +200,13 @@ pub trait ObjectLike: Sealed { /// This might invoke the `__index` metamethod. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> + fn call_async_function( + &self, + name: &str, + args: impl IntoLuaMulti, + ) -> impl Future> + MaybeSend where - R: FromLuaMulti; + R: FromLuaMulti + MaybeSend; /// Converts the object to a string in a human-readable format. /// diff --git a/src/userdata/object.rs b/src/userdata/object.rs index 6418f1e9..7dd9f718 100644 --- a/src/userdata/object.rs +++ b/src/userdata/object.rs @@ -8,7 +8,10 @@ use crate::value::Value; use crate::Function; #[cfg(feature = "async")] -use futures_util::future::{self, Either, Future}; +use { + crate::types::MaybeSend, + futures_util::future::{self, Either, Future}, +}; impl ObjectLike for AnyUserData { #[inline] @@ -35,9 +38,9 @@ impl ObjectLike for AnyUserData { #[cfg(feature = "async")] #[inline] - fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> + fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> + MaybeSend where - R: FromLuaMulti, + R: FromLuaMulti + MaybeSend, { Function(self.0.copy()).call_async(args) } @@ -51,9 +54,13 @@ impl ObjectLike for AnyUserData { } #[cfg(feature = "async")] - fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> + fn call_async_method( + &self, + name: &str, + args: impl IntoLuaMulti, + ) -> impl Future> + MaybeSend where - R: FromLuaMulti, + R: FromLuaMulti + MaybeSend, { self.call_async_function(name, (self, args)) } @@ -72,9 +79,13 @@ impl ObjectLike for AnyUserData { } #[cfg(feature = "async")] - fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> + fn call_async_function( + &self, + name: &str, + args: impl IntoLuaMulti, + ) -> impl Future> + MaybeSend where - R: FromLuaMulti, + R: FromLuaMulti + MaybeSend, { match self.get(name) { Ok(Value::Function(func)) => Either::Left(func.call_async(args)), From 13dc2b53526eb87cbb891e9756fe6db2970499e2 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 12 Jul 2025 13:08:33 +0100 Subject: [PATCH 483/635] Don't release Lua lock prematurely when when accessing `Buffer` bytes (Luau) --- src/buffer.rs | 29 ++++++++++++++++++----------- src/conversion.rs | 2 +- src/serde/de.rs | 5 ++++- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 181e3696..52beffe3 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,6 +1,7 @@ #[cfg(feature = "serde")] use serde::ser::{Serialize, Serializer}; +use crate::state::RawLua; use crate::types::ValueRef; /// A Luau buffer type. @@ -16,12 +17,14 @@ pub struct Buffer(pub(crate) ValueRef); impl Buffer { /// Copies the buffer data into a new `Vec`. pub fn to_vec(&self) -> Vec { - unsafe { self.as_slice().to_vec() } + let lua = self.0.lua.lock(); + self.as_slice(&lua).to_vec() } /// Returns the length of the buffer. pub fn len(&self) -> usize { - unsafe { self.as_slice().len() } + let lua = self.0.lua.lock(); + self.as_slice(&lua).len() } /// Returns `true` if the buffer is empty. @@ -34,7 +37,8 @@ impl Buffer { /// Offset is 0-based. #[track_caller] pub fn read_bytes(&self, offset: usize) -> [u8; N] { - let data = unsafe { self.as_slice() }; + let lua = self.0.lua.lock(); + let data = self.as_slice(&lua); let mut bytes = [0u8; N]; bytes.copy_from_slice(&data[offset..offset + N]); bytes @@ -45,21 +49,23 @@ impl Buffer { /// Offset is 0-based. #[track_caller] pub fn write_bytes(&self, offset: usize, bytes: &[u8]) { + let lua = self.0.lua.lock(); let data = unsafe { - let (buf, size) = self.as_raw_parts(); + let (buf, size) = self.as_raw_parts(&lua); std::slice::from_raw_parts_mut(buf, size) }; data[offset..offset + bytes.len()].copy_from_slice(bytes); } - pub(crate) unsafe fn as_slice(&self) -> &[u8] { - let (buf, size) = self.as_raw_parts(); - std::slice::from_raw_parts(buf, size) + pub(crate) fn as_slice(&self, lua: &RawLua) -> &[u8] { + unsafe { + let (buf, size) = self.as_raw_parts(lua); + std::slice::from_raw_parts(buf, size) + } } #[cfg(feature = "luau")] - unsafe fn as_raw_parts(&self) -> (*mut u8, usize) { - let lua = self.0.lua.lock(); + unsafe fn as_raw_parts(&self, lua: &RawLua) -> (*mut u8, usize) { let mut size = 0usize; let buf = ffi::lua_tobuffer(lua.ref_thread(), self.0.index, &mut size); mlua_assert!(!buf.is_null(), "invalid Luau buffer"); @@ -67,7 +73,7 @@ impl Buffer { } #[cfg(not(feature = "luau"))] - unsafe fn as_raw_parts(&self) -> (*mut u8, usize) { + unsafe fn as_raw_parts(&self, lua: &RawLua) -> (*mut u8, usize) { unreachable!() } } @@ -75,7 +81,8 @@ impl Buffer { #[cfg(feature = "serde")] impl Serialize for Buffer { fn serialize(&self, serializer: S) -> std::result::Result { - serializer.serialize_bytes(unsafe { self.as_slice() }) + let lua = self.0.lua.lock(); + serializer.serialize_bytes(self.as_slice(&lua)) } } diff --git a/src/conversion.rs b/src/conversion.rs index b7dfa305..f1f6ddc0 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -645,7 +645,7 @@ impl FromLua for BString { match value { Value::String(s) => Ok((*s.as_bytes()).into()), #[cfg(feature = "luau")] - Value::Buffer(buf) => unsafe { Ok(buf.as_slice().into()) }, + Value::Buffer(buf) => Ok(buf.to_vec().into()), _ => Ok((*lua .coerce_string(value)? .ok_or_else(|| Error::FromLuaConversionError { diff --git a/src/serde/de.rs b/src/serde/de.rs index 69d9056c..07abd30c 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -165,7 +165,10 @@ impl<'de> serde::Deserializer<'de> for Deserializer { serde_userdata(ud, |value| value.deserialize_any(visitor)) } #[cfg(feature = "luau")] - Value::Buffer(buf) => visitor.visit_bytes(unsafe { buf.as_slice() }), + Value::Buffer(buf) => { + let lua = buf.0.lua.lock(); + visitor.visit_bytes(buf.as_slice(&lua)) + } Value::Function(_) | Value::Thread(_) | Value::UserData(_) From 49389c4aa4c6bf12f00997b579c9c7468251e8f1 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 12 Jul 2025 13:34:39 +0100 Subject: [PATCH 484/635] Wrap `Function::coverage` callback to `RefCell` (Luau) --- src/function.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/function.rs b/src/function.rs index 8ce78a8b..962e8186 100644 --- a/src/function.rs +++ b/src/function.rs @@ -434,7 +434,7 @@ impl Function { /// [`Compiler::set_coverage_level`]: crate::chunk::Compiler::set_coverage_level #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - pub fn coverage(&self, mut func: F) + pub fn coverage(&self, func: F) where F: FnMut(CoverageInfo), { @@ -454,13 +454,16 @@ impl Function { } else { None }; - let rust_callback = &mut *(data as *mut F); - rust_callback(CoverageInfo { - function, - line_defined, - depth, - hits: slice::from_raw_parts(hits, size).to_vec(), - }); + let rust_callback = &*(data as *const RefCell); + if let Ok(mut rust_callback) = rust_callback.try_borrow_mut() { + // Call the Rust callback with CoverageInfo + rust_callback(CoverageInfo { + function, + line_defined, + depth, + hits: slice::from_raw_parts(hits, size).to_vec(), + }); + } } let lua = self.0.lua.lock(); @@ -470,7 +473,8 @@ impl Function { assert_stack(state, 1); lua.push_ref(&self.0); - let func_ptr = &mut func as *mut F as *mut c_void; + let func = RefCell::new(func); + let func_ptr = &func as *const RefCell as *mut c_void; ffi::lua_getcoverage(state, -1, func_ptr, callback::); } } From 95367855c110c6cf59985e45610e0a2d6fd161af Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 12 Jul 2025 15:30:58 +0100 Subject: [PATCH 485/635] Return `AsyncCallFuture` instead of opaque `impl Future` from `ObjectLike` trait. --- src/function.rs | 7 +++++++ src/table.rs | 31 ++++++++++--------------------- src/traits.rs | 22 +++++++--------------- src/userdata/object.rs | 31 ++++++++++--------------------- 4 files changed, 34 insertions(+), 57 deletions(-) diff --git a/src/function.rs b/src/function.rs index 962e8186..92d6f93a 100644 --- a/src/function.rs +++ b/src/function.rs @@ -657,6 +657,13 @@ impl LuaType for Function { #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct AsyncCallFuture(Result>); +#[cfg(feature = "async")] +impl AsyncCallFuture { + pub(crate) fn error(err: Error) -> Self { + AsyncCallFuture(Err(err)) + } +} + #[cfg(feature = "async")] impl Future for AsyncCallFuture { type Output = Result; diff --git a/src/table.rs b/src/table.rs index ea3260a6..822a20c3 100644 --- a/src/table.rs +++ b/src/table.rs @@ -13,10 +13,7 @@ use crate::util::{assert_stack, check_stack, get_metatable_ptr, StackGuard}; use crate::value::{Nil, Value}; #[cfg(feature = "async")] -use { - crate::types::MaybeSend, - futures_util::future::{self, Either, Future}, -}; +use crate::function::AsyncCallFuture; #[cfg(feature = "serde")] use { @@ -892,9 +889,9 @@ impl ObjectLike for Table { #[cfg(feature = "async")] #[inline] - fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> + MaybeSend + fn call_async(&self, args: impl IntoLuaMulti) -> AsyncCallFuture where - R: FromLuaMulti + MaybeSend, + R: FromLuaMulti, { Function(self.0.copy()).call_async(args) } @@ -908,13 +905,9 @@ impl ObjectLike for Table { } #[cfg(feature = "async")] - fn call_async_method( - &self, - name: &str, - args: impl IntoLuaMulti, - ) -> impl Future> + MaybeSend + fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture where - R: FromLuaMulti + MaybeSend, + R: FromLuaMulti, { self.call_async_function(name, (self, args)) } @@ -932,21 +925,17 @@ impl ObjectLike for Table { #[cfg(feature = "async")] #[inline] - fn call_async_function( - &self, - name: &str, - args: impl IntoLuaMulti, - ) -> impl Future> + MaybeSend + fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture where - R: FromLuaMulti + MaybeSend, + R: FromLuaMulti, { match self.get(name) { - Ok(Value::Function(func)) => Either::Left(func.call_async(args)), + Ok(Value::Function(func)) => func.call_async(args), Ok(val) => { let msg = format!("attempt to call a {} value (function '{name}')", val.type_name()); - Either::Right(future::ready(Err(Error::RuntimeError(msg)))) + AsyncCallFuture::error(Error::RuntimeError(msg)) } - Err(err) => Either::Right(future::ready(Err(err))), + Err(err) => AsyncCallFuture::error(err), } } diff --git a/src/traits.rs b/src/traits.rs index 0d11dceb..47dee4c3 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -11,7 +11,7 @@ use crate::util::{check_stack, short_type_name}; use crate::value::Value; #[cfg(feature = "async")] -use std::future::Future; +use {crate::function::AsyncCallFuture, std::future::Future}; /// Trait for types convertible to [`Value`]. pub trait IntoLua: Sized { @@ -162,9 +162,9 @@ pub trait ObjectLike: Sealed { /// arguments. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> + MaybeSend + fn call_async(&self, args: impl IntoLuaMulti) -> AsyncCallFuture where - R: FromLuaMulti + MaybeSend; + R: FromLuaMulti; /// Gets the function associated to key `name` from the object and calls it, /// passing the object itself along with `args` as function arguments. @@ -178,13 +178,9 @@ pub trait ObjectLike: Sealed { /// This might invoke the `__index` metamethod. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_method( - &self, - name: &str, - args: impl IntoLuaMulti, - ) -> impl Future> + MaybeSend + fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture where - R: FromLuaMulti + MaybeSend; + R: FromLuaMulti; /// Gets the function associated to key `name` from the object and calls it, /// passing `args` as function arguments. @@ -200,13 +196,9 @@ pub trait ObjectLike: Sealed { /// This might invoke the `__index` metamethod. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async_function( - &self, - name: &str, - args: impl IntoLuaMulti, - ) -> impl Future> + MaybeSend + fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture where - R: FromLuaMulti + MaybeSend; + R: FromLuaMulti; /// Converts the object to a string in a human-readable format. /// diff --git a/src/userdata/object.rs b/src/userdata/object.rs index 7dd9f718..682730b1 100644 --- a/src/userdata/object.rs +++ b/src/userdata/object.rs @@ -8,10 +8,7 @@ use crate::value::Value; use crate::Function; #[cfg(feature = "async")] -use { - crate::types::MaybeSend, - futures_util::future::{self, Either, Future}, -}; +use crate::function::AsyncCallFuture; impl ObjectLike for AnyUserData { #[inline] @@ -38,9 +35,9 @@ impl ObjectLike for AnyUserData { #[cfg(feature = "async")] #[inline] - fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> + MaybeSend + fn call_async(&self, args: impl IntoLuaMulti) -> AsyncCallFuture where - R: FromLuaMulti + MaybeSend, + R: FromLuaMulti, { Function(self.0.copy()).call_async(args) } @@ -54,13 +51,9 @@ impl ObjectLike for AnyUserData { } #[cfg(feature = "async")] - fn call_async_method( - &self, - name: &str, - args: impl IntoLuaMulti, - ) -> impl Future> + MaybeSend + fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture where - R: FromLuaMulti + MaybeSend, + R: FromLuaMulti, { self.call_async_function(name, (self, args)) } @@ -79,21 +72,17 @@ impl ObjectLike for AnyUserData { } #[cfg(feature = "async")] - fn call_async_function( - &self, - name: &str, - args: impl IntoLuaMulti, - ) -> impl Future> + MaybeSend + fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture where - R: FromLuaMulti + MaybeSend, + R: FromLuaMulti, { match self.get(name) { - Ok(Value::Function(func)) => Either::Left(func.call_async(args)), + Ok(Value::Function(func)) => func.call_async(args), Ok(val) => { let msg = format!("attempt to call a {} value (function '{name}')", val.type_name()); - Either::Right(future::ready(Err(Error::RuntimeError(msg)))) + AsyncCallFuture::error(Error::RuntimeError(msg)) } - Err(err) => Either::Right(future::ready(Err(err))), + Err(err) => AsyncCallFuture::error(err), } } From 8d219503dd0e4243c163392f536fe73249daf3a0 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 12 Jul 2025 15:34:04 +0100 Subject: [PATCH 486/635] Opt-out from `R: MaybeSend` in `AsyncThread` --- src/thread.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/thread.rs b/src/thread.rs index b524582f..c9349d23 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -86,7 +86,7 @@ unsafe impl Sync for Thread {} #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct AsyncThread { thread: Thread, - ret: PhantomData, + ret: PhantomData R>, recycle: bool, } From 1e48817a642454afce579e7af7e1813c5a1ed190 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 12 Jul 2025 19:20:15 +0100 Subject: [PATCH 487/635] Fix deregistering previously-registered userdata --- src/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state.rs b/src/state.rs index 4d762571..ede03c75 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1416,7 +1416,7 @@ impl Lua { let lua = self.lock(); unsafe { // Deregister the type if it already registered - if let Some(&table_id) = (*lua.extra.get()).registered_userdata_t.get(&type_id) { + if let Some(table_id) = (*lua.extra.get()).registered_userdata_t.remove(&type_id) { ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, table_id); } From 1791c599f4c7c633da964d721c40b9c994a14856 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 12 Jul 2025 22:46:37 +0100 Subject: [PATCH 488/635] mlua-sys: v0.8.2 --- mlua-sys/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index de16a990..0b27c544 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.8.1" +version = "0.8.2" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" From 583c35a172d8f5bc429ff2450e738c988a9a6933 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 12 Jul 2025 22:47:20 +0100 Subject: [PATCH 489/635] Prepare for v0.11.0 --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- README.md | 10 ++-------- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 292cd501..3db0a19a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## v0.11.0 (Jul 9, 2025) +## v0.11.0 (Jul 13, 2025) Changes since v0.11.0-beta.3 diff --git a/Cargo.toml b/Cargo.toml index 5631efa9..67018640 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.11.0-beta.3" # remember to update mlua_derive +version = "0.11.0" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.79.0" edition = "2021" diff --git a/README.md b/README.md index c417b3c1..f6ba9850 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,6 @@ [Benchmarks]: https://github.com/khvzak/script-bench-rs [FAQ]: FAQ.md -# The main branch is the development version of `mlua`. Please see the [v0.10](https://github.com/mlua-rs/mlua/tree/v0.10) branch for the stable versions of `mlua`. - -> **Note** -> -> See v0.10 [release notes](https://github.com/mlua-rs/mlua/blob/main/docs/release_notes/v0.10.md). - `mlua` is a set of bindings to the [Lua](https://www.lua.org) programming language for Rust with a goal to provide a _safe_ (as much as possible), high level, easy to use, practical and flexible API. @@ -135,7 +129,7 @@ Add to `Cargo.toml`: ``` toml [dependencies] -mlua = { version = "0.10", features = ["lua54", "vendored"] } +mlua = { version = "0.11", features = ["lua54", "vendored"] } ``` `main.rs` @@ -170,7 +164,7 @@ Add to `Cargo.toml`: crate-type = ["cdylib"] [dependencies] -mlua = { version = "0.10", features = ["lua54", "module"] } +mlua = { version = "0.11", features = ["lua54", "module"] } ``` `lib.rs`: From 928d94d2550baa747c665688456af24542a2ac19 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 14 Jul 2025 15:33:02 +0100 Subject: [PATCH 490/635] v0.11.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3db0a19a..e5710401 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## v0.11.0 (Jul 13, 2025) +## v0.11.0 (Jul 14, 2025) Changes since v0.11.0-beta.3 From 00328b0b644fff8a677d62c7a826ecb13956fd0c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 15 Jul 2025 16:11:31 +0100 Subject: [PATCH 491/635] Protect `Lua::push_c_function` for Lua <5.2 --- src/state.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/state.rs b/src/state.rs index ede03c75..81256b9f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1286,8 +1286,24 @@ impl Lua { /// This function is unsafe because provides a way to execute unsafe C function. pub unsafe fn create_c_function(&self, func: ffi::lua_CFunction) -> Result { let lua = self.lock(); - ffi::lua_pushcfunction(lua.ref_thread(), func); - Ok(Function(lua.pop_ref_thread())) + if cfg!(any(feature = "lua54", feature = "lua53", feature = "lua52")) { + ffi::lua_pushcfunction(lua.ref_thread(), func); + return Ok(Function(lua.pop_ref_thread())); + } + + // Lua <5.2 requires memory allocation to push a C function + let state = lua.state(); + { + let _sg = StackGuard::new(state); + check_stack(state, 3)?; + + if lua.unlikely_memory_error() { + ffi::lua_pushcfunction(state, func); + } else { + protect_lua!(state, 0, 1, |state| ffi::lua_pushcfunction(state, func))?; + } + Ok(Function(lua.pop_ref())) + } } /// Wraps a Rust async function or closure, creating a callable Lua function handle to it. From 459edb6816e97667aa1b8d84458feec4eefdb774 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 15 Jul 2025 16:32:22 +0100 Subject: [PATCH 492/635] Always grow aux ref stack considering the reserve --- src/state/extra.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state/extra.rs b/src/state/extra.rs index 588aa08b..36d7dde6 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -270,7 +270,7 @@ impl ExtraData { // Try to grow max stack size if self.ref_stack_top >= self.ref_stack_size { let mut inc = self.ref_stack_size; // Try to double stack size - while inc > 0 && ffi::lua_checkstack(self.ref_thread, inc) == 0 { + while inc > 0 && ffi::lua_checkstack(self.ref_thread, inc + REF_STACK_RESERVE) == 0 { inc /= 2; } if inc == 0 { From f945a35cbdef44955a1b4e24e2d0e56fc4e31b4e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 15 Jul 2025 19:14:46 +0100 Subject: [PATCH 493/635] Execute metatable destructor in `Table::set_metatable` at the end of invocation Before this change, destructor was executed shortly after pushing metatable to ref_thread. --- src/table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/table.rs b/src/table.rs index 822a20c3..ba506fe4 100644 --- a/src/table.rs +++ b/src/table.rs @@ -510,7 +510,7 @@ impl Table { let lua = self.0.lua.lock(); let ref_thread = lua.ref_thread(); unsafe { - if let Some(metatable) = metatable { + if let Some(metatable) = &metatable { ffi::lua_pushvalue(ref_thread, metatable.0.index); } else { ffi::lua_pushnil(ref_thread); From 78331ceebea2ccd9469d8d55d54970739a5b957f Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 15 Jul 2025 22:43:18 +0100 Subject: [PATCH 494/635] v0.11.1 --- CHANGELOG.md | 5 +++++ Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5710401..62d0e841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## v0.11.1 (Jul 15, 2025) + +- Fixed bug exhausting Lua auxiliary stack and leaving it without reserve (#615) +- `Lua::push_c_function` now correctly handles OOM for Lua 5.1 and Luau + ## v0.11.0 (Jul 14, 2025) Changes since v0.11.0-beta.3 diff --git a/Cargo.toml b/Cargo.toml index 67018640..f37f3ee9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.11.0" # remember to update mlua_derive +version = "0.11.1" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.79.0" edition = "2021" From 815d1bd7c9f334e511a7e6ad4a45261b7b48b4d1 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 25 Jul 2025 14:29:22 +0100 Subject: [PATCH 495/635] Better handling negative zeros to match Lua 5.3+ behavior In Lua 5.3+ the function `lua_isinteger` returns "false" for -0.0 numbers. In earlier Lua versions we should follow the same behavior to avoid losing the sign when converting to Integer. Close #618 --- mlua-sys/src/lua51/compat.rs | 3 ++- mlua-sys/src/lua52/compat.rs | 3 ++- mlua-sys/src/luau/compat.rs | 3 ++- src/state/raw.rs | 6 +++++- tests/conversion.rs | 13 +++++++++++++ 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/mlua-sys/src/lua51/compat.rs b/mlua-sys/src/lua51/compat.rs index 29837f2a..cf4bb348 100644 --- a/mlua-sys/src/lua51/compat.rs +++ b/mlua-sys/src/lua51/compat.rs @@ -186,7 +186,8 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int { if lua_type(L, idx) == LUA_TNUMBER { let n = lua_tonumber(L, idx); let i = lua_tointeger(L, idx); - if (n - i as lua_Number).abs() < lua_Number::EPSILON { + // Lua 5.3+ returns "false" for `-0.0` + if (n - i as lua_Number).abs() < lua_Number::EPSILON && !(n == 0.0 && n.is_sign_negative()) { return 1; } } diff --git a/mlua-sys/src/lua52/compat.rs b/mlua-sys/src/lua52/compat.rs index 04829145..29a126a3 100644 --- a/mlua-sys/src/lua52/compat.rs +++ b/mlua-sys/src/lua52/compat.rs @@ -51,7 +51,8 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int { if lua_type(L, idx) == LUA_TNUMBER { let n = lua_tonumber(L, idx); let i = lua_tointeger(L, idx); - if (n - i as lua_Number).abs() < lua_Number::EPSILON { + // Lua 5.3+ returns "false" for `-0.0` + if (n - i as lua_Number).abs() < lua_Number::EPSILON && !(n == 0.0 && n.is_sign_negative()) { return 1; } } diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index e0c7e10d..f99ea70f 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -120,7 +120,8 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int { if lua_type(L, idx) == LUA_TNUMBER { let n = lua_tonumber(L, idx); let i = lua_tointeger(L, idx); - if (n - i as lua_Number).abs() < lua_Number::EPSILON { + // Lua 5.3+ returns "false" for `-0.0` + if (n - i as lua_Number).abs() < lua_Number::EPSILON && !(n == 0.0 && n.is_sign_negative()) { return 1; } } diff --git a/src/state/raw.rs b/src/state/raw.rs index 74445ef8..b073ee05 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -726,9 +726,13 @@ impl RawLua { ffi::LUA_TNUMBER => { use crate::types::Number; + fn same(n: Number, i: Integer) -> bool { + (n - (i as Number)).abs() < Number::EPSILON && !(n == 0.0 && n.is_sign_negative()) + } + let n = ffi::lua_tonumber(state, idx); match num_traits::cast(n) { - Some(i) if (n - (i as Number)).abs() < Number::EPSILON => Value::Integer(i), + Some(i) if same(n, i) => Value::Integer(i), _ => Value::Number(n), } } diff --git a/tests/conversion.rs b/tests/conversion.rs index 73228532..bee4c2d0 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -372,6 +372,19 @@ fn test_float_from_lua() -> Result<()> { // Should fallback to default conversion assert_eq!(f.call::("42.0")?, 42.0); + // Negative zero + let negative_zero = lua.load("return -0.0").eval::()?; + assert_eq!(negative_zero, 0.0); + assert!(negative_zero.is_sign_negative()); + + // In Lua <5.3 all numbers are floats + #[cfg(not(any(feature = "lua54", feature = "lua53")))] + { + let negative_zero = lua.load("return -0").eval::()?; + assert_eq!(negative_zero, 0.0); + assert!(negative_zero.is_sign_negative()); + } + Ok(()) } From 841bd332e4075266ce783d638d1b510082f0c252 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 25 Jul 2025 15:24:04 +0100 Subject: [PATCH 496/635] Fix LuaJIT negative zero tests --- tests/conversion.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/conversion.rs b/tests/conversion.rs index bee4c2d0..171a3cfe 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -375,10 +375,12 @@ fn test_float_from_lua() -> Result<()> { // Negative zero let negative_zero = lua.load("return -0.0").eval::()?; assert_eq!(negative_zero, 0.0); + // LuaJIT treats -0.0 as a positive zero + #[cfg(not(feature = "luajit"))] assert!(negative_zero.is_sign_negative()); // In Lua <5.3 all numbers are floats - #[cfg(not(any(feature = "lua54", feature = "lua53")))] + #[cfg(not(any(feature = "lua54", feature = "lua53", feature = "luajit")))] { let negative_zero = lua.load("return -0").eval::()?; assert_eq!(negative_zero, 0.0); From b1c69d3005bdbd874aa9ae93c84aa76e1c2dd895 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 25 Jul 2025 21:25:08 +0100 Subject: [PATCH 497/635] Use `to_bits` comparison to check if a float value can be represented as an integer losslessly. This allows to simplify the code while still maintaining "negative zeros" edge case. Thanks @JasonHise for the suggestion. --- mlua-sys/src/lua51/compat.rs | 2 +- mlua-sys/src/lua52/compat.rs | 2 +- mlua-sys/src/luau/compat.rs | 2 +- src/state/raw.rs | 6 +----- tests/conversion.rs | 15 --------------- tests/tests.rs | 15 +++++++++++++++ 6 files changed, 19 insertions(+), 23 deletions(-) diff --git a/mlua-sys/src/lua51/compat.rs b/mlua-sys/src/lua51/compat.rs index cf4bb348..75383b9e 100644 --- a/mlua-sys/src/lua51/compat.rs +++ b/mlua-sys/src/lua51/compat.rs @@ -187,7 +187,7 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int { let n = lua_tonumber(L, idx); let i = lua_tointeger(L, idx); // Lua 5.3+ returns "false" for `-0.0` - if (n - i as lua_Number).abs() < lua_Number::EPSILON && !(n == 0.0 && n.is_sign_negative()) { + if n.to_bits() == (i as lua_Number).to_bits() { return 1; } } diff --git a/mlua-sys/src/lua52/compat.rs b/mlua-sys/src/lua52/compat.rs index 29a126a3..2d9d7aeb 100644 --- a/mlua-sys/src/lua52/compat.rs +++ b/mlua-sys/src/lua52/compat.rs @@ -52,7 +52,7 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int { let n = lua_tonumber(L, idx); let i = lua_tointeger(L, idx); // Lua 5.3+ returns "false" for `-0.0` - if (n - i as lua_Number).abs() < lua_Number::EPSILON && !(n == 0.0 && n.is_sign_negative()) { + if n.to_bits() == (i as lua_Number).to_bits() { return 1; } } diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index f99ea70f..2b2c9a6a 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -121,7 +121,7 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int { let n = lua_tonumber(L, idx); let i = lua_tointeger(L, idx); // Lua 5.3+ returns "false" for `-0.0` - if (n - i as lua_Number).abs() < lua_Number::EPSILON && !(n == 0.0 && n.is_sign_negative()) { + if n.to_bits() == (i as lua_Number).to_bits() { return 1; } } diff --git a/src/state/raw.rs b/src/state/raw.rs index b073ee05..ab448e1a 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -726,13 +726,9 @@ impl RawLua { ffi::LUA_TNUMBER => { use crate::types::Number; - fn same(n: Number, i: Integer) -> bool { - (n - (i as Number)).abs() < Number::EPSILON && !(n == 0.0 && n.is_sign_negative()) - } - let n = ffi::lua_tonumber(state, idx); match num_traits::cast(n) { - Some(i) if same(n, i) => Value::Integer(i), + Some(i) if n.to_bits() == (i as Number).to_bits() => Value::Integer(i), _ => Value::Number(n), } } diff --git a/tests/conversion.rs b/tests/conversion.rs index 171a3cfe..73228532 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -372,21 +372,6 @@ fn test_float_from_lua() -> Result<()> { // Should fallback to default conversion assert_eq!(f.call::("42.0")?, 42.0); - // Negative zero - let negative_zero = lua.load("return -0.0").eval::()?; - assert_eq!(negative_zero, 0.0); - // LuaJIT treats -0.0 as a positive zero - #[cfg(not(feature = "luajit"))] - assert!(negative_zero.is_sign_negative()); - - // In Lua <5.3 all numbers are floats - #[cfg(not(any(feature = "lua54", feature = "lua53", feature = "luajit")))] - { - let negative_zero = lua.load("return -0").eval::()?; - assert_eq!(negative_zero, 0.0); - assert!(negative_zero.is_sign_negative()); - } - Ok(()) } diff --git a/tests/tests.rs b/tests/tests.rs index 94d682d6..a00e5f46 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -602,6 +602,21 @@ fn test_num_conversion() -> Result<()> { assert_eq!(lua.unpack::(lua.pack(1i128 << 64)?)?, 1i128 << 64); + // Negative zero + let negative_zero = lua.load("-0.0").eval::()?; + assert_eq!(negative_zero, 0.0); + // LuaJIT treats -0.0 as a positive zero + #[cfg(not(feature = "luajit"))] + assert!(negative_zero.is_sign_negative()); + + // In Lua <5.3 all numbers are floats + #[cfg(not(any(feature = "lua54", feature = "lua53", feature = "luajit")))] + { + let negative_zero = lua.load("-0").eval::()?; + assert_eq!(negative_zero, 0.0); + assert!(negative_zero.is_sign_negative()); + } + Ok(()) } From cb153a52b2e5b22a96f3ddf6f1a94e0cc56792d5 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 26 Jul 2025 22:23:16 +0100 Subject: [PATCH 498/635] Make Luau registered aliases case-insensitive Executing `require("@my_module")` or `require("@My_Module")` should give the same result and use case-insensitive name. See #620 for details --- src/luau/require.rs | 17 ++++++++++++++--- src/state.rs | 2 ++ tests/luau/require.rs | 5 +++++ .../with_config/src/alias_requirer_uc.luau | 1 + tests/tests.rs | 11 +++++++++++ 5 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 tests/luau/require/with_config/src/alias_requirer_uc.luau diff --git a/src/luau/require.rs b/src/luau/require.rs index 82451f99..cd1b8d80 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -567,10 +567,20 @@ pub(super) fn create_require_function( 1 } - let (error, r#type) = unsafe { - lua.exec_raw::<(Function, Function)>((), move |state| { + unsafe extern "C-unwind" fn to_lowercase(state: *mut ffi::lua_State) -> c_int { + let s = ffi::luaL_checkstring(state, 1); + let s = CStr::from_ptr(s); + callback_error_ext(state, ptr::null_mut(), true, |extra, _| { + let s = s.to_string_lossy().to_lowercase(); + (*extra).raw_lua().push(s).map(|_| 1) + }) + } + + let (error, r#type, to_lowercase) = unsafe { + lua.exec_raw::<(Function, Function, Function)>((), move |state| { ffi::lua_pushcfunctiond(state, error, cstr!("error")); ffi::lua_pushcfunctiond(state, r#type, cstr!("type")); + ffi::lua_pushcfunctiond(state, to_lowercase, cstr!("to_lowercase")); }) }?; @@ -583,6 +593,7 @@ pub(super) fn create_require_function( env.raw_set("LOADER_CACHE", loader_cache)?; env.raw_set("error", error)?; env.raw_set("type", r#type)?; + env.raw_set("to_lowercase", to_lowercase)?; lua.load( r#" @@ -592,7 +603,7 @@ pub(super) fn create_require_function( end -- Check if the module (path) is explicitly registered - local maybe_result = REGISTERED_MODULES[path] + local maybe_result = REGISTERED_MODULES[to_lowercase(path)] if maybe_result ~= nil then return maybe_result end diff --git a/src/state.rs b/src/state.rs index 81256b9f..065532e1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -358,6 +358,8 @@ impl Lua { if cfg!(feature = "luau") && !modname.starts_with('@') { return Err(Error::runtime("module name must begin with '@'")); } + #[cfg(feature = "luau")] + let modname = modname.to_lowercase(); unsafe { self.exec_raw::<()>(value, |state| { ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, LOADED_MODULES_KEY); diff --git a/tests/luau/require.rs b/tests/luau/require.rs index 05c2c684..754e7ee4 100644 --- a/tests/luau/require.rs +++ b/tests/luau/require.rs @@ -179,6 +179,11 @@ fn test_require_with_config() { let res = run_require(&lua, "./tests/luau/require/with_config/src/alias_requirer").unwrap(); assert_eq!("result from dependency", get_str(&res, 1)); + // RequirePathWithAlias (case-insensitive) + let res2 = run_require(&lua, "./tests/luau/require/with_config/src/alias_requirer_uc").unwrap(); + assert_eq!("result from dependency", get_str(&res2, 1)); + assert_eq!(res.to_pointer(), res2.to_pointer()); + // RequirePathWithParentAlias let res = run_require(&lua, "./tests/luau/require/with_config/src/parent_alias_requirer").unwrap(); assert_eq!("result from other_dependency", get_str(&res, 1)); diff --git a/tests/luau/require/with_config/src/alias_requirer_uc.luau b/tests/luau/require/with_config/src/alias_requirer_uc.luau new file mode 100644 index 00000000..7fa5dc9e --- /dev/null +++ b/tests/luau/require/with_config/src/alias_requirer_uc.luau @@ -0,0 +1 @@ +return require("@DeP") diff --git a/tests/tests.rs b/tests/tests.rs index a00e5f46..edbf8f2a 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1242,6 +1242,17 @@ fn test_register_module() -> Result<()> { res.unwrap_err().to_string(), "runtime error: module name must begin with '@'" ); + + // Luau registered modules (aliases) are case-insensitive + let res = lua.register_module("@My_Module", &t); + assert!(res.is_ok()); + lua.load( + r#" + local my_module = require("@MY_MODule") + assert(my_module.name == "my_module") + "#, + ) + .exec()?; } Ok(()) From c035c23a1597d3eaf5b784d73a1ffe0a3ec3dfad Mon Sep 17 00:00:00 2001 From: piz-ewing <78925867+piz-ewing@users.noreply.github.com> Date: Tue, 5 Aug 2025 05:34:36 +0800 Subject: [PATCH 499/635] fix: normalize_chunk_name handles Windows paths with drive letter (#623) Co-authored-by: ewing --- src/luau/require.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/luau/require.rs b/src/luau/require.rs index cd1b8d80..e261bf7d 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -129,7 +129,7 @@ impl TextRequirer { } fn normalize_chunk_name(chunk_name: &str) -> &str { - if let Some((path, line)) = chunk_name.split_once(':') { + if let Some((path, line)) = chunk_name.rsplit_once(':') { if line.parse::().is_ok() { return path; } From bd63f63bc951b695c68f71d2eec9c9540235197b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 9 Aug 2025 19:14:31 +0100 Subject: [PATCH 500/635] Use ascii lowercase for module aliases This matches with Luau 0.686 changes --- src/luau/require.rs | 8 +++++++- src/state.rs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/luau/require.rs b/src/luau/require.rs index e261bf7d..7e232f73 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -570,8 +570,14 @@ pub(super) fn create_require_function( unsafe extern "C-unwind" fn to_lowercase(state: *mut ffi::lua_State) -> c_int { let s = ffi::luaL_checkstring(state, 1); let s = CStr::from_ptr(s); + if !s.to_bytes().iter().any(|&c| c.is_ascii_uppercase()) { + // If the string does not contain any uppercase ASCII letters, return it as is + return 1; + } callback_error_ext(state, ptr::null_mut(), true, |extra, _| { - let s = s.to_string_lossy().to_lowercase(); + let s = (s.to_bytes().iter()) + .map(|&c| c.to_ascii_lowercase()) + .collect::(); (*extra).raw_lua().push(s).map(|_| 1) }) } diff --git a/src/state.rs b/src/state.rs index 065532e1..ea3bc8ea 100644 --- a/src/state.rs +++ b/src/state.rs @@ -359,7 +359,7 @@ impl Lua { return Err(Error::runtime("module name must begin with '@'")); } #[cfg(feature = "luau")] - let modname = modname.to_lowercase(); + let modname = modname.to_ascii_lowercase(); unsafe { self.exec_raw::<()>(value, |state| { ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, LOADED_MODULES_KEY); From c9d6a610e10249583cb4624ff63a99125da39de8 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 10 Aug 2025 00:11:05 +0100 Subject: [PATCH 501/635] mlua-sys: v0.8.3 --- mlua-sys/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 0b27c544..6e6cca62 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.8.2" +version = "0.8.3" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" From bafdb6138c4f19d6cf34f99f359e80ba4d24dce0 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 10 Aug 2025 00:19:54 +0100 Subject: [PATCH 502/635] Update dependencies --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f37f3ee9..16a8f1d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ parking_lot = { version = "0.12", features = ["arc_lock"] } anyhow = { version = "1.0", optional = true } rustversion = "1.0" -ffi = { package = "mlua-sys", version = "0.8.0", path = "mlua-sys" } +ffi = { package = "mlua-sys", version = "0.8.3", path = "mlua-sys" } [dev-dependencies] trybuild = "1.0" @@ -78,8 +78,8 @@ tempfile = "3" static_assertions = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] -criterion = { version = "0.6", features = ["async_tokio"] } -rustyline = "16.0" +criterion = { version = "0.7", features = ["async_tokio"] } +rustyline = "17.0" tokio = { version = "1.0", features = ["full"] } [lints.rust] From 763c2b256482949ea496a1efbb9bcf3185e58ddd Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 10 Aug 2025 00:20:20 +0100 Subject: [PATCH 503/635] Update `repl` example: don't print newline if no values returned --- examples/repl.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/examples/repl.rs b/examples/repl.rs index c0a34a6e..98355cea 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -20,14 +20,16 @@ fn main() { match lua.load(&line).eval::() { Ok(values) => { editor.add_history_entry(line).unwrap(); - println!( - "{}", - values - .iter() - .map(|value| format!("{:#?}", value)) - .collect::>() - .join("\t") - ); + if values.len() > 0 { + println!( + "{}", + values + .iter() + .map(|value| format!("{:#?}", value)) + .collect::>() + .join("\t") + ); + } break; } Err(Error::SyntaxError { From 36560435f776f793c53ccb74a6e8e9ec8ded2995 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 10 Aug 2025 00:32:53 +0100 Subject: [PATCH 504/635] Add `push_into_stack_multi` fastpath to `Variadic` --- src/multi.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/multi.rs b/src/multi.rs index c171962c..ef46c39f 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -297,6 +297,15 @@ impl IntoLuaMulti for Variadic { fn into_lua_multi(self, lua: &Lua) -> Result { MultiValue::from_lua_iter(lua, self) } + + unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { + let nresults = self.len() as i32; + check_stack(lua.state(), nresults + 1)?; + for value in self.0 { + value.push_into_stack(lua)?; + } + Ok(nresults) + } } impl FromLuaMulti for Variadic { From ca7358371408105a8d763d84eb0c6c7bb3979fa8 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 10 Aug 2025 00:53:01 +0100 Subject: [PATCH 505/635] Update CHANGELOG --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62d0e841..76bbaf84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## v0.11.2 (Aug 10, 2025) + +- Faster stack push for `Variadic` +- Fix handling Windows paths with drive letter in Luau require (#623) +- Make Luau registered aliases ascii case-insensitive (#620) +- Fix deserializing negative zeros `-0.0` (#618) + ## v0.11.1 (Jul 15, 2025) - Fixed bug exhausting Lua auxiliary stack and leaving it without reserve (#615) From 3516f4c6ca3822b69cffc14679fe073147fd41df Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 10 Aug 2025 00:53:45 +0100 Subject: [PATCH 506/635] v0.11.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 16a8f1d3..c9da4bff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.11.1" # remember to update mlua_derive +version = "0.11.2" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.79.0" edition = "2021" From f0806a6d6255ee440d703ea472a033c8d4eed881 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 13 Aug 2025 22:49:40 +0100 Subject: [PATCH 507/635] Lower fastpath table creation limit to 1 << 26 When Lua is configured without memory restrictions, we use fastpath for table creation (unprotected mode). In generally it's safe as long as we `abort()` on allocation failure. However some Lua versions have additional restrictions on table size that we need to adhere in mlua too. Probably Luau has the lowest limits. Fixes #627 --- src/util/mod.rs | 2 +- tests/table.rs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/util/mod.rs b/src/util/mod.rs index b69a8675..4d07d3cb 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -122,7 +122,7 @@ pub(crate) unsafe fn push_table( ) -> Result<()> { let narr: c_int = narr.try_into().unwrap_or(c_int::MAX); let nrec: c_int = nrec.try_into().unwrap_or(c_int::MAX); - if protect || narr >= const { 1 << 30 } || nrec >= const { 1 << 27 } { + if protect || narr >= const { 1 << 26 } || nrec >= const { 1 << 26 } { protect_lua!(state, 0, 1, |state| ffi::lua_createtable(state, narr, nrec)) } else { ffi::lua_createtable(state, narr, nrec); diff --git a/tests/table.rs b/tests/table.rs index 3d4f5efa..922da8db 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -61,6 +61,15 @@ fn test_table() -> Result<()> { Ok(()) } +#[test] +#[cfg(target_os = "linux")] // Linux allow overcommiting the memory (relevant for CI) +fn test_table_with_large_capacity() { + let lua = Lua::new(); + + let t = lua.create_table_with_capacity(1 << 26, 1 << 26); + assert!(t.is_ok()); +} + #[test] fn test_table_push_pop() -> Result<()> { let lua = Lua::new(); From df0a44d40598252448bcda9051543c87afae8bcc Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 20 Aug 2025 11:33:21 +0100 Subject: [PATCH 508/635] Make Lua reference values cheap to clone Instead of locking the VM and making a copy on auxiliary thread, track number of references using Rust ref counter. This should also help reducing number of used references (they are limited to to 1M usually) on auxiliary thread. --- benches/benchmark.rs | 17 ++++++++++++++ src/state/extra.rs | 4 ++-- src/state/raw.rs | 14 ++++-------- src/table.rs | 6 ++--- src/types.rs | 2 +- src/types/value_ref.rs | 51 +++++++++++++++++++++++------------------- src/userdata/object.rs | 10 ++++----- 7 files changed, 60 insertions(+), 44 deletions(-) diff --git a/benches/benchmark.rs b/benches/benchmark.rs index b5bb2b38..6d53d9b0 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -128,6 +128,22 @@ fn table_traversal_sequence(c: &mut Criterion) { }); } +fn table_ref_clone(c: &mut Criterion) { + let lua = Lua::new(); + + let t = lua.create_table().unwrap(); + + c.bench_function("table [ref clone]", |b| { + b.iter_batched( + || collect_gc_twice(&lua), + |_| { + let _t2 = t.clone(); + }, + BatchSize::SmallInput, + ); + }); +} + fn function_create(c: &mut Criterion) { let lua = Lua::new(); @@ -399,6 +415,7 @@ criterion_group! { table_traversal_pairs, table_traversal_for_each, table_traversal_sequence, + table_ref_clone, function_create, function_call_sum, diff --git a/src/state/extra.rs b/src/state/extra.rs index 36d7dde6..dc0e06b3 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -12,7 +12,7 @@ use rustc_hash::FxHashMap; use crate::error::Result; use crate::state::RawLua; use crate::stdlib::StdLib; -use crate::types::{AppData, ReentrantMutex, XRc}; +use crate::types::{AppData, ReentrantMutex, ValueRefIndex, XRc}; use crate::userdata::RawUserDataRegistry; use crate::util::{get_internal_metatable, push_internal_userdata, TypeKey, WrappedFailure}; @@ -64,7 +64,7 @@ pub(crate) struct ExtraData { pub(super) wrapped_failure_top: usize, // Pool of `Thread`s (coroutines) for async execution #[cfg(feature = "async")] - pub(super) thread_pool: Vec, + pub(super) thread_pool: Vec, // Address of `WrappedFailure` metatable pub(super) wrapped_failure_mt_ptr: *const c_void, diff --git a/src/state/raw.rs b/src/state/raw.rs index ab448e1a..b7d5ad3e 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -624,7 +624,7 @@ impl RawLua { #[cfg(feature = "async")] pub(crate) unsafe fn create_recycled_thread(&self, func: &Function) -> Result { if let Some(index) = (*self.extra.get()).thread_pool.pop() { - let thread_state = ffi::lua_tothread(self.ref_thread(), index); + let thread_state = ffi::lua_tothread(self.ref_thread(), *index.0); ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index); #[cfg(feature = "luau")] @@ -645,8 +645,9 @@ impl RawLua { pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) { let extra = &mut *self.extra.get(); if extra.thread_pool.len() < extra.thread_pool.capacity() { - extra.thread_pool.push(thread.0.index); - thread.0.drop = false; // Prevent thread from being garbage collected + if let Some(index) = thread.0.index_count.take() { + extra.thread_pool.push(index); + } } } @@ -827,13 +828,6 @@ impl RawLua { ValueRef::new(self, index) } - #[inline] - pub(crate) unsafe fn clone_ref(&self, vref: &ValueRef) -> ValueRef { - ffi::lua_pushvalue(self.ref_thread(), vref.index); - let index = (*self.extra.get()).ref_stack_pop(); - ValueRef::new(self, index) - } - pub(crate) unsafe fn drop_ref(&self, vref: &ValueRef) { let ref_thread = self.ref_thread(); mlua_debug_assert!( diff --git a/src/table.rs b/src/table.rs index ba506fe4..93e68552 100644 --- a/src/table.rs +++ b/src/table.rs @@ -884,7 +884,7 @@ impl ObjectLike for Table { R: FromLuaMulti, { // Convert table to a function and call via pcall that respects the `__call` metamethod. - Function(self.0.copy()).call(args) + Function(self.0.clone()).call(args) } #[cfg(feature = "async")] @@ -893,7 +893,7 @@ impl ObjectLike for Table { where R: FromLuaMulti, { - Function(self.0.copy()).call_async(args) + Function(self.0.clone()).call_async(args) } #[inline] @@ -941,7 +941,7 @@ impl ObjectLike for Table { #[inline] fn to_string(&self) -> Result { - Value::Table(Table(self.0.copy())).to_string() + Value::Table(Table(self.0.clone())).to_string() } } diff --git a/src/types.rs b/src/types.rs index 84cd02e7..144310d3 100644 --- a/src/types.rs +++ b/src/types.rs @@ -18,7 +18,7 @@ pub(crate) type BoxFuture<'a, T> = futures_util::future::LocalBoxFuture<'a, T>; pub use app_data::{AppData, AppDataRef, AppDataRefMut}; pub use either::Either; pub use registry_key::RegistryKey; -pub(crate) use value_ref::ValueRef; +pub(crate) use value_ref::{ValueRef, ValueRefIndex}; /// Type of Lua integer numbers. pub type Integer = ffi::lua_Integer; diff --git a/src/types/value_ref.rs b/src/types/value_ref.rs index 89bac543..b88a391c 100644 --- a/src/types/value_ref.rs +++ b/src/types/value_ref.rs @@ -1,22 +1,39 @@ use std::fmt; use std::os::raw::{c_int, c_void}; +use super::XRc; use crate::state::{RawLua, WeakLua}; /// A reference to a Lua (complex) value stored in the Lua auxiliary thread. +#[derive(Clone)] pub struct ValueRef { pub(crate) lua: WeakLua, + // Keep index separate to avoid additional indirection when accessing it. pub(crate) index: c_int, - pub(crate) drop: bool, + // If `index_count` is `None`, the value does not need to be destroyed. + pub(crate) index_count: Option, +} + +/// A reference to a Lua value index in the auxiliary thread. +/// It's cheap to clone and can be used to track the number of references to a value. +#[derive(Clone)] +pub(crate) struct ValueRefIndex(pub(crate) XRc); + +impl From for ValueRefIndex { + #[inline] + fn from(index: c_int) -> Self { + ValueRefIndex(XRc::new(index)) + } } impl ValueRef { #[inline] - pub(crate) fn new(lua: &RawLua, index: c_int) -> Self { + pub(crate) fn new(lua: &RawLua, index: impl Into) -> Self { + let index = index.into(); ValueRef { lua: lua.weak().clone(), - index, - drop: true, + index: *index.0, + index_count: Some(index), } } @@ -25,16 +42,6 @@ impl ValueRef { let lua = self.lua.lock(); unsafe { ffi::lua_topointer(lua.ref_thread(), self.index) } } - - /// Returns a copy of the value, which is valid as long as the original value is held. - #[inline] - pub(crate) fn copy(&self) -> Self { - ValueRef { - lua: self.lua.clone(), - index: self.index, - drop: false, - } - } } impl fmt::Debug for ValueRef { @@ -43,17 +50,15 @@ impl fmt::Debug for ValueRef { } } -impl Clone for ValueRef { - fn clone(&self) -> Self { - unsafe { self.lua.lock().clone_ref(self) } - } -} - impl Drop for ValueRef { fn drop(&mut self) { - if self.drop { - if let Some(lua) = self.lua.try_lock() { - unsafe { lua.drop_ref(self) }; + if let Some(ValueRefIndex(index)) = self.index_count.take() { + // It's guaranteed that the inner value returns exactly once. + // This means in particular that the value is not dropped. + if XRc::into_inner(index).is_some() { + if let Some(lua) = self.lua.try_lock() { + unsafe { lua.drop_ref(self) }; + } } } } diff --git a/src/userdata/object.rs b/src/userdata/object.rs index 682730b1..c665a51f 100644 --- a/src/userdata/object.rs +++ b/src/userdata/object.rs @@ -15,14 +15,14 @@ impl ObjectLike for AnyUserData { fn get(&self, key: impl IntoLua) -> Result { // `lua_gettable` method used under the hood can work with any Lua value // that has `__index` metamethod - Table(self.0.copy()).get_protected(key) + Table(self.0.clone()).get_protected(key) } #[inline] fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { // `lua_settable` method used under the hood can work with any Lua value // that has `__newindex` metamethod - Table(self.0.copy()).set_protected(key, value) + Table(self.0.clone()).set_protected(key, value) } #[inline] @@ -30,7 +30,7 @@ impl ObjectLike for AnyUserData { where R: FromLuaMulti, { - Function(self.0.copy()).call(args) + Function(self.0.clone()).call(args) } #[cfg(feature = "async")] @@ -39,7 +39,7 @@ impl ObjectLike for AnyUserData { where R: FromLuaMulti, { - Function(self.0.copy()).call_async(args) + Function(self.0.clone()).call_async(args) } #[inline] @@ -88,6 +88,6 @@ impl ObjectLike for AnyUserData { #[inline] fn to_string(&self) -> Result { - Value::UserData(AnyUserData(self.0.copy())).to_string() + Value::UserData(AnyUserData(self.0.clone())).to_string() } } From 5f38445558f069f308273a15991f10909c3eaa61 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 20 Aug 2025 16:25:06 +0100 Subject: [PATCH 509/635] Fix warnings --- src/state/extra.rs | 4 ++-- src/types.rs | 5 ++++- tests/scope.rs | 6 ++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/state/extra.rs b/src/state/extra.rs index dc0e06b3..fb97c7d1 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -12,7 +12,7 @@ use rustc_hash::FxHashMap; use crate::error::Result; use crate::state::RawLua; use crate::stdlib::StdLib; -use crate::types::{AppData, ReentrantMutex, ValueRefIndex, XRc}; +use crate::types::{AppData, ReentrantMutex, XRc}; use crate::userdata::RawUserDataRegistry; use crate::util::{get_internal_metatable, push_internal_userdata, TypeKey, WrappedFailure}; @@ -64,7 +64,7 @@ pub(crate) struct ExtraData { pub(super) wrapped_failure_top: usize, // Pool of `Thread`s (coroutines) for async execution #[cfg(feature = "async")] - pub(super) thread_pool: Vec, + pub(super) thread_pool: Vec, // Address of `WrappedFailure` metatable pub(super) wrapped_failure_mt_ptr: *const c_void, diff --git a/src/types.rs b/src/types.rs index 144310d3..ef1b90f2 100644 --- a/src/types.rs +++ b/src/types.rs @@ -18,7 +18,10 @@ pub(crate) type BoxFuture<'a, T> = futures_util::future::LocalBoxFuture<'a, T>; pub use app_data::{AppData, AppDataRef, AppDataRefMut}; pub use either::Either; pub use registry_key::RegistryKey; -pub(crate) use value_ref::{ValueRef, ValueRefIndex}; +pub(crate) use value_ref::ValueRef; + +#[cfg(feature = "async")] +pub(crate) use value_ref::ValueRefIndex; /// Type of Lua integer numbers. pub type Integer = ffi::lua_Integer; diff --git a/tests/scope.rs b/tests/scope.rs index 9aa3ee19..c8ca041f 100644 --- a/tests/scope.rs +++ b/tests/scope.rs @@ -382,7 +382,8 @@ fn test_scope_userdata_ref() -> Result<()> { modify_userdata(&lua, &ud)?; // We can only borrow userdata scoped - assert!((matches!(ud.borrow::(), Err(Error::UserDataTypeMismatch)))); + #[rustfmt::skip] + assert!(matches!(ud.borrow::(), Err(Error::UserDataTypeMismatch))); ud.borrow_scoped::(|ud_inst| { assert_eq!(ud_inst.0.get(), 2); })?; @@ -419,7 +420,8 @@ fn test_scope_userdata_ref_mut() -> Result<()> { let ud = scope.create_userdata_ref_mut(&mut data)?; modify_userdata(&lua, &ud)?; - assert!((matches!(ud.borrow_mut::(), Err(Error::UserDataTypeMismatch)))); + #[rustfmt::skip] + assert!(matches!(ud.borrow_mut::(), Err(Error::UserDataTypeMismatch))); ud.borrow_mut_scoped::(|ud_inst| { ud_inst.0 += 10; })?; From db7b782d3cd13028b9b16a71623c9ee65a53dcc3 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 23 Aug 2025 09:13:31 +0100 Subject: [PATCH 510/635] Remove lifetimes from short type names --- src/util/short_names.rs | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/util/short_names.rs b/src/util/short_names.rs index 22623ed1..0fae0246 100644 --- a/src/util/short_names.rs +++ b/src/util/short_names.rs @@ -1,6 +1,6 @@ -//! Mostly copied from [bevy_utils] +//! Inspired by bevy's [disqualified] //! -//! [bevy_utils]: https://github.com/bevyengine/bevy/blob/main/crates/bevy_utils/src/short_names.rs +//! [disqualified]: https://github.com/bevyengine/disqualified/blob/main/src/short_name.rs use std::any::type_name; @@ -23,8 +23,7 @@ pub(crate) fn short_type_name() -> String { while index < end_of_string { let rest_of_string = full_name.get(index..end_of_string).unwrap_or_default(); - // Collapse everything up to the next special character, - // then skip over it + // Collapse everything up to the next special character, then skip over it if let Some(special_character_index) = rest_of_string.find(|c: char| [' ', '<', '>', '(', ')', '[', ']', ',', ';'].contains(&c)) { @@ -32,11 +31,16 @@ pub(crate) fn short_type_name() -> String { parsed_name += collapse_type_name(segment_to_collapse); // Insert the special character let special_character = &rest_of_string[special_character_index..=special_character_index]; - parsed_name.push_str(special_character); + parsed_name += special_character; + + // Remove lifetimes like <'_> or <'_, '_, ...> + if parsed_name.ends_with("<'_>") || parsed_name.ends_with("<'_, ") { + _ = parsed_name.split_off(parsed_name.len() - 4); + } match special_character { ">" | ")" | "]" if rest_of_string[special_character_index + 1..].starts_with("::") => { - parsed_name.push_str("::"); + parsed_name += "::"; // Move the index past the "::" index += special_character_index + 3; } @@ -53,14 +57,18 @@ pub(crate) fn short_type_name() -> String { } #[inline(always)] -fn collapse_type_name(string: &str) -> &str { - string.rsplit("::").next().unwrap() +fn collapse_type_name(segment: &str) -> &str { + segment.rsplit("::").next().unwrap() } #[cfg(test)] mod tests { use super::short_type_name; use std::collections::HashMap; + use std::marker::PhantomData; + + struct MyData<'a, 'b>(PhantomData<&'a &'b ()>); + struct MyDataT<'a, T>(PhantomData<&'a T>); #[test] fn tests() { @@ -73,5 +81,7 @@ mod tests { "HashMap>" ); assert_eq!(short_type_name:: i32>(), "dyn Fn(i32) -> i32"); + assert_eq!(short_type_name::>(), "MyDataT<&str>"); + assert_eq!(short_type_name::<(&MyData, [MyData])>(), "(MyData, [MyData])"); } } From 85b280a9d68796ad9c438cc26120839e4665e4fe Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 23 Aug 2025 09:27:23 +0100 Subject: [PATCH 511/635] Update nightly Rust error message matching --- tests/buffer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/buffer.rs b/tests/buffer.rs index 8e82cfd0..8a04534e 100644 --- a/tests/buffer.rs +++ b/tests/buffer.rs @@ -41,7 +41,7 @@ fn test_buffer() -> Result<()> { } #[test] -#[should_panic(expected = "range end index 14 out of range for slice of length 13")] +#[should_panic(expected = "out of range for slice of length 13")] fn test_buffer_out_of_bounds_read() { let lua = Lua::new(); let buf = lua.create_buffer(b"hello, world!").unwrap(); @@ -49,7 +49,7 @@ fn test_buffer_out_of_bounds_read() { } #[test] -#[should_panic(expected = "range end index 16 out of range for slice of length 13")] +#[should_panic(expected = "out of range for slice of length 13")] fn test_buffer_out_of_bounds_write() { let lua = Lua::new(); let buf = lua.create_buffer(b"hello, world!").unwrap(); From c481c87eac9cb73694b7ad02da76859a02f728c7 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 23 Aug 2025 22:36:31 +0100 Subject: [PATCH 512/635] Add `Lua::create_buffer_with_capacity` method This allow creating a preallocated buffer with specified size initialized to zero. --- src/state.rs | 31 ++++++++++++++++++------------- src/state/raw.rs | 14 ++++++++++++++ src/util/mod.rs | 12 +++++------- tests/buffer.rs | 11 +++++++++++ 4 files changed, 48 insertions(+), 20 deletions(-) diff --git a/src/state.rs b/src/state.rs index ea3bc8ea..1312c23c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1150,7 +1150,7 @@ impl Lua { } } - /// Create and return an interned Lua string. + /// Creates and returns an interned Lua string. /// /// Lua strings can be arbitrary `[u8]` data including embedded nulls, so in addition to `&str` /// and `&String`, you can also pass plain `&[u8]` here. @@ -1159,27 +1159,32 @@ impl Lua { unsafe { self.lock().create_string(s) } } - /// Create and return a Luau [buffer] object from a byte slice of data. + /// Creates and returns a Luau [buffer] object from a byte slice of data. /// /// [buffer]: https://luau.org/library#buffer-library #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result { + pub fn create_buffer(&self, data: impl AsRef<[u8]>) -> Result { let lua = self.lock(); - let state = lua.state(); + let data = data.as_ref(); unsafe { - if lua.unlikely_memory_error() { - crate::util::push_buffer(state, buf.as_ref(), false)?; - return Ok(Buffer(lua.pop_ref())); - } - - let _sg = StackGuard::new(state); - check_stack(state, 3)?; - crate::util::push_buffer(state, buf.as_ref(), true)?; - Ok(Buffer(lua.pop_ref())) + let (ptr, buffer) = lua.create_buffer_with_capacity(data.len())?; + ptr.copy_from_nonoverlapping(data.as_ptr(), data.len()); + Ok(buffer) } } + /// Creates and returns a Luau [buffer] object with the specified size. + /// + /// Size limit is 1GB. All bytes will be initialized to zero. + /// + /// [buffer]: https://luau.org/library#buffer-library + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn create_buffer_with_capacity(&self, size: usize) -> Result { + unsafe { Ok(self.lock().create_buffer_with_capacity(size)?.1) } + } + /// Creates and returns a new empty table. #[inline] pub fn create_table(&self) -> Result
{ diff --git a/src/state/raw.rs b/src/state/raw.rs index b7d5ad3e..afcc95e4 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -523,6 +523,20 @@ impl RawLua { Ok(String(self.pop_ref())) } + #[cfg(feature = "luau")] + pub(crate) unsafe fn create_buffer_with_capacity(&self, size: usize) -> Result<(*mut u8, crate::Buffer)> { + let state = self.state(); + if self.unlikely_memory_error() { + let ptr = crate::util::push_buffer(state, size, false)?; + return Ok((ptr, crate::Buffer(self.pop_ref()))); + } + + let _sg = StackGuard::new(state); + check_stack(state, 3)?; + let ptr = crate::util::push_buffer(state, size, true)?; + Ok((ptr, crate::Buffer(self.pop_ref()))) + } + /// See [`Lua::create_table_with_capacity`] pub(crate) unsafe fn create_table_with_capacity(&self, narr: usize, nrec: usize) -> Result
{ let state = self.state(); diff --git a/src/util/mod.rs b/src/util/mod.rs index 4d07d3cb..f195e0ac 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -101,15 +101,13 @@ pub(crate) unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: // Uses 3 stack spaces (when protect), does not call checkstack. #[cfg(feature = "luau")] #[inline(always)] -pub(crate) unsafe fn push_buffer(state: *mut ffi::lua_State, b: &[u8], protect: bool) -> Result<()> { - let data = if protect { - protect_lua!(state, 0, 1, |state| ffi::lua_newbuffer(state, b.len()))? +pub(crate) unsafe fn push_buffer(state: *mut ffi::lua_State, size: usize, protect: bool) -> Result<*mut u8> { + let data = if protect || size > const { 1024 * 1024 * 1024 } { + protect_lua!(state, 0, 1, |state| ffi::lua_newbuffer(state, size))? } else { - ffi::lua_newbuffer(state, b.len()) + ffi::lua_newbuffer(state, size) }; - let buf = slice::from_raw_parts_mut(data as *mut u8, b.len()); - buf.copy_from_slice(b); - Ok(()) + Ok(data as *mut u8) } // Uses 3 stack spaces, does not call checkstack. diff --git a/tests/buffer.rs b/tests/buffer.rs index 8a04534e..54b10451 100644 --- a/tests/buffer.rs +++ b/tests/buffer.rs @@ -55,3 +55,14 @@ fn test_buffer_out_of_bounds_write() { let buf = lua.create_buffer(b"hello, world!").unwrap(); buf.write_bytes(14, b"!!"); } + +#[test] +fn create_large_buffer() { + let lua = Lua::new(); + let err = lua.create_buffer_with_capacity(1_073_741_824 + 1).unwrap_err(); // 1GB + assert!(err.to_string().contains("memory allocation error")); + + // Normal buffer is okay + let buf = lua.create_buffer_with_capacity(1024 * 1024).unwrap(); + assert_eq!(buf.len(), 1024 * 1024); +} From 774a63beceaca8031408ee1468429e5131f2c93d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 24 Aug 2025 11:29:01 +0100 Subject: [PATCH 513/635] Add `Buffer::cursor()` method This can be useful for providing access to buffers through core IO traits. --- src/buffer.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++++--- tests/buffer.rs | 55 ++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 4 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 52beffe3..f0977ccf 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,3 +1,5 @@ +use std::io; + #[cfg(feature = "serde")] use serde::ser::{Serialize, Serializer}; @@ -50,13 +52,18 @@ impl Buffer { #[track_caller] pub fn write_bytes(&self, offset: usize, bytes: &[u8]) { let lua = self.0.lua.lock(); - let data = unsafe { - let (buf, size) = self.as_raw_parts(&lua); - std::slice::from_raw_parts_mut(buf, size) - }; + let data = self.as_slice_mut(&lua); data[offset..offset + bytes.len()].copy_from_slice(bytes); } + /// Returns an adaptor implementing [`io::Read`], [`io::Write`] and [`io::Seek`] over the + /// buffer. + /// + /// Buffer operations are infallible, none of the read/write functions will return a Err. + pub fn cursor(self) -> impl io::Read + io::Write + io::Seek { + BufferCursor(self, 0) + } + pub(crate) fn as_slice(&self, lua: &RawLua) -> &[u8] { unsafe { let (buf, size) = self.as_raw_parts(lua); @@ -64,6 +71,14 @@ impl Buffer { } } + #[allow(clippy::mut_from_ref)] + fn as_slice_mut(&self, lua: &RawLua) -> &mut [u8] { + unsafe { + let (buf, size) = self.as_raw_parts(lua); + std::slice::from_raw_parts_mut(buf, size) + } + } + #[cfg(feature = "luau")] unsafe fn as_raw_parts(&self, lua: &RawLua) -> (*mut u8, usize) { let mut size = 0usize; @@ -78,6 +93,66 @@ impl Buffer { } } +struct BufferCursor(Buffer, usize); + +impl io::Read for BufferCursor { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let lua = self.0 .0.lua.lock(); + let data = self.0.as_slice(&lua); + if self.1 == data.len() { + return Ok(0); + } + let len = buf.len().min(data.len() - self.1); + buf[..len].copy_from_slice(&data[self.1..self.1 + len]); + self.1 += len; + Ok(len) + } +} + +impl io::Write for BufferCursor { + fn write(&mut self, buf: &[u8]) -> io::Result { + let lua = self.0 .0.lua.lock(); + let data = self.0.as_slice_mut(&lua); + if self.1 == data.len() { + return Ok(0); + } + let len = buf.len().min(data.len() - self.1); + data[self.1..self.1 + len].copy_from_slice(&buf[..len]); + self.1 += len; + Ok(len) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl io::Seek for BufferCursor { + fn seek(&mut self, pos: io::SeekFrom) -> io::Result { + let lua = self.0 .0.lua.lock(); + let data = self.0.as_slice(&lua); + let new_offset = match pos { + io::SeekFrom::Start(offset) => offset as i64, + io::SeekFrom::End(offset) => data.len() as i64 + offset, + io::SeekFrom::Current(offset) => self.1 as i64 + offset, + }; + if new_offset < 0 { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid seek to a negative position", + )); + } + if new_offset as usize > data.len() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid seek to a position beyond the end of the buffer", + )); + } + self.1 = new_offset as usize; + Ok(self.1 as u64) + } +} + #[cfg(feature = "serde")] impl Serialize for Buffer { fn serialize(&self, serializer: S) -> std::result::Result { diff --git a/tests/buffer.rs b/tests/buffer.rs index 54b10451..3f07569f 100644 --- a/tests/buffer.rs +++ b/tests/buffer.rs @@ -1,5 +1,7 @@ #![cfg(feature = "luau")] +use std::io::{Read, Seek, SeekFrom, Write}; + use mlua::{Lua, Result, Value}; #[test] @@ -66,3 +68,56 @@ fn create_large_buffer() { let buf = lua.create_buffer_with_capacity(1024 * 1024).unwrap(); assert_eq!(buf.len(), 1024 * 1024); } + +#[test] +fn test_buffer_cursor() -> Result<()> { + let lua = Lua::new(); + let mut cursor = lua.create_buffer(b"hello, world")?.cursor(); + + let mut data = Vec::new(); + cursor.read_to_end(&mut data)?; + assert_eq!(data, b"hello, world"); + + // No more data to read + let mut one = [0u8; 1]; + assert_eq!(cursor.read(&mut one)?, 0); + + // Seek to start + cursor.seek(SeekFrom::Start(0))?; + cursor.read_exact(&mut one)?; + assert_eq!(one, [b'h']); + + // Seek to end -5 + cursor.seek(SeekFrom::End(-5))?; + let mut five = [0u8; 5]; + cursor.read_exact(&mut five)?; + assert_eq!(&five, b"world"); + + // Seek to current -1 + cursor.seek(SeekFrom::Current(-1))?; + cursor.read_exact(&mut one)?; + assert_eq!(one, [b'd']); + + // Invalid seek + assert!(cursor.seek(SeekFrom::Current(-100)).is_err()); + assert!(cursor.seek(SeekFrom::End(1)).is_err()); + + // Write data + let buf = lua.create_buffer_with_capacity(100)?; + cursor = buf.clone().cursor(); + + cursor.write_all(b"hello, ...")?; + cursor.seek(SeekFrom::Current(-3))?; + cursor.write_all(b"Rust!")?; + + assert_eq!(&buf.read_bytes::<12>(0), b"hello, Rust!"); + + // Writing beyond the end of the buffer does nothing + cursor.seek(SeekFrom::End(0))?; + assert_eq!(cursor.write(b".")?, 0); + + // Flush is no-op + cursor.flush()?; + + Ok(()) +} From 347856b806bc6f8783f277e8e0ae339e414feed7 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 25 Aug 2025 12:16:40 +0100 Subject: [PATCH 514/635] Do not try to yield at non-yielable points in Luau interrupt In particular we cannot yeild across metamethod/C-call boundaries. This behaviour matches with Lua 5.3+ yielding from hooks only at safe points. Closes #632 --- src/state.rs | 11 +++++++---- tests/luau.rs | 9 +++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/state.rs b/src/state.rs index 1312c23c..af315feb 100644 --- a/src/state.rs +++ b/src/state.rs @@ -631,13 +631,13 @@ impl Lua { /// /// Any Luau code is guaranteed to call this handler "eventually" /// (in practice this can happen at any function call or at any loop iteration). + /// This is similar to `Lua::set_hook` but in more simplified form. /// /// The provided interrupt function can error, and this error will be propagated through /// the Luau code that was executing at the time the interrupt was triggered. /// Also this can be used to implement continuous execution limits by instructing Luau VM to - /// yield by returning [`VmState::Yield`]. - /// - /// This is similar to `Lua::set_hook` but in more simplified form. + /// yield by returning [`VmState::Yield`]. The yield will happen only at yieldable points + /// of execution (not across metamethod/C-call boundaries). /// /// # Example /// @@ -695,7 +695,10 @@ impl Lua { match result { VmState::Continue => {} VmState::Yield => { - ffi::lua_yield(state, 0); + // We can yield only at yieldable points, otherwise ignore and continue + if ffi::lua_isyieldable(state) != 0 { + ffi::lua_yield(state, 0); + } } } } diff --git a/tests/luau.rs b/tests/luau.rs index f073f068..3ab5b2d5 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -330,6 +330,15 @@ fn test_interrupts() -> Result<()> { assert_eq!(yield_count.load(Ordering::Relaxed), 7); assert_eq!(co.status(), ThreadStatus::Finished); + // Test no yielding at non-yieldable points + yield_count.store(0, Ordering::Relaxed); + let co = lua.create_thread(lua.create_function(|lua, arg: Value| { + (lua.load("return (function(x) return x end)(...)")).call::(arg) + })?)?; + let res = co.resume::("abc")?; + assert_eq!(res, "abc".to_string()); + assert_eq!(yield_count.load(Ordering::Relaxed), 3); + // // Test errors in interrupts // From 75c23e5853c4dac90346a51eab9c32c158d98a07 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 25 Aug 2025 12:53:43 +0100 Subject: [PATCH 515/635] Add `lua_cpcall` to Luau ffi (0.688+) --- mlua-sys/Cargo.toml | 2 +- mlua-sys/src/luau/lua.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 6e6cca62..674f060f 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -41,7 +41,7 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 548.1.0, < 548.2.0", optional = true } luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true } -luau0-src = { version = "0.15.4", optional = true } +luau0-src = { version = "0.15.6", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 8a55eef1..2d5b2bd5 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -235,6 +235,7 @@ unsafe extern "C-unwind" { ) -> c_int; pub fn lua_call(L: *mut lua_State, nargs: c_int, nresults: c_int); pub fn lua_pcall(L: *mut lua_State, nargs: c_int, nresults: c_int, errfunc: c_int) -> c_int; + pub fn lua_cpcall(L: *mut lua_State, f: lua_CFunction, ud: *mut c_void) -> c_int; // // Coroutine functions From 30735d5ff1f6e31287f8d7626c1b465656cd1775 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 25 Aug 2025 22:57:38 +0100 Subject: [PATCH 516/635] Fix thread recovery when pushing a bad arg We should not erase thread stack if a bad argument is pushed before resuming the thread. --- src/thread.rs | 4 ++-- tests/thread.rs | 23 ++++++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index c9349d23..e9142540 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -156,7 +156,6 @@ impl Thread { let thread_state = self.state(); unsafe { let _sg = StackGuard::new(state); - let _thread_sg = StackGuard::with_top(thread_state, 0); let nargs = args.push_into_stack_multi(&lua)?; if nargs > 0 { @@ -165,6 +164,7 @@ impl Thread { pushed_nargs += nargs; } + let _thread_sg = StackGuard::with_top(thread_state, 0); let (_, nresults) = self.resume_inner(&lua, pushed_nargs)?; check_stack(state, nresults + 1)?; ffi::lua_xmove(thread_state, state, nresults); @@ -192,12 +192,12 @@ impl Thread { let thread_state = self.state(); unsafe { let _sg = StackGuard::new(state); - let _thread_sg = StackGuard::with_top(thread_state, 0); check_stack(state, 1)?; error.push_into_stack(&lua)?; ffi::lua_xmove(state, thread_state, 1); + let _thread_sg = StackGuard::with_top(thread_state, 0); let (_, nresults) = self.resume_inner(&lua, ffi::LUA_RESUMEERROR)?; check_stack(state, nresults + 1)?; ffi::lua_xmove(thread_state, state, nresults); diff --git a/tests/thread.rs b/tests/thread.rs index 4cb6ab10..54a6be53 100644 --- a/tests/thread.rs +++ b/tests/thread.rs @@ -1,6 +1,6 @@ use std::panic::catch_unwind; -use mlua::{Error, Function, Lua, Result, Thread, ThreadStatus}; +use mlua::{Error, Function, IntoLua, Lua, Result, Thread, ThreadStatus, Value}; #[test] fn test_thread() -> Result<()> { @@ -252,3 +252,24 @@ fn test_thread_resume_error() -> Result<()> { Ok(()) } + +#[test] +fn test_thread_resume_bad_arg() -> Result<()> { + let lua = Lua::new(); + + struct BadArg; + + impl IntoLua for BadArg { + fn into_lua(self, _lua: &Lua) -> Result { + Err(Error::runtime("bad arg")) + } + } + + let f = lua.create_thread(lua.create_function(|_, ()| Ok("okay"))?)?; + let res = f.resume::<()>((123, BadArg)); + assert!(matches!(res, Err(Error::RuntimeError(msg)) if msg == "bad arg")); + let res = f.resume::(()).unwrap(); + assert_eq!(res, "okay"); + + Ok(()) +} From d399559d30ad3c20293b3c6a0aea24c776974b5c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 28 Aug 2025 18:41:24 +0100 Subject: [PATCH 517/635] Add `Lua::yield_with` to allow yielding Rust async functions and exchange values between Lua coroutine and Rust. This functionality is similar to `coroutine.yield` and `coroutine.resume` without C restrictions. --- src/state.rs | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ src/state/raw.rs | 34 +++++++++++++---- src/thread.rs | 6 +-- tests/async.rs | 34 ++++++++++++++++- 4 files changed, 158 insertions(+), 12 deletions(-) diff --git a/src/state.rs b/src/state.rs index af315feb..401f29b2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -37,6 +37,7 @@ use crate::{buffer::Buffer, chunk::Compiler}; use { crate::types::LightUserData, std::future::{self, Future}, + std::task::Poll, }; #[cfg(feature = "serde")] @@ -2079,6 +2080,101 @@ impl Lua { LightUserData(&ASYNC_POLL_TERMINATE as *const u8 as *mut std::os::raw::c_void) } + #[cfg(feature = "async")] + #[inline(always)] + pub(crate) fn poll_yield() -> LightUserData { + static ASYNC_POLL_YIELD: u8 = 0; + LightUserData(&ASYNC_POLL_YIELD as *const u8 as *mut std::os::raw::c_void) + } + + /// Suspends the current async function, returning the provided arguments to caller. + /// + /// This function is similar to [`coroutine.yield`] but allow yeilding Rust functions + /// and passing values to the caller. + /// Please note that you cannot cross [`Thread`] boundaries (e.g. calling `yield_with` on one + /// thread and resuming on another). + /// + /// # Examples + /// + /// Async iterator: + /// + /// ``` + /// # use mlua::{Lua, Result}; + /// + /// async fn generator(lua: Lua, _: ()) -> Result<()> { + /// for i in 0..10 { + /// lua.yield_with::<()>(i).await?; + /// } + /// Ok(()) + /// } + /// + /// fn main() -> Result<()> { + /// let lua = Lua::new(); + /// lua.globals().set("generator", lua.create_async_function(generator)?)?; + /// + /// lua.load(r#" + /// local n = 0 + /// for i in coroutine.wrap(generator) do + /// n = n + i + /// end + /// assert(n == 45) + /// "#) + /// .exec() + /// } + /// ``` + /// + /// Exchange values on yield: + /// + /// ``` + /// # use mlua::{Lua, Result, Value}; + /// + /// async fn pingpong(lua: Lua, mut val: i32) -> Result<()> { + /// loop { + /// val = lua.yield_with::(val).await? + 1; + /// } + /// Ok(()) + /// } + /// + /// # fn main() -> Result<()> { + /// let lua = Lua::new(); + /// + /// let co = lua.create_thread(lua.create_async_function(pingpong)?)?; + /// assert_eq!(co.resume::(1)?, 1); + /// assert_eq!(co.resume::(2)?, 3); + /// assert_eq!(co.resume::(3)?, 4); + /// + /// # Ok(()) + /// # } + /// ``` + /// + /// [`coroutine.yield`]: https://www.lua.org/manual/5.4/manual.html#pdf-coroutine.yield + #[cfg(feature = "async")] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + pub async fn yield_with(&self, args: impl IntoLuaMulti) -> Result { + let mut args = Some(args.into_lua_multi(self)?); + future::poll_fn(move |_cx| match args.take() { + Some(args) => unsafe { + let lua = self.lock(); + lua.push(Self::poll_yield())?; // yield marker + if args.len() <= 1 { + lua.push(args.front())?; + } else { + lua.push(lua.create_sequence_from(&args)?)?; + } + lua.push(args.len())?; + Poll::Pending + }, + None => unsafe { + let lua = self.lock(); + let state = lua.state(); + let _sg = StackGuard::with_top(state, 0); + let nvals = ffi::lua_gettop(state); + Poll::Ready(R::from_stack_multi(nvals, &lua)) + }, + }) + .await + } + /// Returns a weak reference to the Lua instance. /// /// This is useful for creating a reference to the Lua instance that does not prevent it from diff --git a/src/state/raw.rs b/src/state/raw.rs index afcc95e4..ca671ac9 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -1278,6 +1278,13 @@ impl RawLua { let mut ctx = Context::from_waker(rawlua.waker()); match fut.as_mut().map(|fut| fut.as_mut().poll(&mut ctx)) { Some(Poll::Pending) => { + let fut_nvals = ffi::lua_gettop(state); + if fut_nvals >= 3 && ffi::lua_tolightuserdata(state, -3) == Lua::poll_yield().0 { + // We have some values to yield + ffi::lua_pushnil(state); + ffi::lua_replace(state, -4); + return Ok(3); + } ffi::lua_pushnil(state); ffi::lua_pushlightuserdata(state, Lua::poll_pending().0); Ok(2) @@ -1348,6 +1355,7 @@ impl RawLua { local poll = get_poll(...) local nres, res, res2 = poll() while true do + -- Poll::Ready branch, `nres` is the number of results if nres ~= nil then if nres == 0 then return @@ -1363,10 +1371,20 @@ impl RawLua { return unpack(res, nres) end end - -- `res` is a "pending" value - -- `yield` can return a signal to drop the future that we should propagate - -- to the poller - nres, res, res2 = poll(yield(res)) + + -- Poll::Pending branch + if res2 == nil then + -- `res` is a "pending" value + -- `yield` can return a signal to drop the future that we should propagate + -- to the poller + nres, res, res2 = poll(yield(res)) + elseif res2 == 0 then + nres, res, res2 = poll(yield()) + elseif res2 == 1 then + nres, res, res2 = poll(yield(res)) + else + nres, res, res2 = poll(yield(unpack(res, res2))) + end end "#, ) @@ -1378,14 +1396,14 @@ impl RawLua { #[cfg(feature = "async")] #[inline] - pub(crate) unsafe fn waker(&self) -> &Waker { - (*self.extra.get()).waker.as_ref() + pub(crate) fn waker(&self) -> &Waker { + unsafe { (*self.extra.get()).waker.as_ref() } } #[cfg(feature = "async")] #[inline] - pub(crate) unsafe fn set_waker(&self, waker: NonNull) -> NonNull { - mem::replace(&mut (*self.extra.get()).waker, waker) + pub(crate) fn set_waker(&self, waker: NonNull) -> NonNull { + unsafe { mem::replace(&mut (*self.extra.get()).waker, waker) } } } diff --git a/src/thread.rs b/src/thread.rs index e9142540..0c673390 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -604,7 +604,7 @@ impl Future for AsyncThread { if status.is_yielded() { if !(nresults == 1 && is_poll_pending(thread_state)) { - // Ignore value returned via yield() + // Ignore values returned via yield() cx.waker().wake_by_ref(); } return Poll::Pending; @@ -635,7 +635,7 @@ struct WakerGuard<'lua, 'a> { impl<'lua, 'a> WakerGuard<'lua, 'a> { #[inline] pub fn new(lua: &'lua RawLua, waker: &'a Waker) -> Result> { - let prev = unsafe { lua.set_waker(NonNull::from(waker)) }; + let prev = lua.set_waker(NonNull::from(waker)); Ok(WakerGuard { lua, prev, @@ -647,7 +647,7 @@ impl<'lua, 'a> WakerGuard<'lua, 'a> { #[cfg(feature = "async")] impl Drop for WakerGuard<'_, '_> { fn drop(&mut self) { - unsafe { self.lua.set_waker(self.prev) }; + self.lua.set_waker(self.prev); } } diff --git a/tests/async.rs b/tests/async.rs index 4a8a5b54..ad89344c 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -8,7 +8,7 @@ use futures_util::stream::TryStreamExt; use tokio::sync::Mutex; use mlua::{ - Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, UserData, + Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, ThreadStatus, UserData, UserDataMethods, UserDataRef, Value, }; @@ -667,3 +667,35 @@ async fn test_async_hook() -> Result<()> { Ok(()) } + +#[test] +fn test_async_yield_with() -> Result<()> { + let lua = Lua::new(); + + let func = lua.create_async_function(|lua, (mut a, mut b): (i32, i32)| async move { + let zero = lua.yield_with::(()).await?; + assert!(zero.is_empty()); + let one = lua.yield_with::(a + b).await?; + assert_eq!(one.len(), 1); + + for _ in 0..3 { + (a, b) = lua.yield_with((a + b, a * b)).await?; + } + Ok((0, 0)) + })?; + + let thread = lua.create_thread(func)?; + + let zero = thread.resume::((2, 3))?; // function arguments + assert!(zero.is_empty()); + let one = thread.resume::(())?; // value of "zero" is passed here + assert_eq!(one, 5); + + assert_eq!(thread.resume::<(i32, i32)>(1)?, (5, 6)); // value of "one" is passed here + assert_eq!(thread.resume::<(i32, i32)>((10, 11))?, (21, 110)); + assert_eq!(thread.resume::<(i32, i32)>((11, 12))?, (23, 132)); + assert_eq!(thread.resume::<(i32, i32)>((12, 13))?, (0, 0)); + assert_eq!(thread.status(), ThreadStatus::Finished); + + Ok(()) +} From f06d0020ea58459c7c5d287e31611f69e0d9197f Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 28 Aug 2025 23:49:22 +0100 Subject: [PATCH 518/635] Add test to emulate method through field --- tests/userdata.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/userdata.rs b/tests/userdata.rs index 9dc93d99..f63c5d59 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -525,6 +525,11 @@ fn test_fields() -> Result<()> { Ok(()) }); + // Field that emulates method + fields.add_field_function_get("val_fget", |lua, ud| { + lua.create_function(move |_, ()| Ok(ud.borrow::()?.0)) + }); + // Use userdata "uservalue" storage fields.add_field_function_get("uval", |_, ud| ud.user_value::>()); fields.add_field_function_set("uval", |_, ud, s: Option| ud.set_user_value(s)); @@ -537,6 +542,10 @@ fn test_fields() -> Result<()> { }) }) } + + fn add_methods>(methods: &mut M) { + methods.add_method("dummy", |_, _, ()| Ok(())); + } } globals.set("ud", MyUserData(7))?; @@ -546,6 +555,7 @@ fn test_fields() -> Result<()> { assert(ud.val == 7) ud.val = 10 assert(ud.val == 10) + assert(ud:val_fget() == 10) assert(ud.uval == nil) ud.uval = "hello" From e1ee4058a6d8977b4f8de51a491ca8d407cc93af Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 28 Aug 2025 23:55:58 +0100 Subject: [PATCH 519/635] Add new benchmark to measure complex userdata method calls --- benches/benchmark.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 6d53d9b0..10b16e8a 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -366,6 +366,39 @@ fn userdata_call_method(c: &mut Criterion) { }); } +// A userdata method call that goes through an implicit `__index` function +fn userdata_call_method_complex(c: &mut Criterion) { + struct UserData(u64); + impl LuaUserData for UserData { + fn register(registry: &mut LuaUserDataRegistry) { + registry.add_field_method_get("val", |_, this| Ok(this.0)); + registry.add_method_mut("inc_by", |_, this, by: u64| { + this.0 += by; + Ok(this.0) + }); + } + } + + let lua = Lua::new(); + let ud = lua.create_userdata(UserData(0)).unwrap(); + let inc_by = lua + .load("function(ud, s) return ud:inc_by(s) end") + .eval::() + .unwrap(); + + c.bench_function("userdata [call method complex]", |b| { + b.iter_batched( + || { + collect_gc_twice(&lua); + }, + |_| { + inc_by.call::<()>((&ud, 1)).unwrap(); + }, + BatchSize::SmallInput, + ); + }); +} + fn userdata_async_call_method(c: &mut Criterion) { struct UserData(i64); impl LuaUserData for UserData { @@ -430,6 +463,7 @@ criterion_group! { userdata_create, userdata_call_index, userdata_call_method, + userdata_call_method_complex, userdata_async_call_method, } From 44f49e35d69a4e606e00ab9a3453495506053f56 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 29 Aug 2025 00:18:06 +0100 Subject: [PATCH 520/635] Update CHANGELOG --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76bbaf84..7469129c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## v0.11.3 (TBC, 2025) + +- Add `Lua::yield_with` to use as `coroutine.yield` functional replacement in async functions for any Lua +- Do not try to yield at non-yielable points in Luau interrupt (#632) +- Add `Buffer::cursor` method (Luau) +- Add `Lua::create_buffer_with_capacity` method (Luau) +- Make Lua reference values cheap to clone (only increments ref count) +- Fix panic on large (>67M entries) table creation + ## v0.11.2 (Aug 10, 2025) - Faster stack push for `Variadic` From 13ff0ca798b116d436e1a79f5eff4b008c97fced Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 29 Aug 2025 23:11:21 +0100 Subject: [PATCH 521/635] v0.11.3 --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7469129c..b3b1981f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## v0.11.3 (TBC, 2025) +## v0.11.3 (Aug 30, 2025) - Add `Lua::yield_with` to use as `coroutine.yield` functional replacement in async functions for any Lua - Do not try to yield at non-yielable points in Luau interrupt (#632) diff --git a/Cargo.toml b/Cargo.toml index c9da4bff..c08e03b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.11.2" # remember to update mlua_derive +version = "0.11.3" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.79.0" edition = "2021" From c70a636ca93ed258b30967fc161f649ad43336bd Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 30 Aug 2025 12:51:53 +0100 Subject: [PATCH 522/635] Remove newlines from yield_with examples --- src/state.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/state.rs b/src/state.rs index 401f29b2..1837fc05 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2100,7 +2100,7 @@ impl Lua { /// /// ``` /// # use mlua::{Lua, Result}; - /// + /// # /// async fn generator(lua: Lua, _: ()) -> Result<()> { /// for i in 0..10 { /// lua.yield_with::<()>(i).await?; @@ -2127,7 +2127,7 @@ impl Lua { /// /// ``` /// # use mlua::{Lua, Result, Value}; - /// + /// # /// async fn pingpong(lua: Lua, mut val: i32) -> Result<()> { /// loop { /// val = lua.yield_with::(val).await? + 1; From 5d27cb91b281a5d670964835b86ffcba868382c6 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 1 Sep 2025 22:51:09 +0100 Subject: [PATCH 523/635] Add optional `__namecall` optimization for Luau Add `UserDataRegistry::enable_namecall()` hint to set `__namecall` metamethod to enable Luau-specific method resolution optimization. --- benches/benchmark.rs | 3 +++ src/state/raw.rs | 19 ++++++++++++++++--- src/types.rs | 7 +++++-- src/userdata/registry.rs | 22 +++++++++++++++++++++ src/userdata/util.rs | 41 ++++++++++++++++++++++++++++++++++++++++ src/util/error.rs | 2 ++ tests/userdata.rs | 38 +++++++++++++++++++++++++++++++++++++ 7 files changed, 127 insertions(+), 5 deletions(-) diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 10b16e8a..297bb1d6 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -376,6 +376,9 @@ fn userdata_call_method_complex(c: &mut Criterion) { this.0 += by; Ok(this.0) }); + + #[cfg(feature = "luau")] + registry.enable_namecall(); } } diff --git a/src/state/raw.rs b/src/state/raw.rs index ca671ac9..09831fc7 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -28,8 +28,8 @@ use crate::userdata::{ use crate::util::{ assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state, get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, pop_error, - push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name, - StackGuard, WrappedFailure, + push_internal_userdata, push_string, push_table, push_userdata, rawset_field, safe_pcall, safe_xpcall, + short_type_name, StackGuard, WrappedFailure, }; use crate::value::{Nil, Value}; @@ -928,7 +928,7 @@ impl RawLua { // We generate metatable first to make sure it *always* available when userdata pushed let mt_id = get_metatable_id()?; let protect = !self.unlikely_memory_error(); - crate::util::push_userdata(state, data, protect)?; + push_userdata(state, data, protect)?; ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, mt_id); ffi::lua_setmetatable(state, -2); @@ -1056,6 +1056,18 @@ impl RawLua { field_setters_index = Some(ffi::lua_absindex(state, -1)); } + // Create methods namecall table + #[cfg_attr(not(feature = "luau"), allow(unused_mut))] + let mut methods_map = None; + #[cfg(feature = "luau")] + if registry.enable_namecall { + let map: &mut rustc_hash::FxHashMap<_, crate::types::CallbackPtr> = + methods_map.get_or_insert_with(Default::default); + for (k, m) in ®istry.methods { + map.insert(k.as_bytes().to_vec(), &**m); + } + } + let mut methods_index = None; let methods_nrec = registry.methods.len(); #[cfg(feature = "async")] @@ -1103,6 +1115,7 @@ impl RawLua { field_getters_index, field_setters_index, methods_index, + methods_map, )?; // Update stack guard to keep metatable after return diff --git a/src/types.rs b/src/types.rs index ef1b90f2..79fc72c2 100644 --- a/src/types.rs +++ b/src/types.rs @@ -38,10 +38,13 @@ unsafe impl Send for LightUserData {} unsafe impl Sync for LightUserData {} #[cfg(feature = "send")] -pub(crate) type Callback = Box Result + Send + 'static>; +type CallbackFn<'a> = dyn Fn(&RawLua, c_int) -> Result + Send + 'a; #[cfg(not(feature = "send"))] -pub(crate) type Callback = Box Result + 'static>; +type CallbackFn<'a> = dyn Fn(&RawLua, c_int) -> Result + 'a; + +pub(crate) type Callback = Box>; +pub(crate) type CallbackPtr = *const CallbackFn<'static>; pub(crate) type ScopedCallback<'s> = Box Result + 's>; diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index d6d6827b..b405a49b 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -56,6 +56,9 @@ pub(crate) struct RawUserDataRegistry { pub(crate) destructor: ffi::lua_CFunction, pub(crate) type_id: Option, pub(crate) type_name: StdString, + + #[cfg(feature = "luau")] + pub(crate) enable_namecall: bool, } impl UserDataType { @@ -100,6 +103,8 @@ impl UserDataRegistry { destructor: super::util::destroy_userdata_storage::, type_id: r#type.type_id(), type_name: short_type_name::(), + #[cfg(feature = "luau")] + enable_namecall: false, }; UserDataRegistry { @@ -110,6 +115,23 @@ impl UserDataRegistry { } } + /// Enables support for the namecall optimization in Luau. + /// + /// This enables methods resolution optimization in Luau for complex userdata types with methods + /// and field getters. When enabled, Luau will use a faster lookup path for method calls when a + /// specific syntax is used (e.g. `obj:method()`. + /// + /// This optimization does not play well with async methods, custom `__index` metamethod and + /// field getters as functions. So, it is disabled by default. + /// + /// Use with caution. + #[doc(hidden)] + #[cfg(feature = "luau")] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn enable_namecall(&mut self) { + self.raw.enable_namecall = true; + } + fn box_method(&self, name: &str, method: M) -> Callback where M: Fn(&Lua, &T, A) -> Result + MaybeSend + 'static, diff --git a/src/userdata/util.rs b/src/userdata/util.rs index c443de47..d42eaaed 100644 --- a/src/userdata/util.rs +++ b/src/userdata/util.rs @@ -4,8 +4,11 @@ use std::marker::PhantomData; use std::os::raw::c_int; use std::ptr; +use rustc_hash::FxHashMap; + use super::UserDataStorage; use crate::error::{Error, Result}; +use crate::types::CallbackPtr; use crate::util::{get_userdata, rawget_field, rawset_field, take_userdata}; // This is a trick to check if a type is `Sync` or not. @@ -244,6 +247,7 @@ pub(crate) unsafe fn init_userdata_metatable( field_getters: Option, field_setters: Option, methods: Option, + _methods_map: Option, CallbackPtr>>, // Used only in Luau for `__namecall` ) -> Result<()> { if field_getters.is_some() || methods.is_some() { // Push `__index` generator function @@ -267,6 +271,13 @@ pub(crate) unsafe fn init_userdata_metatable( } rawset_field(state, metatable, "__index")?; + + #[cfg(feature = "luau")] + if let Some(methods_map) = _methods_map { + // In Luau we can speedup method calls by providing a dedicated `__namecall` metamethod + push_userdata_metatable_namecall(state, methods_map)?; + rawset_field(state, metatable, "__namecall")?; + } } if let Some(field_setters) = field_setters { @@ -425,6 +436,36 @@ unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result }) } +#[cfg(feature = "luau")] +unsafe fn push_userdata_metatable_namecall( + state: *mut ffi::lua_State, + methods_map: FxHashMap, CallbackPtr>, +) -> Result<()> { + unsafe extern "C-unwind" fn namecall(state: *mut ffi::lua_State) -> c_int { + let name = ffi::lua_namecallatom(state, ptr::null_mut()); + if name.is_null() { + ffi::luaL_error(state, cstr!("attempt to call an unknown method")); + } + let name_cs = std::ffi::CStr::from_ptr(name); + let methods_map = get_userdata::, CallbackPtr>>(state, ffi::lua_upvalueindex(1)); + let callback_ptr = match (*methods_map).get(name_cs.to_bytes()) { + Some(ptr) => *ptr, + #[rustfmt::skip] + None => ffi::luaL_error(state, cstr!("attempt to call an unknown method '%s'"), name), + }; + crate::state::callback_error_ext(state, ptr::null_mut(), true, |extra, nargs| { + let rawlua = (*extra).raw_lua(); + (*callback_ptr)(rawlua, nargs) + }) + } + + // Automatic destructor is provided for any Luau userdata + crate::util::push_userdata(state, methods_map, true)?; + protect_lua!(state, 1, 1, |state| { + ffi::lua_pushcclosured(state, namecall, cstr!("__namecall"), 1); + }) +} + // This method is called by Lua GC when it's time to collect the userdata. // // This method is usually used to collect internal userdata. diff --git a/src/util/error.rs b/src/util/error.rs index 8629f112..03df8f36 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -402,6 +402,8 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<( "__ipairs", #[cfg(feature = "luau")] "__iter", + #[cfg(feature = "luau")] + "__namecall", #[cfg(feature = "lua54")] "__close", ] { diff --git a/tests/userdata.rs b/tests/userdata.rs index f63c5d59..2df1246c 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -1307,3 +1307,41 @@ fn test_userdata_wrappers() -> Result<()> { Ok(()) } + +#[cfg(feature = "luau")] +#[test] +fn test_userdata_namecall() -> Result<()> { + let lua = Lua::new(); + + struct MyUserData; + + impl UserData for MyUserData { + fn register(registry: &mut mlua::UserDataRegistry) { + registry.add_method("method", |_, _, ()| Ok("method called")); + registry.add_field_method_get("field", |_, _| Ok("field value")); + + registry.add_meta_method(MetaMethod::Index, |_, _, key: StdString| Ok(key)); + + registry.enable_namecall(); + } + } + + let ud = lua.create_userdata(MyUserData)?; + lua.globals().set("ud", &ud)?; + lua.load( + r#" + assert(ud:method() == "method called") + assert(ud.field == "field value") + assert(ud.dynamic_field == "dynamic_field") + local ok, err = pcall(function() return ud:dynamic_field() end) + assert(tostring(err):find("attempt to call an unknown method 'dynamic_field'") ~= nil) + "#, + ) + .exec()?; + + ud.destroy()?; + let err = lua.load("ud:method()").exec().unwrap_err(); + assert!(err.to_string().contains("userdata has been destructed")); + + Ok(()) +} From 537cc995f69eb2eeb446ea3212b9d948036ff5d2 Mon Sep 17 00:00:00 2001 From: Andrew Dunbar Date: Thu, 4 Sep 2025 22:59:24 +0900 Subject: [PATCH 524/635] Copyedit English in README.md (#639) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f6ba9850..80d08df7 100644 --- a/README.md +++ b/README.md @@ -17,14 +17,14 @@ [Benchmarks]: https://github.com/khvzak/script-bench-rs [FAQ]: FAQ.md -`mlua` is a set of bindings to the [Lua](https://www.lua.org) programming language for Rust with a goal to provide a +`mlua` is a set of bindings to the [Lua](https://www.lua.org) programming language for Rust with a goal of providing a _safe_ (as much as possible), high level, easy to use, practical and flexible API. Started as an `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Luau] and allows writing native Lua modules in Rust as well as using Lua in a standalone mode. `mlua` is tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platforms and cross-compilation to `aarch64` (other targets are also supported). -WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua/Luau versions excluding JIT. +WebAssembly (WASM) is supported through the `wasm32-unknown-emscripten` target for all Lua/Luau versions excluding JIT. [GitHub Actions]: https://github.com/mlua-rs/mlua/actions [Luau]: https://luau.org @@ -33,7 +33,7 @@ WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for a ### Feature flags -`mlua` uses feature flags to reduce the amount of dependencies and compiled code, and allow to choose only required set of features. +`mlua` uses feature flags to reduce the number of dependencies and compiled code, and allow choosing only the required set of features. Below is a list of the available feature flags. By default `mlua` does not enable any features. * `lua54`: enable Lua [5.4] support @@ -270,7 +270,7 @@ remain usable after a user generated panic, and such panics should not break int leak Lua stack space. This is mostly important to safely use `mlua` types in Drop impls, as you should not be using panics for general error handling. -Below is a list of `mlua` behaviors that should be considered a bug. +Below is a list of `mlua` behaviors that should be considered bugs. If you encounter them, a bug report would be very welcome: + If you can cause UB with `mlua` without typing the word "unsafe", this is a bug. From 40b507c3ecd3f067c77b463aaf846c7a050db4f4 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 4 Sep 2025 13:26:16 +0100 Subject: [PATCH 525/635] Add `ObjectLike::get_path` helper --- src/table.rs | 12 +- src/traits.rs | 44 ++++++- src/userdata/object.rs | 13 ++- src/util/mod.rs | 2 + src/util/path.rs | 255 +++++++++++++++++++++++++++++++++++++++++ tests/table.rs | 81 +++++++++++++ tests/userdata.rs | 19 ++- 7 files changed, 421 insertions(+), 5 deletions(-) create mode 100644 src/util/path.rs diff --git a/src/table.rs b/src/table.rs index 93e68552..b182a6ca 100644 --- a/src/table.rs +++ b/src/table.rs @@ -6,7 +6,7 @@ use std::string::String as StdString; use crate::error::{Error, Result}; use crate::function::Function; -use crate::state::{LuaGuard, RawLua}; +use crate::state::{LuaGuard, RawLua, WeakLua}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike}; use crate::types::{Integer, LuaType, ValueRef}; use crate::util::{assert_stack, check_stack, get_metatable_ptr, StackGuard}; @@ -943,6 +943,16 @@ impl ObjectLike for Table { fn to_string(&self) -> Result { Value::Table(Table(self.0.clone())).to_string() } + + #[inline] + fn to_value(&self) -> Value { + Value::Table(self.clone()) + } + + #[inline] + fn weak_lua(&self) -> &WeakLua { + &self.0.lua + } } /// A wrapped [`Table`] with customized serialization behavior. diff --git a/src/traits.rs b/src/traits.rs index 47dee4c3..231d1512 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -5,9 +5,9 @@ use std::sync::Arc; use crate::error::{Error, Result}; use crate::multi::MultiValue; use crate::private::Sealed; -use crate::state::{Lua, RawLua}; +use crate::state::{Lua, RawLua, WeakLua}; use crate::types::MaybeSend; -use crate::util::{check_stack, short_type_name}; +use crate::util::{check_stack, parse_lookup_path, short_type_name}; use crate::value::Value; #[cfg(feature = "async")] @@ -200,10 +200,50 @@ pub trait ObjectLike: Sealed { where R: FromLuaMulti; + /// Look up a value by a path of keys. + /// + /// The syntax is similar to accessing nested tables in Lua, with additional support for + /// `?` operator to perform safe navigation. + /// + /// For example, the path `a[1].c` is equivalent to `table.a[1].c` in Lua. + /// With `?` operator, `a[1]?.c` is equivalent to `table.a[1] and table.a[1].c or nil` in Lua. + /// + /// Bracket notation rules: + /// - `[123]` - integer keys + /// - `["string key"]` or `['string key']` - string keys (must be quoted) + /// - String keys support escape sequences: `\"`, `\'`, `\\` + fn get_path(&self, path: &str) -> Result { + let mut current = self.to_value(); + for (key, safe_nil) in parse_lookup_path(path)? { + current = match current { + Value::Table(table) => table.get::(key), + Value::UserData(ud) => ud.get::(key), + _ => { + let type_name = current.type_name(); + let err = format!("attempt to index a {type_name} value with key '{key}'"); + Err(Error::runtime(err)) + } + }?; + if safe_nil && (current == Value::Nil || current == Value::NULL) { + break; + } + } + + let lua = self.weak_lua().lock(); + V::from_lua(current, lua.lua()) + } + /// Converts the object to a string in a human-readable format. /// /// This might invoke the `__tostring` metamethod. fn to_string(&self) -> Result; + + /// Converts the object to a Lua value. + fn to_value(&self) -> Value; + + /// Gets a reference to the associated Lua state. + #[doc(hidden)] + fn weak_lua(&self) -> &WeakLua; } /// A trait for types that can be used as Lua functions. diff --git a/src/userdata/object.rs b/src/userdata/object.rs index c665a51f..cc9543eb 100644 --- a/src/userdata/object.rs +++ b/src/userdata/object.rs @@ -1,6 +1,7 @@ use std::string::String as StdString; use crate::error::{Error, Result}; +use crate::state::WeakLua; use crate::table::Table; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike}; use crate::userdata::AnyUserData; @@ -88,6 +89,16 @@ impl ObjectLike for AnyUserData { #[inline] fn to_string(&self) -> Result { - Value::UserData(AnyUserData(self.0.clone())).to_string() + Value::UserData(self.clone()).to_string() + } + + #[inline] + fn to_value(&self) -> Value { + Value::UserData(self.clone()) + } + + #[inline] + fn weak_lua(&self) -> &WeakLua { + &self.0.lua } } diff --git a/src/util/mod.rs b/src/util/mod.rs index f195e0ac..f8ebf693 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -9,6 +9,7 @@ pub(crate) use error::{ error_traceback, error_traceback_thread, init_error_registry, pop_error, protect_lua_call, protect_lua_closure, WrappedFailure, }; +pub(crate) use path::parse_path as parse_lookup_path; pub(crate) use short_names::short_type_name; pub(crate) use types::TypeKey; pub(crate) use userdata::{ @@ -327,6 +328,7 @@ pub(crate) fn linenumber_to_usize(n: c_int) -> Option { } mod error; +mod path; mod short_names; mod types; mod userdata; diff --git a/src/util/path.rs b/src/util/path.rs new file mode 100644 index 00000000..35cd1f21 --- /dev/null +++ b/src/util/path.rs @@ -0,0 +1,255 @@ +use std::borrow::Cow; +use std::fmt; +use std::iter::Peekable; +use std::str::CharIndices; + +use crate::error::{Error, Result}; +use crate::state::Lua; +use crate::traits::IntoLua; +use crate::types::Integer; +use crate::value::Value; + +#[derive(Debug)] +pub(crate) enum PathKey<'a> { + Str(Cow<'a, str>), + Int(Integer), +} + +impl fmt::Display for PathKey<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + PathKey::Str(s) => write!(f, "{}", s), + PathKey::Int(i) => write!(f, "{}", i), + } + } +} + +impl IntoLua for PathKey<'_> { + fn into_lua(self, lua: &Lua) -> Result { + match self { + PathKey::Str(s) => Ok(Value::String(lua.create_string(s.as_ref())?)), + PathKey::Int(i) => Ok(Value::Integer(i)), + } + } +} + +// Parses a path like `a.b[3]?.c["d"]` into segments of `(key, safe_nil)`. +pub(crate) fn parse_path<'a>(path: &'a str) -> Result, bool)>> { + fn read_ident<'a>(path: &'a str, chars: &mut Peekable>) -> (Cow<'a, str>, bool) { + let mut safe_nil = false; + let start = chars.peek().map(|&(i, _)| i).unwrap_or(path.len()); + let mut end = start; + while let Some(&(pos, c)) = chars.peek() { + if c == '.' || c == '?' || c.is_ascii_whitespace() || c == '[' { + if c == '?' { + safe_nil = true; + chars.next(); // consume '?' + } + break; + } + end = pos + c.len_utf8(); + chars.next(); + } + (Cow::Borrowed(&path[start..end]), safe_nil) + } + + let mut segments = Vec::new(); + let mut chars = path.char_indices().peekable(); + while let Some(&(pos, next)) = chars.peek() { + match next { + '.' => { + // Dot notation: identifier + chars.next(); + let (key, safe_nil) = read_ident(path, &mut chars); + if key.is_empty() { + return Err(Error::runtime(format!("empty key in path at position {pos}"))); + } + segments.push((PathKey::Str(key), safe_nil)); + } + '[' => { + // Bracket notation: either integer or quoted string + chars.next(); + let key = match chars.peek() { + Some(&(pos, c @ '0'..='9' | c @ '-')) => { + // Integer key + let negative = c == '-'; + if negative { + chars.next(); // consume '-' + } + let mut num: Option = None; + while let Some(&(_, c @ '0'..='9')) = chars.peek() { + let new_num = num + .unwrap_or(0) + .checked_mul(10) + .and_then(|n| n.checked_add((c as u8 - b'0') as Integer)) + .ok_or_else(|| { + Error::runtime(format!("integer overflow in path at position {pos}")) + })?; + num = Some(new_num); + chars.next(); // consume digit + } + match num { + Some(n) if negative => PathKey::Int(-n), + Some(n) => PathKey::Int(n), + None => { + let err = format!("invalid integer in path at position {pos}"); + return Err(Error::runtime(err)); + } + } + } + Some((_, '\'' | '"')) => { + // Quoted string + PathKey::Str(unquote_string(path, &mut chars)?) + } + Some((_, ']')) => { + return Err(Error::runtime(format!("empty key in path at position {pos}"))); + } + Some((pos, c)) => { + let err = format!("unexpected character '{c}' in path at position {pos}"); + return Err(Error::runtime(err)); + } + None => { + return Err(Error::runtime("unexpected end of path")); + } + }; + // Expect closing bracket + let mut safe_nil = false; + match chars.next() { + Some((_, ']')) => { + // Check for optional safe-nil operator + if let Some(&(_, '?')) = chars.peek() { + safe_nil = true; + chars.next(); // consume '?' + } + } + Some((pos, c)) => { + let err = format!("expected ']' in path at position {pos}, found '{c}'"); + return Err(Error::runtime(err)); + } + None => { + return Err(Error::runtime("unexpected end of path")); + } + } + segments.push((key, safe_nil)); + } + c if c.is_ascii_whitespace() => { + chars.next(); // Skip whitespace + } + _ if segments.is_empty() => { + // First segment without dot/bracket notation + let (key_cow, safe_nil) = read_ident(path, &mut chars); + if key_cow.is_empty() { + return Err(Error::runtime(format!("empty key in path at position {pos}"))); + } + segments.push((PathKey::Str(key_cow), safe_nil)); + } + c => { + let err = format!("unexpected character '{c}' in path at position {pos}"); + return Err(Error::runtime(err)); + } + } + } + Ok(segments) +} + +fn unquote_string<'a>(path: &'a str, chars: &mut Peekable>) -> Result> { + let (start_pos, first_quote) = chars.next().unwrap(); + let mut result = String::new(); + loop { + match chars.next() { + Some((pos, '\\')) => { + if result.is_empty() { + // First escape found, copy everything up to this point + result.push_str(&path[start_pos + 1..pos]); + } + match chars.next() { + Some((_, '\\')) => result.push('\\'), + Some((_, '"')) => result.push('"'), + Some((_, '\'')) => result.push('\''), + Some((_, other)) => { + result.push('\\'); + result.push(other); + } + None => continue, // will be handled by outer loop + } + } + Some((pos, c)) if c == first_quote => { + if !result.is_empty() { + return Ok(Cow::Owned(result)); + } + // No escapes, return borrowed slice + return Ok(Cow::Borrowed(&path[start_pos + 1..pos])); + } + Some((_, c)) => { + if !result.is_empty() { + result.push(c); + } + // If no escapes yet, continue tracking for potential borrowed slice + } + None => { + let err = format!("unexpected end of string at position {start_pos}"); + return Err(Error::runtime(err)); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::{parse_path, PathKey}; + + #[test] + fn test_parse_path() { + // Test valid paths + let path = parse_path("a.b[3]?.c['d']").unwrap(); + assert_eq!(path.len(), 5); + assert!(matches!(path[0], (PathKey::Str(ref s), false) if s == "a")); + assert!(matches!(path[1], (PathKey::Str(ref s), false) if s == "b")); + assert!(matches!(path[2], (PathKey::Int(3), true))); + assert!(matches!(path[3], (PathKey::Str(ref s), false) if s == "c")); + assert!(matches!(path[4], (PathKey::Str(ref s), false) if s == "d")); + + // Test empty path + let path = parse_path("").unwrap(); + assert_eq!(path.len(), 0); + let path = parse_path(" ").unwrap(); + assert_eq!(path.len(), 0); + + // Test invalid dot syntax + let err = parse_path("a..b").unwrap_err().to_string(); + assert_eq!(err, "runtime error: empty key in path at position 1"); + let err = parse_path("a.b.").unwrap_err().to_string(); + assert_eq!(err, "runtime error: empty key in path at position 3"); + + // Test invalid bracket syntax + let err = parse_path("a[unclosed").unwrap_err().to_string(); + assert_eq!( + err, + "runtime error: unexpected character 'u' in path at position 2" + ); + let err = parse_path("a[]").unwrap_err().to_string(); + assert_eq!(err, "runtime error: empty key in path at position 1"); + let err = parse_path(r#"a["unclosed"#).unwrap_err().to_string(); + assert_eq!(err, "runtime error: unexpected end of string at position 2"); + let err = parse_path(r#"a["#).unwrap_err().to_string(); + assert_eq!(err, "runtime error: unexpected end of path"); + let err = parse_path(r#"a[123"#).unwrap_err().to_string(); + assert_eq!(err, "runtime error: unexpected end of path"); + let err = parse_path(r#"a['bla'123"#).unwrap_err().to_string(); + assert_eq!( + err, + "runtime error: expected ']' in path at position 7, found '1'" + ); + let err = parse_path(r#"a["bla"]x"#).unwrap_err().to_string(); + assert_eq!( + err, + "runtime error: unexpected character 'x' in path at position 8" + ); + + // Test bad integers + let err = parse_path("a[99999999999999999999]").unwrap_err().to_string(); + assert_eq!(err, "runtime error: integer overflow in path at position 2"); + let err = parse_path("a[-]").unwrap_err().to_string(); + assert_eq!(err, "runtime error: invalid integer in path at position 2"); + } +} diff --git a/tests/table.rs b/tests/table.rs index 922da8db..740587ed 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -482,3 +482,84 @@ fn test_table_object_like() -> Result<()> { Ok(()) } + +#[test] +fn test_table_get_path() -> Result<()> { + let lua = Lua::new(); + + // Create a nested table structure + let table = lua + .load( + r#" + { + a = { + b = { + c = "hello", + d = 42 + }, + [1] = "first", + ["special key"] = "special value" + }, + abc = "top level", + x = {}, + ["🚀"] = "rocket", + [1] = { + ["nested-key"] = { + [42] = { + final = "hello!", + }, + }, + ["key\"with\"quotes"] = "value1", + ["key'with'quotes"] = "value2", + ["key\\with\\backslashes"] = "value3", + [-2] = "negative index", + }, + } + "#, + ) + .eval::
()?; + + // Test basic dot notation + assert_eq!(table.get_path::(".a.b.c")?, "hello"); + assert_eq!(table.get_path::("a.b.c")?, "hello"); + assert_eq!(table.get_path::("a.b.d")?, 42); + assert_eq!(table.get_path::("abc")?, "top level"); + + // Test bracket notation with integer keys + assert_eq!(table.get_path::("a[1]")?, "first"); + assert_eq!(table.get_path::("[1][-2]")?, "negative index"); + + // Test bracket notation with string keys + assert_eq!(table.get_path::("a[\"special key\"]")?, "special value"); + assert_eq!(table.get_path::("a['special key']")?, "special value"); + assert_eq!(table.get_path::(r#"[1]["key\"with\"quotes"]"#)?, "value1"); + assert_eq!(table.get_path::(r#"[1]['key"with"quotes']"#)?, "value1"); + assert_eq!(table.get_path::(r#"[1]['key\'with\'quotes']"#)?, "value2"); + assert_eq!( + table.get_path::(r#"[1]["key\\with\\backslashes"]"#)?, + "value3" + ); + + // Test mixed notation + assert_eq!(table.get_path::("[1].nested-key[42].final")?, "hello!"); + + // Test unicode keys + assert_eq!(table.get_path::("🚀")?, "rocket"); + + // Test empty path returns the table itself + assert_eq!(table.get_path::
("")?, table); + + // Test safe navigation + assert_eq!(table.get_path::("a?.b.c")?, "hello"); + assert_eq!(table.get_path::("x.y?.z")?, Value::Nil); + assert_eq!(table.get_path::("[1].nested-key[43]?.final")?, Value::Nil); + + // Test path with whitespace + assert_eq!(table.get_path::(" .a [\"b\"] .c ")?, "hello"); + + // Test indexing non-indexable value + let err = table.get_path::("abc.c").unwrap_err().to_string(); + assert_eq!(err, "runtime error: attempt to index a string value with key 'c'"); + + Ok(()) +} diff --git a/tests/userdata.rs b/tests/userdata.rs index 2df1246c..4e4a5aa0 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -8,7 +8,7 @@ use std::sync::atomic::{AtomicI64, Ordering}; use mlua::{ AnyUserData, Error, ExternalError, Function, Lua, MetaMethod, Nil, ObjectLike, Result, String, UserData, - UserDataFields, UserDataMethods, UserDataRef, Value, Variadic, + UserDataFields, UserDataMethods, UserDataRef, UserDataRegistry, Value, Variadic, }; #[test] @@ -1345,3 +1345,20 @@ fn test_userdata_namecall() -> Result<()> { Ok(()) } + +#[test] +fn test_userdata_get_path() -> Result<()> { + let lua = Lua::new(); + + struct MyUd; + impl UserData for MyUd { + fn register(registry: &mut UserDataRegistry) { + registry.add_field("value", "userdata_value"); + } + } + + let ud = lua.create_userdata(MyUd)?; + assert_eq!(ud.get_path::(".value")?, "userdata_value"); + + Ok(()) +} From bad20374ad95b43270d2fa4f8ea0e8ddee7ba1d7 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 8 Sep 2025 23:09:26 +0100 Subject: [PATCH 526/635] Simplify `Table::clear` method There is no need to traverse array part, lua_next will cover everything --- src/table.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/table.rs b/src/table.rs index b182a6ca..91b6e8e3 100644 --- a/src/table.rs +++ b/src/table.rs @@ -416,14 +416,7 @@ impl Table { lua.push_ref(&self.0); - // Clear array part - for i in 1..=ffi::lua_rawlen(state, -1) { - ffi::lua_pushnil(state); - ffi::lua_rawseti(state, -2, i as Integer); - } - - // Clear hash part - // It must be safe as long as we don't use invalid keys + // This is safe as long as we don't assign new keys ffi::lua_pushnil(state); while ffi::lua_next(state, -2) != 0 { ffi::lua_pop(state, 1); // pop value From 09da7a41e5310593537c89b4d181a8666514f28b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 12 Sep 2025 10:43:27 +0100 Subject: [PATCH 527/635] Add new serde option "detect_mixed_tables" This option would allow detecting mixed tables (with array-like and map-like entries or several borders) to encoding them chosing the best method (as a map or as a table). --- src/serde/de.rs | 50 ++++++++++++++++---- src/table.rs | 120 ++++++++++++++++++++++++++++++++++++++++-------- src/value.rs | 11 +++++ tests/serde.rs | 39 ++++++++++++++++ 4 files changed, 192 insertions(+), 28 deletions(-) diff --git a/src/serde/de.rs b/src/serde/de.rs index 07abd30c..f1ae4062 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -15,11 +15,12 @@ use crate::userdata::AnyUserData; use crate::value::Value; /// A struct for deserializing Lua values into Rust values. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Deserializer { value: Value, options: Options, visited: Rc>>, + len: Option, // A length hint for sequences } /// A struct with options to change default deserializer behavior. @@ -54,6 +55,19 @@ pub struct Options { /// /// Default: **false** pub encode_empty_tables_as_array: bool, + + /// If true, enable detection of mixed tables. + /// + /// A mixed table is a table that has both array-like and map-like entries or several borders. + /// See [`The Length Operator`] documentation for details about borders. + /// + /// When this option is disabled, a table with a non-zero length (with one or more borders) will + /// be always encoded as an array. + /// + /// Default: **false** + /// + /// [`The Length Operator`]: https://www.lua.org/manual/5.4/manual.html#3.4.7 + pub detect_mixed_tables: bool, } impl Default for Options { @@ -70,6 +84,7 @@ impl Options { deny_recursive_tables: true, sort_keys: false, encode_empty_tables_as_array: false, + detect_mixed_tables: false, } } @@ -108,6 +123,15 @@ impl Options { self.encode_empty_tables_as_array = enabled; self } + + /// Sets [`detect_mixed_tables`] option. + /// + /// [`detect_mixed_tables`]: #structfield.detect_mixed_tables + #[must_use] + pub const fn detect_mixed_tables(mut self, enable: bool) -> Self { + self.detect_mixed_tables = enable; + self + } } impl Deserializer { @@ -121,7 +145,7 @@ impl Deserializer { Deserializer { value, options, - visited: Rc::new(RefCell::new(FxHashSet::default())), + ..Default::default() } } @@ -130,8 +154,14 @@ impl Deserializer { value, options, visited, + ..Default::default() } } + + fn with_len(mut self, len: usize) -> Self { + self.len = Some(len); + self + } } impl<'de> serde::Deserializer<'de> for Deserializer { @@ -155,11 +185,13 @@ impl<'de> serde::Deserializer<'de> for Deserializer { Ok(s) => visitor.visit_str(&s), Err(_) => visitor.visit_bytes(&s.as_bytes()), }, - Value::Table(ref t) if t.raw_len() > 0 || t.is_array() => self.deserialize_seq(visitor), - Value::Table(ref t) if self.options.encode_empty_tables_as_array && t.is_empty() => { - self.deserialize_seq(visitor) + Value::Table(ref t) => { + if let Some(len) = t.encode_as_array(self.options) { + self.with_len(len).deserialize_seq(visitor) + } else { + self.deserialize_map(visitor) + } } - Value::Table(_) => self.deserialize_map(visitor), Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(), Value::UserData(ud) if ud.is_serializable() => { serde_userdata(ud, |value| value.deserialize_any(visitor)) @@ -270,14 +302,14 @@ impl<'de> serde::Deserializer<'de> for Deserializer { Value::Table(t) => { let _guard = RecursionGuard::new(&t, &self.visited); - let len = t.raw_len(); + let len = self.len.unwrap_or_else(|| t.raw_len()); let mut deserializer = SeqDeserializer { - seq: t.sequence_values(), + seq: t.sequence_values().with_len(len), options: self.options, visited: self.visited, }; let seq = visitor.visit_seq(&mut deserializer)?; - if deserializer.seq.count() == 0 { + if deserializer.seq.next().is_none() { Ok(seq) } else { Err(de::Error::invalid_length(len, &"fewer elements in the table")) diff --git a/src/table.rs b/src/table.rs index 91b6e8e3..82e40d59 100644 --- a/src/table.rs +++ b/src/table.rs @@ -668,16 +668,25 @@ impl Table { guard: self.0.lua.lock(), table: self, index: 1, + len: None, _phantom: PhantomData, } } /// Iterates over the sequence part of the table, invoking the given closure on each value. + /// + /// This methods is similar to [`Table::sequence_values`], but optimized for performance. #[doc(hidden)] - pub fn for_each_value(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()> - where - V: FromLua, - { + pub fn for_each_value(&self, f: impl FnMut(V) -> Result<()>) -> Result<()> { + self.for_each_value_by_len(None, f) + } + + fn for_each_value_by_len( + &self, + len: impl Into>, + mut f: impl FnMut(V) -> Result<()>, + ) -> Result<()> { + let len = len.into(); let lua = self.0.lua.lock(); let state = lua.state(); unsafe { @@ -685,9 +694,14 @@ impl Table { check_stack(state, 4)?; lua.push_ref(&self.0); - let len = ffi::lua_rawlen(state, -1); - for i in 1..=len { - ffi::lua_rawgeti(state, -1, i as _); + for i in 1.. { + if len.map(|len| i > len).unwrap_or(false) { + break; + } + let t = ffi::lua_rawgeti(state, -1, i as _); + if len.is_none() && t == ffi::LUA_TNIL { + break; + } f(V::from_stack(-1, &lua)?)?; ffi::lua_pop(state, 1); } @@ -720,8 +734,9 @@ impl Table { Ok(()) } + /// Checks if the table has the array metatable attached. #[cfg(feature = "serde")] - pub(crate) fn is_array(&self) -> bool { + fn has_array_metatable(&self) -> bool { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { @@ -737,6 +752,70 @@ impl Table { } } + /// If the table is an array, returns the number of non-nil elements and max index. + /// + /// Returns `None` if the table is not an array. + /// + /// This operation has O(n) complexity. + #[cfg(feature = "serde")] + fn find_array_len(&self) -> Option<(usize, usize)> { + let lua = self.0.lua.lock(); + let ref_thread = lua.ref_thread(); + unsafe { + let _sg = StackGuard::new(ref_thread); + + let (mut count, mut max_index) = (0, 0); + ffi::lua_pushnil(ref_thread); + while ffi::lua_next(ref_thread, self.0.index) != 0 { + if ffi::lua_type(ref_thread, -2) != ffi::LUA_TNUMBER { + return None; + } + + let k = ffi::lua_tonumber(ref_thread, -2); + if k.trunc() != k || k < 1.0 { + return None; + } + max_index = std::cmp::max(max_index, k as usize); + count += 1; + ffi::lua_pop(ref_thread, 1); + } + Some((count, max_index)) + } + } + + /// Determines if the table should be encoded as an array or a map. + /// + /// The algorithm is the following: + /// 1. If `detect_mixed_tables` is enabled, iterate over all keys in the table checking is they + /// all are positive integers. If non-array key is found, return `None` (encode as map). + /// Otherwise check the sparsity of the array. Too sparse arrays are encoded as maps. + /// + /// 2. If `detect_mixed_tables` is disabled, check if the table has a positive length or has the + /// array metatable. If so, encode as array. If the table is empty and + /// `encode_empty_tables_as_array` is enabled, encode as array. + /// + /// Returns the length of the array if it should be encoded as an array. + #[cfg(feature = "serde")] + pub(crate) fn encode_as_array(&self, options: crate::serde::de::Options) -> Option { + if options.detect_mixed_tables { + if let Some((len, max_idx)) = self.find_array_len() { + // If the array is too sparse, serialize it as a map instead + if len < 10 || len * 2 >= max_idx { + return Some(max_idx); + } + } + } else { + let len = self.raw_len(); + if len > 0 || self.has_array_metatable() { + return Some(len); + } + if options.encode_empty_tables_as_array && self.is_empty() { + return Some(0); + } + } + None + } + #[cfg(feature = "luau")] #[inline(always)] fn check_readonly_write(&self, lua: &RawLua) -> Result<()> { @@ -980,6 +1059,15 @@ impl<'a> SerializableTable<'a> { } } +impl TableSequence<'_, V> { + /// Sets the length (hint) of the sequence. + #[cfg(feature = "serde")] + pub(crate) fn with_len(mut self, len: usize) -> Self { + self.len = Some(len); + self + } +} + #[cfg(feature = "serde")] impl Serialize for SerializableTable<'_> { fn serialize(&self, serializer: S) -> StdResult @@ -1001,14 +1089,10 @@ impl Serialize for SerializableTable<'_> { let _guard = RecursionGuard::new(self.table, visited); // Array - let len = self.table.raw_len(); - if len > 0 - || self.table.is_array() - || (self.options.encode_empty_tables_as_array && self.table.is_empty()) - { + if let Some(len) = self.table.encode_as_array(self.options) { let mut seq = serializer.serialize_seq(Some(len))?; let mut serialize_err = None; - let res = self.table.for_each_value::(|value| { + let res = self.table.for_each_value_by_len::(len, |value| { let skip = check_value_for_skip(&value, self.options, visited) .map_err(|err| Error::SerializeError(err.to_string()))?; if skip { @@ -1132,13 +1216,11 @@ pub struct TableSequence<'a, V> { guard: LuaGuard, table: &'a Table, index: Integer, + len: Option, _phantom: PhantomData, } -impl Iterator for TableSequence<'_, V> -where - V: FromLua, -{ +impl Iterator for TableSequence<'_, V> { type Item = Result; fn next(&mut self) -> Option { @@ -1152,7 +1234,7 @@ where lua.push_ref(&self.table.0); match ffi::lua_rawgeti(state, -1, self.index) { - ffi::LUA_TNIL => None, + ffi::LUA_TNIL if self.index as usize > self.len.unwrap_or(0) => None, _ => { self.index += 1; Some(V::from_stack(-1, lua)) diff --git a/src/value.rs b/src/value.rs index 662ea6ae..dd0727bd 100644 --- a/src/value.rs +++ b/src/value.rs @@ -717,6 +717,17 @@ impl<'a> SerializableValue<'a> { self.options.encode_empty_tables_as_array = enabled; self } + + /// If true, enable detection of mixed tables. + /// + /// A mixed table is a table that has both array-like and map-like entries or several borders. + /// + /// Default: **false** + #[must_use] + pub const fn detect_mixed_tables(mut self, enabled: bool) -> Self { + self.options.detect_mixed_tables = enabled; + self + } } #[cfg(feature = "serde")] diff --git a/tests/serde.rs b/tests/serde.rs index f3c965e2..2f07d7f4 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -269,6 +269,45 @@ fn test_serialize_empty_table() -> LuaResult<()> { Ok(()) } +#[test] +fn test_serialize_mixed_table() -> LuaResult<()> { + let lua = Lua::new(); + + // Check that sparse array is serialized similarly when using direct serialization + // and via `Lua::from_value` + let table = lua.load("{1,2,3,nil,5}").eval::()?; + let json1 = serde_json::to_string(&table).unwrap(); + let json2 = lua.from_value::(table)?; + assert_eq!(json1, json2.to_string()); + + // A table with several borders should be correctly encoded when `detect_mixed_tables` is enabled + let table = lua + .load( + r#" + local t = {1,2,3,nil,5,6} + t[10] = 10 + return t + "#, + ) + .eval::()?; + let json = serde_json::to_string(&table.to_serializable().detect_mixed_tables(true)).unwrap(); + assert_eq!(json, r#"[1,2,3,null,5,6,null,null,null,10]"#); + + // A mixed table with both array-like and map-like entries + let table = lua.load(r#"{1,2,3, key="value"}"#).eval::()?; + let json = serde_json::to_string(&table).unwrap(); + assert_eq!(json, r#"[1,2,3]"#); + let json = serde_json::to_string(&table.to_serializable().detect_mixed_tables(true)).unwrap(); + assert_eq!(json, r#"{"1":1,"2":2,"3":3,"key":"value"}"#); + + // A mixed table with duplicate keys of different types + let table = lua.load(r#"{1,2,3, ["1"]="value"}"#).eval::()?; + let json = serde_json::to_string(&table.to_serializable().detect_mixed_tables(true)).unwrap(); + assert_eq!(json, r#"{"1":1,"2":2,"3":3,"1":"value"}"#); + + Ok(()) +} + #[test] fn test_to_value_struct() -> LuaResult<()> { let lua = Lua::new(); From 2beca6ebe19602a545f24e2d35dc0e426495aa39 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 12 Sep 2025 11:49:19 +0100 Subject: [PATCH 528/635] Add test for `Table::for_each_value` --- tests/table.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/table.rs b/tests/table.rs index 740587ed..e0bc5f44 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -272,6 +272,22 @@ fn test_table_for_each() -> Result<()> { Ok(()) } +#[test] +fn test_table_for_each_value() -> Result<()> { + let lua = Lua::new(); + + let table = lua.load("{1, 2, 3, 4, 5, nil, 7}").eval::
()?; + let mut sum = 0; + table.for_each_value::(|v| { + sum += v; + Ok(()) + })?; + // Iterations stops at the first nil + assert_eq!(sum, 1 + 2 + 3 + 4 + 5); + + Ok(()) +} + #[test] fn test_table_scope() -> Result<()> { let lua = Lua::new(); From 53c159b6cbd1299a360fa6dd99667e41b97a7be9 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 12 Sep 2025 11:45:00 +0100 Subject: [PATCH 529/635] Unhide `Value::to_serializable` --- src/value.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/value.rs b/src/value.rs index dd0727bd..9de3b5ad 100644 --- a/src/value.rs +++ b/src/value.rs @@ -491,7 +491,6 @@ impl Value { /// This allows customizing serialization behavior using serde. #[cfg(feature = "serde")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] - #[doc(hidden)] pub fn to_serializable(&self) -> SerializableValue<'_> { SerializableValue::new(self, Default::default(), None) } From ae512f2b496c03eb33e2eda72b4956dd2fe29b5e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 12 Sep 2025 12:40:00 +0100 Subject: [PATCH 530/635] Remove const from SerializableValue (it's not really useful) --- src/value.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/value.rs b/src/value.rs index 9de3b5ad..8fbfa32a 100644 --- a/src/value.rs +++ b/src/value.rs @@ -683,7 +683,7 @@ impl<'a> SerializableValue<'a> { /// /// Default: **true** #[must_use] - pub const fn deny_unsupported_types(mut self, enabled: bool) -> Self { + pub fn deny_unsupported_types(mut self, enabled: bool) -> Self { self.options.deny_unsupported_types = enabled; self } @@ -694,7 +694,7 @@ impl<'a> SerializableValue<'a> { /// /// Default: **true** #[must_use] - pub const fn deny_recursive_tables(mut self, enabled: bool) -> Self { + pub fn deny_recursive_tables(mut self, enabled: bool) -> Self { self.options.deny_recursive_tables = enabled; self } @@ -703,7 +703,7 @@ impl<'a> SerializableValue<'a> { /// /// Default: **false** #[must_use] - pub const fn sort_keys(mut self, enabled: bool) -> Self { + pub fn sort_keys(mut self, enabled: bool) -> Self { self.options.sort_keys = enabled; self } @@ -712,7 +712,7 @@ impl<'a> SerializableValue<'a> { /// /// Default: **false** #[must_use] - pub const fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self { + pub fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self { self.options.encode_empty_tables_as_array = enabled; self } @@ -723,7 +723,7 @@ impl<'a> SerializableValue<'a> { /// /// Default: **false** #[must_use] - pub const fn detect_mixed_tables(mut self, enabled: bool) -> Self { + pub fn detect_mixed_tables(mut self, enabled: bool) -> Self { self.options.detect_mixed_tables = enabled; self } From 54907f80c5cac3d37d765abe5d4deb3661b3fec2 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 12 Sep 2025 12:40:43 +0100 Subject: [PATCH 531/635] Add `SerializableValue` to lib and prelude exports --- src/lib.rs | 5 ++++- src/prelude.rs | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 83247f02..466d4d38 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,7 +142,10 @@ pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn}; #[cfg(feature = "serde")] #[doc(inline)] -pub use crate::serde::{de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt}; +pub use crate::{ + serde::{de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt}, + value::SerializableValue, +}; #[cfg(feature = "serde")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] diff --git a/src/prelude.rs b/src/prelude.rs index 0e4cd0bd..fca5af51 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -36,5 +36,6 @@ pub use crate::{AsyncThread as LuaAsyncThread, LuaNativeAsyncFn}; #[cfg(feature = "serde")] #[doc(no_inline)] pub use crate::{ - DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, SerializeOptions as LuaSerializeOptions, + DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, SerializableValue as LuaSerializableValue, + SerializeOptions as LuaSerializeOptions, }; From 5b38af9746885af710675325ee4dd83bd89f633e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 19 Sep 2025 10:00:28 +0100 Subject: [PATCH 532/635] `AsyncCallFuture` is Unpin --- src/function.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/function.rs b/src/function.rs index 92d6f93a..aa31119c 100644 --- a/src/function.rs +++ b/src/function.rs @@ -18,7 +18,7 @@ use { crate::traits::LuaNativeAsyncFn, crate::types::AsyncCallback, std::future::{self, Future}, - std::pin::Pin, + std::pin::{pin, Pin}, std::task::{Context, Poll}, }; @@ -669,13 +669,9 @@ impl Future for AsyncCallFuture { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - // Safety: We're not moving any pinned data - let this = unsafe { self.get_unchecked_mut() }; + let this = self.get_mut(); match &mut this.0 { - Ok(thread) => { - let pinned_thread = unsafe { Pin::new_unchecked(thread) }; - pinned_thread.poll(cx) - } + Ok(thread) => pin!(thread).poll(cx), Err(err) => Poll::Ready(Err(err.clone())), } } From e08768cc5e2154245fa16a3ae31431ee4dc0981e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 28 Sep 2025 23:41:46 +0100 Subject: [PATCH 533/635] Derive `Default` for `Value` (clippy) --- src/value.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/value.rs b/src/value.rs index 8fbfa32a..ff3ed3a4 100644 --- a/src/value.rs +++ b/src/value.rs @@ -28,9 +28,10 @@ use { /// The non-primitive variants (eg. string/table/function/thread/userdata) contain handle types /// into the internal Lua state. It is a logic error to mix handle types between separate /// `Lua` instances, and doing so will result in a panic. -#[derive(Clone)] +#[derive(Clone, Default)] pub enum Value { /// The Lua value `nil`. + #[default] Nil, /// The Lua value `true` or `false`. Boolean(bool), @@ -579,12 +580,6 @@ impl Value { } } -impl Default for Value { - fn default() -> Self { - Self::Nil - } -} - impl fmt::Debug for Value { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { if fmt.alternate() { From 247208edb1f5d76d9694646a45a44f8e7053b1dd Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 28 Sep 2025 23:46:55 +0100 Subject: [PATCH 534/635] v0.11.4 --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3b1981f..38d88dd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v0.11.4 (Sep 29, 2025) + +- Make `Value::to_serializable` public +- Add new serde option `detect_mixed_tables` (to encode mixed array+map tables) +- Add `ObjectLike::get_path` helper (for tables and userdata) + ## v0.11.3 (Aug 30, 2025) - Add `Lua::yield_with` to use as `coroutine.yield` functional replacement in async functions for any Lua diff --git a/Cargo.toml b/Cargo.toml index c08e03b6..0b2a94e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.11.3" # remember to update mlua_derive +version = "0.11.4" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.79.0" edition = "2021" From 6e353d6c9f46a9ced17222a57b2c8de1b73d0ccd Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 4 Oct 2025 03:52:23 -0500 Subject: [PATCH 535/635] Build/test wasm32-wasip2 in CI (#649) This is a follow-up from mlua-rs/lua-src-rs#13 which verifies/tests that mlua/lua all work when compiled for a WASI target. While this doesn't have formal documentation yet it also codifies in CI configuration how to build for WASI and get tests passing (notably C compiler configuration and some misc Rust flags). This moves some `dev-dependencies` that don't compile for `wasm32-wasip2` to a different section of the manifest. This additionally annotates panicking tests with `#[cfg(not(panic = "abort"))]` to skip those tests on WASI. This does not test either the `send` or `async` feature at this time. Testing `send` requires threads which WASI does not yet support, and testing `async` requires more support in Tokio which is not currently there yet. --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ Cargo.toml | 12 ++++++------ tests/chunk.rs | 1 + tests/tests.rs | 2 ++ tests/thread.rs | 1 + 5 files changed, 45 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8d8e51f5..5b4ad7aa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -256,6 +256,41 @@ jobs: cargo test --tests --features "${{ matrix.lua }},vendored" cargo test --tests --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers" + test_wasm32_wasip2: + name: Test on wasm32-wasip2 + runs-on: ubuntu-latest + needs: build + strategy: + matrix: + lua: [lua54, lua53, lua52, lua51] + steps: + - uses: actions/checkout@main + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: nightly-2025-10-02 + target: wasm32-wasip2 + - name: Install wasi-sdk/Wasmtime + working-directory: ${{ runner.tool_cache }} + run: | + wasi_sdk=27 + wasmtime=v37.0.1 + + curl -LO https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$wasi_sdk/wasi-sdk-$wasi_sdk.0-x86_64-linux.tar.gz + tar xf wasi-sdk-$wasi_sdk.0-x86_64-linux.tar.gz + WASI_SDK_PATH=`pwd`/wasi-sdk-$wasi_sdk.0-x86_64-linux + echo "WASI_SDK_PATH=$WASI_SDK_PATH" >> $GITHUB_ENV + echo "CC_wasm32_wasip2=$WASI_SDK_PATH/bin/clang" >> $GITHUB_ENV + echo "CARGO_TARGET_WASM32_WASIP2_LINKER=$WASI_SDK_PATH/bin/clang" >> $GITHUB_ENV + echo "CARGO_TARGET_WASM32_WASIP2_RUSTFLAGS=-Clink-arg=-Wl,--export=cabi_realloc" >> $GITHUB_ENV + + curl -LO https://github.com/bytecodealliance/wasmtime/releases/download/$wasmtime/wasmtime-$wasmtime-x86_64-linux.tar.xz + tar xf wasmtime-$wasmtime-x86_64-linux.tar.xz + echo "CARGO_TARGET_WASM32_WASIP2_RUNNER=`pwd`/wasmtime-$wasmtime-x86_64-linux/wasmtime -W exceptions" >> $GITHUB_ENV + - name: Run ${{ matrix.lua }} tests + run: | + cargo test --target wasm32-wasip2 --tests --features "${{ matrix.lua }},vendored" + cargo test --target wasm32-wasip2 --tests --features "${{ matrix.lua }},vendored,serde,macros,anyhow,userdata-wrappers" + rustfmt: name: Rustfmt runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 0b2a94e4..47726baa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,18 +66,18 @@ ffi = { package = "mlua-sys", version = "0.8.3", path = "mlua-sys" } [dev-dependencies] trybuild = "1.0" -hyper = { version = "1.2", features = ["full"] } -hyper-util = { version = "0.1.3", features = ["full"] } -http-body-util = "0.1.1" -reqwest = { version = "0.12", features = ["json"] } tokio = { version = "1.0", features = ["macros", "rt", "time"] } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["arbitrary_precision"] } maplit = "1.0" -tempfile = "3" static_assertions = "1.0" -[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] +[target.'cfg(not(target_family = "wasm"))'.dev-dependencies] +hyper = { version = "1.2", features = ["full"] } +hyper-util = { version = "0.1.3", features = ["full"] } +http-body-util = "0.1.1" +reqwest = { version = "0.12", features = ["json"] } +tempfile = "3" criterion = { version = "0.7", features = ["async_tokio"] } rustyline = "17.0" tokio = { version = "1.0", features = ["full"] } diff --git a/tests/chunk.rs b/tests/chunk.rs index ffd7ac0c..3f7b4849 100644 --- a/tests/chunk.rs +++ b/tests/chunk.rs @@ -21,6 +21,7 @@ fn test_chunk_methods() -> Result<()> { } #[test] +#[cfg(not(target_os = "wasi"))] fn test_chunk_path() -> Result<()> { let lua = Lua::new(); diff --git a/tests/tests.rs b/tests/tests.rs index edbf8f2a..30e9ea7d 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -394,6 +394,7 @@ fn test_error() -> Result<()> { } #[test] +#[cfg(not(panic = "abort"))] fn test_panic() -> Result<()> { fn make_lua(options: LuaOptions) -> Result { let lua = Lua::new_with(StdLib::ALL_SAFE, options)?; @@ -897,6 +898,7 @@ fn test_registry_value_reuse() -> Result<()> { } #[test] +#[cfg(not(panic = "abort"))] fn test_application_data() -> Result<()> { let lua = Lua::new(); diff --git a/tests/thread.rs b/tests/thread.rs index 54a6be53..ab4dcc7c 100644 --- a/tests/thread.rs +++ b/tests/thread.rs @@ -199,6 +199,7 @@ fn test_coroutine_from_closure() -> Result<()> { } #[test] +#[cfg(not(panic = "abort"))] fn test_coroutine_panic() { match catch_unwind(|| -> Result<()> { // check that coroutines propagate panics correctly From a4c8b206970a6de9f25c268c72f446b51a4fecce Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 13 Oct 2025 12:36:42 +0100 Subject: [PATCH 536/635] impl `IntoLuaMulti` for `&MultiValue` --- src/multi.rs | 17 +++++++++++++++++ tests/multi.rs | 20 ++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/multi.rs b/src/multi.rs index ef46c39f..f67b7119 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -204,6 +204,23 @@ impl IntoLuaMulti for MultiValue { } } +impl IntoLuaMulti for &MultiValue { + #[inline] + fn into_lua_multi(self, _: &Lua) -> Result { + Ok(self.clone()) + } + + #[inline] + unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { + let nresults = self.len() as i32; + check_stack(lua.state(), nresults + 1)?; + for value in &self.0 { + lua.push_value(value)?; + } + Ok(nresults) + } +} + impl FromLuaMulti for MultiValue { #[inline] fn from_lua_multi(values: MultiValue, _: &Lua) -> Result { diff --git a/tests/multi.rs b/tests/multi.rs index 32085557..7468c49d 100644 --- a/tests/multi.rs +++ b/tests/multi.rs @@ -72,6 +72,26 @@ fn test_multivalue() { let _multi2 = MultiValue::from_vec(vec); } +#[test] +fn test_multivalue_by_ref() -> Result<()> { + let lua = Lua::new(); + let multi = MultiValue::from_vec(vec![ + Value::Integer(3), + Value::String(lua.create_string("hello")?), + Value::Boolean(true), + ]); + + let f = lua.create_function(|_, (i, s, b): (i32, String, bool)| { + assert_eq!(i, 3); + assert_eq!(s.to_str()?, "hello"); + assert_eq!(b, true); + Ok(()) + })?; + f.call::<()>(&multi)?; + + Ok(()) +} + #[test] fn test_variadic() { let mut var = Variadic::with_capacity(3); From 3a2fd1ec59afea42bb6f99ef1a6773d8a9aaa923 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 17 Oct 2025 20:00:46 +0100 Subject: [PATCH 537/635] Make `AnyUserData::type_name` public --- src/userdata.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/userdata.rs b/src/userdata.rs index f82e66d9..1be0a718 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -921,8 +921,10 @@ impl AnyUserData { lua.get_userdata_ref_type_id(&self.0).ok().flatten() } - /// Returns a type name of this `UserData` (from a metatable field). - pub(crate) fn type_name(&self) -> Result> { + /// Returns a type name of this userdata (from a metatable field). + /// + /// If no type name is set, returns `None`. + pub fn type_name(&self) -> Result> { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { From 1152519074336f2bf93d5c3e38267d2c6aa4fab8 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 26 Oct 2025 20:14:46 +0000 Subject: [PATCH 538/635] Add `add_method_once` and `add_async_method_once` UserData methods (experimental). They will allow implementing userdata methods that can be called only once, destructing userdata instance during the call. --- src/userdata.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++++- tests/async.rs | 26 ++++++++++++++++++++--- tests/userdata.rs | 33 +++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 4 deletions(-) diff --git a/src/userdata.rs b/src/userdata.rs index 1be0a718..ef320480 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -12,7 +12,7 @@ use crate::string::String; use crate::table::{Table, TablePairs}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::types::{MaybeSend, ValueRef}; -use crate::util::{check_stack, get_userdata, push_string, take_userdata, StackGuard}; +use crate::util::{check_stack, get_userdata, push_string, short_type_name, take_userdata, StackGuard}; use crate::value::Value; #[cfg(feature = "async")] @@ -273,6 +273,29 @@ pub trait UserDataMethods { A: FromLuaMulti, R: IntoLuaMulti; + /// Add a method which accepts `T` as the first parameter. + /// + /// The userdata `T` will be moved out of the userdata container. This is useful for + /// methods that need to consume the userdata. + /// + /// The method can be called only once per userdata instance, subsequent calls will result in a + /// [`Error::UserDataDestructed`] error. + #[doc(hidden)] + fn add_method_once(&mut self, name: impl Into, method: M) + where + T: 'static, + M: Fn(&Lua, T, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, + R: IntoLuaMulti, + { + let name = name.into(); + let method_name = format!("{}.{name}", short_type_name::()); + self.add_function(name, move |lua, (ud, args): (AnyUserData, A)| { + let this = (ud.take()).map_err(|err| Error::bad_self_argument(&method_name, err))?; + method(lua, this, args) + }); + } + /// Add an async method which accepts a `&T` as the first parameter and returns [`Future`]. /// /// Refer to [`add_method`] for more information about the implementation. @@ -303,6 +326,34 @@ pub trait UserDataMethods { MR: Future> + MaybeSend + 'static, R: IntoLuaMulti; + /// Add an async method which accepts a `T` as the first parameter and returns [`Future`]. + /// + /// The userdata `T` will be moved out of the userdata container. This is useful for + /// methods that need to consume the userdata. + /// + /// The method can be called only once per userdata instance, subsequent calls will result in a + /// [`Error::UserDataDestructed`] error. + #[cfg(feature = "async")] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + #[doc(hidden)] + fn add_async_method_once(&mut self, name: impl Into, method: M) + where + T: 'static, + M: Fn(Lua, T, A) -> MR + MaybeSend + 'static, + A: FromLuaMulti, + MR: Future> + MaybeSend + 'static, + R: IntoLuaMulti, + { + let name = name.into(); + let method_name = format!("{}.{name}", short_type_name::()); + self.add_async_function(name, move |lua, (ud, args): (AnyUserData, A)| { + match (ud.take()).map_err(|err| Error::bad_self_argument(&method_name, err)) { + Ok(this) => either::Either::Left(method(lua, this, args)), + Err(err) => either::Either::Right(async move { Err(err) }), + } + }); + } + /// Add a regular method as a function which accepts generic arguments. /// /// The first argument will be a [`AnyUserData`] of type `T` if the method is called with Lua diff --git a/tests/async.rs b/tests/async.rs index ad89344c..761b07e0 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -423,9 +423,9 @@ async fn test_async_thread_pool() -> Result<()> { #[tokio::test] async fn test_async_userdata() -> Result<()> { - struct MyUserData(u64); + struct MyUserdata(u64); - impl UserData for MyUserData { + impl UserData for MyUserdata { fn add_methods>(methods: &mut M) { methods.add_async_method("get_value", |_, data, ()| async move { sleep_ms(10).await; @@ -438,6 +438,11 @@ async fn test_async_userdata() -> Result<()> { Ok(()) }); + methods.add_async_method_once("take_value", |_, data, ()| async move { + sleep_ms(10).await; + Ok(data.0) + }); + methods.add_async_function("sleep", |_, n| async move { sleep_ms(n).await; Ok(format!("elapsed:{}ms", n)) @@ -479,7 +484,7 @@ async fn test_async_userdata() -> Result<()> { let lua = Lua::new(); let globals = lua.globals(); - let userdata = lua.create_userdata(MyUserData(11))?; + let userdata = lua.create_userdata(MyUserdata(11))?; globals.set("userdata", &userdata)?; lua.load( @@ -518,6 +523,21 @@ async fn test_async_userdata() -> Result<()> { #[cfg(not(any(feature = "lua51", feature = "luau")))] assert_eq!(userdata.call_async::(()).await?, "elapsed:24ms"); + // Take value + let userdata2 = lua.create_userdata(MyUserdata(0))?; + globals.set("userdata2", userdata2)?; + lua.load("assert(userdata:take_value() == 24)") + .exec_async() + .await?; + match lua.load("userdata2.take_value(userdata)").exec_async().await { + Err(Error::CallbackError { cause, .. }) => { + let err = cause.to_string(); + assert!(err.contains("bad argument `self` to `MyUserdata.take_value`")); + assert!(err.contains("userdata has been destructed")); + } + r => panic!("expected Err(CallbackError), got {r:?}"), + } + Ok(()) } diff --git a/tests/userdata.rs b/tests/userdata.rs index 4e4a5aa0..5b62474d 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -428,6 +428,39 @@ fn test_userdata_destroy() -> Result<()> { Ok(()) } +#[test] +fn test_userdata_method_once() -> Result<()> { + struct MyUserdata(Arc); + + impl UserData for MyUserdata { + fn add_methods>(methods: &mut M) { + methods.add_method_once("take_value", |_, this, ()| Ok(*this.0)); + } + } + + let lua = Lua::new(); + let rc = Arc::new(42); + let userdata = lua.create_userdata(MyUserdata(rc.clone()))?; + lua.globals().set("userdata", &userdata)?; + + // Control userdata + let userdata2 = lua.create_userdata(MyUserdata(rc.clone()))?; + lua.globals().set("userdata2", userdata2)?; + + assert_eq!(lua.load("userdata:take_value()").eval::()?, 42); + match lua.load("userdata2.take_value(userdata)").eval::() { + Err(Error::CallbackError { cause, .. }) => { + let err = cause.to_string(); + assert!(err.contains("bad argument `self` to `MyUserdata.take_value`")); + assert!(err.contains("userdata has been destructed")); + } + r => panic!("expected Err(CallbackError), got {r:?}"), + } + assert_eq!(Arc::strong_count(&rc), 2); + + Ok(()) +} + #[test] fn test_user_values() -> Result<()> { struct MyUserData; From ddd44bdd365129ff246230de60c38ec01b002e46 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 27 Oct 2025 20:57:56 +0000 Subject: [PATCH 539/635] Add `LUA_LOADED_TABLE` constant (Luau) --- mlua-sys/src/luau/compat.rs | 2 +- mlua-sys/src/luau/lauxlib.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index 2b2c9a6a..09c5d0bb 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -544,7 +544,7 @@ pub unsafe fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_ch pub unsafe fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int) { luaL_checkstack(L, 3, cstr!("not enough stack slots available")); - luaL_getsubtable(L, LUA_REGISTRYINDEX, cstr!("_LOADED")); + luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); if lua_getfield(L, -1, modname) == LUA_TNIL { lua_pop(L, 1); lua_pushcfunction(L, openf); diff --git a/mlua-sys/src/luau/lauxlib.rs b/mlua-sys/src/luau/lauxlib.rs index ab850892..45071277 100644 --- a/mlua-sys/src/luau/lauxlib.rs +++ b/mlua-sys/src/luau/lauxlib.rs @@ -5,6 +5,9 @@ use std::ptr; use super::lua::{self, lua_CFunction, lua_Number, lua_State, lua_Unsigned, LUA_REGISTRYINDEX}; +// Key, in the registry, for table of loaded modules +pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED"); + #[repr(C)] pub struct luaL_Reg { pub name: *const c_char, From 0619f264defa4361103e666703667323a4cd4aeb Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 28 Oct 2025 14:49:22 +0000 Subject: [PATCH 540/635] Add `Lua::traceback` function to generate stack traces at different levels This is similar to `debug.traceback`, through does not require debug module. Close #652 --- mlua-sys/src/lua51/compat.rs | 93 +++++++++++++++++++----------------- mlua-sys/src/luau/compat.rs | 84 +++++++++++++++++++------------- mlua-sys/src/luau/lauxlib.rs | 15 ++++++ src/state.rs | 22 ++++++++- tests/tests.rs | 74 ++++++++++++++++++++++++++++ 5 files changed, 211 insertions(+), 77 deletions(-) diff --git a/mlua-sys/src/lua51/compat.rs b/mlua-sys/src/lua51/compat.rs index 75383b9e..19591684 100644 --- a/mlua-sys/src/lua51/compat.rs +++ b/mlua-sys/src/lua51/compat.rs @@ -2,6 +2,7 @@ //! //! Based on github.com/keplerproject/lua-compat-5.3 +use std::ffi::CStr; use std::os::raw::{c_char, c_int, c_void}; use std::{mem, ptr}; @@ -20,8 +21,8 @@ unsafe fn compat53_reverse(L: *mut lua_State, mut a: c_int, mut b: c_int) { } } -const COMPAT53_LEVELS1: c_int = 12; // size of the first part of the stack -const COMPAT53_LEVELS2: c_int = 10; // size of the second part of the stack +const COMPAT53_LEVELS1: c_int = 10; // size of the first part of the stack +const COMPAT53_LEVELS2: c_int = 11; // size of the second part of the stack unsafe fn compat53_countlevels(L: *mut lua_State) -> c_int { let mut ar: lua_Debug = mem::zeroed(); @@ -88,11 +89,10 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) -> lua_pop(L, 1); // remove value (but keep name) return 1; } else if compat53_findfield(L, objidx, level - 1) != 0 { - // try recursively - lua_remove(L, -2); // remove table (but keep name) - lua_pushliteral(L, c"."); - lua_insert(L, -2); // place '.' between the two names - lua_concat(L, 3); + // stack: lib_name, lib_table, field_name (top) + lua_pushliteral(L, c"."); // place '.' between the two names + lua_replace(L, -3); // (in the slot occupied by table) + lua_concat(L, 3); // lib_name.field_name return 1; } } @@ -101,13 +101,20 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) -> 0 // not found } -unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, ar: *mut lua_Debug) -> c_int { +unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, L1: *mut lua_State, ar: *mut lua_Debug) -> c_int { let top = lua_gettop(L); - lua_getinfo(L, cstr!("f"), ar); // push function + lua_getinfo(L1, cstr!("f"), ar); // push function + lua_xmove(L1, L, 1); // and move onto L lua_pushvalue(L, LUA_GLOBALSINDEX); + luaL_checkstack(L, 6, cstr!("not enough stack")); // slots for 'findfield' if compat53_findfield(L, top + 1, 2) != 0 { + let name = lua_tostring(L, -1); + if CStr::from_ptr(name).to_bytes().starts_with(b"_G.") { + lua_pushstring(L, name.add(3)); // push name without prefix + lua_remove(L, -2); // remove original name + } lua_copy(L, -1, top + 1); // move name to proper place - lua_pop(L, 2); // remove pushed values + lua_settop(L, top + 1); // remove pushed values 1 } else { lua_settop(L, top); // remove function and global table @@ -115,27 +122,23 @@ unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, ar: *mut lua_Debug) -> } } -unsafe fn compat53_pushfuncname(L: *mut lua_State, ar: *mut lua_Debug) { - if *(*ar).namewhat != b'\0' as c_char { - // is there a name? - lua_pushfstring(L, cstr!("function '%s'"), (*ar).name); +unsafe fn compat53_pushfuncname(L: *mut lua_State, L1: *mut lua_State, ar: *mut lua_Debug) { + // try first a global name + if compat53_pushglobalfuncname(L, L1, ar) != 0 { + lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1)); + lua_remove(L, -2); // remove name + } else if *(*ar).namewhat != b'\0' as c_char { + // use name from code + lua_pushfstring(L, cstr!("%s '%s'"), (*ar).namewhat, (*ar).name); } else if *(*ar).what == b'm' as c_char { // main? lua_pushliteral(L, c"main chunk"); - } else if *(*ar).what == b'C' as c_char { - if compat53_pushglobalfuncname(L, ar) != 0 { - lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1)); - lua_remove(L, -2); // remove name - } else { - lua_pushliteral(L, c"?"); - } + } else if *(*ar).what != b'C' as c_char { + // for Lua functions, use + let short_src = (*ar).short_src.as_ptr(); + lua_pushfstring(L, cstr!("function <%s:%d>"), short_src, (*ar).linedefined); } else { - lua_pushfstring( - L, - cstr!("function <%s:%d>"), - (*ar).short_src.as_ptr(), - (*ar).linedefined, - ); + lua_pushliteral(L, c"?"); } } @@ -459,32 +462,36 @@ pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const let mut ar: lua_Debug = mem::zeroed(); let top = lua_gettop(L); let numlevels = compat53_countlevels(L1); - let mark = if numlevels > COMPAT53_LEVELS1 + COMPAT53_LEVELS2 { - COMPAT53_LEVELS1 - } else { - 0 - }; + #[rustfmt::skip] + let mut limit = if numlevels - level > COMPAT53_LEVELS1 + COMPAT53_LEVELS2 { COMPAT53_LEVELS1 } else { -1 }; if !msg.is_null() { lua_pushfstring(L, cstr!("%s\n"), msg); } lua_pushliteral(L, c"stack traceback:"); while lua_getstack(L1, level, &mut ar) != 0 { - level += 1; - if level == mark { + if limit == 0 { // too many levels? - lua_pushliteral(L, c"\n\t..."); // add a '...' - level = numlevels - COMPAT53_LEVELS2; // and skip to last ones + let n = numlevels - level - COMPAT53_LEVELS2; + // add warning about skip ("n + 1" because we skip current level too) + lua_pushfstring(L, cstr!("\n\t...\t(skipping %d levels)"), n + 1); // add warning about skip + level += n; // and skip to last levels } else { - lua_getinfo(L1, cstr!("Slnt"), &mut ar); - lua_pushfstring(L, cstr!("\n\t%s:"), ar.short_src.as_ptr()); - if ar.currentline > 0 { - lua_pushfstring(L, cstr!("%d:"), ar.currentline); + lua_getinfo(L1, cstr!("Sln"), &mut ar); + if *ar.what != b't' as c_char { + if ar.currentline <= 0 { + lua_pushfstring(L, cstr!("\n\t%s: in "), ar.short_src.as_ptr()); + } else { + lua_pushfstring(L, cstr!("\n\t%s:%d: in "), ar.short_src.as_ptr(), ar.currentline); + } + compat53_pushfuncname(L, L1, &mut ar); + lua_concat(L, lua_gettop(L) - top); + } else { + lua_pushstring(L, cstr!("\n\t(...tail calls...)")); } - lua_pushliteral(L, c" in "); - compat53_pushfuncname(L, &mut ar); - lua_concat(L, lua_gettop(L) - top); } + level += 1; + limit -= 1; } lua_concat(L, lua_gettop(L) - top); } diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index 09c5d0bb..9034ae4c 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -23,8 +23,8 @@ unsafe fn compat53_reverse(L: *mut lua_State, mut a: c_int, mut b: c_int) { } } -const COMPAT53_LEVELS1: c_int = 12; // size of the first part of the stack -const COMPAT53_LEVELS2: c_int = 10; // size of the second part of the stack +const COMPAT53_LEVELS1: c_int = 10; // size of the first part of the stack +const COMPAT53_LEVELS2: c_int = 11; // size of the second part of the stack unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) -> c_int { if level == 0 || lua_istable(L, -1) == 0 { @@ -41,11 +41,10 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) -> lua_pop(L, 1); // remove value (but keep name) return 1; } else if compat53_findfield(L, objidx, level - 1) != 0 { - // try recursively - lua_remove(L, -2); // remove table (but keep name) - lua_pushliteral(L, c"."); - lua_insert(L, -2); // place '.' between the two names - lua_concat(L, 3); + // stack: lib_name, lib_table, field_name (top) + lua_pushliteral(L, c"."); // place '.' between the two names + lua_replace(L, -3); // (in the slot occupied by table) + lua_concat(L, 3); // lib_name.field_name return 1; } } @@ -54,14 +53,25 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) -> 0 // not found } -unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int { +unsafe fn compat53_pushglobalfuncname( + L: *mut lua_State, + L1: *mut lua_State, + level: c_int, + ar: *mut lua_Debug, +) -> c_int { let top = lua_gettop(L); - // push function - lua_getinfo(L, level, cstr!("f"), ar); + lua_getinfo(L1, level, cstr!("f"), ar); // push function + lua_xmove(L1, L, 1); // and move onto L lua_pushvalue(L, LUA_GLOBALSINDEX); + luaL_checkstack(L, 6, cstr!("not enough stack")); // slots for 'findfield' if compat53_findfield(L, top + 1, 2) != 0 { + let name = lua_tostring(L, -1); + if CStr::from_ptr(name).to_bytes().starts_with(b"_G.") { + lua_pushstring(L, name.add(3)); // push name without prefix + lua_remove(L, -2); // remove original name + } lua_copy(L, -1, top + 1); // move name to proper place - lua_pop(L, 2); // remove pushed values + lua_settop(L, top + 1); // remove pushed values 1 } else { lua_settop(L, top); // remove function and global table @@ -69,13 +79,15 @@ unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, level: c_int, ar: *mut } } -unsafe fn compat53_pushfuncname(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) { +unsafe fn compat53_pushfuncname(L: *mut lua_State, L1: *mut lua_State, level: c_int, ar: *mut lua_Debug) { if !(*ar).name.is_null() { // is there a name? lua_pushfstring(L, cstr!("function '%s'"), (*ar).name); - } else if compat53_pushglobalfuncname(L, level, ar) != 0 { + } else if compat53_pushglobalfuncname(L, L1, level, ar) != 0 { lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1)); - lua_remove(L, -2); // remove name + } else if *(*ar).what != b'C' as c_char { + // for Lua functions, use + lua_pushfstring(L, cstr!("function <%s:%d>"), (*ar).short_src, (*ar).linedefined); } else { lua_pushliteral(L, c"?"); } @@ -452,36 +464,42 @@ pub unsafe fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer { pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, mut level: c_int) { let mut ar: lua_Debug = mem::zeroed(); - let top = lua_gettop(L); let numlevels = lua_stackdepth(L); - let mark = if numlevels > COMPAT53_LEVELS1 + COMPAT53_LEVELS2 { - COMPAT53_LEVELS1 - } else { - 0 - }; + #[rustfmt::skip] + let mut limit = if numlevels - level > COMPAT53_LEVELS1 + COMPAT53_LEVELS2 { COMPAT53_LEVELS1 } else { -1 }; + + let mut buf: luaL_Strbuf = mem::zeroed(); + luaL_buffinit(L, &mut buf); if !msg.is_null() { - lua_pushfstring(L, cstr!("%s\n"), msg); + luaL_addstring(&mut buf, msg); + luaL_addstring(&mut buf, cstr!("\n")); } - lua_pushliteral(L, c"stack traceback:"); - while lua_getinfo(L1, level, cstr!(""), &mut ar) != 0 { - if level + 1 == mark { + luaL_addstring(&mut buf, cstr!("stack traceback:")); + while lua_getinfo(L1, level, cstr!("sln"), &mut ar) != 0 { + if limit == 0 { // too many levels? - lua_pushliteral(L, c"\n\t..."); // add a '...' - level = numlevels - COMPAT53_LEVELS2; // and skip to last ones + let n = numlevels - level - COMPAT53_LEVELS2; + // add warning about skip ("n + 1" because we skip current level too) + lua_pushfstring(L, cstr!("\n\t...\t(skipping %d levels)"), n + 1); + luaL_addvalue(&mut buf); + level += n; // and skip to last levels } else { - lua_getinfo(L1, level, cstr!("sln"), &mut ar); - lua_pushfstring(L, cstr!("\n\t%s:"), ar.short_src); + luaL_addstring(&mut buf, cstr!("\n\t")); + luaL_addstring(&mut buf, ar.short_src); + luaL_addstring(&mut buf, cstr!(":")); if ar.currentline > 0 { - lua_pushfstring(L, cstr!("%d:"), ar.currentline); + luaL_addunsigned(&mut buf, ar.currentline as _); + luaL_addstring(&mut buf, cstr!(":")); } - lua_pushliteral(L, c" in "); - compat53_pushfuncname(L, level, &mut ar); - lua_concat(L, lua_gettop(L) - top); + luaL_addstring(&mut buf, cstr!(" in ")); + compat53_pushfuncname(L, L1, level, &mut ar); + luaL_addvalue(&mut buf); } level += 1; + limit -= 1; } - lua_concat(L, lua_gettop(L) - top); + luaL_pushresult(&mut buf); } pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) -> *const c_char { diff --git a/mlua-sys/src/luau/lauxlib.rs b/mlua-sys/src/luau/lauxlib.rs index 45071277..6e3587dd 100644 --- a/mlua-sys/src/luau/lauxlib.rs +++ b/mlua-sys/src/luau/lauxlib.rs @@ -212,3 +212,18 @@ pub unsafe fn luaL_addstring(B: *mut luaL_Strbuf, s: *const c_char) { } luaL_addlstring(B, s, len); } + +pub unsafe fn luaL_addunsigned(B: *mut luaL_Strbuf, mut n: lua_Unsigned) { + let mut buf: [c_char; 32] = [0; 32]; + let mut i = 32; + loop { + i -= 1; + let digit = (n % 10) as u8; + buf[i] = (b'0' + digit) as c_char; + n /= 10; + if n == 0 { + break; + } + } + luaL_addlstring(B, buf.as_ptr().add(i), 32 - i); +} diff --git a/src/state.rs b/src/state.rs index 1837fc05..b6e23cb7 100644 --- a/src/state.rs +++ b/src/state.rs @@ -875,7 +875,7 @@ impl Lua { } } - /// Gets information about the interpreter runtime stack at a given level. + /// Gets information about the interpreter runtime stack at the given level. /// /// This function calls callback `f`, passing the [`Debug`] structure that can be used to get /// information about the function executing at a given level. @@ -899,6 +899,26 @@ impl Lua { } } + /// Creates a traceback of the call stack at the given level. + /// + /// The `msg` parameter, if provided, is added at the beginning of the traceback. + /// The `level` parameter works the same way as in [`Lua::inspect_stack`]. + pub fn traceback(&self, msg: Option<&str>, level: usize) -> Result { + let lua = self.lock(); + unsafe { + check_stack(lua.state(), 3)?; + protect_lua!(lua.state(), 0, 1, |state| { + let msg = match msg { + Some(s) => ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len()), + None => ptr::null(), + }; + // `protect_lua` adds it's own call frame, so we need to increase level by 1 + ffi::luaL_traceback(state, state, msg, (level + 1) as c_int); + })?; + Ok(String(lua.pop_ref())) + } + } + /// Returns the amount of memory (in bytes) currently used inside this Lua state. pub fn used_memory(&self) -> usize { let lua = self.lock(); diff --git a/tests/tests.rs b/tests/tests.rs index 30e9ea7d..30803fc7 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1390,6 +1390,80 @@ fn test_inspect_stack() -> Result<()> { Ok(()) } +#[test] +fn test_traceback() -> Result<()> { + let lua = Lua::new(); + + // Test traceback at level 0 (not inside any function) + let traceback = lua.traceback(None, 0)?.to_string_lossy(); + assert!(traceback.contains("stack traceback:")); + + // Test traceback with a message prefix + let traceback = lua.traceback(Some("error occurred"), 0)?.to_string_lossy(); + assert!(traceback.starts_with("error occurred")); + assert!(traceback.contains("stack traceback:")); + + // Test traceback inside a function + let get_traceback = lua.create_function(|lua, (msg, level): (Option, usize)| { + lua.traceback(msg.as_deref(), level) + })?; + lua.globals().set("get_traceback", get_traceback)?; + + lua.load( + r#" + local function foo() + -- Level 1 is inside foo (the caller) + local traceback = get_traceback(nil, 1) + return traceback + end + local function bar() + local result = foo() + return result + end + local function baz() + local result = bar() + return result + end + + local traceback = baz() + assert(traceback:match("in %a+ 'foo'")) + assert(traceback:match("in %a+ 'bar'")) + assert(traceback:match("in %a+ 'baz'")) + "#, + ) + .exec()?; + + // Test traceback at different levels + lua.load( + r#" + local function foo() + local tb0 = get_traceback(nil, 0) + local tb1 = get_traceback(nil, 1) + local tb2 = get_traceback(nil, 2) + return tb0, tb1, tb2 + end + local function bar() + local tb0, tb1, tb2 = foo() + return tb0, tb1, tb2 + end + + local tb0, tb1, tb2 = bar() + + assert(tb0:match("in %a+ 'get_traceback'")) + assert(tb0:match("in %a+ 'foo'")) + + assert(not tb1:match("in %a+ 'get_traceback'")) + assert(tb1:match("in %a+ 'foo'")) + + assert(not tb2:match("in %a+ 'foo'")) + assert(tb1:match("in %a+ 'bar'")) + "#, + ) + .exec()?; + + Ok(()) +} + #[test] fn test_multi_states() -> Result<()> { let lua = Lua::new(); From f2fd010c5f5b1645201f2d07f1413880bb24dd0a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 28 Oct 2025 16:04:24 +0000 Subject: [PATCH 541/635] Add missing `lua_remove` when discovering function name --- mlua-sys/src/luau/compat.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index 9034ae4c..582bb43b 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -85,6 +85,7 @@ unsafe fn compat53_pushfuncname(L: *mut lua_State, L1: *mut lua_State, level: c_ lua_pushfstring(L, cstr!("function '%s'"), (*ar).name); } else if compat53_pushglobalfuncname(L, L1, level, ar) != 0 { lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1)); + lua_remove(L, -2); // remove name } else if *(*ar).what != b'C' as c_char { // for Lua functions, use lua_pushfstring(L, cstr!("function <%s:%d>"), (*ar).short_src, (*ar).linedefined); From 72ac247dca46d7858fe7e90895972988afa7f1dc Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 4 Nov 2025 23:20:53 +0000 Subject: [PATCH 542/635] Fix `MaybeSend` doc --- src/types.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types.rs b/src/types.rs index 79fc72c2..d17fb04f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -120,6 +120,7 @@ pub trait MaybeSend: Send {} #[cfg(feature = "send")] impl MaybeSend for T {} +/// A trait that adds `Send` requirement if `send` feature is enabled. #[cfg(not(feature = "send"))] pub trait MaybeSend {} #[cfg(not(feature = "send"))] From 0611906c6ab2631682671af66bec04fa2e4df026 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 5 Nov 2025 22:07:14 +0000 Subject: [PATCH 543/635] Add `Lua::type_metatable` helper to get metatable of a primitive type. The accompany function `Lua::set_type_metatable` already exists. --- src/state.rs | 63 ++++++++++++++++++++---------------------------- src/state/raw.rs | 42 +++++++++++++++++++++++++++++++- src/table.rs | 8 ++---- tests/types.rs | 20 ++++++++++----- 4 files changed, 83 insertions(+), 50 deletions(-) diff --git a/src/state.rs b/src/state.rs index b6e23cb7..aa27f2f8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1514,7 +1514,27 @@ impl Lua { unsafe { self.lock().make_userdata(UserDataStorage::new(ud)) } } - /// Sets the metatable for a Lua builtin type. + /// Gets the metatable of a Lua built-in (primitive) type. + /// + /// The metatable is shared by all values of the given type. + /// + /// See [`Lua::set_type_metatable`] for examples. + #[allow(private_bounds)] + pub fn type_metatable(&self) -> Option
{ + let lua = self.lock(); + let state = lua.state(); + unsafe { + let _sg = StackGuard::new(state); + assert_stack(state, 2); + + if lua.push_primitive_type::() && ffi::lua_getmetatable(state, -1) != 0 { + return Some(Table(lua.pop_ref())); + } + } + None + } + + /// Sets the metatable for a Lua built-in (primitive) type. /// /// The metatable will be shared by all values of the given type. /// @@ -1541,44 +1561,13 @@ impl Lua { let _sg = StackGuard::new(state); assert_stack(state, 2); - match T::TYPE_ID { - ffi::LUA_TBOOLEAN => { - ffi::lua_pushboolean(state, 0); - } - ffi::LUA_TLIGHTUSERDATA => { - ffi::lua_pushlightuserdata(state, ptr::null_mut()); - } - ffi::LUA_TNUMBER => { - ffi::lua_pushnumber(state, 0.); - } - #[cfg(feature = "luau")] - ffi::LUA_TVECTOR => { - #[cfg(not(feature = "luau-vector4"))] - ffi::lua_pushvector(state, 0., 0., 0.); - #[cfg(feature = "luau-vector4")] - ffi::lua_pushvector(state, 0., 0., 0., 0.); + if lua.push_primitive_type::() { + match metatable { + Some(metatable) => lua.push_ref(&metatable.0), + None => ffi::lua_pushnil(state), } - ffi::LUA_TSTRING => { - ffi::lua_pushstring(state, b"\0" as *const u8 as *const _); - } - ffi::LUA_TFUNCTION => match self.load("function() end").eval::() { - Ok(func) => lua.push_ref(&func.0), - Err(_) => return, - }, - ffi::LUA_TTHREAD => { - ffi::lua_pushthread(state); - } - #[cfg(feature = "luau")] - ffi::LUA_TBUFFER => { - ffi::lua_newbuffer(state, 0); - } - _ => return, - } - match metatable { - Some(metatable) => lua.push_ref(&metatable.0), - None => ffi::lua_pushnil(state), + ffi::lua_setmetatable(state, -2); } - ffi::lua_setmetatable(state, -2); } } diff --git a/src/state/raw.rs b/src/state/raw.rs index 09831fc7..508ac64d 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -19,7 +19,7 @@ use crate::thread::Thread; use crate::traits::IntoLua; use crate::types::{ AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData, - MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc, + LuaType, MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc, }; use crate::userdata::{ init_userdata_metatable, AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, @@ -665,6 +665,46 @@ impl RawLua { } } + /// Pushes a primitive type value onto the Lua stack. + pub(crate) unsafe fn push_primitive_type(&self) -> bool { + match T::TYPE_ID { + ffi::LUA_TBOOLEAN => { + ffi::lua_pushboolean(self.state(), 0); + } + ffi::LUA_TLIGHTUSERDATA => { + ffi::lua_pushlightuserdata(self.state(), ptr::null_mut()); + } + ffi::LUA_TNUMBER => { + ffi::lua_pushnumber(self.state(), 0.); + } + #[cfg(feature = "luau")] + ffi::LUA_TVECTOR => { + #[cfg(not(feature = "luau-vector4"))] + ffi::lua_pushvector(self.state(), 0., 0., 0.); + #[cfg(feature = "luau-vector4")] + ffi::lua_pushvector(self.state(), 0., 0., 0., 0.); + } + ffi::LUA_TSTRING => { + ffi::lua_pushstring(self.state(), b"\0" as *const u8 as *const _); + } + ffi::LUA_TFUNCTION => { + unsafe extern "C-unwind" fn func(_state: *mut ffi::lua_State) -> c_int { + 0 + } + ffi::lua_pushcfunction(self.state(), func); + } + ffi::LUA_TTHREAD => { + ffi::lua_pushthread(self.state()); + } + #[cfg(feature = "luau")] + ffi::LUA_TBUFFER => { + ffi::lua_newbuffer(self.state(), 0); + } + _ => return false, + } + true + } + /// Pushes a value that implements `IntoLua` onto the Lua stack. /// /// Uses up to 2 stack spaces to push a single value, does not call `checkstack`. diff --git a/src/table.rs b/src/table.rs index 82e40d59..4b184838 100644 --- a/src/table.rs +++ b/src/table.rs @@ -1,14 +1,14 @@ use std::collections::HashSet; use std::fmt; use std::marker::PhantomData; -use std::os::raw::{c_int, c_void}; +use std::os::raw::c_void; use std::string::String as StdString; use crate::error::{Error, Result}; use crate::function::Function; use crate::state::{LuaGuard, RawLua, WeakLua}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike}; -use crate::types::{Integer, LuaType, ValueRef}; +use crate::types::{Integer, ValueRef}; use crate::util::{assert_stack, check_stack, get_metatable_ptr, StackGuard}; use crate::value::{Nil, Value}; @@ -935,10 +935,6 @@ where } } -impl LuaType for Table { - const TYPE_ID: c_int = ffi::LUA_TTABLE; -} - impl ObjectLike for Table { #[inline] fn get(&self, key: impl IntoLua) -> Result { diff --git a/tests/types.rs b/tests/types.rs index 27bbe04d..6c7a0185 100644 --- a/tests/types.rs +++ b/tests/types.rs @@ -31,7 +31,9 @@ fn test_boolean_type_metatable() -> Result<()> { let mt = lua.create_table()?; mt.set("__add", Function::wrap(|a, b| Ok(a || b)))?; - lua.set_type_metatable::(Some(mt)); + assert_eq!(lua.type_metatable::(), None); + lua.set_type_metatable::(Some(mt.clone())); + assert_eq!(lua.type_metatable::().unwrap(), mt); lua.load(r#"assert(true + true == true)"#).exec().unwrap(); lua.load(r#"assert(true + false == true)"#).exec().unwrap(); @@ -52,7 +54,8 @@ fn test_lightuserdata_type_metatable() -> Result<()> { Ok(LightUserData((a.0 as usize + b.0 as usize) as *mut c_void)) }), )?; - lua.set_type_metatable::(Some(mt)); + lua.set_type_metatable::(Some(mt.clone())); + assert_eq!(lua.type_metatable::().unwrap(), mt); let res = lua .load( @@ -77,7 +80,9 @@ fn test_number_type_metatable() -> Result<()> { let mt = lua.create_table()?; mt.set("__call", Function::wrap(|n1: f64, n2: f64| Ok(n1 * n2)))?; - lua.set_type_metatable::(Some(mt)); + lua.set_type_metatable::(Some(mt.clone())); + assert_eq!(lua.type_metatable::().unwrap(), mt); + lua.load(r#"assert((1.5)(3.0) == 4.5)"#).exec().unwrap(); lua.load(r#"assert((5)(5) == 25)"#).exec().unwrap(); @@ -93,7 +98,8 @@ fn test_string_type_metatable() -> Result<()> { "__add", Function::wrap(|a: String, b: String| Ok(format!("{a}{b}"))), )?; - lua.set_type_metatable::(Some(mt)); + lua.set_type_metatable::(Some(mt.clone())); + assert_eq!(lua.type_metatable::().unwrap(), mt); lua.load(r#"assert(("foo" + "bar") == "foobar")"#).exec().unwrap(); @@ -109,7 +115,8 @@ fn test_function_type_metatable() -> Result<()> { "__index", Function::wrap(|_: Function, key: String| Ok(format!("function.{key}"))), )?; - lua.set_type_metatable::(Some(mt)); + lua.set_type_metatable::(Some(mt.clone())); + assert_eq!(lua.type_metatable::(), Some(mt)); lua.load(r#"assert((function() end).foo == "function.foo")"#) .exec() @@ -127,7 +134,8 @@ fn test_thread_type_metatable() -> Result<()> { "__index", Function::wrap(|_: Thread, key: String| Ok(format!("thread.{key}"))), )?; - lua.set_type_metatable::(Some(mt)); + lua.set_type_metatable::(Some(mt.clone())); + assert_eq!(lua.type_metatable::(), Some(mt)); lua.load(r#"assert((coroutine.create(function() end)).foo == "thread.foo")"#) .exec() From feec72bcbd65dfa20136685e6e0c6adf7cfd72d9 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 6 Nov 2025 23:09:26 +0000 Subject: [PATCH 544/635] Reduce number of allocations when calling async function Instead of creating a uniq poller with upvalue on each async call, return future directly and pass it to the poller This also gives about 3-5% perf improvements --- src/state.rs | 10 +++++++--- src/state/raw.rs | 44 ++++++++++++++++++++------------------------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/state.rs b/src/state.rs index aa27f2f8..7cf2a787 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2176,9 +2176,13 @@ impl Lua { None => unsafe { let lua = self.lock(); let state = lua.state(); - let _sg = StackGuard::with_top(state, 0); - let nvals = ffi::lua_gettop(state); - Poll::Ready(R::from_stack_multi(nvals, &lua)) + let top = ffi::lua_gettop(state); + if top == 0 || ffi::lua_type(state, 1) != ffi::LUA_TUSERDATA { + // This must be impossible scenario if used correctly + return Poll::Ready(R::from_stack_multi(0, &lua)); + } + let _sg = StackGuard::with_top(state, 1); + Poll::Ready(R::from_stack_multi(top - 1, &lua)) }, }) .await diff --git a/src/state/raw.rs b/src/state/raw.rs index 508ac64d..b19e871c 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -1287,7 +1287,7 @@ impl RawLua { } } - unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int { + unsafe extern "C-unwind" fn get_future_callback(state: *mut ffi::lua_State) -> c_int { // Async functions cannot be scoped and therefore destroyed, // so the first upvalue is always valid let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); @@ -1301,33 +1301,27 @@ impl RawLua { let extra = XRc::clone(&(*upvalue).extra); let protect = !rawlua.unlikely_memory_error(); push_internal_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?; - if protect { - protect_lua!(state, 1, 1, fn(state) { - ffi::lua_pushcclosure(state, poll_future, 1); - })?; - } else { - ffi::lua_pushcclosure(state, poll_future, 1); - } Ok(1) }) } unsafe extern "C-unwind" fn poll_future(state: *mut ffi::lua_State) -> c_int { - let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); - callback_error_ext(state, (*upvalue).extra.get(), true, |extra, nargs| { + // Future is always passed in the first argument + let future = get_userdata::(state, 1); + callback_error_ext(state, (*future).extra.get(), true, |extra, nargs| { // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) // The lock must be already held as the future is polled let rawlua = (*extra).raw_lua(); - if nargs == 1 && ffi::lua_tolightuserdata(state, -1) == Lua::poll_terminate().0 { + if nargs == 2 && ffi::lua_tolightuserdata(state, -1) == Lua::poll_terminate().0 { // Destroy the future and terminate the Lua thread - (*upvalue).data.take(); + (*future).data.take(); ffi::lua_pushinteger(state, -1); return Ok(1); } - let fut = &mut (*upvalue).data; + let fut = &mut (*future).data; let mut ctx = Context::from_waker(rawlua.waker()); match fut.as_mut().map(|fut| fut.as_mut().poll(&mut ctx)) { Some(Poll::Pending) => { @@ -1366,7 +1360,7 @@ impl RawLua { } let state = self.state(); - let get_poll = unsafe { + let get_future = unsafe { let _sg = StackGuard::new(state); check_stack(state, 4)?; @@ -1376,10 +1370,10 @@ impl RawLua { push_internal_userdata(state, upvalue, protect)?; if protect { protect_lua!(state, 1, 1, fn(state) { - ffi::lua_pushcclosure(state, call_callback, 1); + ffi::lua_pushcclosure(state, get_future_callback, 1); })?; } else { - ffi::lua_pushcclosure(state, call_callback, 1); + ffi::lua_pushcclosure(state, get_future_callback, 1); } Function(self.pop_ref()) @@ -1398,15 +1392,17 @@ impl RawLua { let coroutine = lua.globals().get::
("coroutine")?; // Prepare environment for the async poller - let env = lua.create_table_with_capacity(0, 3)?; - env.set("get_poll", get_poll)?; + let env = lua.create_table_with_capacity(0, 4)?; + env.set("get_future", get_future)?; + env.set("poll", unsafe { lua.create_c_function(poll_future)? })?; env.set("yield", coroutine.get::("yield")?)?; env.set("unpack", unsafe { lua.create_c_function(unpack)? })?; lua.load( r#" - local poll = get_poll(...) - local nres, res, res2 = poll() + local poll, yield = poll, yield + local future = get_future(...) + local nres, res, res2 = poll(future) while true do -- Poll::Ready branch, `nres` is the number of results if nres ~= nil then @@ -1430,13 +1426,13 @@ impl RawLua { -- `res` is a "pending" value -- `yield` can return a signal to drop the future that we should propagate -- to the poller - nres, res, res2 = poll(yield(res)) + nres, res, res2 = poll(future, yield(res)) elseif res2 == 0 then - nres, res, res2 = poll(yield()) + nres, res, res2 = poll(future, yield()) elseif res2 == 1 then - nres, res, res2 = poll(yield(res)) + nres, res, res2 = poll(future, yield(res)) else - nres, res, res2 = poll(yield(unpack(res, res2))) + nres, res, res2 = poll(future, yield(unpack(res, res2))) end end "#, From 1bd1359f4333460a8f46177a8cbbeaa295d61a61 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 6 Nov 2025 23:28:37 +0000 Subject: [PATCH 545/635] Exclude the first arg when checking for `yield_with` call. This is part of the previous commit --- src/state/raw.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index b19e871c..e951e31b 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -1325,7 +1325,7 @@ impl RawLua { let mut ctx = Context::from_waker(rawlua.waker()); match fut.as_mut().map(|fut| fut.as_mut().poll(&mut ctx)) { Some(Poll::Pending) => { - let fut_nvals = ffi::lua_gettop(state); + let fut_nvals = ffi::lua_gettop(state) - 1; // Exclude the future itself if fut_nvals >= 3 && ffi::lua_tolightuserdata(state, -3) == Lua::poll_yield().0 { // We have some values to yield ffi::lua_pushnil(state); From cd56f92a7f1bae9f6d3b3f55906295ee54909a44 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 16 Nov 2025 23:12:06 +0000 Subject: [PATCH 546/635] Update Luau definitions in mlua-sys to 0.700 --- mlua-sys/Cargo.toml | 4 ++-- mlua-sys/src/luau/compat.rs | 10 ++-------- mlua-sys/src/luau/lua.rs | 7 +++++++ mlua-sys/src/luau/luarequire.rs | 33 ++++++++++++++++++++++++++------- 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 674f060f..2c2b38dc 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.8.3" +version = "0.9.0" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" @@ -41,7 +41,7 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 548.1.0, < 548.2.0", optional = true } luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true } -luau0-src = { version = "0.15.6", optional = true } +luau0-src = { version = "0.16.0", optional = true, git = "https://github.com/mlua-rs/luau-src-rs" } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index 582bb43b..bf2a7b95 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -203,9 +203,7 @@ pub unsafe fn lua_rawgeti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_in #[inline(always)] pub unsafe fn lua_rawgetp(L: *mut lua_State, idx: c_int, p: *const c_void) -> c_int { - let abs_i = lua_absindex(L, idx); - lua_pushlightuserdata(L, p as *mut c_void); - lua_rawget(L, abs_i) + lua_rawgetptagged(L, idx, p, 0) } #[inline(always)] @@ -239,11 +237,7 @@ pub unsafe fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer) { #[inline(always)] pub unsafe fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void) { - let abs_i = lua_absindex(L, idx); - luaL_checkstack(L, 1, cstr!("not enough stack slots available")); - lua_pushlightuserdata(L, p as *mut c_void); - lua_insert(L, -2); - lua_rawset(L, abs_i); + lua_rawsetptagged(L, idx, p, 0) } #[inline(always)] diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 2d5b2bd5..bd21360d 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -203,6 +203,7 @@ unsafe extern "C-unwind" { pub fn lua_rawget(L: *mut lua_State, idx: c_int) -> c_int; #[link_name = "lua_rawgeti"] pub fn lua_rawgeti_(L: *mut lua_State, idx: c_int, n: c_int) -> c_int; + pub fn lua_rawgetptagged(L: *mut lua_State, idx: c_int, p: *const c_void, tag: c_int) -> c_int; pub fn lua_createtable(L: *mut lua_State, narr: c_int, nrec: c_int); pub fn lua_setreadonly(L: *mut lua_State, idx: c_int, enabled: c_int); @@ -220,6 +221,7 @@ unsafe extern "C-unwind" { pub fn lua_rawset(L: *mut lua_State, idx: c_int); #[link_name = "lua_rawseti"] pub fn lua_rawseti_(L: *mut lua_State, idx: c_int, n: c_int); + pub fn lua_rawsetptagged(L: *mut lua_State, idx: c_int, p: *const c_void, tag: c_int); pub fn lua_setmetatable(L: *mut lua_State, objindex: c_int) -> c_int; pub fn lua_setfenv(L: *mut lua_State, idx: c_int) -> c_int; @@ -545,4 +547,9 @@ unsafe extern "C" { unsafe extern "C" { pub fn luau_setfflag(name: *const c_char, value: c_int) -> c_int; pub fn lua_getmetatablepointer(L: *mut lua_State, idx: c_int) -> *const c_void; + pub fn lua_gcdump( + L: *mut lua_State, + file: *mut c_void, + category_name: Option *const c_char>, + ); } diff --git a/mlua-sys/src/luau/luarequire.rs b/mlua-sys/src/luau/luarequire.rs index 64e45e25..4a929ded 100644 --- a/mlua-sys/src/luau/luarequire.rs +++ b/mlua-sys/src/luau/luarequire.rs @@ -23,6 +23,16 @@ pub enum luarequire_WriteResult { Failure, } +/// Represents whether a configuration file is present, and if so, its syntax. +#[repr(C)] +pub enum luarequire_ConfigStatus { + Absent, + // Signals the presence of multiple configuration files + Ambiguous, + PresentJson, + PresentLuau, +} + #[repr(C)] pub struct luarequire_Configuration { // Returns whether requires are permitted from the given chunkname. @@ -90,13 +100,14 @@ pub struct luarequire_Configuration { size_out: *mut usize, ) -> luarequire_WriteResult, - // Returns whether a configuration file is present in the current context. - // If not, require-by-string will call to_parent until either a configuration file is present or + // Returns whether a configuration file is present in the current context, and if so, its syntax. + // If not present, require-by-string will call to_parent until either a configuration file is present or // NAVIGATE_FAILURE is returned (at root). - pub is_config_present: unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> bool, + pub get_config_status: + unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> luarequire_ConfigStatus, // Parses the configuration file in the current context for the given alias and returns its - // value or WRITE_FAILURE if not found. This function is only called if is_config_present + // value or WRITE_FAILURE if not found. This function is only called if get_config_status // returns true. If this function pointer is set, get_config must not be set. Opting in to this // function pointer disables parsing configuration files internally and can be used for finer // control over the configuration file parsing process. @@ -111,9 +122,10 @@ pub struct luarequire_Configuration { ) -> luarequire_WriteResult, >, - // Provides the contents of the configuration file in the current context. This function is only called - // if is_config_present returns true. If this function pointer is set, get_alias must not be set. Opting - // in to this function pointer enables parsing configuration files internally. + // Provides the contents of the configuration file in the current context. + // This function is only called if get_config_status does not return CONFIG_ABSENT. If this function + // pointer is set, get_alias must not be set. Opting in to this function pointer enables parsing + // configuration files internally. pub get_config: Option< unsafe extern "C-unwind" fn( L: *mut lua_State, @@ -124,6 +136,13 @@ pub struct luarequire_Configuration { ) -> luarequire_WriteResult, >, + // Returns the maximum number of milliseconds to allow for executing a given Luau-syntax configuration + // file. This function is only called if get_config_status returns CONFIG_PRESENT_LUAU and can be left + // undefined if support for Luau-syntax configuration files is not needed. A default value of 2000ms is + // used. Negative values are treated as infinite. + pub get_luau_config_timeout: + Option c_int>, + // Executes the module and places the result on the stack. Returns the number of results placed on the // stack. // Returning -1 directs the requiring thread to yield. In this case, this thread should be resumed with From 9a7f75ad6be281e7aff4029b1161fc6e080244dc Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 16 Nov 2025 23:13:11 +0000 Subject: [PATCH 547/635] Update `require` implementation to satisfy Luau 0.700 --- Cargo.toml | 4 +- src/luau/require.rs | 322 +++++++---------------------------------- src/luau/require/fs.rs | 278 +++++++++++++++++++++++++++++++++++ 3 files changed, 333 insertions(+), 271 deletions(-) create mode 100644 src/luau/require/fs.rs diff --git a/Cargo.toml b/Cargo.toml index 47726baa..e1a239b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "mlua" version = "0.11.4" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] -rust-version = "1.79.0" +rust-version = "1.80.0" edition = "2021" repository = "https://github.com/mlua-rs/mlua" documentation = "https://docs.rs/mlua" @@ -62,7 +62,7 @@ parking_lot = { version = "0.12", features = ["arc_lock"] } anyhow = { version = "1.0", optional = true } rustversion = "1.0" -ffi = { package = "mlua-sys", version = "0.8.3", path = "mlua-sys" } +ffi = { package = "mlua-sys", version = "0.9.0", path = "mlua-sys" } [dev-dependencies] trybuild = "1.0" diff --git a/src/luau/require.rs b/src/luau/require.rs index 7e232f73..d8c75b4d 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -1,12 +1,10 @@ use std::cell::RefCell; -use std::collections::VecDeque; use std::ffi::CStr; use std::io::Result as IoResult; use std::ops::{Deref, DerefMut}; use std::os::raw::{c_char, c_int, c_void}; -use std::path::{Component, Path, PathBuf}; use std::result::Result as StdResult; -use std::{env, fmt, fs, mem, ptr}; +use std::{fmt, mem, ptr}; use crate::error::{Error, Result}; use crate::function::Function; @@ -14,6 +12,9 @@ use crate::state::{callback_error_ext, Lua}; use crate::table::Table; use crate::types::MaybeSend; +// TODO: Rename to FsRequirer +pub use fs::TextRequirer; + /// An error that can occur during navigation in the Luau `require-by-string` system. #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] @@ -50,6 +51,9 @@ impl From for NavigateError { #[cfg(feature = "luau")] type WriteResult = ffi::luarequire_WriteResult; +#[cfg(feature = "luau")] +type ConfigStatus = ffi::luarequire_ConfigStatus; + /// A trait for handling modules loading and navigation in the Luau `require-by-string` system. #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] @@ -73,7 +77,7 @@ pub trait Require { /// Navigate to the given child directory. fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError>; - /// Returns whether the context is currently pointing at a module + /// Returns whether the context is currently pointing at a module. fn has_module(&self) -> bool; /// Provides a cache key representing the current module. @@ -103,226 +107,31 @@ impl fmt::Debug for dyn Require { } } -/// The standard implementation of Luau `require-by-string` navigation. -#[derive(Default, Debug)] -pub struct TextRequirer { - /// An absolute path to the current Luau module (not mapped to a physical file) - abs_path: PathBuf, - /// A relative path to the current Luau module (not mapped to a physical file) - rel_path: PathBuf, - /// A physical path to the current Luau module, which is a file or a directory with an - /// `init.lua(u)` file - resolved_path: Option, -} - -impl TextRequirer { - /// The prefix used for chunk names in the require system. - /// Only chunk names starting with this prefix are allowed to be used in `require`. - const CHUNK_PREFIX: &str = "@"; - - /// The file extensions that are considered valid for Luau modules. - const FILE_EXTENSIONS: &[&str] = &["luau", "lua"]; - - /// Creates a new `TextRequirer` instance. - pub fn new() -> Self { - Self::default() - } - - fn normalize_chunk_name(chunk_name: &str) -> &str { - if let Some((path, line)) = chunk_name.rsplit_once(':') { - if line.parse::().is_ok() { - return path; - } - } - chunk_name - } - - // Normalizes the path by removing unnecessary components - fn normalize_path(path: &Path) -> PathBuf { - let mut components = VecDeque::new(); - - for comp in path.components() { - match comp { - Component::Prefix(..) | Component::RootDir => { - components.push_back(comp); - } - Component::CurDir => {} - Component::ParentDir => { - if matches!(components.back(), None | Some(Component::ParentDir)) { - components.push_back(Component::ParentDir); - } else if matches!(components.back(), Some(Component::Normal(..))) { - components.pop_back(); - } - } - Component::Normal(..) => components.push_back(comp), - } - } - - if matches!(components.front(), None | Some(Component::Normal(..))) { - components.push_front(Component::CurDir); - } - - // Join the components back together - components.into_iter().collect() - } - - /// Resolve a Luau module path to a physical file or directory. - /// - /// Empty directories without init files are considered valid as "intermediate" directories. - fn resolve_module(path: &Path) -> StdResult, NavigateError> { - let mut found_path = None; - - if path.components().next_back() != Some(Component::Normal("init".as_ref())) { - let current_ext = (path.extension().and_then(|s| s.to_str())) - .map(|s| format!("{s}.")) - .unwrap_or_default(); - for ext in Self::FILE_EXTENSIONS { - let candidate = path.with_extension(format!("{current_ext}{ext}")); - if candidate.is_file() && found_path.replace(candidate).is_some() { - return Err(NavigateError::Ambiguous); - } - } - } - if path.is_dir() { - for component in Self::FILE_EXTENSIONS.iter().map(|ext| format!("init.{ext}")) { - let candidate = path.join(component); - if candidate.is_file() && found_path.replace(candidate).is_some() { - return Err(NavigateError::Ambiguous); - } - } - - if found_path.is_none() { - // Directories without init files are considered valid "intermediate" path - return Ok(None); - } - } - - Ok(Some(found_path.ok_or(NavigateError::NotFound)?)) - } -} - -impl Require for TextRequirer { - fn is_require_allowed(&self, chunk_name: &str) -> bool { - chunk_name.starts_with(Self::CHUNK_PREFIX) - } - - fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError> { - if !chunk_name.starts_with(Self::CHUNK_PREFIX) { - return Err(NavigateError::NotFound); - } - let chunk_name = Self::normalize_chunk_name(&chunk_name[1..]); - let chunk_path = Self::normalize_path(chunk_name.as_ref()); - - if chunk_path.extension() == Some("rs".as_ref()) { - // Special case for Rust source files, reset to the current directory - let chunk_filename = chunk_path.file_name().unwrap(); - let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?; - self.abs_path = Self::normalize_path(&cwd.join(chunk_filename)); - self.rel_path = ([Component::CurDir, Component::Normal(chunk_filename)].into_iter()).collect(); - self.resolved_path = None; - - return Ok(()); - } - - if chunk_path.is_absolute() { - let resolved_path = Self::resolve_module(&chunk_path)?; - self.abs_path = chunk_path.clone(); - self.rel_path = chunk_path; - self.resolved_path = resolved_path; - } else { - // Relative path - let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?; - let abs_path = Self::normalize_path(&cwd.join(&chunk_path)); - let resolved_path = Self::resolve_module(&abs_path)?; - self.abs_path = abs_path; - self.rel_path = chunk_path; - self.resolved_path = resolved_path; - } - - Ok(()) - } - - fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> { - let path = Self::normalize_path(path.as_ref()); - let resolved_path = Self::resolve_module(&path)?; - - self.abs_path = path.clone(); - self.rel_path = path; - self.resolved_path = resolved_path; - - Ok(()) - } - - fn to_parent(&mut self) -> StdResult<(), NavigateError> { - let mut abs_path = self.abs_path.clone(); - if !abs_path.pop() { - // It's important to return `NotFound` if we reached the root, as it's a "recoverable" error if we - // cannot go beyond the root directory. - // Luau "require-by-string` has a special logic to search for config file to resolve aliases. - return Err(NavigateError::NotFound); - } - let mut rel_parent = self.rel_path.clone(); - rel_parent.pop(); - let resolved_path = Self::resolve_module(&abs_path)?; - - self.abs_path = abs_path; - self.rel_path = Self::normalize_path(&rel_parent); - self.resolved_path = resolved_path; - - Ok(()) - } - - fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> { - let abs_path = self.abs_path.join(name); - let rel_path = self.rel_path.join(name); - let resolved_path = Self::resolve_module(&abs_path)?; - - self.abs_path = abs_path; - self.rel_path = rel_path; - self.resolved_path = resolved_path; - - Ok(()) - } - - fn has_module(&self) -> bool { - (self.resolved_path.as_deref()) - .map(Path::is_file) - .unwrap_or(false) - } - - fn cache_key(&self) -> String { - self.resolved_path.as_deref().unwrap().display().to_string() - } - - fn has_config(&self) -> bool { - self.abs_path.is_dir() && self.abs_path.join(".luaurc").is_file() - } - - fn config(&self) -> IoResult> { - fs::read(self.abs_path.join(".luaurc")) - } - - fn loader(&self, lua: &Lua) -> Result { - let name = format!("@{}", self.rel_path.display()); - lua.load(self.resolved_path.as_deref().unwrap()) - .set_name(name) - .into_function() - } +struct Context { + require: Box, + config_cache: Option>>, } -struct Context(Box); - impl Deref for Context { type Target = dyn Require; fn deref(&self) -> &Self::Target { - &*self.0 + &*self.require } } impl DerefMut for Context { fn deref_mut(&mut self) -> &mut Self::Target { - &mut *self.0 + &mut *self.require + } +} + +impl Context { + fn new(require: impl Require + MaybeSend + 'static) -> Self { + Context { + require: Box::new(require), + config_cache: None, + } } } @@ -447,9 +256,18 @@ pub(super) unsafe extern "C-unwind" fn init_config(config: *mut ffi::luarequire_ write_to_buffer(buffer, buffer_size, size_out, cache_key.as_bytes()) } - unsafe extern "C-unwind" fn is_config_present(state: *mut ffi::lua_State, ctx: *mut c_void) -> bool { - let this = try_borrow!(state, ctx); - this.has_config() + unsafe extern "C-unwind" fn get_config_status( + state: *mut ffi::lua_State, + ctx: *mut c_void, + ) -> ConfigStatus { + let mut this = try_borrow_mut!(state, ctx); + if this.has_config() { + this.config_cache = Some(this.config()); + if let Some(Ok(data)) = &this.config_cache { + return detect_config_format(data); + } + } + ConfigStatus::Absent } unsafe extern "C-unwind" fn get_config( @@ -459,8 +277,10 @@ pub(super) unsafe extern "C-unwind" fn init_config(config: *mut ffi::luarequire_ buffer_size: usize, size_out: *mut usize, ) -> WriteResult { - let this = try_borrow!(state, ctx); - let config = callback_error_ext(state, ptr::null_mut(), true, move |_, _| Ok(this.config()?)); + let mut this = try_borrow_mut!(state, ctx); + let config = callback_error_ext(state, ptr::null_mut(), true, move |_, _| { + Ok(this.config_cache.take().unwrap_or_else(|| this.config())?) + }); write_to_buffer(buffer, buffer_size, size_out, &config) } @@ -489,12 +309,24 @@ pub(super) unsafe extern "C-unwind" fn init_config(config: *mut ffi::luarequire_ (*config).get_chunkname = get_chunkname; (*config).get_loadname = get_loadname; (*config).get_cache_key = get_cache_key; - (*config).is_config_present = is_config_present; + (*config).get_config_status = get_config_status; (*config).get_alias = None; (*config).get_config = Some(get_config); (*config).load = load; } +/// Detect configuration file format (JSON or Luau) +fn detect_config_format(data: &[u8]) -> ConfigStatus { + let data = data.trim_ascii(); + if data.starts_with(b"{") { + let data = &data[1..].trim_ascii_start(); + if data.starts_with(b"\"") || data == b"}" { + return ConfigStatus::PresentJson; + } + } + ConfigStatus::PresentLuau +} + /// Helper function to write data to a buffer #[cfg(feature = "luau")] unsafe fn write_to_buffer( @@ -545,7 +377,7 @@ pub(super) fn create_require_function( let (get_cache_key, find_current_file, proxyrequire, registered_modules, loader_cache) = unsafe { lua.exec_raw::<(Function, Function, Function, Table, Table)>((), move |state| { - let context = Context(Box::new(require)); + let context = Context::new(require); let context_ptr = ffi::lua_newuserdata_t(state, RefCell::new(context)); ffi::lua_pushcclosured(state, get_cache_key, cstr!("get_cache_key"), 1); ffi::lua_pushcfunctiond(state, find_current_file, cstr!("find_current_file")); @@ -637,52 +469,4 @@ pub(super) fn create_require_function( .into_function() } -#[cfg(test)] -mod tests { - use std::path::Path; - - use super::TextRequirer; - - #[test] - fn test_path_normalize() { - for (input, expected) in [ - // Basic formatting checks - ("", "./"), - (".", "./"), - ("a/relative/path", "./a/relative/path"), - // Paths containing extraneous '.' and '/' symbols - ("./remove/extraneous/symbols/", "./remove/extraneous/symbols"), - ("./remove/extraneous//symbols", "./remove/extraneous/symbols"), - ("./remove/extraneous/symbols/.", "./remove/extraneous/symbols"), - ("./remove/extraneous/./symbols", "./remove/extraneous/symbols"), - ("../remove/extraneous/symbols/", "../remove/extraneous/symbols"), - ("../remove/extraneous//symbols", "../remove/extraneous/symbols"), - ("../remove/extraneous/symbols/.", "../remove/extraneous/symbols"), - ("../remove/extraneous/./symbols", "../remove/extraneous/symbols"), - ("/remove/extraneous/symbols/", "/remove/extraneous/symbols"), - ("/remove/extraneous//symbols", "/remove/extraneous/symbols"), - ("/remove/extraneous/symbols/.", "/remove/extraneous/symbols"), - ("/remove/extraneous/./symbols", "/remove/extraneous/symbols"), - // Paths containing '..' - ("./remove/me/..", "./remove"), - ("./remove/me/../", "./remove"), - ("../remove/me/..", "../remove"), - ("../remove/me/../", "../remove"), - ("/remove/me/..", "/remove"), - ("/remove/me/../", "/remove"), - ("./..", "../"), - ("./../", "../"), - ("../..", "../../"), - ("../../", "../../"), - // '..' disappears if path is absolute and component is non-erasable - ("/../", "/"), - ] { - let path = TextRequirer::normalize_path(input.as_ref()); - assert_eq!( - &path, - expected.as_ref() as &Path, - "wrong normalization for {input}" - ); - } - } -} +mod fs; diff --git a/src/luau/require/fs.rs b/src/luau/require/fs.rs new file mode 100644 index 00000000..4e7261bf --- /dev/null +++ b/src/luau/require/fs.rs @@ -0,0 +1,278 @@ +use std::collections::VecDeque; +use std::io::Result as IoResult; +use std::path::{Component, Path, PathBuf}; +use std::result::Result as StdResult; +use std::{env, fs}; + +use crate::error::Result; +use crate::function::Function; +use crate::state::Lua; + +use super::{NavigateError, Require}; + +/// The standard implementation of Luau `require-by-string` navigation. +#[derive(Default, Debug)] +pub struct TextRequirer { + /// An absolute path to the current Luau module (not mapped to a physical file) + abs_path: PathBuf, + /// A relative path to the current Luau module (not mapped to a physical file) + rel_path: PathBuf, + /// A physical path to the current Luau module, which is a file or a directory with an + /// `init.lua(u)` file + resolved_path: Option, +} + +impl TextRequirer { + /// The prefix used for chunk names in the require system. + /// Only chunk names starting with this prefix are allowed to be used in `require`. + const CHUNK_PREFIX: &str = "@"; + + /// The file extensions that are considered valid for Luau modules. + const FILE_EXTENSIONS: &[&str] = &["luau", "lua"]; + + /// The filename for the JSON configuration file. + const LUAURC_CONFIG_FILENAME: &str = ".luaurc"; + + /// The filename for the Luau configuration file. + const LUAU_CONFIG_FILENAME: &str = ".config.luau"; + + /// Creates a new `TextRequirer` instance. + pub fn new() -> Self { + Self::default() + } + + fn normalize_chunk_name(chunk_name: &str) -> &str { + if let Some((path, line)) = chunk_name.rsplit_once(':') { + if line.parse::().is_ok() { + return path; + } + } + chunk_name + } + + // Normalizes the path by removing unnecessary components + fn normalize_path(path: &Path) -> PathBuf { + let mut components = VecDeque::new(); + + for comp in path.components() { + match comp { + Component::Prefix(..) | Component::RootDir => { + components.push_back(comp); + } + Component::CurDir => {} + Component::ParentDir => { + if matches!(components.back(), None | Some(Component::ParentDir)) { + components.push_back(Component::ParentDir); + } else if matches!(components.back(), Some(Component::Normal(..))) { + components.pop_back(); + } + } + Component::Normal(..) => components.push_back(comp), + } + } + + if matches!(components.front(), None | Some(Component::Normal(..))) { + components.push_front(Component::CurDir); + } + + // Join the components back together + components.into_iter().collect() + } + + /// Resolve a Luau module path to a physical file or directory. + /// + /// Empty directories without init files are considered valid as "intermediate" directories. + fn resolve_module(path: &Path) -> StdResult, NavigateError> { + let mut found_path = None; + + if path.components().next_back() != Some(Component::Normal("init".as_ref())) { + let current_ext = (path.extension().and_then(|s| s.to_str())) + .map(|s| format!("{s}.")) + .unwrap_or_default(); + for ext in Self::FILE_EXTENSIONS { + let candidate = path.with_extension(format!("{current_ext}{ext}")); + if candidate.is_file() && found_path.replace(candidate).is_some() { + return Err(NavigateError::Ambiguous); + } + } + } + if path.is_dir() { + for component in Self::FILE_EXTENSIONS.iter().map(|ext| format!("init.{ext}")) { + let candidate = path.join(component); + if candidate.is_file() && found_path.replace(candidate).is_some() { + return Err(NavigateError::Ambiguous); + } + } + + if found_path.is_none() { + // Directories without init files are considered valid "intermediate" path + return Ok(None); + } + } + + Ok(Some(found_path.ok_or(NavigateError::NotFound)?)) + } +} + +impl Require for TextRequirer { + fn is_require_allowed(&self, chunk_name: &str) -> bool { + chunk_name.starts_with(Self::CHUNK_PREFIX) + } + + fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError> { + if !chunk_name.starts_with(Self::CHUNK_PREFIX) { + return Err(NavigateError::NotFound); + } + let chunk_name = Self::normalize_chunk_name(&chunk_name[1..]); + let chunk_path = Self::normalize_path(chunk_name.as_ref()); + + if chunk_path.extension() == Some("rs".as_ref()) { + // Special case for Rust source files, reset to the current directory + let chunk_filename = chunk_path.file_name().unwrap(); + let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?; + self.abs_path = Self::normalize_path(&cwd.join(chunk_filename)); + self.rel_path = ([Component::CurDir, Component::Normal(chunk_filename)].into_iter()).collect(); + self.resolved_path = None; + + return Ok(()); + } + + if chunk_path.is_absolute() { + let resolved_path = Self::resolve_module(&chunk_path)?; + self.abs_path = chunk_path.clone(); + self.rel_path = chunk_path; + self.resolved_path = resolved_path; + } else { + // Relative path + let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?; + let abs_path = Self::normalize_path(&cwd.join(&chunk_path)); + let resolved_path = Self::resolve_module(&abs_path)?; + self.abs_path = abs_path; + self.rel_path = chunk_path; + self.resolved_path = resolved_path; + } + + Ok(()) + } + + fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> { + let path = Self::normalize_path(path.as_ref()); + let resolved_path = Self::resolve_module(&path)?; + + self.abs_path = path.clone(); + self.rel_path = path; + self.resolved_path = resolved_path; + + Ok(()) + } + + fn to_parent(&mut self) -> StdResult<(), NavigateError> { + let mut abs_path = self.abs_path.clone(); + if !abs_path.pop() { + // It's important to return `NotFound` if we reached the root, as it's a "recoverable" error if we + // cannot go beyond the root directory. + // Luau "require-by-string` has a special logic to search for config file to resolve aliases. + return Err(NavigateError::NotFound); + } + let mut rel_parent = self.rel_path.clone(); + rel_parent.pop(); + let resolved_path = Self::resolve_module(&abs_path)?; + + self.abs_path = abs_path; + self.rel_path = Self::normalize_path(&rel_parent); + self.resolved_path = resolved_path; + + Ok(()) + } + + fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> { + let abs_path = self.abs_path.join(name); + let rel_path = self.rel_path.join(name); + let resolved_path = Self::resolve_module(&abs_path)?; + + self.abs_path = abs_path; + self.rel_path = rel_path; + self.resolved_path = resolved_path; + + Ok(()) + } + + fn has_module(&self) -> bool { + (self.resolved_path.as_deref()) + .map(Path::is_file) + .unwrap_or(false) + } + + fn cache_key(&self) -> String { + self.resolved_path.as_deref().unwrap().display().to_string() + } + + fn has_config(&self) -> bool { + self.abs_path.is_dir() && self.abs_path.join(Self::LUAURC_CONFIG_FILENAME).is_file() + || self.abs_path.is_dir() && self.abs_path.join(Self::LUAU_CONFIG_FILENAME).is_file() + } + + fn config(&self) -> IoResult> { + if self.abs_path.join(Self::LUAURC_CONFIG_FILENAME).is_file() { + return fs::read(self.abs_path.join(Self::LUAURC_CONFIG_FILENAME)); + } + fs::read(self.abs_path.join(Self::LUAU_CONFIG_FILENAME)) + } + + fn loader(&self, lua: &Lua) -> Result { + let name = format!("@{}", self.rel_path.display()); + lua.load(self.resolved_path.as_deref().unwrap()) + .set_name(name) + .into_function() + } +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use super::TextRequirer; + + #[test] + fn test_path_normalize() { + for (input, expected) in [ + // Basic formatting checks + ("", "./"), + (".", "./"), + ("a/relative/path", "./a/relative/path"), + // Paths containing extraneous '.' and '/' symbols + ("./remove/extraneous/symbols/", "./remove/extraneous/symbols"), + ("./remove/extraneous//symbols", "./remove/extraneous/symbols"), + ("./remove/extraneous/symbols/.", "./remove/extraneous/symbols"), + ("./remove/extraneous/./symbols", "./remove/extraneous/symbols"), + ("../remove/extraneous/symbols/", "../remove/extraneous/symbols"), + ("../remove/extraneous//symbols", "../remove/extraneous/symbols"), + ("../remove/extraneous/symbols/.", "../remove/extraneous/symbols"), + ("../remove/extraneous/./symbols", "../remove/extraneous/symbols"), + ("/remove/extraneous/symbols/", "/remove/extraneous/symbols"), + ("/remove/extraneous//symbols", "/remove/extraneous/symbols"), + ("/remove/extraneous/symbols/.", "/remove/extraneous/symbols"), + ("/remove/extraneous/./symbols", "/remove/extraneous/symbols"), + // Paths containing '..' + ("./remove/me/..", "./remove"), + ("./remove/me/../", "./remove"), + ("../remove/me/..", "../remove"), + ("../remove/me/../", "../remove"), + ("/remove/me/..", "/remove"), + ("/remove/me/../", "/remove"), + ("./..", "../"), + ("./../", "../"), + ("../..", "../../"), + ("../../", "../../"), + // '..' disappears if path is absolute and component is non-erasable + ("/../", "/"), + ] { + let path = TextRequirer::normalize_path(input.as_ref()); + assert_eq!( + &path, + expected.as_ref() as &Path, + "wrong normalization for {input}" + ); + } + } +} From 0beaac228c3d386440010e2df8ba5b6dea9cca8a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 16 Nov 2025 23:51:51 +0000 Subject: [PATCH 548/635] Update Luau `require` tests --- tests/luau/require.rs | 69 +++++++++++++++---- .../with_config/chained_aliases/.luaurc | 9 +++ .../chained_aliases/outer_dependency.luau | 1 + .../chained_aliases/subdirectory/.luaurc | 10 +++ .../subdirectory/failing_requirer_cyclic.luau | 1 + .../failing_requirer_missing.luau | 1 + .../subdirectory/inner_dependency.luau | 1 + .../subdirectory/successful_requirer.luau | 7 ++ .../require/with_config_luau/.config.luau | 8 +++ .../chained_aliases/.config.luau | 11 +++ .../chained_aliases/outer_dependency.luau | 1 + .../chained_aliases/subdirectory/.config.luau | 12 ++++ .../subdirectory/failing_requirer_cyclic.luau | 1 + .../failing_requirer_missing.luau | 1 + .../subdirectory/inner_dependency.luau | 1 + .../subdirectory/successful_requirer.luau | 7 ++ .../require/with_config_luau/src/.config.luau | 8 +++ .../with_config_luau/src/alias_requirer.luau | 1 + .../src/alias_requirer_uc.luau | 1 + .../with_config_luau/src/dependency.luau | 1 + .../src/directory_alias_requirer.luau | 1 + .../src/other_dependency.luau | 1 + .../src/parent_alias_requirer.luau | 1 + .../subdirectory/subdirectory_dependency.luau | 1 + 24 files changed, 141 insertions(+), 15 deletions(-) create mode 100644 tests/luau/require/with_config/chained_aliases/.luaurc create mode 100644 tests/luau/require/with_config/chained_aliases/outer_dependency.luau create mode 100644 tests/luau/require/with_config/chained_aliases/subdirectory/.luaurc create mode 100644 tests/luau/require/with_config/chained_aliases/subdirectory/failing_requirer_cyclic.luau create mode 100644 tests/luau/require/with_config/chained_aliases/subdirectory/failing_requirer_missing.luau create mode 100644 tests/luau/require/with_config/chained_aliases/subdirectory/inner_dependency.luau create mode 100644 tests/luau/require/with_config/chained_aliases/subdirectory/successful_requirer.luau create mode 100644 tests/luau/require/with_config_luau/.config.luau create mode 100644 tests/luau/require/with_config_luau/chained_aliases/.config.luau create mode 100644 tests/luau/require/with_config_luau/chained_aliases/outer_dependency.luau create mode 100644 tests/luau/require/with_config_luau/chained_aliases/subdirectory/.config.luau create mode 100644 tests/luau/require/with_config_luau/chained_aliases/subdirectory/failing_requirer_cyclic.luau create mode 100644 tests/luau/require/with_config_luau/chained_aliases/subdirectory/failing_requirer_missing.luau create mode 100644 tests/luau/require/with_config_luau/chained_aliases/subdirectory/inner_dependency.luau create mode 100644 tests/luau/require/with_config_luau/chained_aliases/subdirectory/successful_requirer.luau create mode 100644 tests/luau/require/with_config_luau/src/.config.luau create mode 100644 tests/luau/require/with_config_luau/src/alias_requirer.luau create mode 100644 tests/luau/require/with_config_luau/src/alias_requirer_uc.luau create mode 100644 tests/luau/require/with_config_luau/src/dependency.luau create mode 100644 tests/luau/require/with_config_luau/src/directory_alias_requirer.luau create mode 100644 tests/luau/require/with_config_luau/src/other_dependency.luau create mode 100644 tests/luau/require/with_config_luau/src/parent_alias_requirer.luau create mode 100644 tests/luau/require/with_config_luau/src/subdirectory/subdirectory_dependency.luau diff --git a/tests/luau/require.rs b/tests/luau/require.rs index 754e7ee4..7dfd7a86 100644 --- a/tests/luau/require.rs +++ b/tests/luau/require.rs @@ -1,7 +1,7 @@ use std::io::Result as IoResult; use std::result::Result as StdResult; -use mlua::{Error, IntoLua, Lua, MultiValue, NavigateError, Require, Result, TextRequirer, Value}; +use mlua::{Error, FromLua, IntoLua, Lua, MultiValue, NavigateError, Require, Result, TextRequirer, Value}; fn run_require(lua: &Lua, path: impl IntoLua) -> Result { lua.load(r#"return require(...)"#).call(path) @@ -11,9 +11,14 @@ fn run_require_pcall(lua: &Lua, path: impl IntoLua) -> Result { lua.load(r#"return pcall(require, ...)"#).call(path) } +#[track_caller] +fn get_value(value: &Value, key: impl IntoLua) -> V { + value.as_table().unwrap().get(key).unwrap() +} + #[track_caller] fn get_str(value: &Value, key: impl IntoLua) -> String { - value.as_table().unwrap().get::(key).unwrap() + get_value(value, key) } #[test] @@ -47,6 +52,16 @@ fn test_require_errors() { assert!(res.is_err()); assert!((res.unwrap_err().to_string()).contains("require is not supported in this context")); + // RequireAliasThatDoesNotExist + let res = run_require(&lua, "@this.alias.does.not.exist"); + assert!(res.is_err()); + assert!((res.unwrap_err().to_string()).contains("@this.alias.does.not.exist is not a valid alias")); + + // IllegalAlias + let res = run_require(&lua, "@"); + assert!(res.is_err()); + assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias")); + // Test throwing mlua::Error struct MyRequire(TextRequirer); @@ -171,40 +186,64 @@ fn test_require_without_config() { assert!(res.is_table()); } -#[test] -fn test_require_with_config() { +fn test_require_with_config_inner(r#type: &str) { let lua = Lua::new(); + let base_path = format!("./tests/luau/require/{type}"); + // RequirePathWithAlias - let res = run_require(&lua, "./tests/luau/require/with_config/src/alias_requirer").unwrap(); + let res = run_require(&lua, format!("{base_path}/src/alias_requirer")).unwrap(); assert_eq!("result from dependency", get_str(&res, 1)); // RequirePathWithAlias (case-insensitive) - let res2 = run_require(&lua, "./tests/luau/require/with_config/src/alias_requirer_uc").unwrap(); + let res2 = run_require(&lua, format!("{base_path}/src/alias_requirer_uc")).unwrap(); assert_eq!("result from dependency", get_str(&res2, 1)); assert_eq!(res.to_pointer(), res2.to_pointer()); // RequirePathWithParentAlias - let res = run_require(&lua, "./tests/luau/require/with_config/src/parent_alias_requirer").unwrap(); + let res = run_require(&lua, format!("{base_path}/src/parent_alias_requirer")).unwrap(); assert_eq!("result from other_dependency", get_str(&res, 1)); // RequirePathWithAliasPointingToDirectory + let res = run_require(&lua, format!("{base_path}/src/directory_alias_requirer")).unwrap(); + assert_eq!("result from subdirectory_dependency", get_str(&res, 1)); + + // RequireChainedAliasesSuccess let res = run_require( &lua, - "./tests/luau/require/with_config/src/directory_alias_requirer", + format!("{base_path}/chained_aliases/subdirectory/successful_requirer"), ) .unwrap(); - assert_eq!("result from subdirectory_dependency", get_str(&res, 1)); + assert_eq!("result from inner_dependency", get_str(&get_value(&res, 1), 1)); + assert_eq!("result from outer_dependency", get_str(&get_value(&res, 2), 1)); - // RequireAliasThatDoesNotExist - let res = run_require(&lua, "@this.alias.does.not.exist"); + // RequireChainedAliasesFailureCyclic + let res = run_require( + &lua, + format!("{base_path}/chained_aliases/subdirectory/failing_requirer_cyclic"), + ); assert!(res.is_err()); - assert!((res.unwrap_err().to_string()).contains("@this.alias.does.not.exist is not a valid alias")); + let err_msg = "error requiring module \"@cyclicentry\": detected alias cycle (@cyclic1 -> @cyclic2 -> @cyclic3 -> @cyclic1)"; + assert!(res.unwrap_err().to_string().contains(err_msg)); - // IllegalAlias - let res = run_require(&lua, "@"); + // RequireChainedAliasesFailureMissing + let res = run_require( + &lua, + format!("{base_path}/chained_aliases/subdirectory/failing_requirer_missing"), + ); assert!(res.is_err()); - assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias")); + let err_msg = "error requiring module \"@brokenchain\": @missing is not a valid alias"; + assert!(res.unwrap_err().to_string().contains(err_msg)); +} + +#[test] +fn test_require_with_config() { + test_require_with_config_inner("with_config"); +} + +#[test] +fn test_require_with_config_luau() { + test_require_with_config_inner("with_config_luau"); } #[cfg(all(feature = "async", not(windows)))] diff --git a/tests/luau/require/with_config/chained_aliases/.luaurc b/tests/luau/require/with_config/chained_aliases/.luaurc new file mode 100644 index 00000000..42e61fcd --- /dev/null +++ b/tests/luau/require/with_config/chained_aliases/.luaurc @@ -0,0 +1,9 @@ +{ + "aliases":{ + "outer": "./", + "cyclicentry": "@cyclic1", + "cyclic1": "@cyclic2", + "cyclic2": "@cyclic3", + "cyclic3": "@cyclic1" + } +} diff --git a/tests/luau/require/with_config/chained_aliases/outer_dependency.luau b/tests/luau/require/with_config/chained_aliases/outer_dependency.luau new file mode 100644 index 00000000..69ffda57 --- /dev/null +++ b/tests/luau/require/with_config/chained_aliases/outer_dependency.luau @@ -0,0 +1 @@ +return {"result from outer_dependency"} diff --git a/tests/luau/require/with_config/chained_aliases/subdirectory/.luaurc b/tests/luau/require/with_config/chained_aliases/subdirectory/.luaurc new file mode 100644 index 00000000..96b72086 --- /dev/null +++ b/tests/luau/require/with_config/chained_aliases/subdirectory/.luaurc @@ -0,0 +1,10 @@ +{ + "aliases":{ + "passthroughinner": "./inner_dependency", + "passthroughouter": "@outer", + "dep": "@passthroughinner", + "outerdep": "@outer/outer_dependency", + "outerdir": "@passthroughouter", + "brokenchain": "@missing" + } +} diff --git a/tests/luau/require/with_config/chained_aliases/subdirectory/failing_requirer_cyclic.luau b/tests/luau/require/with_config/chained_aliases/subdirectory/failing_requirer_cyclic.luau new file mode 100644 index 00000000..9f5ed488 --- /dev/null +++ b/tests/luau/require/with_config/chained_aliases/subdirectory/failing_requirer_cyclic.luau @@ -0,0 +1 @@ +return require("@cyclicentry") diff --git a/tests/luau/require/with_config/chained_aliases/subdirectory/failing_requirer_missing.luau b/tests/luau/require/with_config/chained_aliases/subdirectory/failing_requirer_missing.luau new file mode 100644 index 00000000..75703849 --- /dev/null +++ b/tests/luau/require/with_config/chained_aliases/subdirectory/failing_requirer_missing.luau @@ -0,0 +1 @@ +return require("@brokenchain") diff --git a/tests/luau/require/with_config/chained_aliases/subdirectory/inner_dependency.luau b/tests/luau/require/with_config/chained_aliases/subdirectory/inner_dependency.luau new file mode 100644 index 00000000..917d461f --- /dev/null +++ b/tests/luau/require/with_config/chained_aliases/subdirectory/inner_dependency.luau @@ -0,0 +1 @@ +return {"result from inner_dependency"} diff --git a/tests/luau/require/with_config/chained_aliases/subdirectory/successful_requirer.luau b/tests/luau/require/with_config/chained_aliases/subdirectory/successful_requirer.luau new file mode 100644 index 00000000..988e467b --- /dev/null +++ b/tests/luau/require/with_config/chained_aliases/subdirectory/successful_requirer.luau @@ -0,0 +1,7 @@ +local result = {} + +table.insert(result, require("@dep")) +table.insert(result, require("@outerdep")) +table.insert(result, require("@outerdir/outer_dependency")) + +return result diff --git a/tests/luau/require/with_config_luau/.config.luau b/tests/luau/require/with_config_luau/.config.luau new file mode 100644 index 00000000..b64979de --- /dev/null +++ b/tests/luau/require/with_config_luau/.config.luau @@ -0,0 +1,8 @@ +return { + luau = { + aliases = { + dep = "./this_should_be_overwritten_by_child_luaurc", + otherdep = "./src/other_dependency" + } + } +} diff --git a/tests/luau/require/with_config_luau/chained_aliases/.config.luau b/tests/luau/require/with_config_luau/chained_aliases/.config.luau new file mode 100644 index 00000000..fa04eb6f --- /dev/null +++ b/tests/luau/require/with_config_luau/chained_aliases/.config.luau @@ -0,0 +1,11 @@ +return { + luau = { + aliases = { + outer = "./", + cyclicentry = "@cyclic1", + cyclic1 = "@cyclic2", + cyclic2 = "@cyclic3", + cyclic3 = "@cyclic1" + } + } +} diff --git a/tests/luau/require/with_config_luau/chained_aliases/outer_dependency.luau b/tests/luau/require/with_config_luau/chained_aliases/outer_dependency.luau new file mode 100644 index 00000000..69ffda57 --- /dev/null +++ b/tests/luau/require/with_config_luau/chained_aliases/outer_dependency.luau @@ -0,0 +1 @@ +return {"result from outer_dependency"} diff --git a/tests/luau/require/with_config_luau/chained_aliases/subdirectory/.config.luau b/tests/luau/require/with_config_luau/chained_aliases/subdirectory/.config.luau new file mode 100644 index 00000000..b7faaa47 --- /dev/null +++ b/tests/luau/require/with_config_luau/chained_aliases/subdirectory/.config.luau @@ -0,0 +1,12 @@ +return { + luau = { + aliases = { + passthroughinner = "./inner_dependency", + passthroughouter = "@outer", + dep = "@passthroughinner", + outerdep = "@outer/outer_dependency", + outerdir = "@passthroughouter", + brokenchain = "@missing" + } + } +} diff --git a/tests/luau/require/with_config_luau/chained_aliases/subdirectory/failing_requirer_cyclic.luau b/tests/luau/require/with_config_luau/chained_aliases/subdirectory/failing_requirer_cyclic.luau new file mode 100644 index 00000000..9f5ed488 --- /dev/null +++ b/tests/luau/require/with_config_luau/chained_aliases/subdirectory/failing_requirer_cyclic.luau @@ -0,0 +1 @@ +return require("@cyclicentry") diff --git a/tests/luau/require/with_config_luau/chained_aliases/subdirectory/failing_requirer_missing.luau b/tests/luau/require/with_config_luau/chained_aliases/subdirectory/failing_requirer_missing.luau new file mode 100644 index 00000000..75703849 --- /dev/null +++ b/tests/luau/require/with_config_luau/chained_aliases/subdirectory/failing_requirer_missing.luau @@ -0,0 +1 @@ +return require("@brokenchain") diff --git a/tests/luau/require/with_config_luau/chained_aliases/subdirectory/inner_dependency.luau b/tests/luau/require/with_config_luau/chained_aliases/subdirectory/inner_dependency.luau new file mode 100644 index 00000000..917d461f --- /dev/null +++ b/tests/luau/require/with_config_luau/chained_aliases/subdirectory/inner_dependency.luau @@ -0,0 +1 @@ +return {"result from inner_dependency"} diff --git a/tests/luau/require/with_config_luau/chained_aliases/subdirectory/successful_requirer.luau b/tests/luau/require/with_config_luau/chained_aliases/subdirectory/successful_requirer.luau new file mode 100644 index 00000000..988e467b --- /dev/null +++ b/tests/luau/require/with_config_luau/chained_aliases/subdirectory/successful_requirer.luau @@ -0,0 +1,7 @@ +local result = {} + +table.insert(result, require("@dep")) +table.insert(result, require("@outerdep")) +table.insert(result, require("@outerdir/outer_dependency")) + +return result diff --git a/tests/luau/require/with_config_luau/src/.config.luau b/tests/luau/require/with_config_luau/src/.config.luau new file mode 100644 index 00000000..63d3bbc8 --- /dev/null +++ b/tests/luau/require/with_config_luau/src/.config.luau @@ -0,0 +1,8 @@ +return { + luau = { + aliases = { + dep = "./dependency", + subdir = "./subdirectory" + } + } +} diff --git a/tests/luau/require/with_config_luau/src/alias_requirer.luau b/tests/luau/require/with_config_luau/src/alias_requirer.luau new file mode 100644 index 00000000..4375a783 --- /dev/null +++ b/tests/luau/require/with_config_luau/src/alias_requirer.luau @@ -0,0 +1 @@ +return require("@dep") diff --git a/tests/luau/require/with_config_luau/src/alias_requirer_uc.luau b/tests/luau/require/with_config_luau/src/alias_requirer_uc.luau new file mode 100644 index 00000000..7fa5dc9e --- /dev/null +++ b/tests/luau/require/with_config_luau/src/alias_requirer_uc.luau @@ -0,0 +1 @@ +return require("@DeP") diff --git a/tests/luau/require/with_config_luau/src/dependency.luau b/tests/luau/require/with_config_luau/src/dependency.luau new file mode 100644 index 00000000..07466f42 --- /dev/null +++ b/tests/luau/require/with_config_luau/src/dependency.luau @@ -0,0 +1 @@ +return {"result from dependency"} diff --git a/tests/luau/require/with_config_luau/src/directory_alias_requirer.luau b/tests/luau/require/with_config_luau/src/directory_alias_requirer.luau new file mode 100644 index 00000000..3b19d4ff --- /dev/null +++ b/tests/luau/require/with_config_luau/src/directory_alias_requirer.luau @@ -0,0 +1 @@ +return(require("@subdir/subdirectory_dependency")) diff --git a/tests/luau/require/with_config_luau/src/other_dependency.luau b/tests/luau/require/with_config_luau/src/other_dependency.luau new file mode 100644 index 00000000..8c582dc2 --- /dev/null +++ b/tests/luau/require/with_config_luau/src/other_dependency.luau @@ -0,0 +1 @@ +return {"result from other_dependency"} diff --git a/tests/luau/require/with_config_luau/src/parent_alias_requirer.luau b/tests/luau/require/with_config_luau/src/parent_alias_requirer.luau new file mode 100644 index 00000000..a8e8de09 --- /dev/null +++ b/tests/luau/require/with_config_luau/src/parent_alias_requirer.luau @@ -0,0 +1 @@ +return require("@otherdep") diff --git a/tests/luau/require/with_config_luau/src/subdirectory/subdirectory_dependency.luau b/tests/luau/require/with_config_luau/src/subdirectory/subdirectory_dependency.luau new file mode 100644 index 00000000..8bbd0beb --- /dev/null +++ b/tests/luau/require/with_config_luau/src/subdirectory/subdirectory_dependency.luau @@ -0,0 +1 @@ +return {"result from subdirectory_dependency"} From 676f3a698375b09bfc1db10560bf1c9d982aecf4 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 17 Nov 2025 13:44:29 +0000 Subject: [PATCH 549/635] Fix tests --- src/luau/require.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/luau/require.rs b/src/luau/require.rs index d8c75b4d..94d5c761 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -16,8 +16,6 @@ use crate::types::MaybeSend; pub use fs::TextRequirer; /// An error that can occur during navigation in the Luau `require-by-string` system. -#[cfg(any(feature = "luau", doc))] -#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[derive(Debug, Clone)] pub enum NavigateError { Ambiguous, @@ -55,8 +53,6 @@ type WriteResult = ffi::luarequire_WriteResult; type ConfigStatus = ffi::luarequire_ConfigStatus; /// A trait for handling modules loading and navigation in the Luau `require-by-string` system. -#[cfg(any(feature = "luau", doc))] -#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub trait Require { /// Returns `true` if "require" is permitted for the given chunk name. fn is_require_allowed(&self, chunk_name: &str) -> bool; @@ -316,6 +312,7 @@ pub(super) unsafe extern "C-unwind" fn init_config(config: *mut ffi::luarequire_ } /// Detect configuration file format (JSON or Luau) +#[cfg(feature = "luau")] fn detect_config_format(data: &[u8]) -> ConfigStatus { let data = data.trim_ascii(); if data.starts_with(b"{") { From a2728928cf8e54127bea37b525a395fbd6efa26e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 17 Nov 2025 23:13:04 +0000 Subject: [PATCH 550/635] Temporary disable some send tests on nightly Aparently there is a regression in the compiler and sync detection does not work correctly --- tests/send.rs | 1 + tests/userdata.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/send.rs b/tests/send.rs index 9def807d..becfad20 100644 --- a/tests/send.rs +++ b/tests/send.rs @@ -47,6 +47,7 @@ fn test_userdata_multithread_access_send_only() -> Result<()> { Ok(()) } +#[rustversion::stable] #[test] fn test_userdata_multithread_access_sync() -> Result<()> { let lua = Lua::new(); diff --git a/tests/userdata.rs b/tests/userdata.rs index 5b62474d..6f10869d 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -957,6 +957,7 @@ fn test_nested_userdata_gc() -> Result<()> { } #[cfg(feature = "userdata-wrappers")] +#[rustversion::stable] #[test] fn test_userdata_wrappers() -> Result<()> { #[derive(Debug)] From 6835537e3bcb40eab28b2fed2609782a7f1f8999 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 19 Nov 2025 11:25:59 +0000 Subject: [PATCH 551/635] Switch to released verson of luau0-src --- mlua-sys/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 2c2b38dc..fc61a35f 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -41,7 +41,7 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 548.1.0, < 548.2.0", optional = true } luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true } -luau0-src = { version = "0.16.0", optional = true, git = "https://github.com/mlua-rs/luau-src-rs" } +luau0-src = { version = "0.16.0", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } From 121971f54ebd3903a53cf980b3c7a16296e1d4be Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 21 Nov 2025 15:11:27 +0000 Subject: [PATCH 552/635] Add `Lua::set_memory_category` and `Lua::heap_dump` functions to profile Luau memory usage. This functionality uses Luau private api to dump heap mempory in JSON format for inspection. The new type `HeapDump` represents memory snapshot with some basic API to calculate stats. --- Cargo.toml | 1 + src/lib.rs | 2 +- src/luau/heap_dump.rs | 178 +++++++++++++++++++++++ src/luau/json.rs | 327 ++++++++++++++++++++++++++++++++++++++++++ src/luau/mod.rs | 57 +++++++- src/state/extra.rs | 4 + tests/luau.rs | 65 +++++++++ 7 files changed, 631 insertions(+), 3 deletions(-) create mode 100644 src/luau/heap_dump.rs create mode 100644 src/luau/json.rs diff --git a/Cargo.toml b/Cargo.toml index e1a239b1..7855dacf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ serde-value = { version = "0.7", optional = true } parking_lot = { version = "0.12", features = ["arc_lock"] } anyhow = { version = "1.0", optional = true } rustversion = "1.0" +libc = "0.2" ffi = { package = "mlua-sys", version = "0.9.0", path = "mlua-sys" } diff --git a/src/lib.rs b/src/lib.rs index 466d4d38..61df03af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -132,7 +132,7 @@ pub use crate::{ buffer::Buffer, chunk::{CompileConstant, Compiler}, function::CoverageInfo, - luau::{NavigateError, Require, TextRequirer}, + luau::{HeapDump, NavigateError, Require, TextRequirer}, vector::Vector, }; diff --git a/src/luau/heap_dump.rs b/src/luau/heap_dump.rs new file mode 100644 index 00000000..6920cad6 --- /dev/null +++ b/src/luau/heap_dump.rs @@ -0,0 +1,178 @@ +use std::collections::HashMap; +use std::hash::Hash; +use std::mem; +use std::os::raw::c_char; + +use crate::state::ExtraData; + +use super::json::{self, Json}; + +/// Represents a heap dump of a Luau memory state. +#[cfg(any(feature = "luau", doc))] +#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] +pub struct HeapDump { + data: Json<'static>, // refers to the contents of `buf` + buf: Box, +} + +impl HeapDump { + /// Dumps the current Lua heap state. + pub(crate) unsafe fn new(state: *mut ffi::lua_State) -> Option { + unsafe extern "C" fn category_name(state: *mut ffi::lua_State, cat: u8) -> *const c_char { + (&*ExtraData::get(state)) + .mem_categories + .get(cat as usize) + .map(|s| s.as_ptr()) + .unwrap_or(cstr!("unknown")) + } + + let mut buf = Vec::new(); + unsafe { + let file = libc::tmpfile(); + if file.is_null() { + return None; + } + ffi::lua_gcdump(state, file as *mut _, Some(category_name)); + libc::fseek(file, 0, libc::SEEK_END); + let len = libc::ftell(file) as usize; + libc::rewind(file); + if len > 0 { + buf.reserve(len); + libc::fread(buf.as_mut_ptr() as *mut _, 1, len, file); + buf.set_len(len); + } + libc::fclose(file); + } + + let buf = String::from_utf8(buf).ok()?.into_boxed_str(); + let data = json::parse(unsafe { mem::transmute::<&str, &'static str>(&buf) }).ok()?; + Some(HeapDump { data, buf }) + } + + /// Returns the raw JSON representation of the heap dump. + /// + /// The JSON structure is an internal detail and may change in future versions. + #[doc(hidden)] + pub fn to_json(&self) -> &str { + &self.buf + } + + /// Returns the total size of the Lua heap in bytes. + pub fn size(&self) -> u64 { + self.data["stats"]["size"].as_u64().unwrap_or_default() + } + + /// Returns a mapping from object type to (count, total size in bytes). + /// + /// If `category` is provided, only objects in that category are considered. + pub fn size_by_type<'a>(&'a self, category: Option<&str>) -> HashMap<&'a str, (usize, u64)> { + self.size_by_type_inner(category).unwrap_or_default() + } + + fn size_by_type_inner<'a>(&'a self, category: Option<&str>) -> Option> { + let category_id = match category { + // If we cannot find the category, return empty result + Some(cat) => Some(self.find_category_id(cat)?), + None => None, + }; + + let mut size_by_type = HashMap::new(); + let objects = self.data["objects"].as_object()?; + for obj in objects.values() { + if let Some(cat_id) = category_id { + if obj["cat"].as_i64()? != cat_id { + continue; + } + } + update_size(&mut size_by_type, obj["type"].as_str()?, obj["size"].as_u64()?); + } + Some(size_by_type) + } + + /// Returns a mapping from category name to total size in bytes. + pub fn size_by_category(&self) -> HashMap<&str, u64> { + let mut size_by_category = HashMap::new(); + if let Some(categories) = self.data["stats"]["categories"].as_object() { + for cat in categories.values() { + if let Some(cat_name) = cat["name"].as_str() { + size_by_category.insert(cat_name, cat["size"].as_u64().unwrap_or_default()); + } + } + } + size_by_category + } + + /// Returns a mapping from userdata type to (count, total size in bytes). + pub fn size_by_userdata<'a>(&'a self, category: Option<&str>) -> HashMap<&'a str, (usize, u64)> { + self.size_by_userdata_inner(category).unwrap_or_default() + } + + fn size_by_userdata_inner<'a>( + &'a self, + category: Option<&str>, + ) -> Option> { + let category_id = match category { + // If we cannot find the category, return empty result + Some(cat) => Some(self.find_category_id(cat)?), + None => None, + }; + + let mut size_by_userdata = HashMap::new(); + let objects = self.data["objects"].as_object()?; + for obj in objects.values() { + if obj["type"] != "userdata" { + continue; + } + if let Some(cat_id) = category_id { + if obj["cat"].as_i64()? != cat_id { + continue; + } + } + + // Determine userdata type from metatable + let mut ud_type = "unknown"; + if let Some(metatable_addr) = obj["metatable"].as_str() { + if let Some(t) = get_key(objects, &objects[metatable_addr], "__type") { + ud_type = t; + } + } + update_size(&mut size_by_userdata, ud_type, obj["size"].as_u64()?); + } + Some(size_by_userdata) + } + + /// Finds the category ID for a given category name. + fn find_category_id(&self, category: &str) -> Option { + let categories = self.data["stats"]["categories"].as_object()?; + for (cat_id, cat) in categories { + if cat["name"].as_str() == Some(category) { + return cat_id.parse().ok(); + } + } + None + } +} + +/// Updates the size mapping for a given key. +fn update_size(size_type: &mut HashMap, key: K, size: u64) { + let (ref mut count, ref mut total_size) = size_type.entry(key).or_insert((0, 0)); + *count += 1; + *total_size += size; +} + +/// Retrieves the value associated with a given `key` from a Lua table `tbl`. +fn get_key<'a>(objects: &'a HashMap<&'a str, Json>, tbl: &Json, key: &str) -> Option<&'a str> { + let pairs = tbl["pairs"].as_array()?; + for kv in pairs.chunks_exact(2) { + #[rustfmt::skip] + let (Some(key_addr), Some(val_addr)) = (kv[0].as_str(), kv[1].as_str()) else { continue; }; + if objects[key_addr]["type"] == "string" && objects[key_addr]["data"].as_str() == Some(key) { + if objects[val_addr]["type"] == "string" { + return objects[val_addr]["data"].as_str(); + } else { + break; + } + } + } + None +} diff --git a/src/luau/json.rs b/src/luau/json.rs new file mode 100644 index 00000000..06b71606 --- /dev/null +++ b/src/luau/json.rs @@ -0,0 +1,327 @@ +use std::array; +use std::collections::HashMap; +use std::iter::Peekable; +use std::ops::Index; +use std::str::CharIndices; + +// A simple JSON parser and representation. +// This parser supports only a subset of JSON specification and is intended for Luau's use cases. + +#[derive(Debug, PartialEq)] +pub(crate) enum Json<'a> { + Null, + Bool(bool), + Integer(i64), + Number(f64), + String(&'a str), + Array(Vec>), + Object(HashMap<&'a str, Json<'a>>), +} + +impl<'a> Index<&str> for Json<'a> { + type Output = Json<'a>; + + fn index(&self, key: &str) -> &Self::Output { + match self { + Json::Object(map) => map.get(key).unwrap_or(&Json::Null), + _ => &Json::Null, + } + } +} + +impl PartialEq<&str> for Json<'_> { + fn eq(&self, other: &&str) -> bool { + matches!(self, Json::String(s) if s == other) + } +} + +impl<'a> Json<'a> { + pub(crate) fn as_str(&self) -> Option<&'a str> { + match self { + Json::String(s) => Some(s), + _ => None, + } + } + + pub(crate) fn as_i64(&self) -> Option { + match self { + Json::Integer(i) => Some(*i), + Json::Number(n) if n.fract() == 0.0 => Some(*n as i64), + _ => None, + } + } + + pub(crate) fn as_u64(&self) -> Option { + self.as_i64() + .and_then(|i| if i >= 0 { Some(i as u64) } else { None }) + } + + pub(crate) fn as_array(&self) -> Option<&[Json<'a>]> { + match self { + Json::Array(arr) => Some(arr), + _ => None, + } + } + + pub(crate) fn as_object(&self) -> Option<&HashMap<&'a str, Json<'a>>> { + match self { + Json::Object(map) => Some(map), + _ => None, + } + } +} + +pub(crate) fn parse<'a>(s: &'a str) -> Result, &'static str> { + let s = s.trim_ascii(); + let mut chars = s.char_indices().peekable(); + let value = parse_value(s, &mut chars)?; + Ok(value) +} + +fn parse_value<'a>(s: &'a str, chars: &mut Peekable) -> Result, &'static str> { + skip_whitespace(chars); + match chars.peek() { + Some((_, '{')) => parse_object(s, chars), + Some((_, '[')) => parse_array(s, chars), + Some((_, '"')) => parse_string(s, chars).map(Json::String), + Some((_, 't' | 'f')) => parse_bool(chars), + Some((_, 'n')) => parse_null(chars), + Some((_, '-' | '0'..='9')) => parse_number(chars), + Some(_) => Err("unexpected character"), + None => Err("unexpected end of input"), + } +} + +fn parse_object<'a>(s: &'a str, chars: &mut Peekable) -> Result, &'static str> { + chars.next(); // consume '{' + + let mut map = HashMap::new(); + skip_whitespace(chars); + if matches!(chars.peek(), Some((_, '}'))) { + chars.next(); + return Ok(Json::Object(map)); + } + loop { + skip_whitespace(chars); + let key = parse_string(s, chars)?; + skip_whitespace(chars); + if !matches!(chars.next(), Some((_, ':'))) { + return Err("expected ':'"); + } + let value = parse_value(s, chars)?; + map.insert(key, value); + skip_whitespace(chars); + match chars.next() { + Some((_, ',')) => continue, + Some((_, '}')) => break, + _ => return Err("expected ',' or '}'"), + } + } + Ok(Json::Object(map)) +} + +fn parse_array<'a>(s: &'a str, chars: &mut Peekable) -> Result, &'static str> { + chars.next(); // consume '[' + + let mut arr = Vec::new(); + skip_whitespace(chars); + if matches!(chars.peek(), Some((_, ']'))) { + chars.next(); + return Ok(Json::Array(arr)); + } + loop { + skip_whitespace(chars); + arr.push(parse_value(s, chars)?); + skip_whitespace(chars); + match chars.next() { + Some((_, ',')) => continue, + Some((_, ']')) => return Ok(Json::Array(arr)), + _ => return Err("expected ',' or ']'"), + } + } +} + +fn parse_string<'a>(s: &'a str, chars: &mut Peekable) -> Result<&'a str, &'static str> { + if !matches!(chars.next(), Some((_, '"'))) { + return Err("expected string starting with '\"'"); + } + let start = chars.peek().map(|(i, _)| *i).unwrap_or(0); + for (i, c) in chars { + if c == '"' { + return Ok(&s[start..i]); + } + } + Err("unterminated string") +} + +fn parse_number(chars: &mut Peekable) -> Result, &'static str> { + let mut is_float = false; + let mut num = String::new(); + while let Some((_, c @ ('0'..='9' | '-' | '.' | 'e' | 'E' | '+'))) = chars.peek() { + num.push(*c); + is_float = is_float || matches!(c, '.' | 'e' | 'E'); + chars.next(); + } + if !is_float { + let i = num.parse::().map_err(|_| "invalid integer")?; + return Ok(Json::Integer(i)); + } + let n = num.parse::().map_err(|_| "invalid number")?; + Ok(Json::Number(n)) +} + +fn parse_bool(chars: &mut Peekable) -> Result, &'static str> { + let bool = next_chars(chars); + if bool == [Some('t'), Some('r'), Some('u'), Some('e')] { + return Ok(Json::Bool(true)); + } + if bool == [Some('f'), Some('a'), Some('l'), Some('s')] && matches!(chars.next(), Some((_, 'e'))) { + return Ok(Json::Bool(false)); + } + Err("invalid boolean literal") +} + +fn parse_null(chars: &mut Peekable) -> Result, &'static str> { + if next_chars(chars) == [Some('n'), Some('u'), Some('l'), Some('l')] { + return Ok(Json::Null); + } + Err("invalid \"null\" literal") +} + +fn skip_whitespace(chars: &mut Peekable) { + while let Some((_, ' ' | '\n' | '\r' | '\t')) = chars.peek() { + chars.next(); + } +} + +fn next_chars(chars: &mut Peekable) -> [Option; N] { + array::from_fn(|_| chars.next().map(|(_, c)| c)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse() { + assert_eq!(parse("null").unwrap(), Json::Null); + assert_eq!(parse("true").unwrap(), Json::Bool(true)); + assert_eq!(parse("false").unwrap(), Json::Bool(false)); + assert_eq!(parse("42").unwrap(), Json::Integer(42)); + assert_eq!(parse("42.0").unwrap(), Json::Number(42.0)); + assert_eq!(parse(r#""hello""#).unwrap(), Json::String("hello")); + assert_eq!( + parse("[1,2.0,3]").unwrap(), + Json::Array(vec![Json::Integer(1), Json::Number(2.0), Json::Integer(3)]) + ); + let mut obj = HashMap::new(); + obj.insert("key", Json::String("value")); + assert_eq!(parse(r#"{"key":"value"}"#).unwrap(), Json::Object(obj)); + } + + #[test] + fn test_whitespace_handling() { + assert_eq!(parse(" null ").unwrap(), Json::Null); + assert_eq!(parse(" true ").unwrap(), Json::Bool(true)); + assert_eq!( + parse(" [ 1 , 2.0 , 3 ] ").unwrap(), + Json::Array(vec![Json::Integer(1), Json::Number(2.0), Json::Integer(3)]) + ); + let mut obj = HashMap::new(); + obj.insert("key", Json::String("value")); + assert_eq!(parse(r#" { "key" : "value" } "#).unwrap(), Json::Object(obj)); + } + + #[test] + fn test_empty_collections() { + assert_eq!(parse("[]").unwrap(), Json::Array(vec![])); + assert_eq!(parse("{}").unwrap(), Json::Object(HashMap::new())); + assert_eq!(parse("[ ]").unwrap(), Json::Array(vec![])); + assert_eq!(parse("{ }").unwrap(), Json::Object(HashMap::new())); + } + + #[test] + fn test_nested_structures() { + assert_eq!( + parse(r#"{"nested":{"inner":"value"}}"#).unwrap(), + Json::Object({ + let mut outer = HashMap::new(); + let mut inner = HashMap::new(); + inner.insert("inner", Json::String("value")); + outer.insert("nested", Json::Object(inner)); + outer + }) + ); + assert_eq!( + parse("[[1,2],[3,4]]").unwrap(), + Json::Array(vec![ + Json::Array(vec![Json::Integer(1), Json::Integer(2)]), + Json::Array(vec![Json::Integer(3), Json::Integer(4)]) + ]) + ); + } + + #[test] + fn test_numbers() { + assert_eq!(parse("0").unwrap(), Json::Integer(0)); + assert_eq!(parse("-42").unwrap(), Json::Integer(-42)); + assert_eq!(parse("3.14").unwrap(), Json::Number(3.14)); + assert_eq!(parse("-3.14").unwrap(), Json::Number(-3.14)); + assert_eq!(parse("1e10").unwrap(), Json::Number(1e10)); + assert_eq!(parse("1E10").unwrap(), Json::Number(1E10)); + assert_eq!(parse("1e-10").unwrap(), Json::Number(1e-10)); + assert_eq!(parse("1.5e+10").unwrap(), Json::Number(1.5e+10)); + } + + #[test] + fn test_strings() { + assert_eq!(parse(r#""""#).unwrap(), Json::String("")); + assert_eq!(parse(r#""hello world""#).unwrap(), Json::String("hello world")); + assert_eq!( + parse(r#""with spaces and 123""#).unwrap(), + Json::String("with spaces and 123") + ); + } + + #[test] + fn test_mixed_array() { + assert_eq!( + parse(r#"[null, true, false, 35.1, 42, "text", [], {}]"#).unwrap(), + Json::Array(vec![ + Json::Null, + Json::Bool(true), + Json::Bool(false), + Json::Number(35.1), + Json::Integer(42), + Json::String("text"), + Json::Array(vec![]), + Json::Object(HashMap::new()) + ]) + ); + } + + #[test] + fn test_object_multiple_keys() { + let mut obj = HashMap::new(); + obj.insert("a", Json::Integer(1)); + obj.insert("b", Json::Bool(true)); + obj.insert("c", Json::Null); + assert_eq!(parse(r#"{"a":1,"b":true,"c":null}"#).unwrap(), Json::Object(obj)); + } + + #[test] + fn test_error_cases() { + assert!(parse("").is_err()); + assert!(parse("nul").is_err()); + assert!(parse("tru").is_err()); + assert!(parse("fals").is_err()); + assert!(parse(r#""unterminated"#).is_err()); + assert!(parse("[1,2,]").is_err()); + assert!(parse(r#"{"key""#).is_err()); + assert!(parse(r#"{"key":"value""#).is_err()); + assert!(parse(r#"{"key":"value",}"#).is_err()); + assert!(parse("invalid").is_err()); + assert!(parse("[1 2]").is_err()); + assert!(parse(r#"{"key":"value" "key2":"value2"}"#).is_err()); + } +} diff --git a/src/luau/mod.rs b/src/luau/mod.rs index 042e751a..a12271c7 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -1,14 +1,15 @@ -use std::ffi::CStr; +use std::ffi::{CStr, CString}; use std::os::raw::c_int; use std::ptr; use crate::chunk::ChunkMode; -use crate::error::Result; +use crate::error::{Error, Result}; use crate::function::Function; use crate::state::{callback_error_ext, ExtraData, Lua}; use crate::traits::{FromLuaMulti, IntoLua}; use crate::types::MaybeSend; +pub use heap_dump::HeapDump; pub use require::{NavigateError, Require, TextRequirer}; // Since Luau has some missing standard functions, we re-implement them here @@ -22,6 +23,56 @@ impl Lua { require::create_require_function(self, require) } + /// Set the memory category for subsequent allocations from this Lua state. + /// + /// The category "main" is reserved for the default memory category. + /// Maximum of 255 categories can be registered. + /// + /// Return error if too many categories are registered or if the category name is invalid. + /// + /// See [`Lua::heap_dump`] for tracking memory usage by category. + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn set_memory_category(&self, category: &str) -> Result<()> { + let lua = self.lock(); + + if category.contains(|c| !matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_')) { + return Err(Error::runtime("invalid memory category name")); + } + let cat_id = unsafe { + let extra = ExtraData::get(lua.state()); + match ((*extra).mem_categories.iter().enumerate()) + .find(|&(_, name)| name.as_bytes() == category.as_bytes()) + { + Some((id, _)) => id as u8, + None => { + let new_id = (*extra).mem_categories.len() as u8; + if new_id == 255 { + return Err(Error::runtime("too many memory categories registered")); + } + (*extra).mem_categories.push(CString::new(category).unwrap()); + new_id + } + } + }; + unsafe { ffi::lua_setmemcat(lua.main_state(), cat_id as i32) }; + + Ok(()) + } + + /// Dumps the current Lua heap state. + /// + /// The returned `HeapDump` can be used to analyze memory usage. + /// It's recommended to call [`Lua::gc_collect`] before dumping the heap. + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn heap_dump(&self) -> Result { + let lua = self.lock(); + unsafe { + heap_dump::HeapDump::new(lua.main_state()).ok_or_else(|| Error::runtime("unable to dump heap")) + } + } + pub(crate) unsafe fn configure_luau(&self) -> Result<()> { let globals = self.globals(); @@ -96,4 +147,6 @@ unsafe extern "C-unwind" fn lua_loadstring(state: *mut ffi::lua_State) -> c_int }) } +mod heap_dump; +mod json; mod require; diff --git a/src/state/extra.rs b/src/state/extra.rs index fb97c7d1..85f6a951 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -94,6 +94,8 @@ pub(crate) struct ExtraData { pub(super) compiler: Option, #[cfg(feature = "luau-jit")] pub(super) enable_jit: bool, + #[cfg(feature = "luau")] + pub(crate) mem_categories: Vec, } impl Drop for ExtraData { @@ -196,6 +198,8 @@ impl ExtraData { enable_jit: true, #[cfg(feature = "luau")] running_gc: false, + #[cfg(feature = "luau")] + mem_categories: vec![std::ffi::CString::new("main").unwrap()], })); // Store it in the registry diff --git a/tests/luau.rs b/tests/luau.rs index 3ab5b2d5..07495f95 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -470,5 +470,70 @@ fn test_typeof_error() -> Result<()> { Ok(()) } +#[test] +fn test_memory_category() -> Result<()> { + let lua = Lua::new(); + + lua.set_memory_category("main").unwrap(); + + // Invalid category names should be rejected + let err = lua.set_memory_category("invalid$"); + assert!(err.is_err()); + + for i in 0..254 { + let name = format!("category_{}", i); + lua.set_memory_category(&name).unwrap(); + } + // 255th category should fail + let err = lua.set_memory_category("category_254"); + assert!(err.is_err()); + + Ok(()) +} + +#[test] +fn test_heap_dump() -> Result<()> { + let lua = Lua::new(); + + // Assign a new memory category and create few objects + lua.set_memory_category("test_category")?; + let _t = lua.create_table()?; + let _ud = lua.create_any_userdata("hello, world")?; + + let dump = lua.heap_dump()?; + + assert!(dump.size() > 0); + let size_by_category = dump.size_by_category(); + assert_eq!(size_by_category.len(), 2); + assert!(size_by_category.contains_key("test_category")); + assert!(size_by_category["main"] < dump.size()); + + // Check size by type within the category + let size_by_type = dump.size_by_type(Some("test_category")); + assert!(!size_by_type.is_empty()); + assert!(size_by_type.contains_key("table")); + assert!(size_by_type.contains_key("userdata")); + // Try non-existent category + let size_by_type2 = dump.size_by_type(Some("non_existent_category")); + assert!(size_by_type2.is_empty()); + // Remove category filter + let size_by_type_all = dump.size_by_type(None); + assert!(size_by_type.len() < size_by_type_all.len()); + + // Check size by userdata type within the category + let size_by_udtype = dump.size_by_userdata(Some("test_category")); + assert_eq!(size_by_udtype.len(), 1); + assert!(size_by_udtype.contains_key("&str")); + assert_eq!(size_by_udtype["&str"].0, 1); + // Try non-existent category + let size_by_udtype2 = dump.size_by_userdata(Some("non_existent_category")); + assert!(size_by_udtype2.is_empty()); + // Remove category filter + let size_by_udtype_all = dump.size_by_userdata(None); + assert!(size_by_udtype.len() < size_by_udtype_all.len()); + + Ok(()) +} + #[path = "luau/require.rs"] mod require; From ce4fc80e18663b211f2533d6fc67b512055abc51 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 22 Nov 2025 12:58:59 +0000 Subject: [PATCH 553/635] Bump luau-src to 0.17.0 (Luau 0.701) --- mlua-sys/Cargo.toml | 2 +- mlua-sys/src/luau/luarequire.rs | 11 +++++++++++ src/luau/require.rs | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index fc61a35f..53a414a2 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -41,7 +41,7 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 548.1.0, < 548.2.0", optional = true } luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true } -luau0-src = { version = "0.16.0", optional = true } +luau0-src = { version = "0.17.0", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/mlua-sys/src/luau/luarequire.rs b/mlua-sys/src/luau/luarequire.rs index 4a929ded..809acc2e 100644 --- a/mlua-sys/src/luau/luarequire.rs +++ b/mlua-sys/src/luau/luarequire.rs @@ -58,6 +58,17 @@ pub struct luarequire_Configuration { path: *const c_char, ) -> luarequire_NavigateResult, + // Provides a final override opportunity if an alias cannot be found in configuration files. If + // NAVIGATE_SUCCESS is returned, this must update the internal state to point at the aliased module. + // Can be left undefined. + pub to_alias_fallback: Option< + unsafe extern "C-unwind" fn( + L: *mut lua_State, + ctx: *mut c_void, + alias_unprefixed: *const c_char, + ) -> luarequire_NavigateResult, + >, + // Navigates through the context by making mutations to the internal state. pub to_parent: unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> luarequire_NavigateResult, diff --git a/src/luau/require.rs b/src/luau/require.rs index 94d5c761..13e968b2 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -299,6 +299,7 @@ pub(super) unsafe extern "C-unwind" fn init_config(config: *mut ffi::luarequire_ (*config).is_require_allowed = is_require_allowed; (*config).reset = reset; (*config).jump_to_alias = jump_to_alias; + (*config).to_alias_fallback = None; (*config).to_parent = to_parent; (*config).to_child = to_child; (*config).is_module_present = is_module_present; From 1b500b7d47dbb242aa9a8b723b108fb8df8b499a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 22 Nov 2025 13:36:50 +0000 Subject: [PATCH 554/635] Remove generic from internal definition of `RawLua::create_string` --- src/state.rs | 2 +- src/state/raw.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/state.rs b/src/state.rs index 7cf2a787..70644441 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1180,7 +1180,7 @@ impl Lua { /// and `&String`, you can also pass plain `&[u8]` here. #[inline] pub fn create_string(&self, s: impl AsRef<[u8]>) -> Result { - unsafe { self.lock().create_string(s) } + unsafe { self.lock().create_string(s.as_ref()) } } /// Creates and returns a Luau [buffer] object from a byte slice of data. diff --git a/src/state/raw.rs b/src/state/raw.rs index e951e31b..95c1fe63 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -510,16 +510,16 @@ impl RawLua { } /// See [`Lua::create_string`] - pub(crate) unsafe fn create_string(&self, s: impl AsRef<[u8]>) -> Result { + pub(crate) unsafe fn create_string(&self, s: &[u8]) -> Result { let state = self.state(); if self.unlikely_memory_error() { - push_string(state, s.as_ref(), false)?; + push_string(state, s, false)?; return Ok(String(self.pop_ref())); } let _sg = StackGuard::new(state); check_stack(state, 3)?; - push_string(state, s.as_ref(), true)?; + push_string(state, s, true)?; Ok(String(self.pop_ref())) } From 12b24b6c5ba4237a0e51662a68fe6d70f119fb58 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 22 Nov 2025 13:47:17 +0000 Subject: [PATCH 555/635] mlua-sys: v0.9.0 From 2e4184e7e45d0935d9b23923c2b7827075579142 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 22 Nov 2025 13:49:23 +0000 Subject: [PATCH 556/635] Update spelling --- tests/tests.rs | 2 +- typos.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tests.rs b/tests/tests.rs index 30803fc7..d9320fbb 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -438,7 +438,7 @@ fn test_panic() -> Result<()> { { let lua = make_lua(LuaOptions::default())?; match catch_unwind(AssertUnwindSafe(|| -> Result<()> { - let _catched_panic = lua + let _caught_panic = lua .load( r#" -- Set global diff --git a/typos.toml b/typos.toml index 8692cfc1..ef768641 100644 --- a/typos.toml +++ b/typos.toml @@ -1,5 +1,5 @@ [default] -extend-ignore-identifiers-re = ["catched", "2nd", "ser"] +extend-ignore-identifiers-re = ["2nd", "ser"] [default.extend-words] thr = "thr" From aee647c6c06806ac4a06979aec80c2eee2b81e20 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 22 Nov 2025 13:55:07 +0000 Subject: [PATCH 557/635] Update CHANGELOG --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38d88dd4..5c3e1773 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## v0.11.5 (Nov 22, 2025) + +- Luau updated to 0.701 +- Added `Lua::set_memory_category` and `Lua::heap_dump` functions to profile (Luau) memory +- Added `Lua::type_metatable` helper to get metatable of a primitive type +- Added `Lua::traceback` function to generate stack traces at different levels +- Added `add_method_once` /`add_async_method_once` UserData methods (experimental) +- Make `AnyUserData::type_name` public +- impl `IntoLuaMulti` for `&MultiValue` +- Bugfixes and async perf improvements + ## v0.11.4 (Sep 29, 2025) - Make `Value::to_serializable` public From e9de70a030dd0fd8fbe641626d07ee83ac09c44c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 22 Nov 2025 13:57:04 +0000 Subject: [PATCH 558/635] (CI) Move from x86_64-apple-darwin to aarch64-apple-darwin --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5b4ad7aa..05986ff2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: - os: ubuntu-latest target: x86_64-unknown-linux-gnu - os: macos-latest - target: x86_64-apple-darwin + target: aarch64-apple-darwin - os: windows-latest target: x86_64-pc-windows-msvc steps: @@ -110,7 +110,7 @@ jobs: - os: ubuntu-latest target: x86_64-unknown-linux-gnu - os: macos-latest - target: x86_64-apple-darwin + target: aarch64-apple-darwin - os: windows-latest target: x86_64-pc-windows-msvc steps: @@ -199,7 +199,7 @@ jobs: - os: ubuntu-latest target: x86_64-unknown-linux-gnu - os: macos-latest - target: x86_64-apple-darwin + target: aarch64-apple-darwin steps: - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@stable From d2a8670bef8ae516bacf2cf3df49bca32191fe7f Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 22 Nov 2025 13:58:15 +0000 Subject: [PATCH 559/635] (CI) Update wasi/wasmtime --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 05986ff2..818d9686 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -272,8 +272,8 @@ jobs: - name: Install wasi-sdk/Wasmtime working-directory: ${{ runner.tool_cache }} run: | - wasi_sdk=27 - wasmtime=v37.0.1 + wasi_sdk=29 + wasmtime=v39.0.0 curl -LO https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$wasi_sdk/wasi-sdk-$wasi_sdk.0-x86_64-linux.tar.gz tar xf wasi-sdk-$wasi_sdk.0-x86_64-linux.tar.gz From a7f105c698671e3999db63992984c4860c987d20 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 22 Nov 2025 14:27:02 +0000 Subject: [PATCH 560/635] Update `Lua::set_memory_category` doc --- src/luau/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/luau/mod.rs b/src/luau/mod.rs index a12271c7..88113e81 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -27,6 +27,8 @@ impl Lua { /// /// The category "main" is reserved for the default memory category. /// Maximum of 255 categories can be registered. + /// The category is set per Lua thread (state) and affects all allocations made from that + /// thread. /// /// Return error if too many categories are registered or if the category name is invalid. /// @@ -55,12 +57,12 @@ impl Lua { } } }; - unsafe { ffi::lua_setmemcat(lua.main_state(), cat_id as i32) }; + unsafe { ffi::lua_setmemcat(lua.state(), cat_id as i32) }; Ok(()) } - /// Dumps the current Lua heap state. + /// Dumps the current Lua VM heap state. /// /// The returned `HeapDump` can be used to analyze memory usage. /// It's recommended to call [`Lua::gc_collect`] before dumping the heap. @@ -68,9 +70,7 @@ impl Lua { #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn heap_dump(&self) -> Result { let lua = self.lock(); - unsafe { - heap_dump::HeapDump::new(lua.main_state()).ok_or_else(|| Error::runtime("unable to dump heap")) - } + unsafe { heap_dump::HeapDump::new(lua.state()).ok_or_else(|| Error::runtime("unable to dump heap")) } } pub(crate) unsafe fn configure_luau(&self) -> Result<()> { From 0245d4ce6b33e56e85804c250be11eb5bdf2ded7 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 22 Nov 2025 16:13:08 +0000 Subject: [PATCH 561/635] v0.11.5 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7855dacf..9124dab3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.11.4" # remember to update mlua_derive +version = "0.11.5" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.80.0" edition = "2021" From 39a7d3b862eab76359cab9df600289b4502f511a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 31 Dec 2025 19:26:30 +0200 Subject: [PATCH 562/635] Update `SYS_MIN_ALIGN` --- mlua-sys/src/lib.rs | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/mlua-sys/src/lib.rs b/mlua-sys/src/lib.rs index 730bf371..784b5d3d 100644 --- a/mlua-sys/src/lib.rs +++ b/mlua-sys/src/lib.rs @@ -40,14 +40,22 @@ pub const LUA_MAX_UPVALUES: c_int = 200; #[doc(hidden)] pub const LUA_TRACEBACK_STACK: c_int = 11; -// Copied from https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/common/alloc.rs -// The minimum alignment guaranteed by the architecture. This value is used to -// add fast paths for low alignment values. -#[cfg(any( +// The minimum alignment guaranteed by the architecture. +// Copied from https://github.com/rust-lang/rust/blob/main/library/std/src/sys/alloc/mod.rs +#[doc(hidden)] +#[rustfmt::skip] +pub const SYS_MIN_ALIGN: usize = if cfg!(any( + all(target_arch = "riscv32", any(target_os = "espidf", target_os = "zkvm")), + all(target_arch = "xtensa", target_os = "espidf"), +)) { + // The allocator on the esp-idf and zkvm platforms guarantees 4 byte alignment. + 4 +} else if cfg!(any( target_arch = "x86", target_arch = "arm", target_arch = "m68k", target_arch = "csky", + target_arch = "loongarch32", target_arch = "mips", target_arch = "mips32r6", target_arch = "powerpc", @@ -55,12 +63,11 @@ pub const LUA_TRACEBACK_STACK: c_int = 11; target_arch = "sparc", target_arch = "wasm32", target_arch = "hexagon", - all(target_arch = "riscv32", not(any(target_os = "espidf", target_os = "zkvm"))), - all(target_arch = "xtensa", not(target_os = "espidf")), -))] -#[doc(hidden)] -pub const SYS_MIN_ALIGN: usize = 8; -#[cfg(any( + target_arch = "riscv32", + target_arch = "xtensa", +)) { + 8 +} else if cfg!(any( target_arch = "x86_64", target_arch = "aarch64", target_arch = "arm64ec", @@ -71,16 +78,11 @@ pub const SYS_MIN_ALIGN: usize = 8; target_arch = "sparc64", target_arch = "riscv64", target_arch = "wasm64", -))] -#[doc(hidden)] -pub const SYS_MIN_ALIGN: usize = 16; -// The allocator on the esp-idf and zkvm platforms guarantee 4 byte alignment. -#[cfg(any( - all(target_arch = "riscv32", any(target_os = "espidf", target_os = "zkvm")), - all(target_arch = "xtensa", target_os = "espidf"), -))] -#[doc(hidden)] -pub const SYS_MIN_ALIGN: usize = 4; +)) { + 16 +} else { + panic!("no value for SYS_MIN_ALIGN") +}; #[macro_use] mod macros; From da526595bb4f4e44dbaeff1e035e295d4945ccc8 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 13 Jan 2026 00:05:01 +0000 Subject: [PATCH 563/635] mlua-sys: Add Lua 5.5 support --- mlua-sys/Cargo.toml | 8 +- mlua-sys/README.md | 3 +- mlua-sys/build/find_normal.rs | 18 +- mlua-sys/build/find_vendored.rs | 3 + mlua-sys/build/main.rs | 16 +- mlua-sys/src/lib.rs | 11 +- mlua-sys/src/lua55/lauxlib.rs | 211 ++++++++++++ mlua-sys/src/lua55/lua.rs | 578 ++++++++++++++++++++++++++++++++ mlua-sys/src/lua55/lualib.rs | 55 +++ mlua-sys/src/lua55/mod.rs | 9 + 10 files changed, 888 insertions(+), 24 deletions(-) create mode 100644 mlua-sys/src/lua55/lauxlib.rs create mode 100644 mlua-sys/src/lua55/lua.rs create mode 100644 mlua-sys/src/lua55/lualib.rs create mode 100644 mlua-sys/src/lua55/mod.rs diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 53a414a2..99fce400 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -12,14 +12,15 @@ license = "MIT" links = "lua" build = "build/main.rs" description = """ -Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau +Low level (FFI) bindings to Lua 5.5/5.4/5.3/5.2/5.1 (including LuaJIT) and Luau """ [package.metadata.docs.rs] -features = ["lua54", "vendored"] +features = ["lua55", "vendored"] rustdoc-args = ["--cfg", "docsrs"] [features] +lua55 = [] lua54 = [] lua53 = [] lua52 = [] @@ -34,12 +35,13 @@ external = [] module = [] [dependencies] +libc = "0.2" [build-dependencies] cc = "1.0" cfg-if = "1.0" pkg-config = "0.3.17" -lua-src = { version = ">= 548.1.0, < 548.2.0", optional = true } +lua-src = { version = ">= 550.0.0, < 550.1.0", optional = true } luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true } luau0-src = { version = "0.17.0", optional = true } diff --git a/mlua-sys/README.md b/mlua-sys/README.md index 927ebbd6..def6e91e 100644 --- a/mlua-sys/README.md +++ b/mlua-sys/README.md @@ -1,8 +1,9 @@ # mlua-sys -Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and [Luau]. +Low level (FFI) bindings to Lua 5.5/5.4/5.3/5.2/5.1 (including [LuaJIT]) and [Luau]. Intended to be consumed by the [mlua] crate. +[LuaJIT]: https://github.com/LuaJIT/LuaJIT [Luau]: https://github.com/luau-lang/luau [mlua]: https://crates.io/crates/mlua diff --git a/mlua-sys/build/find_normal.rs b/mlua-sys/build/find_normal.rs index dbbc18c4..b91b1c01 100644 --- a/mlua-sys/build/find_normal.rs +++ b/mlua-sys/build/find_normal.rs @@ -31,18 +31,16 @@ pub fn probe_lua() { // Find using `pkg-config` + #[cfg(feature = "lua55")] + let (incl_bound, excl_bound, alt_probe, ver) = ("5.5", "5.6", ["lua5.5", "lua-5.5", "lua55"], "5.5"); #[cfg(feature = "lua54")] - let (incl_bound, excl_bound, alt_probe, ver) = - ("5.4", "5.5", ["lua5.4", "lua-5.4", "lua54"], "5.4"); + let (incl_bound, excl_bound, alt_probe, ver) = ("5.4", "5.5", ["lua5.4", "lua-5.4", "lua54"], "5.4"); #[cfg(feature = "lua53")] - let (incl_bound, excl_bound, alt_probe, ver) = - ("5.3", "5.4", ["lua5.3", "lua-5.3", "lua53"], "5.3"); + let (incl_bound, excl_bound, alt_probe, ver) = ("5.3", "5.4", ["lua5.3", "lua-5.3", "lua53"], "5.3"); #[cfg(feature = "lua52")] - let (incl_bound, excl_bound, alt_probe, ver) = - ("5.2", "5.3", ["lua5.2", "lua-5.2", "lua52"], "5.2"); + let (incl_bound, excl_bound, alt_probe, ver) = ("5.2", "5.3", ["lua5.2", "lua-5.2", "lua52"], "5.2"); #[cfg(feature = "lua51")] - let (incl_bound, excl_bound, alt_probe, ver) = - ("5.1", "5.2", ["lua5.1", "lua-5.1", "lua51"], "5.1"); + let (incl_bound, excl_bound, alt_probe, ver) = ("5.1", "5.2", ["lua5.1", "lua-5.1", "lua51"], "5.1"); #[cfg(feature = "luajit")] let (incl_bound, excl_bound, alt_probe, ver) = ("2.0.4", "2.2", [], "JIT"); @@ -54,9 +52,7 @@ pub fn probe_lua() { if lua.is_err() { for pkg in alt_probe { - lua = pkg_config::Config::new() - .cargo_metadata(true) - .probe(pkg); + lua = pkg_config::Config::new().cargo_metadata(true).probe(pkg); if lua.is_ok() { break; diff --git a/mlua-sys/build/find_vendored.rs b/mlua-sys/build/find_vendored.rs index e3ddbecf..78105250 100644 --- a/mlua-sys/build/find_vendored.rs +++ b/mlua-sys/build/find_vendored.rs @@ -1,6 +1,9 @@ #![allow(dead_code)] pub fn probe_lua() { + #[cfg(feature = "lua55")] + let artifacts = lua_src::Build::new().build(lua_src::Lua55); + #[cfg(feature = "lua54")] let artifacts = lua_src::Build::new().build(lua_src::Lua54); diff --git a/mlua-sys/build/main.rs b/mlua-sys/build/main.rs index 53074f8e..5bb8ddbe 100644 --- a/mlua-sys/build/main.rs +++ b/mlua-sys/build/main.rs @@ -1,19 +1,21 @@ cfg_if::cfg_if! { - if #[cfg(all(feature = "lua54", not(any(feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] { + if #[cfg(all(feature = "lua55", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] { include!("main_inner.rs"); - } else if #[cfg(all(feature = "lua53", not(any(feature = "lua54", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] { + } else if #[cfg(all(feature = "lua54", not(any(feature = "lua55", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] { include!("main_inner.rs"); - } else if #[cfg(all(feature = "lua52", not(any(feature = "lua54", feature = "lua53", feature = "lua51", feature = "luajit", feature = "luau"))))] { + } else if #[cfg(all(feature = "lua53", not(any(feature = "lua55", feature = "lua54", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] { include!("main_inner.rs"); - } else if #[cfg(all(feature = "lua51", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit", feature = "luau"))))] { + } else if #[cfg(all(feature = "lua52", not(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua51", feature = "luajit", feature = "luau"))))] { include!("main_inner.rs"); - } else if #[cfg(all(feature = "luajit", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luau"))))] { + } else if #[cfg(all(feature = "lua51", not(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit", feature = "luau"))))] { include!("main_inner.rs"); - } else if #[cfg(all(feature = "luau", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit"))))] { + } else if #[cfg(all(feature = "luajit", not(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luau"))))] { + include!("main_inner.rs"); + } else if #[cfg(all(feature = "luau", not(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit"))))] { include!("main_inner.rs"); } else { fn main() { - compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau"); + compile_error!("You can enable only one of the features: lua55, lua54, lua53, lua52, lua51, luajit, luajit52, luau"); } } } diff --git a/mlua-sys/src/lib.rs b/mlua-sys/src/lib.rs index 784b5d3d..e6672c88 100644 --- a/mlua-sys/src/lib.rs +++ b/mlua-sys/src/lib.rs @@ -1,4 +1,4 @@ -//! Low level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau. +//! Low level bindings to Lua 5.5/5.4/5.3/5.2/5.1 (including LuaJIT) and Luau. #![allow(non_camel_case_types, non_snake_case)] #![allow(clippy::missing_safety_doc)] @@ -8,6 +8,9 @@ use std::os::raw::c_int; +#[cfg(any(feature = "lua55", doc))] +pub use lua55::*; + #[cfg(any(feature = "lua54", doc))] pub use lua54::*; @@ -23,7 +26,7 @@ pub use lua51::*; #[cfg(any(feature = "luau", doc))] pub use luau::*; -#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] +#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))] #[doc(hidden)] pub const LUA_MAX_UPVALUES: c_int = 255; @@ -87,6 +90,10 @@ pub const SYS_MIN_ALIGN: usize = if cfg!(any( #[macro_use] mod macros; +#[cfg(any(feature = "lua55", doc))] +#[cfg_attr(docsrs, doc(cfg(feature = "lua55")))] +pub mod lua55; + #[cfg(any(feature = "lua54", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] pub mod lua54; diff --git a/mlua-sys/src/lua55/lauxlib.rs b/mlua-sys/src/lua55/lauxlib.rs new file mode 100644 index 00000000..daf346ab --- /dev/null +++ b/mlua-sys/src/lua55/lauxlib.rs @@ -0,0 +1,211 @@ +//! Contains definitions from `lauxlib.h`. + +use std::os::raw::{c_char, c_int, c_uint, c_void}; +use std::ptr; + +use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State}; + +// Extra error code for 'luaL_loadfilex' +pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1; + +// Key, in the registry, for table of loaded modules +pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED"); + +// Key, in the registry, for table of preloaded loaders +pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD"); + +#[repr(C)] +pub struct luaL_Reg { + pub name: *const c_char, + pub func: lua_CFunction, +} + +#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))] +unsafe extern "C-unwind" { + pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number, sz: usize); + + pub fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int; + pub fn luaL_callmeta(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int; + pub fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char; + pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int; + pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char; + pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize) + -> *const c_char; + pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number; + pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number; + pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer; + pub fn luaL_optinteger(L: *mut lua_State, arg: c_int, def: lua_Integer) -> lua_Integer; + + pub fn luaL_checkstack(L: *mut lua_State, sz: c_int, msg: *const c_char); + pub fn luaL_checktype(L: *mut lua_State, arg: c_int, t: c_int); + pub fn luaL_checkany(L: *mut lua_State, arg: c_int); + + pub fn luaL_newmetatable(L: *mut lua_State, tname: *const c_char) -> c_int; + pub fn luaL_setmetatable(L: *mut lua_State, tname: *const c_char); + pub fn luaL_testudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void; + pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void; + + pub fn luaL_where(L: *mut lua_State, lvl: c_int); + pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> c_int; + + pub fn luaL_checkoption( + L: *mut lua_State, + arg: c_int, + def: *const c_char, + lst: *const *const c_char, + ) -> c_int; + + pub fn luaL_fileresult(L: *mut lua_State, stat: c_int, fname: *const c_char) -> c_int; + pub fn luaL_execresult(L: *mut lua_State, stat: c_int) -> c_int; + pub fn luaL_alloc(L: *mut lua_State, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void; +} + +// Pre-defined references +pub const LUA_NOREF: c_int = -2; +pub const LUA_REFNIL: c_int = -1; + +#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))] +unsafe extern "C-unwind" { + pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int; + pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int); + + pub fn luaL_loadfilex(L: *mut lua_State, filename: *const c_char, mode: *const c_char) -> c_int; +} + +#[inline(always)] +pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int { + luaL_loadfilex(L, f, ptr::null()) +} + +#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))] +unsafe extern "C-unwind" { + pub fn luaL_loadbufferx( + L: *mut lua_State, + buff: *const c_char, + sz: usize, + name: *const c_char, + mode: *const c_char, + ) -> c_int; + pub fn luaL_loadstring(L: *mut lua_State, s: *const c_char) -> c_int; + + pub fn luaL_newstate() -> *mut lua_State; + + #[link_name = "luaL_makeseed"] + pub fn luaL_makeseed_(L: *mut lua_State) -> c_uint; + + pub fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer; + + // TODO: luaL_addgsub + + pub fn luaL_gsub( + L: *mut lua_State, + s: *const c_char, + p: *const c_char, + r: *const c_char, + ) -> *const c_char; + + pub fn luaL_setfuncs(L: *mut lua_State, l: *const luaL_Reg, nup: c_int); + + pub fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_char) -> c_int; + + pub fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, level: c_int); + + pub fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int); +} + +// +// Some useful macros (implemented as Rust functions) +// + +// TODO: luaL_newlibtable, luaL_newlib + +#[inline(always)] +pub unsafe fn luaL_argcheck(L: *mut lua_State, cond: c_int, arg: c_int, extramsg: *const c_char) { + if cond == 0 { + luaL_argerror(L, arg, extramsg); + } +} + +#[inline(always)] +pub unsafe fn luaL_checkstring(L: *mut lua_State, n: c_int) -> *const c_char { + luaL_checklstring(L, n, ptr::null_mut()) +} + +#[inline(always)] +pub unsafe fn luaL_optstring(L: *mut lua_State, n: c_int, d: *const c_char) -> *const c_char { + luaL_optlstring(L, n, d, ptr::null_mut()) +} + +#[inline(always)] +pub unsafe fn luaL_typename(L: *mut lua_State, i: c_int) -> *const c_char { + lua::lua_typename(L, lua::lua_type(L, i)) +} + +#[inline(always)] +pub unsafe fn luaL_dofile(L: *mut lua_State, filename: *const c_char) -> c_int { + let status = luaL_loadfile(L, filename); + if status == 0 { + lua::lua_pcall(L, 0, lua::LUA_MULTRET, 0) + } else { + status + } +} + +#[inline(always)] +pub unsafe fn luaL_dostring(L: *mut lua_State, s: *const c_char) -> c_int { + let status = luaL_loadstring(L, s); + if status == 0 { + lua::lua_pcall(L, 0, lua::LUA_MULTRET, 0) + } else { + status + } +} + +#[inline(always)] +pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) { + lua::lua_getfield(L, lua::LUA_REGISTRYINDEX, n); +} + +// luaL_opt would be implemented here but it is undocumented, so it's omitted + +#[inline(always)] +pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int { + luaL_loadbufferx(L, s, sz, n, ptr::null()) +} + +pub unsafe fn luaL_loadbufferenv( + L: *mut lua_State, + data: *const c_char, + size: usize, + name: *const c_char, + mode: *const c_char, + mut env: c_int, +) -> c_int { + if env != 0 { + env = lua::lua_absindex(L, env); + } + let status = luaL_loadbufferx(L, data, size, name, mode); + if status == lua::LUA_OK && env != 0 { + lua::lua_pushvalue(L, env); + lua::lua_setupvalue(L, -2, 1); + } + status +} + +pub unsafe fn luaL_makeseed(L: *mut lua_State) -> c_uint { + #[cfg(macos)] + return libc::arc4random(); + #[cfg(linux)] + { + let mut seed = 0u32; + let buf = &mut seed as *mut _ as *mut c_void; + if libc::getrandom(buf, 4, libc::GRND_NONBLOCK) == 4 { + return seed; + } + } + luaL_makeseed_(L) +} + +// +// TODO: Generic Buffer Manipulation +// diff --git a/mlua-sys/src/lua55/lua.rs b/mlua-sys/src/lua55/lua.rs new file mode 100644 index 00000000..8f551523 --- /dev/null +++ b/mlua-sys/src/lua55/lua.rs @@ -0,0 +1,578 @@ +//! Contains definitions from `lua.h`. + +use std::ffi::CStr; +use std::marker::{PhantomData, PhantomPinned}; +use std::os::raw::{c_char, c_double, c_int, c_uchar, c_uint, c_void}; +use std::{mem, ptr}; + +// Mark for precompiled code (`Lua`) +pub const LUA_SIGNATURE: &[u8] = b"\x1bLua"; + +// Option for multiple returns in 'lua_pcall' and 'lua_call' +pub const LUA_MULTRET: c_int = -1; + +// Size of the Lua stack +#[doc(hidden)] +pub const LUAI_MAXSTACK: c_int = libc::INT_MAX; + +// Size of a raw memory area associated with a Lua state with very fast access. +pub const LUA_EXTRASPACE: usize = mem::size_of::<*const ()>(); + +// +// Pseudo-indices +// +pub const LUA_REGISTRYINDEX: c_int = -(libc::INT_MAX / 2 + 1000); + +pub const fn lua_upvalueindex(i: c_int) -> c_int { + LUA_REGISTRYINDEX - i +} + +// +// Thread status +// +pub const LUA_OK: c_int = 0; +pub const LUA_YIELD: c_int = 1; +pub const LUA_ERRRUN: c_int = 2; +pub const LUA_ERRSYNTAX: c_int = 3; +pub const LUA_ERRMEM: c_int = 4; +pub const LUA_ERRERR: c_int = 5; + +/// A raw Lua state associated with a thread. +#[repr(C)] +pub struct lua_State { + _data: [u8; 0], + _marker: PhantomData<(*mut u8, PhantomPinned)>, +} + +// +// Basic types +// +pub const LUA_TNONE: c_int = -1; + +pub const LUA_TNIL: c_int = 0; +pub const LUA_TBOOLEAN: c_int = 1; +pub const LUA_TLIGHTUSERDATA: c_int = 2; +pub const LUA_TNUMBER: c_int = 3; +pub const LUA_TSTRING: c_int = 4; +pub const LUA_TTABLE: c_int = 5; +pub const LUA_TFUNCTION: c_int = 6; +pub const LUA_TUSERDATA: c_int = 7; +pub const LUA_TTHREAD: c_int = 8; + +pub const LUA_NUMTYPES: c_int = 9; + +/// Minimum Lua stack available to a C function +pub const LUA_MINSTACK: c_int = 20; + +// Predefined values in the registry +// index 1 is reserved for the reference mechanism +pub const LUA_RIDX_GLOBALS: lua_Integer = 2; +pub const LUA_RIDX_MAINTHREAD: lua_Integer = 3; +pub const LUA_RIDX_LAST: lua_Integer = 3; + +/// A Lua number, usually equivalent to `f64` +pub type lua_Number = c_double; + +/// A Lua integer, usually equivalent to `i64` +pub type lua_Integer = i64; + +/// A Lua unsigned integer, usually equivalent to `u64` +pub type lua_Unsigned = u64; + +/// Type for continuation-function contexts +pub type lua_KContext = isize; + +/// Type for native C functions that can be passed to Lua +pub type lua_CFunction = unsafe extern "C-unwind" fn(L: *mut lua_State) -> c_int; + +/// Type for continuation functions +pub type lua_KFunction = + unsafe extern "C-unwind" fn(L: *mut lua_State, status: c_int, ctx: lua_KContext) -> c_int; + +// Type for functions that read/write blocks when loading/dumping Lua chunks +#[rustfmt::skip] +pub type lua_Reader = + unsafe extern "C-unwind" fn(L: *mut lua_State, ud: *mut c_void, sz: *mut usize) -> *const c_char; +#[rustfmt::skip] +pub type lua_Writer = + unsafe extern "C-unwind" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int; + +/// Type for memory-allocation functions (no unwinding) +#[rustfmt::skip] +pub type lua_Alloc = + unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void; + +/// Type for warning functions +pub type lua_WarnFunction = unsafe extern "C-unwind" fn(ud: *mut c_void, msg: *const c_char, tocont: c_int); + +#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))] +unsafe extern "C-unwind" { + // + // State manipulation + // + pub fn lua_newstate(f: lua_Alloc, ud: *mut c_void, seed: c_uint) -> *mut lua_State; + pub fn lua_close(L: *mut lua_State); + pub fn lua_newthread(L: *mut lua_State) -> *mut lua_State; + pub fn lua_closethread(L: *mut lua_State, from: *mut lua_State) -> c_int; + + pub fn lua_atpanic(L: *mut lua_State, panicf: lua_CFunction) -> lua_CFunction; + + pub fn lua_version(L: *mut lua_State) -> lua_Number; + + // + // Basic stack manipulation + // + pub fn lua_absindex(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_gettop(L: *mut lua_State) -> c_int; + pub fn lua_settop(L: *mut lua_State, idx: c_int); + pub fn lua_pushvalue(L: *mut lua_State, idx: c_int); + pub fn lua_rotate(L: *mut lua_State, idx: c_int, n: c_int); + pub fn lua_copy(L: *mut lua_State, fromidx: c_int, toidx: c_int); + pub fn lua_checkstack(L: *mut lua_State, sz: c_int) -> c_int; + + pub fn lua_xmove(from: *mut lua_State, to: *mut lua_State, n: c_int); + + // + // Access functions (stack -> C) + // + pub fn lua_isnumber(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_isstring(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_iscfunction(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_isuserdata(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_type(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_typename(L: *mut lua_State, tp: c_int) -> *const c_char; + + pub fn lua_tonumberx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Number; + pub fn lua_tointegerx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Integer; + pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char; + #[link_name = "lua_rawlen"] + fn lua_rawlen_(L: *mut lua_State, idx: c_int) -> lua_Unsigned; + pub fn lua_tocfunction(L: *mut lua_State, idx: c_int) -> Option; + pub fn lua_touserdata(L: *mut lua_State, idx: c_int) -> *mut c_void; + pub fn lua_tothread(L: *mut lua_State, idx: c_int) -> *mut lua_State; + pub fn lua_topointer(L: *mut lua_State, idx: c_int) -> *const c_void; +} + +// lua_rawlen's return type changed from size_t to lua_Unsigned int in Lua 5.4. +// This adapts the crate API to the new Lua ABI. +#[inline(always)] +pub unsafe fn lua_rawlen(L: *mut lua_State, idx: c_int) -> usize { + lua_rawlen_(L, idx) as usize +} + +// +// Comparison and arithmetic functions +// +pub const LUA_OPADD: c_int = 0; +pub const LUA_OPSUB: c_int = 1; +pub const LUA_OPMUL: c_int = 2; +pub const LUA_OPMOD: c_int = 3; +pub const LUA_OPPOW: c_int = 4; +pub const LUA_OPDIV: c_int = 5; +pub const LUA_OPIDIV: c_int = 6; +pub const LUA_OPBAND: c_int = 7; +pub const LUA_OPBOR: c_int = 8; +pub const LUA_OPBXOR: c_int = 9; +pub const LUA_OPSHL: c_int = 10; +pub const LUA_OPSHR: c_int = 11; +pub const LUA_OPUNM: c_int = 12; +pub const LUA_OPBNOT: c_int = 13; + +pub const LUA_OPEQ: c_int = 0; +pub const LUA_OPLT: c_int = 1; +pub const LUA_OPLE: c_int = 2; + +#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))] +unsafe extern "C-unwind" { + pub fn lua_arith(L: *mut lua_State, op: c_int); + pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int; + pub fn lua_compare(L: *mut lua_State, idx1: c_int, idx2: c_int, op: c_int) -> c_int; +} + +#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))] +unsafe extern "C-unwind" { + // + // Push functions (C -> stack) + // + 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); + pub fn lua_pushlstring(L: *mut lua_State, s: *const c_char, len: usize) -> *const c_char; + pub fn lua_pushexternalstring( + L: *mut lua_State, + s: *const c_char, + len: usize, + falloc: lua_Alloc, + ud: *mut c_void, + ) -> *const c_char; + pub fn lua_pushstring(L: *mut lua_State, s: *const c_char) -> *const c_char; + // lua_pushvfstring + pub fn lua_pushfstring(L: *mut lua_State, fmt: *const c_char, ...) -> *const c_char; + pub fn lua_pushcclosure(L: *mut lua_State, f: lua_CFunction, n: c_int); + pub fn lua_pushboolean(L: *mut lua_State, b: c_int); + pub fn lua_pushlightuserdata(L: *mut lua_State, p: *mut c_void); + pub fn lua_pushthread(L: *mut lua_State) -> c_int; + + // + // Get functions (Lua -> stack) + // + pub fn lua_getglobal(L: *mut lua_State, name: *const c_char) -> c_int; + pub fn lua_gettable(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_getfield(L: *mut lua_State, idx: c_int, k: *const c_char) -> c_int; + pub fn lua_geti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_int; + pub fn lua_rawget(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_rawgeti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_int; + pub fn lua_rawgetp(L: *mut lua_State, idx: c_int, p: *const c_void) -> c_int; + + pub fn lua_createtable(L: *mut lua_State, narr: c_int, nrec: c_int); + pub fn lua_newuserdatauv(L: *mut lua_State, sz: usize, nuvalue: c_int) -> *mut c_void; + pub fn lua_getmetatable(L: *mut lua_State, objindex: c_int) -> c_int; + pub fn lua_getiuservalue(L: *mut lua_State, idx: c_int, n: c_int) -> c_int; + + // + // Set functions (stack -> Lua) + // + pub fn lua_setglobal(L: *mut lua_State, name: *const c_char); + pub fn lua_settable(L: *mut lua_State, idx: c_int); + pub fn lua_setfield(L: *mut lua_State, idx: c_int, k: *const c_char); + pub fn lua_seti(L: *mut lua_State, idx: c_int, n: lua_Integer); + pub fn lua_rawset(L: *mut lua_State, idx: c_int); + pub fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer); + pub fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void); + pub fn lua_setmetatable(L: *mut lua_State, objindex: c_int) -> c_int; + pub fn lua_setiuservalue(L: *mut lua_State, idx: c_int, n: c_int) -> c_int; + + // + // 'load' and 'call' functions (load and run Lua code) + // + pub fn lua_callk( + L: *mut lua_State, + nargs: c_int, + nresults: c_int, + ctx: lua_KContext, + k: Option, + ); + pub fn lua_pcallk( + L: *mut lua_State, + nargs: c_int, + nresults: c_int, + errfunc: c_int, + ctx: lua_KContext, + k: Option, + ) -> c_int; + + pub fn lua_load( + L: *mut lua_State, + reader: lua_Reader, + data: *mut c_void, + chunkname: *const c_char, + mode: *const c_char, + ) -> c_int; + + pub fn lua_dump(L: *mut lua_State, writer: lua_Writer, data: *mut c_void, strip: c_int) -> c_int; +} + +#[inline(always)] +pub unsafe fn lua_call(L: *mut lua_State, n: c_int, r: c_int) { + lua_callk(L, n, r, 0, None) +} + +#[inline(always)] +pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_int { + lua_pcallk(L, n, r, f, 0, None) +} + +#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))] +unsafe extern "C-unwind" { + // + // Coroutine functions + // + pub fn lua_yieldk( + L: *mut lua_State, + nresults: c_int, + ctx: lua_KContext, + k: Option, + ) -> c_int; + pub fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int; + pub fn lua_status(L: *mut lua_State) -> c_int; + pub fn lua_isyieldable(L: *mut lua_State) -> c_int; +} + +#[inline(always)] +pub unsafe fn lua_yield(L: *mut lua_State, n: c_int) -> c_int { + lua_yieldk(L, n, 0, None) +} + +// +// Warning-related functions +// +#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))] +unsafe extern "C-unwind" { + pub fn lua_setwarnf(L: *mut lua_State, f: Option, ud: *mut c_void); + pub fn lua_warning(L: *mut lua_State, msg: *const c_char, tocont: c_int); +} + +// +// Garbage-collection options +// +pub const LUA_GCSTOP: c_int = 0; +pub const LUA_GCRESTART: c_int = 1; +pub const LUA_GCCOLLECT: c_int = 2; +pub const LUA_GCCOUNT: c_int = 3; +pub const LUA_GCCOUNTB: c_int = 4; +pub const LUA_GCSTEP: c_int = 5; +pub const LUA_GCISRUNNING: c_int = 6; +pub const LUA_GCGEN: c_int = 7; +pub const LUA_GCINC: c_int = 8; +pub const LUA_GCPARAM: c_int = 9; + +// Parameters for GC generational mode +pub const LUA_GCPMINORMUL: c_int = 0; // control minor collections +pub const LUA_GCPMAJORMINOR: c_int = 1; // control shift major->minor +pub const LUA_GCPMINORMAJOR: c_int = 2; // control shift minor->major + +// Parameters for GC incremental mode +pub const LUA_GCPPAUSE: c_int = 3; // size of pause between successive GCs +pub const LUA_GCPSTEPMUL: c_int = 4; // GC "speed" +pub const LUA_GCPSTEPSIZE: c_int = 5; // GC granularity + +pub const LUA_GCPNUM: c_int = 6; // number of parameters + +#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))] +unsafe extern "C-unwind" { + pub fn lua_gc(L: *mut lua_State, what: c_int, ...) -> c_int; +} + +#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))] +unsafe extern "C-unwind" { + // + // Miscellaneous functions + // + #[link_name = "lua_error"] + fn lua_error_(L: *mut lua_State) -> c_int; + pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_concat(L: *mut lua_State, n: c_int); + pub fn lua_len(L: *mut lua_State, idx: c_int); + pub fn lua_numbertocstring(L: *mut lua_State, idx: c_int, buff: *mut c_char) -> c_uint; + pub fn lua_stringtonumber(L: *mut lua_State, s: *const c_char) -> usize; + pub fn lua_getallocf(L: *mut lua_State, ud: *mut *mut c_void) -> lua_Alloc; + pub fn lua_setallocf(L: *mut lua_State, f: lua_Alloc, ud: *mut c_void); + + pub fn lua_toclose(L: *mut lua_State, idx: c_int); + pub fn lua_closeslot(L: *mut lua_State, idx: c_int); +} + +// lua_error does not return but is declared to return int, and Rust translates +// ! to void which can cause link-time errors if the platform linker is aware +// of return types and requires they match (for example: wasm does this). +#[inline(always)] +pub unsafe fn lua_error(L: *mut lua_State) -> ! { + lua_error_(L); + unreachable!(); +} + +// +// Some useful macros (implemented as Rust functions) +// +#[inline(always)] +pub unsafe fn lua_getextraspace(L: *mut lua_State) -> *mut c_void { + (L as *mut c_char).sub(LUA_EXTRASPACE) as *mut c_void +} + +#[inline(always)] +pub unsafe fn lua_tonumber(L: *mut lua_State, i: c_int) -> lua_Number { + lua_tonumberx(L, i, 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()) +} + +#[inline(always)] +pub unsafe fn lua_pop(L: *mut lua_State, n: c_int) { + lua_settop(L, -n - 1) +} + +#[inline(always)] +pub unsafe fn lua_newtable(L: *mut lua_State) { + lua_createtable(L, 0, 0) +} + +#[inline(always)] +pub unsafe fn lua_register(L: *mut lua_State, n: *const c_char, f: lua_CFunction) { + lua_pushcfunction(L, f); + lua_setglobal(L, n) +} + +#[inline(always)] +pub unsafe fn lua_pushcfunction(L: *mut lua_State, f: lua_CFunction) { + lua_pushcclosure(L, f, 0) +} + +#[inline(always)] +pub unsafe fn lua_isfunction(L: *mut lua_State, n: c_int) -> c_int { + (lua_type(L, n) == LUA_TFUNCTION) as c_int +} + +#[inline(always)] +pub unsafe fn lua_istable(L: *mut lua_State, n: c_int) -> c_int { + (lua_type(L, n) == LUA_TTABLE) as c_int +} + +#[inline(always)] +pub unsafe fn lua_islightuserdata(L: *mut lua_State, n: c_int) -> c_int { + (lua_type(L, n) == LUA_TLIGHTUSERDATA) as c_int +} + +#[inline(always)] +pub unsafe fn lua_isnil(L: *mut lua_State, n: c_int) -> c_int { + (lua_type(L, n) == LUA_TNIL) as c_int +} + +#[inline(always)] +pub unsafe fn lua_isboolean(L: *mut lua_State, n: c_int) -> c_int { + (lua_type(L, n) == LUA_TBOOLEAN) as c_int +} + +#[inline(always)] +pub unsafe fn lua_isthread(L: *mut lua_State, n: c_int) -> c_int { + (lua_type(L, n) == LUA_TTHREAD) as c_int +} + +#[inline(always)] +pub unsafe fn lua_isnone(L: *mut lua_State, n: c_int) -> c_int { + (lua_type(L, n) == LUA_TNONE) as c_int +} + +#[inline(always)] +pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int { + (lua_type(L, n) <= 0) as c_int +} + +#[inline(always)] +pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) { + lua_pushstring(L, s.as_ptr()); +} + +#[inline(always)] +pub unsafe fn lua_pushglobaltable(L: *mut lua_State) -> c_int { + lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS) +} + +#[inline(always)] +pub unsafe fn lua_tolightuserdata(L: *mut lua_State, idx: c_int) -> *mut c_void { + if lua_islightuserdata(L, idx) != 0 { + return lua_touserdata(L, idx); + } + ptr::null_mut() +} + +#[inline(always)] +pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char { + lua_tolstring(L, i, ptr::null_mut()) +} + +#[inline(always)] +pub unsafe fn lua_insert(L: *mut lua_State, idx: c_int) { + lua_rotate(L, idx, 1) +} + +#[inline(always)] +pub unsafe fn lua_remove(L: *mut lua_State, idx: c_int) { + lua_rotate(L, idx, -1); + lua_pop(L, 1) +} + +#[inline(always)] +pub unsafe fn lua_replace(L: *mut lua_State, idx: c_int) { + lua_copy(L, -1, idx); + lua_pop(L, 1) +} + +#[inline(always)] +pub unsafe fn lua_xpush(from: *mut lua_State, to: *mut lua_State, idx: c_int) { + lua_pushvalue(from, idx); + lua_xmove(from, to, 1); +} + +#[inline(always)] +pub unsafe fn lua_newuserdata(L: *mut lua_State, sz: usize) -> *mut c_void { + lua_newuserdatauv(L, sz, 1) +} + +#[inline(always)] +pub unsafe fn lua_getuservalue(L: *mut lua_State, idx: c_int) -> c_int { + lua_getiuservalue(L, idx, 1) +} + +#[inline(always)] +pub unsafe fn lua_setuservalue(L: *mut lua_State, idx: c_int) -> c_int { + lua_setiuservalue(L, idx, 1) +} + +// +// Debug API +// + +// Maximum size for the description of the source of a function in debug information. +const LUA_IDSIZE: usize = 60; + +// Event codes +pub const LUA_HOOKCALL: c_int = 0; +pub const LUA_HOOKRET: c_int = 1; +pub const LUA_HOOKLINE: c_int = 2; +pub const LUA_HOOKCOUNT: c_int = 3; +pub const LUA_HOOKTAILCALL: c_int = 4; + +// Event masks +pub const LUA_MASKCALL: c_int = 1 << (LUA_HOOKCALL as usize); +pub const LUA_MASKRET: c_int = 1 << (LUA_HOOKRET as usize); +pub const LUA_MASKLINE: c_int = 1 << (LUA_HOOKLINE as usize); +pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize); + +/// Type for functions to be called on debug events. +pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug); + +#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))] +unsafe extern "C-unwind" { + pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int; + pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int; + pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char; + pub fn lua_setlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char; + pub fn lua_getupvalue(L: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char; + pub fn lua_setupvalue(L: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char; + + pub fn lua_upvalueid(L: *mut lua_State, fidx: c_int, n: c_int) -> *mut c_void; + pub fn lua_upvaluejoin(L: *mut lua_State, fidx1: c_int, n1: c_int, fidx2: c_int, n2: c_int); + + pub fn lua_sethook(L: *mut lua_State, func: Option, mask: c_int, count: c_int); + pub fn lua_gethook(L: *mut lua_State) -> Option; + pub fn lua_gethookmask(L: *mut lua_State) -> c_int; + pub fn lua_gethookcount(L: *mut lua_State) -> c_int; +} + +#[repr(C)] +pub struct lua_Debug { + pub event: c_int, + pub name: *const c_char, // (n) + pub namewhat: *const c_char, // (n) 'global', 'local', 'field', 'method' + pub what: *const c_char, // (S) 'Lua', 'C', 'main', 'tail' + pub source: *const c_char, // (S) + pub srclen: usize, // (S) + pub currentline: c_int, // (l) + pub linedefined: c_int, // (S) + pub lastlinedefined: c_int, // (S) + pub nups: c_uchar, // (u) number of upvalues + pub nparams: c_uchar, // (u) number of parameters + pub isvararg: c_char, // (u) + pub extraargs: c_uchar, // (t) number of extra arguments + pub istailcall: c_char, // (t) + pub ftransfer: c_int, // (r) index of first value transferred + pub ntransfer: c_int, // (r) number of transferred values + pub short_src: [c_char; LUA_IDSIZE], // (S) + // lua.h mentions this is for private use + i_ci: *mut c_void, +} diff --git a/mlua-sys/src/lua55/lualib.rs b/mlua-sys/src/lua55/lualib.rs new file mode 100644 index 00000000..33903add --- /dev/null +++ b/mlua-sys/src/lua55/lualib.rs @@ -0,0 +1,55 @@ +//! Contains definitions from `lualib.h`. + +use std::os::raw::{c_char, c_int}; + +use super::lua::lua_State; + +pub const LUA_GLIBK: c_int = 1; + +pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); +pub const LUA_LOADLIBK: c_int = LUA_GLIBK << 1; + +pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); +pub const LUA_COLIBK: c_int = LUA_GLIBK << 2; + +pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); +pub const LUA_DBLIBK: c_int = LUA_GLIBK << 3; + +pub const LUA_IOLIBNAME: *const c_char = cstr!("io"); +pub const LUA_IOLIBK: c_int = LUA_GLIBK << 4; + +pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); +pub const LUA_MATHLIBK: c_int = LUA_GLIBK << 5; + +pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); +pub const LUA_OSLIBK: c_int = LUA_GLIBK << 6; + +pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); +pub const LUA_STRLIBK: c_int = LUA_GLIBK << 7; + +pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); +pub const LUA_TABLIBK: c_int = LUA_GLIBK << 8; + +pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8"); +pub const LUA_UTF8LIBK: c_int = LUA_GLIBK << 9; + +#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))] +unsafe extern "C-unwind" { + pub fn luaopen_base(L: *mut lua_State) -> c_int; + pub fn luaopen_package(L: *mut lua_State) -> c_int; + pub fn luaopen_coroutine(L: *mut lua_State) -> c_int; + pub fn luaopen_debug(L: *mut lua_State) -> c_int; + pub fn luaopen_io(L: *mut lua_State) -> c_int; + pub fn luaopen_math(L: *mut lua_State) -> c_int; + pub fn luaopen_os(L: *mut lua_State) -> c_int; + pub fn luaopen_string(L: *mut lua_State) -> c_int; + pub fn luaopen_table(L: *mut lua_State) -> c_int; + pub fn luaopen_utf8(L: *mut lua_State) -> c_int; + + // open all builtin libraries + pub fn luaL_openselectedlibs(L: *mut lua_State, load: c_int, preload: c_int); +} + +pub unsafe fn luaL_openlibs(L: *mut lua_State) { + luaL_openselectedlibs(L, !0, 0); +} diff --git a/mlua-sys/src/lua55/mod.rs b/mlua-sys/src/lua55/mod.rs new file mode 100644 index 00000000..8b653a4c --- /dev/null +++ b/mlua-sys/src/lua55/mod.rs @@ -0,0 +1,9 @@ +//! Low level bindings to Lua 5.5. + +pub use lauxlib::*; +pub use lua::*; +pub use lualib::*; + +pub mod lauxlib; +pub mod lua; +pub mod lualib; From ee9232eda1e26e6beaf68c6b1cc355604d2bf8ea Mon Sep 17 00:00:00 2001 From: psentee <135014396+psentee@users.noreply.github.com> Date: Tue, 13 Jan 2026 16:08:38 +0100 Subject: [PATCH 564/635] AnyUserData::is_proxy (#666) --- src/userdata.rs | 8 ++++++++ tests/userdata.rs | 3 +++ 2 files changed, 11 insertions(+) diff --git a/src/userdata.rs b/src/userdata.rs index ef320480..86e05f65 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -679,6 +679,14 @@ impl AnyUserData { matches!(type_id, Some(type_id) if type_id == TypeId::of::()) } + /// Checks whether the type of this userdata is a [proxy object] for `T`. + /// + /// [proxy object]: crate::Lua::create_proxy + #[inline] + pub fn is_proxy(&self) -> bool { + self.is::>() + } + /// Borrow this userdata immutably if it is of type `T`. /// /// # Errors diff --git a/tests/userdata.rs b/tests/userdata.rs index 6f10869d..a84eb526 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -731,6 +731,9 @@ fn test_userdata_proxy() -> Result<()> { let globals = lua.globals(); globals.set("MyUserData", lua.create_proxy::()?)?; + assert!(!globals.get::("MyUserData")?.is_proxy::<()>()); + assert!(globals.get::("MyUserData")?.is_proxy::()); + lua.load( r#" assert(MyUserData.static_field == 123) From 77d7d5d6bd629e7380e5b1455a5cb74de6735b04 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 13 Jan 2026 23:43:19 +0000 Subject: [PATCH 565/635] Add initial Lua 5.5 support --- Cargo.toml | 3 +- README.md | 4 +- src/debug.rs | 40 ++++++++++--- src/function.rs | 15 +++-- src/memory.rs | 2 +- src/state.rs | 83 ++++++++++++++++++++------- src/state/extra.rs | 4 +- src/state/raw.rs | 32 ++++++++--- src/stdlib.rs | 23 ++++++-- src/thread.rs | 8 +-- src/types.rs | 6 +- src/userdata.rs | 93 ++++++++++++++++++++++--------- src/util/error.rs | 24 +++++--- src/util/mod.rs | 2 +- tests/async.rs | 4 +- tests/hooks.rs | 2 +- tests/memory.rs | 4 +- tests/module/loader/tests/load.rs | 2 + tests/tests.rs | 12 ++-- tests/thread.rs | 5 +- tests/userdata.rs | 30 ++++++++-- tests/value.rs | 2 +- 22 files changed, 286 insertions(+), 114 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9124dab3..5873c9c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["lua", "luajit", "luau", "async", "scripting"] categories = ["api-bindings", "asynchronous"] license = "MIT" description = """ -High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau +High level bindings to Lua 5.5/5.4/5.3/5.2/5.1 (including LuaJIT) and Luau with async/await features and support of writing native Lua modules in Rust. """ @@ -26,6 +26,7 @@ members = [ ] [features] +lua55 = ["ffi/lua55"] lua54 = ["ffi/lua54"] lua53 = ["ffi/lua53"] lua52 = ["ffi/lua52"] diff --git a/README.md b/README.md index 80d08df7..c2e2eba2 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ `mlua` is a set of bindings to the [Lua](https://www.lua.org) programming language for Rust with a goal of providing a _safe_ (as much as possible), high level, easy to use, practical and flexible API. -Started as an `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Luau] and allows writing native Lua modules in Rust as well as using Lua in a standalone mode. +Started as an `rlua` fork, `mlua` supports Lua 5.5, 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Luau] and allows writing native Lua modules in Rust as well as using Lua in a standalone mode. `mlua` is tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platforms and cross-compilation to `aarch64` (other targets are also supported). @@ -36,6 +36,7 @@ WebAssembly (WASM) is supported through the `wasm32-unknown-emscripten` target f `mlua` uses feature flags to reduce the number of dependencies and compiled code, and allow choosing only the required set of features. Below is a list of the available feature flags. By default `mlua` does not enable any features. +* `lua55`: enable Lua [5.5] support * `lua54`: enable Lua [5.4] support * `lua53`: enable Lua [5.3] support * `lua52`: enable Lua [5.2] support @@ -55,6 +56,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl * `anyhow`: enable `anyhow::Error` conversion into Lua * `userdata-wrappers`: opt into `impl UserData` for `Rc`/`Arc`/`Rc>`/`Arc>` where `T: UserData` +[5.5]: https://www.lua.org/manual/5.5/manual.html [5.4]: https://www.lua.org/manual/5.4/manual.html [5.3]: https://www.lua.org/manual/5.3/manual.html [5.2]: https://www.lua.org/manual/5.2/manual.html diff --git a/src/debug.rs b/src/debug.rs index ace8f0e7..e37b0cf4 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -159,10 +159,10 @@ impl<'a> Debug<'a> { /// Corresponds to the `t` "what" mask. Returns true if the hook is in a function tail call, /// false otherwise. - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))] #[cfg_attr( docsrs, - doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))) + doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))) )] pub fn is_tail_call(&self) -> bool { unsafe { @@ -191,9 +191,9 @@ impl<'a> Debug<'a> { #[cfg(not(feature = "luau"))] let stack = DebugStack { num_ups: (*self.ar).nups as _, - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))] num_params: (*self.ar).nparams as _, - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))] is_vararg: (*self.ar).isvararg != 0, }; #[cfg(feature = "luau")] @@ -248,17 +248,41 @@ pub struct DebugStack { /// Number of upvalues. pub num_ups: u8, /// Number of parameters. - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] + #[cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luau" + ))] #[cfg_attr( docsrs, - doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))) + doc(cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luau" + ))) )] pub num_params: u8, /// Whether the function is a vararg function. - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] + #[cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luau" + ))] #[cfg_attr( docsrs, - doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))) + doc(cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luau" + ))) )] pub is_vararg: bool, } diff --git a/src/function.rs b/src/function.rs index aa31119c..e0cb0693 100644 --- a/src/function.rs +++ b/src/function.rs @@ -276,7 +276,7 @@ impl Function { #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] ffi::lua_getfenv(state, -1); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))] for i in 1..=255 { // Traverse upvalues until we find the _ENV one match ffi::lua_getupvalue(state, -1, i) { @@ -316,7 +316,7 @@ impl Function { lua.push_ref(&env.0); ffi::lua_setfenv(state, -2); } - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))] for i in 1..=255 { match ffi::lua_getupvalue(state, -1, i) { s if s.is_null() => return Ok(false), @@ -400,11 +400,14 @@ impl Function { _state: *mut ffi::lua_State, buf: *const c_void, buf_len: usize, - data: *mut c_void, + data_ptr: *mut c_void, ) -> c_int { - let data = &mut *(data as *mut Vec); - let buf = slice::from_raw_parts(buf as *const u8, buf_len); - data.extend_from_slice(buf); + // If `data` is null, then it's a signal that write is finished. + if !data_ptr.is_null() && buf_len > 0 { + let data = &mut *(data_ptr as *mut Vec); + let buf = slice::from_raw_parts(buf as *const u8, buf_len); + data.extend_from_slice(buf); + } 0 } diff --git a/src/memory.rs b/src/memory.rs index e5bab386..e4c2ce6f 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -83,7 +83,7 @@ impl MemoryState { } // Does nothing apart from calling `f()`, we don't need to bypass any limits - #[cfg(any(feature = "lua52", feature = "lua53", feature = "lua54"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))] #[inline] pub(crate) unsafe fn relax_limit_with(_state: *mut ffi::lua_State, f: impl FnOnce()) { f(); diff --git a/src/state.rs b/src/state.rs index 70644441..5698c301 100644 --- a/src/state.rs +++ b/src/state.rs @@ -73,8 +73,8 @@ pub(crate) struct LuaGuard(ArcReentrantMutexGuard); #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum GCMode { Incremental, - #[cfg(feature = "lua54")] - #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] + #[cfg(any(feature = "lua55", feature = "lua54"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))] Generational, } @@ -249,7 +249,7 @@ impl Lua { ffi::luaL_loadstring as _, ffi::luaL_openlibs as _, ]); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))] { _symbols.push(ffi::lua_getglobal as _); _symbols.push(ffi::lua_setglobal as _); @@ -382,7 +382,7 @@ impl Lua { #[cfg(not(feature = "luau"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub fn preload_module(&self, modname: &str, func: Function) -> Result<()> { - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))] let preload = unsafe { self.exec_raw::>((), |state| { ffi::lua_getfield(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_PRELOAD_TABLE); @@ -814,8 +814,8 @@ impl Lua { } /// Sets the warning function to be used by Lua to emit warnings. - #[cfg(feature = "lua54")] - #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] + #[cfg(any(feature = "lua55", feature = "lua54"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))] pub fn set_warning_function(&self, callback: F) where F: Fn(&Lua, &str, bool) -> Result<()> + MaybeSend + 'static, @@ -847,8 +847,8 @@ impl Lua { /// Removes warning function previously set by `set_warning_function`. /// /// This function has no effect if a warning function was not previously set. - #[cfg(feature = "lua54")] - #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] + #[cfg(any(feature = "lua55", feature = "lua54"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))] pub fn remove_warning_function(&self) { let lua = self.lock(); unsafe { @@ -861,8 +861,8 @@ impl Lua { /// /// A message in a call with `incomplete` set to `true` should be continued in /// another call to this function. - #[cfg(feature = "lua54")] - #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] + #[cfg(any(feature = "lua55", feature = "lua54"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))] pub fn warning(&self, msg: impl AsRef, incomplete: bool) { let msg = msg.as_ref(); let mut bytes = vec![0; msg.len() + 1]; @@ -954,7 +954,13 @@ impl Lua { } /// Returns `true` if the garbage collector is currently running automatically. - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] + #[cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luau" + ))] pub fn gc_is_running(&self) -> bool { let lua = self.lock(); unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCISRUNNING, 0) != 0 } @@ -1019,8 +1025,12 @@ impl Lua { let lua = self.lock(); let state = lua.main_state(); unsafe { - #[cfg(not(feature = "luau"))] + #[cfg(feature = "lua55")] + return ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, pause); + + #[cfg(not(any(feature = "lua55", feature = "luau")))] return ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause); + #[cfg(feature = "luau")] return ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause); } @@ -1034,7 +1044,18 @@ impl Lua { /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 pub fn gc_set_step_multiplier(&self, step_multiplier: c_int) -> c_int { let lua = self.lock(); - unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCSETSTEPMUL, step_multiplier) } + unsafe { + #[cfg(feature = "lua55")] + return ffi::lua_gc( + lua.main_state(), + ffi::LUA_GCPARAM, + ffi::LUA_GCPSTEPMUL, + step_multiplier, + ); + + #[cfg(not(feature = "lua55"))] + return ffi::lua_gc(lua.main_state(), ffi::LUA_GCSETSTEPMUL, step_multiplier); + } } /// Changes the collector to incremental mode with the given parameters. @@ -1073,12 +1094,19 @@ impl Lua { #[cfg(not(feature = "luau"))] let _ = step_size; // Ignored - GCMode::Incremental + return GCMode::Incremental; } + #[cfg(feature = "lua55")] + let prev_mode = unsafe { + ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, pause); + ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPMUL, step_multiplier); + ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPSIZE, step_size); + ffi::lua_gc(state, ffi::LUA_GCINC) + }; #[cfg(feature = "lua54")] let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCINC, pause, step_multiplier, step_size) }; - #[cfg(feature = "lua54")] + #[cfg(any(feature = "lua55", feature = "lua54"))] match prev_mode { ffi::LUA_GCINC => GCMode::Incremental, ffi::LUA_GCGEN => GCMode::Generational, @@ -1092,11 +1120,19 @@ impl Lua { /// can be found in the Lua 5.4 [documentation][lua_doc]. /// /// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#2.5.2 - #[cfg(feature = "lua54")] - #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] + #[cfg(any(feature = "lua55", feature = "lua54"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))] pub fn gc_gen(&self, minor_multiplier: c_int, major_multiplier: c_int) -> GCMode { let lua = self.lock(); let state = lua.main_state(); + #[cfg(feature = "lua55")] + let prev_mode = unsafe { + ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMUL, minor_multiplier); + ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMAJOR, major_multiplier); + // TODO: LUA_GCPMAJORMINOR + ffi::lua_gc(state, ffi::LUA_GCGEN) + }; + #[cfg(not(feature = "lua55"))] let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCGEN, minor_multiplier, major_multiplier) }; match prev_mode { ffi::LUA_GCGEN => GCMode::Generational, @@ -1317,7 +1353,12 @@ impl Lua { /// This function is unsafe because provides a way to execute unsafe C function. pub unsafe fn create_c_function(&self, func: ffi::lua_CFunction) -> Result { let lua = self.lock(); - if cfg!(any(feature = "lua54", feature = "lua53", feature = "lua52")) { + if cfg!(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52" + )) { ffi::lua_pushcfunction(lua.ref_thread(), func); return Ok(Function(lua.pop_ref_thread())); } @@ -1578,7 +1619,7 @@ impl Lua { unsafe { let _sg = StackGuard::new(state); assert_stack(state, 1); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))] ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] ffi::lua_pushvalue(state, ffi::LUA_GLOBALSINDEX); @@ -1610,7 +1651,7 @@ impl Lua { lua.push_ref(&globals.0); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))] ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX); @@ -2210,7 +2251,7 @@ impl Lua { })?, )?; - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))] let searchers: Table = package.get("searchers")?; #[cfg(any(feature = "lua51", feature = "luajit"))] let searchers: Table = package.get("loaders")?; diff --git a/src/state/extra.rs b/src/state/extra.rs index 85f6a951..d021ca45 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -77,7 +77,7 @@ pub(crate) struct ExtraData { pub(super) hook_callback: Option, #[cfg(not(feature = "luau"))] pub(super) hook_triggers: crate::debug::HookTriggers, - #[cfg(feature = "lua54")] + #[cfg(any(feature = "lua55", feature = "lua54"))] pub(super) warn_callback: Option, #[cfg(feature = "luau")] pub(super) interrupt_callback: Option, @@ -182,7 +182,7 @@ impl ExtraData { hook_callback: None, #[cfg(not(feature = "luau"))] hook_triggers: Default::default(), - #[cfg(feature = "lua54")] + #[cfg(any(feature = "lua55", feature = "lua54"))] warn_callback: None, #[cfg(feature = "luau")] interrupt_callback: None, diff --git a/src/state/raw.rs b/src/state/raw.rs index 95c1fe63..416adeaf 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -122,6 +122,12 @@ impl RawLua { pub(super) unsafe fn new(libs: StdLib, options: &LuaOptions) -> XRc> { let mem_state: *mut MemoryState = Box::into_raw(Box::default()); + #[cfg(feature = "lua55")] + let mut state = { + let seed = ffi::luaL_makeseed(ptr::null_mut()); + ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void, seed) + }; + #[cfg(not(feature = "lua55"))] let mut state = ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void); // If state is null then switch to Lua internal allocator if state.is_null() { @@ -153,7 +159,7 @@ impl RawLua { (|| -> Result<()> { let _sg = StackGuard::new(state); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))] ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] ffi::lua_pushvalue(state, ffi::LUA_GLOBALSINDEX); @@ -416,7 +422,7 @@ impl RawLua { VmState::Yield => { // Only count and line events can yield if event == ffi::LUA_HOOKCOUNT || event == ffi::LUA_HOOKLINE { - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] if ffi::lua_isyieldable(state) != 0 { ffi::lua_yield(state, 0); } @@ -768,7 +774,7 @@ impl RawLua { ffi::LUA_TLIGHTUSERDATA => Value::LightUserData(LightUserData(ffi::lua_touserdata(state, idx))), - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] ffi::LUA_TNUMBER => { if ffi::lua_isinteger(state, idx) != 0 { Value::Integer(ffi::lua_tointeger(state, idx)) @@ -899,7 +905,7 @@ impl RawLua { #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] ffi::lua_xpush(self.ref_thread(), state, ExtraData::ERROR_TRACEBACK_IDX); // Lua 5.2+ support light C functions that does not require extra allocations - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))] ffi::lua_pushcfunction(state, crate::util::error_traceback); } @@ -1279,7 +1285,13 @@ impl RawLua { #[cfg(feature = "async")] pub(crate) fn create_async_callback(&self, func: AsyncCallback) -> Result { // Ensure that the coroutine library is loaded - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] + #[cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luau" + ))] unsafe { if !(*self.extra.get()).libs.contains(StdLib::COROUTINE) { load_std_libs(self.main_state(), StdLib::COROUTINE)?; @@ -1492,7 +1504,13 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> #[cfg(feature = "luajit")] let _gc_guard = GcGuard::new(state); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] + #[cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luau" + ))] { if libs.contains(StdLib::COROUTINE) { requiref(state, ffi::LUA_COLIBNAME, ffi::luaopen_coroutine, 1)?; @@ -1516,7 +1534,7 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> requiref(state, ffi::LUA_STRLIBNAME, ffi::luaopen_string, 1)?; } - #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))] { if libs.contains(StdLib::UTF8) { requiref(state, ffi::LUA_UTF8LIBNAME, ffi::luaopen_utf8, 1)?; diff --git a/src/stdlib.rs b/src/stdlib.rs index 78cf772d..46eb33bc 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -6,10 +6,22 @@ pub struct StdLib(u32); impl StdLib { /// [`coroutine`](https://www.lua.org/manual/5.4/manual.html#6.2) library - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] + #[cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luau" + ))] #[cfg_attr( docsrs, - doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))) + doc(cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luau" + ))) )] pub const COROUTINE: StdLib = StdLib(1); @@ -28,8 +40,11 @@ impl StdLib { pub const STRING: StdLib = StdLib(1 << 4); /// [`utf8`](https://www.lua.org/manual/5.4/manual.html#6.5) library - #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))] + #[cfg_attr( + docsrs, + doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))) + )] pub const UTF8: StdLib = StdLib(1 << 5); /// [`bit`](https://www.lua.org/manual/5.2/manual.html#6.7) library diff --git a/src/thread.rs b/src/thread.rs index 0c673390..ee2f98c9 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -336,22 +336,22 @@ impl Thread { } ThreadStatusInner::Running => Err(Error::runtime("cannot reset a running thread")), ThreadStatusInner::Finished => Ok(()), - #[cfg(not(any(feature = "lua54", feature = "luau")))] + #[cfg(not(any(feature = "lua55", feature = "lua54", feature = "luau")))] ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => { Err(Error::runtime("cannot reset non-finished thread")) } - #[cfg(any(feature = "lua54", feature = "luau"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))] ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => { let thread_state = self.state(); #[cfg(all(feature = "lua54", not(feature = "vendored")))] let status = ffi::lua_resetthread(thread_state); - #[cfg(all(feature = "lua54", feature = "vendored"))] + #[cfg(any(feature = "lua55", all(feature = "lua54", feature = "vendored")))] let status = { let lua = self.0.lua.lock(); ffi::lua_closethread(thread_state, lua.state()) }; - #[cfg(feature = "lua54")] + #[cfg(any(feature = "lua55", feature = "lua54"))] if status != ffi::LUA_OK { return Err(pop_error(thread_state, status)); } diff --git a/src/types.rs b/src/types.rs index d17fb04f..d05d35c2 100644 --- a/src/types.rs +++ b/src/types.rs @@ -108,10 +108,12 @@ pub(crate) type ThreadCollectionCallback = XRc; -#[cfg(all(feature = "send", feature = "lua54"))] +#[cfg(feature = "send")] +#[cfg(any(feature = "lua55", feature = "lua54"))] pub(crate) type WarnCallback = XRc Result<()> + Send>; -#[cfg(all(not(feature = "send"), feature = "lua54"))] +#[cfg(not(feature = "send"))] +#[cfg(any(feature = "lua55", feature = "lua54"))] pub(crate) type WarnCallback = XRc Result<()>>; /// A trait that adds `Send` requirement if `send` feature is enabled. diff --git a/src/userdata.rs b/src/userdata.rs index 86e05f65..493e04b1 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -56,32 +56,53 @@ pub enum MetaMethod { /// The unary minus (`-`) operator. Unm, /// The floor division (//) operator. - #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))] + #[cfg_attr( + docsrs, + doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))) + )] IDiv, /// The bitwise AND (&) operator. - #[cfg(any(feature = "lua54", feature = "lua53"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] + #[cfg_attr( + docsrs, + doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))) + )] BAnd, /// The bitwise OR (|) operator. - #[cfg(any(feature = "lua54", feature = "lua53"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] + #[cfg_attr( + docsrs, + doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))) + )] BOr, /// The bitwise XOR (binary ~) operator. - #[cfg(any(feature = "lua54", feature = "lua53"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] + #[cfg_attr( + docsrs, + doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))) + )] BXor, /// The bitwise NOT (unary ~) operator. - #[cfg(any(feature = "lua54", feature = "lua53"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] + #[cfg_attr( + docsrs, + doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))) + )] BNot, /// The bitwise left shift (<<) operator. - #[cfg(any(feature = "lua54", feature = "lua53"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] + #[cfg_attr( + docsrs, + doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))) + )] Shl, /// The bitwise right shift (>>) operator. - #[cfg(any(feature = "lua54", feature = "lua53"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] + #[cfg_attr( + docsrs, + doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))) + )] Shr, /// The string concatenation operator `..`. Concat, @@ -106,10 +127,22 @@ pub enum MetaMethod { /// The `__pairs` metamethod. /// /// This is not an operator, but it will be called by the built-in `pairs` function. - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] + #[cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luajit52" + ))] #[cfg_attr( docsrs, - doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))) + doc(cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luajit52" + ))) )] Pairs, /// The `__ipairs` metamethod. @@ -135,8 +168,8 @@ pub enum MetaMethod { /// [documentation][lua_doc]. /// /// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#3.3.8 - #[cfg(feature = "lua54")] - #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] + #[cfg(any(feature = "lua55", feature = "lua54"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))] Close, /// The `__name`/`__type` metafield. /// @@ -176,19 +209,19 @@ impl MetaMethod { MetaMethod::Pow => "__pow", MetaMethod::Unm => "__unm", - #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))] MetaMethod::IDiv => "__idiv", - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] MetaMethod::BAnd => "__band", - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] MetaMethod::BOr => "__bor", - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] MetaMethod::BXor => "__bxor", - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] MetaMethod::BNot => "__bnot", - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] MetaMethod::Shl => "__shl", - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] MetaMethod::Shr => "__shr", MetaMethod::Concat => "__concat", @@ -201,14 +234,20 @@ impl MetaMethod { MetaMethod::Call => "__call", MetaMethod::ToString => "__tostring", - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] + #[cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luajit52" + ))] MetaMethod::Pairs => "__pairs", #[cfg(any(feature = "lua52", feature = "luajit52"))] MetaMethod::IPairs => "__ipairs", #[cfg(feature = "luau")] MetaMethod::Iter => "__iter", - #[cfg(feature = "lua54")] + #[cfg(any(feature = "lua55", feature = "lua54"))] MetaMethod::Close => "__close", #[rustfmt::skip] diff --git a/src/util/error.rs b/src/util/error.rs index 03df8f36..64c2481f 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -373,19 +373,19 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<( "__mod", "__pow", "__unm", - #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))] "__idiv", - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] "__band", - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] "__bor", - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] "__bxor", - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] "__bnot", - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] "__shl", - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] "__shr", "__concat", "__len", @@ -396,7 +396,13 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<( "__newindex", "__call", "__tostring", - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] + #[cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luajit52" + ))] "__pairs", #[cfg(any(feature = "lua53", feature = "lua52", feature = "luajit52"))] "__ipairs", @@ -404,7 +410,7 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<( "__iter", #[cfg(feature = "luau")] "__namecall", - #[cfg(feature = "lua54")] + #[cfg(any(feature = "lua55", feature = "lua54"))] "__close", ] { ffi::lua_pushvalue(state, -1); diff --git a/src/util/mod.rs b/src/util/mod.rs index f8ebf693..9f93f55c 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -220,7 +220,7 @@ pub(crate) unsafe extern "C-unwind" fn safe_xpcall(state: *mut ffi::lua_State) - // Returns Lua main thread for Lua >= 5.2 or checks that the passed thread is main for Lua 5.1. // Does not call lua_checkstack, uses 1 stack space. pub(crate) unsafe fn get_main_state(state: *mut ffi::lua_State) -> Option<*mut ffi::lua_State> { - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))] { ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_MAINTHREAD); let main_state = ffi::lua_tothread(state, -1); diff --git a/tests/async.rs b/tests/async.rs index 761b07e0..de6e9fea 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -249,7 +249,7 @@ async fn test_async_return_async_closure() -> Result<()> { Ok(()) } -#[cfg(feature = "lua54")] +#[cfg(any(feature = "lua55", feature = "lua54"))] #[tokio::test] async fn test_async_lua54_to_be_closed() -> Result<()> { let lua = Lua::new(); @@ -670,7 +670,7 @@ async fn test_async_hook() -> Result<()> { static HOOK_CALLED: AtomicBool = AtomicBool::new(false); lua.set_global_hook(mlua::HookTriggers::new().every_line(), move |_, _| { if !HOOK_CALLED.swap(true, Ordering::Relaxed) { - #[cfg(any(feature = "lu53", feature = "lua54"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] return Ok(mlua::VmState::Yield); } Ok(mlua::VmState::Continue) diff --git a/tests/hooks.rs b/tests/hooks.rs index 4f635052..c5d7da32 100644 --- a/tests/hooks.rs +++ b/tests/hooks.rs @@ -274,7 +274,7 @@ fn test_hook_yield() -> Result<()> { co.set_hook(HookTriggers::EVERY_LINE, move |_lua, _debug| Ok(VmState::Yield))?; - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] { assert!(co.resume::<()>(()).is_ok()); assert!(co.resume::<()>(()).is_ok()); diff --git a/tests/memory.rs b/tests/memory.rs index ba1d7e48..f8e6c54b 100644 --- a/tests/memory.rs +++ b/tests/memory.rs @@ -72,13 +72,13 @@ fn test_gc_control() -> Result<()> { let lua = Lua::new(); let globals = lua.globals(); - #[cfg(feature = "lua54")] + #[cfg(any(feature = "lua55", feature = "lua54"))] { assert_eq!(lua.gc_gen(0, 0), GCMode::Incremental); assert_eq!(lua.gc_inc(0, 0, 0), GCMode::Generational); } - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] { assert!(lua.gc_is_running()); lua.gc_stop(); diff --git a/tests/module/loader/tests/load.rs b/tests/module/loader/tests/load.rs index 08473b99..873da7d8 100644 --- a/tests/module/loader/tests/load.rs +++ b/tests/module/loader/tests/load.rs @@ -42,6 +42,7 @@ fn test_module_error() -> Result<()> { } #[cfg(any( + feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", @@ -70,6 +71,7 @@ fn test_module_from_thread() -> Result<()> { } #[cfg(any( + feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", diff --git a/tests/tests.rs b/tests/tests.rs index d9320fbb..0c548d1e 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -581,7 +581,7 @@ fn test_num_conversion() -> Result<()> { assert_eq!(lua.load("1.0").eval::()?, 1); assert_eq!(lua.load("1.0").eval::()?, 1.0); - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] assert_eq!(lua.load("1.0").eval::()?, "1.0"); #[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))] assert_eq!(lua.load("1.0").eval::()?, "1"); @@ -611,7 +611,7 @@ fn test_num_conversion() -> Result<()> { assert!(negative_zero.is_sign_negative()); // In Lua <5.3 all numbers are floats - #[cfg(not(any(feature = "lua54", feature = "lua53", feature = "luajit")))] + #[cfg(not(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luajit")))] { let negative_zero = lua.load("-0").eval::()?; assert_eq!(negative_zero, 0.0); @@ -675,7 +675,7 @@ fn test_pcall_xpcall() -> Result<()> { assert_eq!(globals.get::("pcall_error")?, "testerror"); assert_eq!(globals.get::("xpcall_statusr")?, false); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit"))] assert_eq!(globals.get::("xpcall_error")?, "testerror"); #[cfg(feature = "lua51")] assert!(globals @@ -1167,7 +1167,7 @@ fn test_context_thread() -> Result<()> { ) .into_function()?; - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] f.call::<()>(lua.current_thread())?; #[cfg(any( @@ -1343,7 +1343,7 @@ fn test_inspect_stack() -> Result<()> { })?; lua.globals().set("stack_info", stack_info)?; - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] lua.load( r#" local stack_info = stack_info @@ -1483,7 +1483,7 @@ fn test_multi_states() -> Result<()> { } #[test] -#[cfg(feature = "lua54")] +#[cfg(any(feature = "lua55", feature = "lua54"))] fn test_warnings() -> Result<()> { let lua = Lua::new(); lua.set_app_data::>(Vec::new()); diff --git a/tests/thread.rs b/tests/thread.rs index ab4dcc7c..71eb24c9 100644 --- a/tests/thread.rs +++ b/tests/thread.rs @@ -140,7 +140,7 @@ fn test_thread_reset() -> Result<()> { let _ = thread.resume::(MyUserData(arc.clone())); assert_eq!(thread.status(), ThreadStatus::Error); assert_eq!(Arc::strong_count(&arc), 2); - #[cfg(feature = "lua54")] + #[cfg(any(feature = "lua55", feature = "lua54"))] { assert!(thread.reset(func.clone()).is_err()); // Reset behavior has changed in Lua v5.4.4 @@ -149,7 +149,7 @@ fn test_thread_reset() -> Result<()> { assert!(thread.reset(func.clone()).is_ok()); assert_eq!(thread.status(), ThreadStatus::Resumable); } - #[cfg(any(feature = "lua54", feature = "luau"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))] { assert!(thread.reset(func.clone()).is_ok()); assert_eq!(thread.status(), ThreadStatus::Resumable); @@ -181,6 +181,7 @@ fn test_coroutine_from_closure() -> Result<()> { lua.globals().set("main", thrd_main)?; #[cfg(any( + feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", diff --git a/tests/userdata.rs b/tests/userdata.rs index a84eb526..3e19009e 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use std::string::String as StdString; use std::sync::Arc; -#[cfg(feature = "lua54")] +#[cfg(any(feature = "lua55", feature = "lua54"))] use std::sync::atomic::{AtomicI64, Ordering}; use mlua::{ @@ -138,7 +138,13 @@ fn test_metamethods() -> Result<()> { Err("no such custom index".into_lua_err()) } }); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] + #[cfg(any( + feature = "lua55", + 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): (UserDataRef, i64)| { @@ -165,7 +171,13 @@ fn test_metamethods() -> Result<()> { 10 ); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] + #[cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luajit52" + ))] let pairs_it = lua .load( r#" @@ -190,7 +202,13 @@ fn test_metamethods() -> Result<()> { assert_eq!(lua.load("userdata2.inner").eval::()?, 3); assert!(lua.load("userdata2.nonexist_field").eval::<()>().is_err()); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] + #[cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luajit52" + ))] assert_eq!(pairs_it.call::(())?, 28); let userdata2: Value = globals.get("userdata2")?; @@ -209,7 +227,7 @@ fn test_metamethods() -> Result<()> { Ok(()) } -#[cfg(feature = "lua54")] +#[cfg(any(feature = "lua55", feature = "lua54"))] #[test] fn test_metamethod_close() -> Result<()> { #[derive(Clone)] @@ -651,7 +669,7 @@ fn test_metatable() -> Result<()> { globals.set("ud", MyUserData)?; lua.load(r#"assert(ud:my_type_name() == "MyUserData")"#).exec()?; - #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))] lua.load(r#"assert(tostring(ud):sub(1, 11) == "MyUserData:")"#) .exec()?; #[cfg(feature = "luau")] diff --git a/tests/value.rs b/tests/value.rs index 0ff48448..ebe753dd 100644 --- a/tests/value.rs +++ b/tests/value.rs @@ -239,7 +239,7 @@ fn test_value_conversions() -> Result<()> { assert_eq!(Value::Integer(1).as_u32(), Some(1u32)); assert_eq!(Value::Integer(1).as_i64(), Some(1i64)); assert_eq!(Value::Integer(1).as_u64(), Some(1u64)); - #[cfg(any(feature = "lua54", feature = "lua53"))] + #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))] { assert_eq!(Value::Integer(mlua::Integer::MAX).as_i32(), None); assert_eq!(Value::Integer(mlua::Integer::MAX).as_u32(), None); From e1701b6b56853d27a3066a4d6a8ba4c2dd45f00c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 14 Jan 2026 15:57:52 +0000 Subject: [PATCH 566/635] cargo fmt --- tests/memory.rs | 8 +++++++- tests/tests.rs | 24 +++++++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/tests/memory.rs b/tests/memory.rs index f8e6c54b..930aa95d 100644 --- a/tests/memory.rs +++ b/tests/memory.rs @@ -78,7 +78,13 @@ fn test_gc_control() -> Result<()> { assert_eq!(lua.gc_inc(0, 0, 0), GCMode::Generational); } - #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] + #[cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luau" + ))] { assert!(lua.gc_is_running()); lua.gc_stop(); diff --git a/tests/tests.rs b/tests/tests.rs index 0c548d1e..41a6aa97 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -675,7 +675,13 @@ fn test_pcall_xpcall() -> Result<()> { assert_eq!(globals.get::("pcall_error")?, "testerror"); assert_eq!(globals.get::("xpcall_statusr")?, false); - #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit"))] + #[cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luajit" + ))] assert_eq!(globals.get::("xpcall_error")?, "testerror"); #[cfg(feature = "lua51")] assert!(globals @@ -1167,7 +1173,13 @@ fn test_context_thread() -> Result<()> { ) .into_function()?; - #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] + #[cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luajit52" + ))] f.call::<()>(lua.current_thread())?; #[cfg(any( @@ -1343,7 +1355,13 @@ fn test_inspect_stack() -> Result<()> { })?; lua.globals().set("stack_info", stack_info)?; - #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] + #[cfg(any( + feature = "lua55", + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luau" + ))] lua.load( r#" local stack_info = stack_info From 9b24bb23195720fa356b52c33e47be68993a3815 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 14 Jan 2026 16:01:04 +0000 Subject: [PATCH 567/635] Add Lua 5.5 to CI --- .github/workflows/main.yml | 30 +++++++++++++++--------------- tarpaulin.toml | 12 ++++++------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 818d9686..9cbb0d0b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] rust: [stable] - lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] + lua: [lua55, lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu @@ -43,7 +43,7 @@ jobs: needs: build strategy: matrix: - lua: [lua54, lua53, lua52, lua51, luajit] + lua: [lua55, lua54, lua53, lua52, lua51, luajit] steps: - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@stable @@ -59,7 +59,7 @@ jobs: needs: build strategy: matrix: - lua: [lua54, lua53, lua52, lua51, luajit] + lua: [lua55, lua54, lua53, lua52, lua51, luajit] steps: - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@stable @@ -81,7 +81,7 @@ jobs: needs: build strategy: matrix: - lua: [lua54, lua53, lua52, lua51] + lua: [lua55, lua54, lua53, lua52, lua51] steps: - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@stable @@ -105,7 +105,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] rust: [stable, nightly] - lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit, luau-vector4] + lua: [lua55, lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit, luau-vector4] include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu @@ -126,8 +126,8 @@ jobs: cargo test --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers" cargo test --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers,send" shell: bash - - name: Run compile tests (macos lua54) - if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }} + - name: Run compile tests (macos lua55) + if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua55' }} run: | TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" --tests -- --ignored TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serde,macros" --tests -- --ignored @@ -141,7 +141,7 @@ jobs: matrix: os: [ubuntu-latest] rust: [nightly] - lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] + lua: [lua55, lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu @@ -168,7 +168,7 @@ jobs: matrix: os: [ubuntu-latest] rust: [nightly] - lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] + lua: [lua55, lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu @@ -194,7 +194,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] rust: [stable] - lua: [lua54, lua53, lua52, lua51, luajit] + lua: [lua55, lua54, lua53, lua52, lua51, luajit] include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu @@ -219,7 +219,7 @@ jobs: needs: build strategy: matrix: - lua: [lua54, luajit] + lua: [lua55, lua54, luajit] defaults: run: shell: msys2 {0} @@ -240,7 +240,7 @@ jobs: needs: build strategy: matrix: - lua: [lua54, lua53, lua52, lua51, luau] + lua: [lua55, lua54, lua53, lua52, lua51, luau] steps: - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@stable @@ -262,7 +262,7 @@ jobs: needs: build strategy: matrix: - lua: [lua54, lua53, lua52, lua51] + lua: [lua55, lua54, lua53, lua52, lua51] steps: - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@stable @@ -273,7 +273,7 @@ jobs: working-directory: ${{ runner.tool_cache }} run: | wasi_sdk=29 - wasmtime=v39.0.0 + wasmtime=v40.0.1 curl -LO https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$wasi_sdk/wasi-sdk-$wasi_sdk.0-x86_64-linux.tar.gz tar xf wasi-sdk-$wasi_sdk.0-x86_64-linux.tar.gz @@ -306,7 +306,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] + lua: [lua55, lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] steps: - uses: actions/checkout@main - uses: dtolnay/rust-toolchain@stable diff --git a/tarpaulin.toml b/tarpaulin.toml index 48bd33f3..110456cf 100644 --- a/tarpaulin.toml +++ b/tarpaulin.toml @@ -1,11 +1,11 @@ -[lua54] -features = "lua54,vendored,async,send,serde,macros,anyhow,userdata-wrappers" +[lua55] +features = "lua55,vendored,async,send,serde,macros,anyhow,userdata-wrappers" -[lua54_non_send] -features = "lua54,vendored,async,serde,macros,anyhow,userdata-wrappers" +[lua55_non_send] +features = "lua55,vendored,async,serde,macros,anyhow,userdata-wrappers" -[lua54_with_memory_limit] -features = "lua54,vendored,async,send,serde,macros,anyhow,userdata-wrappers" +[lua55_with_memory_limit] +features = "lua55,vendored,async,send,serde,macros,anyhow,userdata-wrappers" rustflags = "--cfg force_memory_limit" [lua51] From 4c5465229e1bd64183fb603b1c1912eda9b48e79 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 14 Jan 2026 21:44:25 +0000 Subject: [PATCH 568/635] Update CI --- .github/workflows/main.yml | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9cbb0d0b..b67adccb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,28 +31,12 @@ jobs: cargo build --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers,send" shell: bash - name: Build ${{ matrix.lua }} pkg-config - if: ${{ matrix.os == 'ubuntu-latest' }} + if: ${{ matrix.os == 'ubuntu-latest' && matrix.lua != 'lua55' }} run: | sudo apt-get update sudo apt-get install -y --no-install-recommends liblua5.4-dev liblua5.3-dev liblua5.2-dev liblua5.1-0-dev libluajit-5.1-dev cargo build --features "${{ matrix.lua }}" - build_aarch64_cross_macos: - name: Cross-compile to aarch64-apple-darwin - runs-on: macos-latest - needs: build - strategy: - matrix: - lua: [lua55, lua54, lua53, lua52, lua51, luajit] - steps: - - uses: actions/checkout@main - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - target: aarch64-apple-darwin - - name: Cross-compile - run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers" - build_aarch64_cross_ubuntu: name: Cross-compile to aarch64-unknown-linux-gnu runs-on: ubuntu-latest @@ -219,7 +203,7 @@ jobs: needs: build strategy: matrix: - lua: [lua55, lua54, luajit] + lua: [lua54, luajit] defaults: run: shell: msys2 {0} From b1f99aa852e78a67c41a0b86568e7c598ee8f4d5 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 15 Jan 2026 15:21:16 +0000 Subject: [PATCH 569/635] Update tests --- mlua-sys/src/lua55/lua.rs | 4 ++-- tests/module/Cargo.toml | 1 + tests/module/loader/Cargo.toml | 1 + tests/serde.rs | 8 ++++---- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/mlua-sys/src/lua55/lua.rs b/mlua-sys/src/lua55/lua.rs index 8f551523..60bd4ec3 100644 --- a/mlua-sys/src/lua55/lua.rs +++ b/mlua-sys/src/lua55/lua.rs @@ -13,7 +13,7 @@ pub const LUA_MULTRET: c_int = -1; // Size of the Lua stack #[doc(hidden)] -pub const LUAI_MAXSTACK: c_int = libc::INT_MAX; +pub const LUAI_MAXSTACK: c_int = c_int::MAX; // Size of a raw memory area associated with a Lua state with very fast access. pub const LUA_EXTRASPACE: usize = mem::size_of::<*const ()>(); @@ -21,7 +21,7 @@ pub const LUA_EXTRASPACE: usize = mem::size_of::<*const ()>(); // // Pseudo-indices // -pub const LUA_REGISTRYINDEX: c_int = -(libc::INT_MAX / 2 + 1000); +pub const LUA_REGISTRYINDEX: c_int = -(c_int::MAX / 2 + 1000); pub const fn lua_upvalueindex(i: c_int) -> c_int { LUA_REGISTRYINDEX - i diff --git a/tests/module/Cargo.toml b/tests/module/Cargo.toml index c2e0da8d..9cf87683 100644 --- a/tests/module/Cargo.toml +++ b/tests/module/Cargo.toml @@ -13,6 +13,7 @@ members = [ ] [features] +lua55 = ["mlua/lua55"] lua54 = ["mlua/lua54"] lua53 = ["mlua/lua53"] lua52 = ["mlua/lua52"] diff --git a/tests/module/loader/Cargo.toml b/tests/module/loader/Cargo.toml index b51f002c..ddd6123f 100644 --- a/tests/module/loader/Cargo.toml +++ b/tests/module/loader/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Aleksandr Orlenko "] edition = "2021" [features] +lua55 = ["mlua/lua55"] lua54 = ["mlua/lua54"] lua53 = ["mlua/lua53"] lua52 = ["mlua/lua52"] diff --git a/tests/serde.rs b/tests/serde.rs index 2f07d7f4..d3cc00ed 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -36,7 +36,7 @@ fn test_serialize() -> Result<(), Box> { _integer = 123, _number = 321.99, _string = "test string serialization", - _table_arr = {nil, "value 1", nil, "value 2", {}}, + _table_arr = {null, "value 1", 2, "value 3", {}}, _table_map = {["table"] = "map", ["null"] = null}, _bytes = "\240\040\140\040", _userdata = ud, @@ -53,7 +53,7 @@ fn test_serialize() -> Result<(), Box> { "_integer": 123, "_number": 321.99, "_string": "test string serialization", - "_table_arr": [null, "value 1", null, "value 2", {}], + "_table_arr": [null, "value 1", 2, "value 3", {}], "_table_map": {"table": "map", "null": null}, "_bytes": [240, 40, 140, 40], "_userdata": [123, "test userdata"], @@ -184,7 +184,7 @@ fn test_serialize_sorted() -> LuaResult<()> { _integer = 123, _number = 321.99, _string = "test string serialization", - _table_arr = {nil, "value 1", nil, "value 2", {}}, + _table_arr = {null, "value 1", 2, "value 3", {}}, _table_map = {["table"] = "map", ["null"] = null}, _bytes = "\240\040\140\040", _null = null, @@ -198,7 +198,7 @@ fn test_serialize_sorted() -> LuaResult<()> { let json = serde_json::to_string(&value.to_serializable().sort_keys(true)).unwrap(); assert_eq!( json, - r#"{"_bool":true,"_bytes":[240,40,140,40],"_empty_array":[],"_empty_map":{},"_integer":123,"_null":null,"_number":321.99,"_string":"test string serialization","_table_arr":[null,"value 1",null,"value 2",{}],"_table_map":{"null":null,"table":"map"}}"# + r#"{"_bool":true,"_bytes":[240,40,140,40],"_empty_array":[],"_empty_map":{},"_integer":123,"_null":null,"_number":321.99,"_string":"test string serialization","_table_arr":[null,"value 1",2,"value 3",{}],"_table_map":{"null":null,"table":"map"}}"# ); Ok(()) From 1be9e6ce2dae1670c7d783fcf7007dd8df4130eb Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 18 Jan 2026 00:03:43 +0000 Subject: [PATCH 570/635] Add Lua 5.5 external strings support --- mlua-sys/src/lua55/lua.rs | 2 +- src/conversion.rs | 20 ++++++++++++++++++++ src/state.rs | 11 +++++++++++ src/state/raw.rs | 17 +++++++++++++++++ src/util/mod.rs | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 1 deletion(-) diff --git a/mlua-sys/src/lua55/lua.rs b/mlua-sys/src/lua55/lua.rs index 60bd4ec3..dd6067d6 100644 --- a/mlua-sys/src/lua55/lua.rs +++ b/mlua-sys/src/lua55/lua.rs @@ -204,7 +204,7 @@ unsafe extern "C-unwind" { L: *mut lua_State, s: *const c_char, len: usize, - falloc: lua_Alloc, + falloc: Option, ud: *mut c_void, ) -> *const c_char; pub fn lua_pushstring(L: *mut lua_State, s: *const c_char) -> *const c_char; diff --git a/src/conversion.rs b/src/conversion.rs index f1f6ddc0..b02e861f 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -500,11 +500,21 @@ impl FromLua for crate::Buffer { impl IntoLua for StdString { #[inline] fn into_lua(self, lua: &Lua) -> Result { + #[cfg(feature = "lua55")] + if true { + return Ok(Value::String(lua.create_external_string(self)?)); + } + Ok(Value::String(lua.create_string(self)?)) } #[inline] unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + #[cfg(feature = "lua55")] + if lua.unlikely_memory_error() { + return crate::util::push_external_string(lua.state(), self.into(), false); + } + push_bytes_into_stack(self, lua) } } @@ -591,6 +601,11 @@ impl FromLua for Box { impl IntoLua for CString { #[inline] fn into_lua(self, lua: &Lua) -> Result { + #[cfg(feature = "lua55")] + if true { + return Ok(Value::String(lua.create_external_string(self)?)); + } + Ok(Value::String(lua.create_string(self.as_bytes())?)) } } @@ -635,6 +650,11 @@ impl IntoLua for Cow<'_, CStr> { impl IntoLua for BString { #[inline] fn into_lua(self, lua: &Lua) -> Result { + #[cfg(feature = "lua55")] + if true { + return Ok(Value::String(lua.create_external_string(self)?)); + } + Ok(Value::String(lua.create_string(self)?)) } } diff --git a/src/state.rs b/src/state.rs index 5698c301..0808ad2a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1219,6 +1219,17 @@ impl Lua { unsafe { self.lock().create_string(s.as_ref()) } } + /// Creates and returns an external Lua string. + /// + /// External string is a string where the memory is managed by Rust code, and Lua only holds a + /// reference to it. This can be used to avoid copying large strings into Lua memory. + #[cfg(feature = "lua55")] + #[cfg_attr(docsrs, doc(cfg(feature = "lua55")))] + #[inline] + pub fn create_external_string(&self, s: impl Into>) -> Result { + unsafe { self.lock().create_external_string(s.into()) } + } + /// Creates and returns a Luau [buffer] object from a byte slice of data. /// /// [buffer]: https://luau.org/library#buffer-library diff --git a/src/state/raw.rs b/src/state/raw.rs index 416adeaf..a24fec35 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -529,6 +529,23 @@ impl RawLua { Ok(String(self.pop_ref())) } + /// Creates an external string, that is, a string that uses memory not managed by Lua. + /// + /// Modifies the input data to add `\0` terminator. + #[cfg(feature = "lua55")] + pub(crate) unsafe fn create_external_string(&self, bytes: Vec) -> Result { + let state = self.state(); + if self.unlikely_memory_error() { + crate::util::push_external_string(state, bytes, false)?; + return Ok(String(self.pop_ref())); + } + + let _sg = StackGuard::new(state); + check_stack(state, 3)?; + crate::util::push_external_string(state, bytes, true)?; + Ok(String(self.pop_ref())) + } + #[cfg(feature = "luau")] pub(crate) unsafe fn create_buffer_with_capacity(&self, size: usize) -> Result<(*mut u8, crate::Buffer)> { let state = self.state(); diff --git a/src/util/mod.rs b/src/util/mod.rs index 9f93f55c..89057084 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -99,6 +99,38 @@ pub(crate) unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: } } +// Uses 3 (or 1 if unprotected) stack spaces, does not call checkstack. +#[cfg(feature = "lua55")] +pub(crate) unsafe fn push_external_string( + state: *mut ffi::lua_State, + mut bytes: Vec, + protect: bool, +) -> Result<()> { + bytes.push(0); + let s_len = bytes.len() - 1; // exclude null terminator + let s_ptr = bytes.as_ptr() as *const c_char; + let bytes_ud = Box::into_raw(Box::new(bytes)); + + unsafe extern "C" fn dealloc(ud: *mut c_void, _: *mut c_void, _: usize, _: usize) -> *mut c_void { + drop(Box::from_raw(ud as *mut Vec)); + ptr::null_mut() + } + + if protect { + let res = protect_lua!(state, 0, 1, move |state| { + ffi::lua_pushexternalstring(state, s_ptr, s_len, Some(dealloc), bytes_ud as *mut _); + }); + if res.is_err() { + // Deallocate on error + drop(Box::from_raw(bytes_ud)); + return res; + } + } else { + ffi::lua_pushexternalstring(state, s_ptr, s_len, Some(dealloc), bytes_ud as *mut _); + } + Ok(()) +} + // Uses 3 stack spaces (when protect), does not call checkstack. #[cfg(feature = "luau")] #[inline(always)] From 8e6d652a21255d24ea32d5d751e164ee2bc16715 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 20 Jan 2026 13:05:00 +0000 Subject: [PATCH 571/635] Bump luau-src to 0.18.0 (Luau 0.705) --- mlua-sys/Cargo.toml | 2 +- mlua-sys/src/luau/lauxlib.rs | 3 +++ mlua-sys/src/luau/luarequire.rs | 12 ++++++++++++ src/luau/require.rs | 1 + 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 99fce400..4240a40a 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -43,7 +43,7 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 550.0.0, < 550.1.0", optional = true } luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true } -luau0-src = { version = "0.17.0", optional = true } +luau0-src = { version = "0.18.0", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/mlua-sys/src/luau/lauxlib.rs b/mlua-sys/src/luau/lauxlib.rs index 6e3587dd..0c3b0377 100644 --- a/mlua-sys/src/luau/lauxlib.rs +++ b/mlua-sys/src/luau/lauxlib.rs @@ -85,6 +85,9 @@ unsafe extern "C-unwind" { pub fn luaL_callyieldable(L: *mut lua_State, nargs: c_int, nresults: c_int) -> c_int; + #[link_name = "luaL_traceback"] + pub fn luaL_traceback_(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, level: c_int); + // sandbox libraries and globals #[link_name = "luaL_sandbox"] pub fn luaL_sandbox_(L: *mut lua_State); diff --git a/mlua-sys/src/luau/luarequire.rs b/mlua-sys/src/luau/luarequire.rs index 809acc2e..0574efd4 100644 --- a/mlua-sys/src/luau/luarequire.rs +++ b/mlua-sys/src/luau/luarequire.rs @@ -58,6 +58,18 @@ pub struct luarequire_Configuration { path: *const c_char, ) -> luarequire_NavigateResult, + // Provides an initial alias override opportunity prior to searching for configuration files. + // If NAVIGATE_SUCCESS is returned, the internal state must be updated to point at the + // aliased location. + // Can be left undefined. + pub to_alias_override: Option< + unsafe extern "C-unwind" fn( + L: *mut lua_State, + ctx: *mut c_void, + alias_unprefixed: *const c_char, + ) -> luarequire_NavigateResult, + >, + // Provides a final override opportunity if an alias cannot be found in configuration files. If // NAVIGATE_SUCCESS is returned, this must update the internal state to point at the aliased module. // Can be left undefined. diff --git a/src/luau/require.rs b/src/luau/require.rs index 13e968b2..27dd8b4b 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -299,6 +299,7 @@ pub(super) unsafe extern "C-unwind" fn init_config(config: *mut ffi::luarequire_ (*config).is_require_allowed = is_require_allowed; (*config).reset = reset; (*config).jump_to_alias = jump_to_alias; + (*config).to_alias_override = None; (*config).to_alias_fallback = None; (*config).to_parent = to_parent; (*config).to_child = to_child; From a162b0cecaf1a4b53ed8997c403a01df9be50347 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 20 Jan 2026 14:01:04 +0000 Subject: [PATCH 572/635] Update Lua 5.1 FFI bindings (add buffer manipulation, etc) --- mlua-sys/src/lua51/lauxlib.rs | 56 ++++++++++++++++++++++++++++++++--- mlua-sys/src/lua51/lua.rs | 5 +++- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/mlua-sys/src/lua51/lauxlib.rs b/mlua-sys/src/lua51/lauxlib.rs index b80b45fe..2b990835 100644 --- a/mlua-sys/src/lua51/lauxlib.rs +++ b/mlua-sys/src/lua51/lauxlib.rs @@ -107,8 +107,6 @@ pub unsafe fn luaL_optstring(L: *mut lua_State, n: c_int, d: *const c_char) -> * luaL_optlstring(L, n, d, ptr::null_mut()) } -// Deprecated from 5.3: luaL_checkint, luaL_optint, luaL_checklong, luaL_optlong - #[inline(always)] pub unsafe fn luaL_typename(L: *mut lua_State, i: c_int) -> *const c_char { lua::lua_typename(L, lua::lua_type(L, i)) @@ -138,8 +136,58 @@ pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) { lua::lua_getfield_(L, lua::LUA_REGISTRYINDEX, n); } -// TODO: luaL_opt +#[inline(always)] +pub unsafe fn luaL_opt( + L: *mut lua_State, + f: unsafe extern "C-unwind" fn(*mut lua_State, c_int) -> T, + n: c_int, + d: T, +) -> T { + if lua::lua_isnoneornil(L, n) != 0 { + d + } else { + f(L, n) + } +} // -// TODO: Generic Buffer Manipulation +// Generic Buffer Manipulation // + +// The buffer size used by the lauxlib buffer system. +// The "16384" workaround is taken from the LuaJIT source code. +#[rustfmt::skip] +pub const LUAL_BUFFERSIZE: usize = if libc::BUFSIZ > 16384 { 8192 } else { libc::BUFSIZ as usize }; + +#[repr(C)] +pub struct luaL_Buffer { + pub p: *mut c_char, // current position in buffer + pub lvl: c_int, // number of strings in the stack + pub L: *mut lua_State, + pub buffer: [c_char; LUAL_BUFFERSIZE], +} + +#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))] +unsafe extern "C-unwind" { + pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Buffer); + pub fn luaL_prepbuffer(B: *mut luaL_Buffer) -> *mut c_char; + pub fn luaL_addlstring(B: *mut luaL_Buffer, s: *const c_char, l: usize); + pub fn luaL_addstring(B: *mut luaL_Buffer, s: *const c_char); + pub fn luaL_addvalue(B: *mut luaL_Buffer); + pub fn luaL_pushresult(B: *mut luaL_Buffer); +} + +#[inline(always)] +pub unsafe fn luaL_addchar(B: *mut luaL_Buffer, c: c_char) { + let buffer_end = (*B).buffer.as_mut_ptr().add(LUAL_BUFFERSIZE); + if (*B).p >= buffer_end { + luaL_prepbuffer(B); + } + *(*B).p = c; + (*B).p = (*B).p.add(1); +} + +#[inline(always)] +pub unsafe fn luaL_addsize(B: *mut luaL_Buffer, n: usize) { + (*B).p = (*B).p.add(n); +} diff --git a/mlua-sys/src/lua51/lua.rs b/mlua-sys/src/lua51/lua.rs index e47acd3b..fbce6b96 100644 --- a/mlua-sys/src/lua51/lua.rs +++ b/mlua-sys/src/lua51/lua.rs @@ -270,7 +270,10 @@ pub unsafe fn lua_pushcfunction(L: *mut lua_State, f: lua_CFunction) { lua_pushcclosure(L, f, 0) } -// TODO: lua_strlen +#[inline(always)] +pub unsafe fn lua_strlen(L: *mut lua_State, i: c_int) -> usize { + lua_objlen(L, i) +} #[inline(always)] pub unsafe fn lua_isfunction(L: *mut lua_State, n: c_int) -> c_int { From 0b5ef91f442442efb3c53c1da47762710a7b3343 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 20 Jan 2026 14:29:35 +0000 Subject: [PATCH 573/635] Update Lua 5.2 FFI bindings (add buffer manipulation, etc) --- mlua-sys/src/lua52/lauxlib.rs | 63 ++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/mlua-sys/src/lua52/lauxlib.rs b/mlua-sys/src/lua52/lauxlib.rs index b4611d94..2704f1c5 100644 --- a/mlua-sys/src/lua52/lauxlib.rs +++ b/mlua-sys/src/lua52/lauxlib.rs @@ -173,6 +173,67 @@ pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: luaL_loadbufferx(L, s, sz, n, ptr::null()) } +#[inline(always)] +pub unsafe fn luaL_opt( + L: *mut lua_State, + f: unsafe extern "C-unwind" fn(*mut lua_State, c_int) -> T, + n: c_int, + d: T, +) -> T { + if lua::lua_isnoneornil(L, n) != 0 { + d + } else { + f(L, n) + } +} + // -// TODO: Generic Buffer Manipulation +// Generic Buffer Manipulation // + +// The buffer size used by the lauxlib buffer system. +// The "16384" workaround is taken from the LuaJIT source code. +#[rustfmt::skip] +pub const LUAL_BUFFERSIZE: usize = if libc::BUFSIZ > 16384 { 8192 } else { libc::BUFSIZ as usize }; + +#[repr(C)] +pub struct luaL_Buffer { + pub b: *mut c_char, // buffer address + pub size: usize, // buffer size + pub n: usize, // number of characters in buffer + pub L: *mut lua_State, + pub initb: [c_char; LUAL_BUFFERSIZE], // initial buffer space +} + +#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))] +unsafe extern "C-unwind" { + pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Buffer); + pub fn luaL_prepbuffsize(B: *mut luaL_Buffer, sz: usize) -> *mut c_char; + pub fn luaL_addlstring(B: *mut luaL_Buffer, s: *const c_char, l: usize); + pub fn luaL_addstring(B: *mut luaL_Buffer, s: *const c_char); + pub fn luaL_addvalue(B: *mut luaL_Buffer); + pub fn luaL_pushresult(B: *mut luaL_Buffer); + pub fn luaL_pushresultsize(B: *mut luaL_Buffer, sz: usize); + pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Buffer, sz: usize) -> *mut c_char; +} + +// Macro implementations as inline functions + +#[inline(always)] +pub unsafe fn luaL_prepbuffer(B: *mut luaL_Buffer) -> *mut c_char { + luaL_prepbuffsize(B, LUAL_BUFFERSIZE) +} + +#[inline(always)] +pub unsafe fn luaL_addchar(B: *mut luaL_Buffer, c: c_char) { + if (*B).n >= (*B).size { + luaL_prepbuffsize(B, 1); + } + *(*B).b.add((*B).n) = c; + (*B).n += 1; +} + +#[inline(always)] +pub unsafe fn luaL_addsize(B: *mut luaL_Buffer, n: usize) { + (*B).n += n; +} From 3366f47d404dbb4e886504c9dc593ff296ae371f Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 20 Jan 2026 14:42:22 +0000 Subject: [PATCH 574/635] Minor Lua 5.2 fixes --- mlua-sys/src/lua52/compat.rs | 4 ++-- mlua-sys/src/lua52/lauxlib.rs | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/mlua-sys/src/lua52/compat.rs b/mlua-sys/src/lua52/compat.rs index 2d9d7aeb..4cddd436 100644 --- a/mlua-sys/src/lua52/compat.rs +++ b/mlua-sys/src/lua52/compat.rs @@ -125,7 +125,7 @@ pub unsafe fn lua_rawget(L: *mut lua_State, idx: c_int) -> c_int { #[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 to lua_Integer"); + let n = n.try_into().expect("cannot convert index to c_int"); lua_rawgeti_(L, idx, n); lua_type(L, -1) } @@ -153,7 +153,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"); + let n = n.try_into().expect("cannot convert index to c_int"); lua_rawseti_(L, idx, n) } diff --git a/mlua-sys/src/lua52/lauxlib.rs b/mlua-sys/src/lua52/lauxlib.rs index 2704f1c5..6835c0f7 100644 --- a/mlua-sys/src/lua52/lauxlib.rs +++ b/mlua-sys/src/lua52/lauxlib.rs @@ -166,8 +166,6 @@ pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) { lua::lua_getfield_(L, lua::LUA_REGISTRYINDEX, n); } -// luaL_opt would be implemented here but it is undocumented, so it's omitted - #[inline(always)] pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int { luaL_loadbufferx(L, s, sz, n, ptr::null()) From ad167612dcd1cd4d55959c5908f9e7056e40be90 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 20 Jan 2026 15:36:36 +0000 Subject: [PATCH 575/635] Update Lua 5.3 FFI bindings (add buffer manipulation, etc) --- mlua-sys/src/lua53/lauxlib.rs | 67 ++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/mlua-sys/src/lua53/lauxlib.rs b/mlua-sys/src/lua53/lauxlib.rs index 959c6367..f832dd2f 100644 --- a/mlua-sys/src/lua53/lauxlib.rs +++ b/mlua-sys/src/lua53/lauxlib.rs @@ -1,7 +1,7 @@ //! Contains definitions from `lauxlib.h`. use std::os::raw::{c_char, c_int, c_void}; -use std::ptr; +use std::{mem, ptr}; use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State}; @@ -166,13 +166,72 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> luaL_tolstring_(L, lua::lua_absindex(L, idx), len) } -// luaL_opt would be implemented here but it is undocumented, so it's omitted - #[inline(always)] pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int { luaL_loadbufferx(L, s, sz, n, ptr::null()) } +#[inline(always)] +pub unsafe fn luaL_opt( + L: *mut lua_State, + f: unsafe extern "C-unwind" fn(*mut lua_State, c_int) -> T, + n: c_int, + d: T, +) -> T { + if lua::lua_isnoneornil(L, n) != 0 { + d + } else { + f(L, n) + } +} + // -// TODO: Generic Buffer Manipulation +// Generic Buffer Manipulation // + +// The buffer size used by the lauxlib buffer system. +// In Lua 5.3: LUAL_BUFFERSIZE = (int)(0x80 * sizeof(void*) * sizeof(lua_Integer)) +#[rustfmt::skip] +pub const LUAL_BUFFERSIZE: usize = 0x80 * mem::size_of::<*const ()>() * mem::size_of::(); + +#[repr(C)] +pub struct luaL_Buffer { + pub b: *mut c_char, // buffer address + pub size: usize, // buffer size + pub n: usize, // number of characters in buffer + pub L: *mut lua_State, + pub initb: [c_char; LUAL_BUFFERSIZE], // initial buffer space +} + +#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] +unsafe extern "C-unwind" { + pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Buffer); + pub fn luaL_prepbuffsize(B: *mut luaL_Buffer, sz: usize) -> *mut c_char; + pub fn luaL_addlstring(B: *mut luaL_Buffer, s: *const c_char, l: usize); + pub fn luaL_addstring(B: *mut luaL_Buffer, s: *const c_char); + pub fn luaL_addvalue(B: *mut luaL_Buffer); + pub fn luaL_pushresult(B: *mut luaL_Buffer); + pub fn luaL_pushresultsize(B: *mut luaL_Buffer, sz: usize); + pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Buffer, sz: usize) -> *mut c_char; +} + +// Macro implementations as inline functions + +#[inline(always)] +pub unsafe fn luaL_prepbuffer(B: *mut luaL_Buffer) -> *mut c_char { + luaL_prepbuffsize(B, LUAL_BUFFERSIZE) +} + +#[inline(always)] +pub unsafe fn luaL_addchar(B: *mut luaL_Buffer, c: c_char) { + if (*B).n >= (*B).size { + luaL_prepbuffsize(B, 1); + } + *(*B).b.add((*B).n) = c; + (*B).n += 1; +} + +#[inline(always)] +pub unsafe fn luaL_addsize(B: *mut luaL_Buffer, n: usize) { + (*B).n += n; +} From 8f086bf83767b71ea1149619f90857e048feea52 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 22 Jan 2026 12:34:58 +0000 Subject: [PATCH 576/635] Update Lua 5.4 FFI bindings (add buffer manipulation, etc) --- mlua-sys/src/lua54/lauxlib.rs | 100 ++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 6 deletions(-) diff --git a/mlua-sys/src/lua54/lauxlib.rs b/mlua-sys/src/lua54/lauxlib.rs index 9c3de5b5..193fd5a9 100644 --- a/mlua-sys/src/lua54/lauxlib.rs +++ b/mlua-sys/src/lua54/lauxlib.rs @@ -1,7 +1,7 @@ //! Contains definitions from `lauxlib.h`. -use std::os::raw::{c_char, c_int, c_void}; -use std::ptr; +use std::os::raw::{c_char, c_double, c_int, c_long, c_void}; +use std::{mem, ptr}; use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State}; @@ -91,7 +91,7 @@ unsafe extern "C-unwind" { pub fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer; - // TODO: luaL_addgsub + pub fn luaL_addgsub(B: *mut luaL_Buffer, s: *const c_char, p: *const c_char, r: *const c_char); pub fn luaL_gsub( L: *mut lua_State, @@ -162,8 +162,6 @@ pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) { lua::lua_getfield(L, lua::LUA_REGISTRYINDEX, n); } -// luaL_opt would be implemented here but it is undocumented, so it's omitted - #[inline(always)] pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int { luaL_loadbufferx(L, s, sz, n, ptr::null()) @@ -188,6 +186,96 @@ pub unsafe fn luaL_loadbufferenv( status } +#[inline(always)] +pub unsafe fn luaL_opt( + L: *mut lua_State, + f: unsafe extern "C-unwind" fn(*mut lua_State, c_int) -> T, + n: c_int, + d: T, +) -> T { + if lua::lua_isnoneornil(L, n) != 0 { + d + } else { + f(L, n) + } +} + // -// TODO: Generic Buffer Manipulation +// Generic Buffer Manipulation // + +// The buffer size used by the lauxlib buffer system. +// LUAL_BUFFERSIZE = (int)(16 * sizeof(void*) * sizeof(lua_Number)) +#[rustfmt::skip] +pub const LUAL_BUFFERSIZE: usize = 16 * mem::size_of::<*const ()>() * mem::size_of::(); + +// Union used for the initial buffer with maximum alignment. +// This ensures proper alignment for the buffer data. +#[repr(C)] +pub union luaL_BufferInit { + // Alignment matches LUAI_MAXALIGN + pub _align_n: lua_Number, + pub _align_u: c_double, + pub _align_s: *mut c_void, + pub _align_i: lua_Integer, + pub _align_l: c_long, + // Initial buffer space + pub b: [c_char; LUAL_BUFFERSIZE], +} + +#[repr(C)] +pub struct luaL_Buffer { + pub b: *mut c_char, // buffer address + pub size: usize, // buffer size + pub n: usize, // number of characters in buffer + pub L: *mut lua_State, + pub init: luaL_BufferInit, // initial buffer (union with alignment) +} + +#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] +unsafe extern "C-unwind" { + pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Buffer); + pub fn luaL_prepbuffsize(B: *mut luaL_Buffer, sz: usize) -> *mut c_char; + pub fn luaL_addlstring(B: *mut luaL_Buffer, s: *const c_char, l: usize); + pub fn luaL_addstring(B: *mut luaL_Buffer, s: *const c_char); + pub fn luaL_addvalue(B: *mut luaL_Buffer); + pub fn luaL_pushresult(B: *mut luaL_Buffer); + pub fn luaL_pushresultsize(B: *mut luaL_Buffer, sz: usize); + pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Buffer, sz: usize) -> *mut c_char; +} + +// Macro implementations as inline functions + +#[inline(always)] +pub unsafe fn luaL_prepbuffer(B: *mut luaL_Buffer) -> *mut c_char { + luaL_prepbuffsize(B, LUAL_BUFFERSIZE) +} + +#[inline(always)] +pub unsafe fn luaL_addchar(B: *mut luaL_Buffer, c: c_char) { + if (*B).n >= (*B).size { + luaL_prepbuffsize(B, 1); + } + *(*B).b.add((*B).n) = c; + (*B).n += 1; +} + +#[inline(always)] +pub unsafe fn luaL_addsize(B: *mut luaL_Buffer, n: usize) { + (*B).n += n; +} + +#[inline(always)] +pub unsafe fn luaL_buffsub(B: *mut luaL_Buffer, n: usize) { + (*B).n -= n; +} + +#[inline(always)] +pub unsafe fn luaL_bufflen(B: *mut luaL_Buffer) -> usize { + (*B).n +} + +#[inline(always)] +pub unsafe fn luaL_buffaddr(B: *mut luaL_Buffer) -> *mut c_char { + (*B).b +} From 386c6d8ed86d248777f63ec58cc9ac391411f9f4 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 22 Jan 2026 12:54:55 +0000 Subject: [PATCH 577/635] Update Lua 5.5 FFI bindings (add buffer manipulation, etc) --- mlua-sys/src/lua55/lauxlib.rs | 100 ++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 6 deletions(-) diff --git a/mlua-sys/src/lua55/lauxlib.rs b/mlua-sys/src/lua55/lauxlib.rs index daf346ab..06df4808 100644 --- a/mlua-sys/src/lua55/lauxlib.rs +++ b/mlua-sys/src/lua55/lauxlib.rs @@ -1,7 +1,7 @@ //! Contains definitions from `lauxlib.h`. -use std::os::raw::{c_char, c_int, c_uint, c_void}; -use std::ptr; +use std::os::raw::{c_char, c_double, c_int, c_long, c_uint, c_void}; +use std::{mem, ptr}; use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State}; @@ -95,7 +95,7 @@ unsafe extern "C-unwind" { pub fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer; - // TODO: luaL_addgsub + pub fn luaL_addgsub(B: *mut luaL_Buffer, s: *const c_char, p: *const c_char, r: *const c_char); pub fn luaL_gsub( L: *mut lua_State, @@ -166,8 +166,6 @@ pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) { lua::lua_getfield(L, lua::LUA_REGISTRYINDEX, n); } -// luaL_opt would be implemented here but it is undocumented, so it's omitted - #[inline(always)] pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int { luaL_loadbufferx(L, s, sz, n, ptr::null()) @@ -206,6 +204,96 @@ pub unsafe fn luaL_makeseed(L: *mut lua_State) -> c_uint { luaL_makeseed_(L) } +#[inline(always)] +pub unsafe fn luaL_opt( + L: *mut lua_State, + f: unsafe extern "C-unwind" fn(*mut lua_State, c_int) -> T, + n: c_int, + d: T, +) -> T { + if lua::lua_isnoneornil(L, n) != 0 { + d + } else { + f(L, n) + } +} + // -// TODO: Generic Buffer Manipulation +// Generic Buffer Manipulation // + +// The buffer size used by the lauxlib buffer system. +// LUAL_BUFFERSIZE = (int)(16 * sizeof(void*) * sizeof(lua_Number)) +#[rustfmt::skip] +pub const LUAL_BUFFERSIZE: usize = 16 * mem::size_of::<*const ()>() * mem::size_of::(); + +// Union used for the initial buffer with maximum alignment. +// This ensures proper alignment for the buffer data. +#[repr(C)] +pub union luaL_BufferInit { + // Alignment matches LUAI_MAXALIGN + pub _align_n: lua_Number, + pub _align_u: c_double, + pub _align_s: *mut c_void, + pub _align_i: lua_Integer, + pub _align_l: c_long, + // Initial buffer space + pub b: [c_char; LUAL_BUFFERSIZE], +} + +#[repr(C)] +pub struct luaL_Buffer { + pub b: *mut c_char, // buffer address + pub size: usize, // buffer size + pub n: usize, // number of characters in buffer + pub L: *mut lua_State, + pub init: luaL_BufferInit, // initial buffer (union with alignment) +} + +#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))] +unsafe extern "C-unwind" { + pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Buffer); + pub fn luaL_prepbuffsize(B: *mut luaL_Buffer, sz: usize) -> *mut c_char; + pub fn luaL_addlstring(B: *mut luaL_Buffer, s: *const c_char, l: usize); + pub fn luaL_addstring(B: *mut luaL_Buffer, s: *const c_char); + pub fn luaL_addvalue(B: *mut luaL_Buffer); + pub fn luaL_pushresult(B: *mut luaL_Buffer); + pub fn luaL_pushresultsize(B: *mut luaL_Buffer, sz: usize); + pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Buffer, sz: usize) -> *mut c_char; +} + +// Macro implementations as inline functions + +#[inline(always)] +pub unsafe fn luaL_prepbuffer(B: *mut luaL_Buffer) -> *mut c_char { + luaL_prepbuffsize(B, LUAL_BUFFERSIZE) +} + +#[inline(always)] +pub unsafe fn luaL_addchar(B: *mut luaL_Buffer, c: c_char) { + if (*B).n >= (*B).size { + luaL_prepbuffsize(B, 1); + } + *(*B).b.add((*B).n) = c; + (*B).n += 1; +} + +#[inline(always)] +pub unsafe fn luaL_addsize(B: *mut luaL_Buffer, n: usize) { + (*B).n += n; +} + +#[inline(always)] +pub unsafe fn luaL_buffsub(B: *mut luaL_Buffer, n: usize) { + (*B).n -= n; +} + +#[inline(always)] +pub unsafe fn luaL_bufflen(B: *mut luaL_Buffer) -> usize { + (*B).n +} + +#[inline(always)] +pub unsafe fn luaL_buffaddr(B: *mut luaL_Buffer) -> *mut c_char { + (*B).b +} From e9efb7312528e9b2605ba127e8cfb674998d0eb3 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 22 Jan 2026 14:05:57 +0000 Subject: [PATCH 578/635] Update Luau FFI bindings (added some missing functions) --- mlua-sys/src/luau/lauxlib.rs | 14 +++++++++++++- mlua-sys/src/luau/lua.rs | 29 ++++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/mlua-sys/src/luau/lauxlib.rs b/mlua-sys/src/luau/lauxlib.rs index 0c3b0377..db92513f 100644 --- a/mlua-sys/src/luau/lauxlib.rs +++ b/mlua-sys/src/luau/lauxlib.rs @@ -122,7 +122,19 @@ pub unsafe fn luaL_optstring(L: *mut lua_State, n: c_int, d: *const c_char) -> * luaL_optlstring(L, n, d, ptr::null_mut()) } -// TODO: luaL_opt +#[inline(always)] +pub unsafe fn luaL_opt( + L: *mut lua_State, + f: unsafe extern "C-unwind" fn(*mut lua_State, c_int) -> T, + n: c_int, + d: T, +) -> T { + if lua::lua_isnoneornil(L, n) != 0 { + d + } else { + f(L, n) + } +} #[inline(always)] pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) -> c_int { diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index bd21360d..5fc67219 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -37,6 +37,16 @@ pub const LUA_ERRRUN: c_int = 2; pub const LUA_ERRSYNTAX: c_int = 3; pub const LUA_ERRMEM: c_int = 4; pub const LUA_ERRERR: c_int = 5; +pub const LUA_BREAK: c_int = 6; // yielded for a debug breakpoint + +// +// Coroutine status +// +pub const LUA_CORUN: c_int = 0; // running +pub const LUA_COSUS: c_int = 1; // suspended +pub const LUA_CONOR: c_int = 2; // 'normal' (it resumed another coroutine) +pub const LUA_COFIN: c_int = 3; // finished +pub const LUA_COERR: c_int = 4; // finished with error /// A raw Lua state associated with a thread. #[repr(C)] @@ -145,8 +155,10 @@ unsafe extern "C-unwind" { pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int; pub fn lua_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char; pub fn lua_tostringatom(L: *mut lua_State, idx: c_int, atom: *mut c_int) -> *const c_char; + pub fn lua_tolstringatom(L: *mut lua_State, idx: c_int, len: *mut usize, atom: *mut c_int) -> *const c_char; pub fn lua_namecallatom(L: *mut lua_State, atom: *mut c_int) -> *const c_char; - pub fn lua_objlen(L: *mut lua_State, idx: c_int) -> usize; + #[link_name = "lua_objlen"] + pub fn lua_objlen_(L: *mut lua_State, idx: c_int) -> c_int; pub fn lua_tocfunction(L: *mut lua_State, idx: c_int) -> Option; pub fn lua_tolightuserdata(L: *mut lua_State, idx: c_int) -> *mut c_void; pub fn lua_tolightuserdatatagged(L: *mut lua_State, idx: c_int, tag: c_int) -> *mut c_void; @@ -218,6 +230,7 @@ unsafe extern "C-unwind" { // pub fn lua_settable(L: *mut lua_State, idx: c_int); pub fn lua_setfield(L: *mut lua_State, idx: c_int, k: *const c_char); + pub fn lua_rawsetfield(L: *mut lua_State, idx: c_int, k: *const c_char); pub fn lua_rawset(L: *mut lua_State, idx: c_int); #[link_name = "lua_rawseti"] pub fn lua_rawseti_(L: *mut lua_State, idx: c_int, n: c_int); @@ -251,6 +264,12 @@ unsafe extern "C-unwind" { pub fn lua_isyieldable(L: *mut lua_State) -> c_int; pub fn lua_getthreaddata(L: *mut lua_State) -> *mut c_void; pub fn lua_setthreaddata(L: *mut lua_State, data: *mut c_void); + pub fn lua_costatus(L: *mut lua_State, co: *mut lua_State) -> c_int; +} + +#[inline(always)] +pub unsafe fn lua_objlen(L: *mut lua_State, idx: c_int) -> usize { + lua_objlen_(L, idx) as usize } // @@ -287,7 +306,7 @@ unsafe extern "C-unwind" { pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int; pub fn lua_rawiter(L: *mut lua_State, idx: c_int, iter: c_int) -> c_int; pub fn lua_concat(L: *mut lua_State, n: c_int); - // TODO: lua_encodepointer + pub fn lua_encodepointer(L: *mut lua_State, p: usize) -> usize; pub fn lua_clock() -> c_double; pub fn lua_setuserdatatag(L: *mut lua_State, idx: c_int, tag: c_int); pub fn lua_setuserdatadtor(L: *mut lua_State, tag: c_int, dtor: Option); @@ -298,6 +317,7 @@ unsafe extern "C-unwind" { pub fn lua_getlightuserdataname(L: *mut lua_State, tag: c_int) -> *const c_char; pub fn lua_clonefunction(L: *mut lua_State, idx: c_int); pub fn lua_cleartable(L: *mut lua_State, idx: c_int); + pub fn lua_clonetable(L: *mut lua_State, idx: c_int); pub fn lua_getallocf(L: *mut lua_State, ud: *mut *mut c_void) -> lua_Alloc; } @@ -357,7 +377,10 @@ pub unsafe fn lua_newuserdata_t(L: *mut lua_State, data: T) -> *mut T { ud_ptr } -// TODO: lua_strlen +#[inline(always)] +pub unsafe fn lua_strlen(L: *mut lua_State, i: c_int) -> usize { + lua_objlen(L, i) +} #[inline(always)] pub unsafe fn lua_isfunction(L: *mut lua_State, n: c_int) -> c_int { From 3c40cfe199b43642631c41d3b744c49018cf97e7 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 22 Jan 2026 14:09:14 +0000 Subject: [PATCH 579/635] mlua-sys: Use 2024 edition --- mlua-sys/Cargo.toml | 4 ++-- mlua-sys/src/lua51/compat.rs | 6 +----- mlua-sys/src/lua52/lauxlib.rs | 2 +- mlua-sys/src/lua53/lauxlib.rs | 2 +- mlua-sys/src/lua54/lauxlib.rs | 2 +- mlua-sys/src/lua55/lauxlib.rs | 2 +- mlua-sys/src/luau/lauxlib.rs | 2 +- mlua-sys/src/luau/lua.rs | 7 ++++++- 8 files changed, 14 insertions(+), 13 deletions(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 4240a40a..dc82cdf2 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -2,8 +2,8 @@ name = "mlua-sys" version = "0.9.0" authors = ["Aleksandr Orlenko "] -rust-version = "1.71" -edition = "2021" +rust-version = "1.85" +edition = "2024" repository = "https://github.com/mlua-rs/mlua" documentation = "https://docs.rs/mlua-sys" readme = "README.md" diff --git a/mlua-sys/src/lua51/compat.rs b/mlua-sys/src/lua51/compat.rs index 19591684..5dc02546 100644 --- a/mlua-sys/src/lua51/compat.rs +++ b/mlua-sys/src/lua51/compat.rs @@ -55,11 +55,7 @@ unsafe fn compat53_checkmode( while *st != 0 && *st != c { st = st.offset(1); } - if *st == c { - st - } else { - ptr::null() - } + if *st == c { st } else { ptr::null() } } if !mode.is_null() && strchr(mode, *modename).is_null() { diff --git a/mlua-sys/src/lua52/lauxlib.rs b/mlua-sys/src/lua52/lauxlib.rs index 6835c0f7..635bf926 100644 --- a/mlua-sys/src/lua52/lauxlib.rs +++ b/mlua-sys/src/lua52/lauxlib.rs @@ -32,7 +32,7 @@ unsafe extern "C-unwind" { pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int; pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char; pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize) - -> *const c_char; + -> *const c_char; pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number; pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number; pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer; diff --git a/mlua-sys/src/lua53/lauxlib.rs b/mlua-sys/src/lua53/lauxlib.rs index f832dd2f..4483b9ab 100644 --- a/mlua-sys/src/lua53/lauxlib.rs +++ b/mlua-sys/src/lua53/lauxlib.rs @@ -31,7 +31,7 @@ unsafe extern "C-unwind" { pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int; pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char; pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize) - -> *const c_char; + -> *const c_char; pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number; pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number; pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer; diff --git a/mlua-sys/src/lua54/lauxlib.rs b/mlua-sys/src/lua54/lauxlib.rs index 193fd5a9..26b31bfe 100644 --- a/mlua-sys/src/lua54/lauxlib.rs +++ b/mlua-sys/src/lua54/lauxlib.rs @@ -30,7 +30,7 @@ unsafe extern "C-unwind" { pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int; pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char; pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize) - -> *const c_char; + -> *const c_char; pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number; pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number; pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer; diff --git a/mlua-sys/src/lua55/lauxlib.rs b/mlua-sys/src/lua55/lauxlib.rs index 06df4808..13a8524f 100644 --- a/mlua-sys/src/lua55/lauxlib.rs +++ b/mlua-sys/src/lua55/lauxlib.rs @@ -30,7 +30,7 @@ unsafe extern "C-unwind" { pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int; pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char; pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize) - -> *const c_char; + -> *const c_char; pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number; pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number; pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer; diff --git a/mlua-sys/src/luau/lauxlib.rs b/mlua-sys/src/luau/lauxlib.rs index db92513f..f57c3cdc 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_Number, lua_State, lua_Unsigned, LUA_REGISTRYINDEX}; +use super::lua::{self, LUA_REGISTRYINDEX, lua_CFunction, lua_Number, lua_State, lua_Unsigned}; // Key, in the registry, for table of loaded modules pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED"); diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 5fc67219..98ee5bc7 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -155,7 +155,12 @@ unsafe extern "C-unwind" { pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int; pub fn lua_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char; pub fn lua_tostringatom(L: *mut lua_State, idx: c_int, atom: *mut c_int) -> *const c_char; - pub fn lua_tolstringatom(L: *mut lua_State, idx: c_int, len: *mut usize, atom: *mut c_int) -> *const c_char; + pub fn lua_tolstringatom( + L: *mut lua_State, + idx: c_int, + len: *mut usize, + atom: *mut c_int, + ) -> *const c_char; pub fn lua_namecallatom(L: *mut lua_State, atom: *mut c_int) -> *const c_char; #[link_name = "lua_objlen"] pub fn lua_objlen_(L: *mut lua_State, idx: c_int) -> c_int; From c80a97b526ab57e5202c401da9ea1a446d56a59e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 22 Jan 2026 15:25:18 +0000 Subject: [PATCH 580/635] Ignore userdata_multithread_access_sync test --- tests/send.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/send.rs b/tests/send.rs index becfad20..8c49d599 100644 --- a/tests/send.rs +++ b/tests/send.rs @@ -47,8 +47,8 @@ fn test_userdata_multithread_access_send_only() -> Result<()> { Ok(()) } -#[rustversion::stable] #[test] +#[ignore = "rust change https://github.com/rust-lang/rust/pull/135634"] fn test_userdata_multithread_access_sync() -> Result<()> { let lua = Lua::new(); From e33bcf793851d2a61b4340e2536bddec25a3bd6b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 22 Jan 2026 15:32:14 +0000 Subject: [PATCH 581/635] Add Lua 5.5 external string null byte test --- tests/string.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/string.rs b/tests/string.rs index f6bdd995..7f61b67b 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -157,3 +157,18 @@ fn test_bytes_into_iter() -> Result<()> { Ok(()) } + +#[cfg(feature = "lua55")] +#[test] +fn test_external_string() -> Result<()> { + let lua = Lua::new(); + + let s = lua.create_external_string(b"abc\0")?; + assert_eq!( + s.as_bytes(), + b"abc\0", + "Trailing null byte should be preserved if present explicitly" + ); + + Ok(()) +} From 86d63ef27b257583675d8cd515d60c60ee650fe9 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 22 Jan 2026 15:46:31 +0000 Subject: [PATCH 582/635] Fix missing BUFSIZ for wasm32 in libc --- mlua-sys/src/lua51/lauxlib.rs | 8 ++++++-- mlua-sys/src/lua52/lauxlib.rs | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/mlua-sys/src/lua51/lauxlib.rs b/mlua-sys/src/lua51/lauxlib.rs index 2b990835..767d8fe0 100644 --- a/mlua-sys/src/lua51/lauxlib.rs +++ b/mlua-sys/src/lua51/lauxlib.rs @@ -154,10 +154,14 @@ pub unsafe fn luaL_opt( // Generic Buffer Manipulation // +#[cfg(target_arch = "wasm32")] +const BUFSIZ: usize = 1024; // WASI libc's BUFSIZ is 1024 +#[cfg(not(target_arch = "wasm32"))] +const BUFSIZ: usize = libc::BUFSIZ as usize; + // The buffer size used by the lauxlib buffer system. // The "16384" workaround is taken from the LuaJIT source code. -#[rustfmt::skip] -pub const LUAL_BUFFERSIZE: usize = if libc::BUFSIZ > 16384 { 8192 } else { libc::BUFSIZ as usize }; +pub const LUAL_BUFFERSIZE: usize = if BUFSIZ > 16384 { 8192 } else { BUFSIZ }; #[repr(C)] pub struct luaL_Buffer { diff --git a/mlua-sys/src/lua52/lauxlib.rs b/mlua-sys/src/lua52/lauxlib.rs index 635bf926..eb954a46 100644 --- a/mlua-sys/src/lua52/lauxlib.rs +++ b/mlua-sys/src/lua52/lauxlib.rs @@ -189,10 +189,14 @@ pub unsafe fn luaL_opt( // Generic Buffer Manipulation // +#[cfg(target_arch = "wasm32")] +const BUFSIZ: usize = 1024; // WASI libc's BUFSIZ is 1024 +#[cfg(not(target_arch = "wasm32"))] +const BUFSIZ: usize = libc::BUFSIZ as usize; + // The buffer size used by the lauxlib buffer system. // The "16384" workaround is taken from the LuaJIT source code. -#[rustfmt::skip] -pub const LUAL_BUFFERSIZE: usize = if libc::BUFSIZ > 16384 { 8192 } else { libc::BUFSIZ as usize }; +pub const LUAL_BUFFERSIZE: usize = if BUFSIZ > 16384 { 8192 } else { BUFSIZ }; #[repr(C)] pub struct luaL_Buffer { From 86d0c9bddb1f93937aeb09eb2c76971b6858547a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 22 Jan 2026 22:44:30 +0000 Subject: [PATCH 583/635] More multi borrow "send" test fixes due to regression in Rust 1.93+ --- tests/send.rs | 7 ++++--- tests/userdata.rs | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/send.rs b/tests/send.rs index 8c49d599..6aa81003 100644 --- a/tests/send.rs +++ b/tests/send.rs @@ -48,7 +48,6 @@ fn test_userdata_multithread_access_send_only() -> Result<()> { } #[test] -#[ignore = "rust change https://github.com/rust-lang/rust/pull/135634"] fn test_userdata_multithread_access_sync() -> Result<()> { let lua = Lua::new(); @@ -76,11 +75,13 @@ fn test_userdata_multithread_access_sync() -> Result<()> { std::thread::scope(|s| { s.spawn(|| { // Getting another shared reference for `Sync` type is allowed. - let _ = lua.globals().get::>("ud").unwrap(); + // FIXME: does not work due to https://github.com/rust-lang/rust/pull/135634 + // let _ = lua.globals().get::>("ud").unwrap(); }); }); - lua.load("ud:method()").exec().unwrap(); + // FIXME: does not work due to https://github.com/rust-lang/rust/pull/135634 + // lua.load("ud:method()").exec().unwrap(); Ok(()) } diff --git a/tests/userdata.rs b/tests/userdata.rs index 3e19009e..dc8ca8b1 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -978,7 +978,6 @@ fn test_nested_userdata_gc() -> Result<()> { } #[cfg(feature = "userdata-wrappers")] -#[rustversion::stable] #[test] fn test_userdata_wrappers() -> Result<()> { #[derive(Debug)] @@ -1340,8 +1339,9 @@ fn test_userdata_wrappers() -> Result<()> { assert_eq!(ud.borrow::()?.0, 20); // Multiple read borrows are allowed with parking_lot::RwLock - let _borrow1 = ud.borrow::()?; - let _borrow2 = ud.borrow::()?; + let _borrow1 = ud.borrow::().unwrap(); + // FIXME: does not work due to https://github.com/rust-lang/rust/pull/135634 + // let _borrow2 = ud.borrow::().unwrap(); assert!(matches!( ud.borrow_mut::(), Err(Error::UserDataBorrowMutError) From 3f8f016daa7bcad8c9face9ff559dfad0daca481 Mon Sep 17 00:00:00 2001 From: psentee <135014396+psentee@users.noreply.github.com> Date: Fri, 23 Jan 2026 12:59:59 +0100 Subject: [PATCH 584/635] Add num_params, is_vararg, nups to FunctionInfo (#665) --- src/function.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/function.rs b/src/function.rs index e0cb0693..7f7a4497 100644 --- a/src/function.rs +++ b/src/function.rs @@ -50,6 +50,15 @@ pub struct FunctionInfo { pub line_defined: Option, /// The line number where the definition of the function ends (not set by Luau). pub last_line_defined: Option, + /// Number of function parameters + #[cfg(any(not(any(feature = "lua51", feature = "luajit")), doc))] + pub num_params: usize, + /// True if function accepts variable args + #[cfg(any(not(any(feature = "lua51", feature = "luajit")), doc))] + pub is_vararg: bool, + /// Number of upvalues + #[cfg(any(not(feature = "luau"), doc))] + pub nups: usize, } /// Luau function coverage snapshot. @@ -343,7 +352,8 @@ impl Function { /// Returns information about the function. /// - /// Corresponds to the `>Sn` what mask for [`lua_getinfo`] when applied to the function. + /// Corresponds to the `>Snu` (`>Sn` for Luau) what mask for + /// [`lua_getinfo`] when applied to the function. /// /// [`lua_getinfo`]: https://www.lua.org/manual/5.4/manual.html#lua_getinfo pub fn info(&self) -> FunctionInfo { @@ -356,7 +366,7 @@ impl Function { let mut ar: ffi::lua_Debug = mem::zeroed(); lua.push_ref(&self.0); #[cfg(not(feature = "luau"))] - let res = ffi::lua_getinfo(state, cstr!(">Sn"), &mut ar); + let res = ffi::lua_getinfo(state, cstr!(">Snu"), &mut ar); #[cfg(feature = "luau")] let res = ffi::lua_getinfo(state, -1, cstr!("sn"), &mut ar); mlua_assert!(res != 0, "lua_getinfo failed with `>Sn`"); @@ -381,6 +391,12 @@ impl Function { last_line_defined: linenumber_to_usize(ar.lastlinedefined), #[cfg(feature = "luau")] last_line_defined: None, + #[cfg(not(any(feature = "lua51", feature = "luajit")))] + num_params: ar.nparams as usize, + #[cfg(not(any(feature = "lua51", feature = "luajit")))] + is_vararg: ar.isvararg != 0, + #[cfg(not(feature = "luau"))] + nups: ar.nups as usize, } } } From 171cdf1758ffa84721b397b0215a8c27798f6c0e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 23 Jan 2026 12:44:22 +0000 Subject: [PATCH 585/635] Some fixes and more tests for `Function::info` --- src/function.rs | 31 ++++++++++++++++++++----------- tests/function.rs | 19 +++++++++++++++++++ 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/function.rs b/src/function.rs index 7f7a4497..f967ca84 100644 --- a/src/function.rs +++ b/src/function.rs @@ -32,6 +32,7 @@ pub struct Function(pub(crate) ValueRef); /// /// [`Lua Debug Interface`]: https://www.lua.org/manual/5.4/manual.html#4.7 #[derive(Clone, Debug)] +#[non_exhaustive] pub struct FunctionInfo { /// A (reasonable) name of the function (`None` if the name cannot be found). pub name: Option, @@ -50,15 +51,16 @@ pub struct FunctionInfo { pub line_defined: Option, /// The line number where the definition of the function ends (not set by Luau). pub last_line_defined: Option, - /// Number of function parameters + /// The number of upvalues of the function. + pub num_upvalues: u8, + /// The number of parameters of the function (always 0 for C). #[cfg(any(not(any(feature = "lua51", feature = "luajit")), doc))] - pub num_params: usize, - /// True if function accepts variable args + #[cfg_attr(docsrs, doc(cfg(not(any(feature = "lua51", feature = "luajit")))))] + pub num_params: u8, + /// Whether the function is a variadic function (always true for C). #[cfg(any(not(any(feature = "lua51", feature = "luajit")), doc))] + #[cfg_attr(docsrs, doc(cfg(not(any(feature = "lua51", feature = "luajit")))))] pub is_vararg: bool, - /// Number of upvalues - #[cfg(any(not(feature = "luau"), doc))] - pub nups: usize, } /// Luau function coverage snapshot. @@ -365,11 +367,16 @@ impl Function { let mut ar: ffi::lua_Debug = mem::zeroed(); lua.push_ref(&self.0); + #[cfg(not(feature = "luau"))] let res = ffi::lua_getinfo(state, cstr!(">Snu"), &mut ar); + #[cfg(not(feature = "luau"))] + mlua_assert!(res != 0, "lua_getinfo failed with `>Snu`"); + + #[cfg(feature = "luau")] + let res = ffi::lua_getinfo(state, -1, cstr!("snau"), &mut ar); #[cfg(feature = "luau")] - let res = ffi::lua_getinfo(state, -1, cstr!("sn"), &mut ar); - mlua_assert!(res != 0, "lua_getinfo failed with `>Sn`"); + mlua_assert!(res != 0, "lua_getinfo failed with `snau`"); FunctionInfo { name: ptr_to_lossy_str(ar.name).map(|s| s.into_owned()), @@ -391,12 +398,14 @@ impl Function { last_line_defined: linenumber_to_usize(ar.lastlinedefined), #[cfg(feature = "luau")] last_line_defined: None, + #[cfg(not(feature = "luau"))] + num_upvalues: ar.nups as _, + #[cfg(feature = "luau")] + num_upvalues: ar.nupvals, #[cfg(not(any(feature = "lua51", feature = "luajit")))] - num_params: ar.nparams as usize, + num_params: ar.nparams, #[cfg(not(any(feature = "lua51", feature = "luajit")))] is_vararg: ar.isvararg != 0, - #[cfg(not(feature = "luau"))] - nups: ar.nups as usize, } } } diff --git a/tests/function.rs b/tests/function.rs index f4afa6b2..d9970898 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -194,6 +194,25 @@ fn test_function_info() -> Result<()> { assert_eq!(print_info.what, "C"); assert_eq!(print_info.line_defined, None); + // Function with upvalues and params + #[cfg(not(any(feature = "lua51", feature = "luajit")))] + { + let func_with_upvalues = lua + .load( + r#" + local x, y = ... + return function(a, ...) + return a*x + y + end + "#, + ) + .call::((10, 20))?; + let func_with_upvalues_info = func_with_upvalues.info(); + assert_eq!(func_with_upvalues_info.num_upvalues, 2); + assert_eq!(func_with_upvalues_info.num_params, 1); + assert_eq!(func_with_upvalues_info.is_vararg, true); + } + Ok(()) } From 93617eef4e3fedd1cc3c39b1f64bea0190fa9323 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 23 Jan 2026 15:16:35 +0000 Subject: [PATCH 586/635] Update trybuild (compile) messages --- .../compile/async_any_userdata_method.stderr | 34 +++-- tests/compile/async_nonstatic_userdata.stderr | 6 +- tests/compile/lua_norefunwindsafe.stderr | 80 +++++++---- tests/compile/ref_nounwindsafe.stderr | 129 +++++------------- tests/compile/scope_callback_capture.stderr | 16 +-- tests/compile/scope_invariance.stderr | 2 +- tests/compile/scope_mutable_aliasing.stderr | 6 + tests/compile/scope_userdata_borrow.stderr | 6 + 8 files changed, 133 insertions(+), 146 deletions(-) diff --git a/tests/compile/async_any_userdata_method.stderr b/tests/compile/async_any_userdata_method.stderr index 970feeff..3e01c45b 100644 --- a/tests/compile/async_any_userdata_method.stderr +++ b/tests/compile/async_any_userdata_method.stderr @@ -1,15 +1,19 @@ error[E0596]: cannot borrow `s` as mutable, as it is a captured variable in a `Fn` closure --> tests/compile/async_any_userdata_method.rs:9:49 | -9 | reg.add_async_method("t", |_, this, ()| async { - | ^^^^^ cannot borrow as mutable + 8 | let mut s = &s; + | ----- `s` declared here, outside the closure + 9 | reg.add_async_method("t", |_, this, ()| async { + | ------------- ^^^^^ cannot borrow as mutable + | | + | in this closure 10 | s = &*this; | - mutable borrow occurs due to use of `s` in closure error[E0373]: async block may outlive the current function, but it borrows `this`, which is owned by the current function --> tests/compile/async_any_userdata_method.rs:9:49 | -9 | reg.add_async_method("t", |_, this, ()| async { + 9 | reg.add_async_method("t", |_, this, ()| async { | ^^^^^ may outlive borrowed value `this` 10 | s = &*this; | ---- `this` is borrowed here @@ -17,7 +21,7 @@ error[E0373]: async block may outlive the current function, but it borrows `this note: async block is returned here --> tests/compile/async_any_userdata_method.rs:9:49 | -9 | reg.add_async_method("t", |_, this, ()| async { + 9 | reg.add_async_method("t", |_, this, ()| async { | _________________________________________________^ 10 | | s = &*this; 11 | | Ok(()) @@ -25,13 +29,13 @@ note: async block is returned here | |_________^ help: to force the async block to take ownership of `this` (and any other referenced variables), use the `move` keyword | -9 | reg.add_async_method("t", |_, this, ()| async move { + 9 | reg.add_async_method("t", |_, this, ()| async move { | ++++ error: lifetime may not live long enough --> tests/compile/async_any_userdata_method.rs:9:49 | -9 | reg.add_async_method("t", |_, this, ()| async { + 9 | reg.add_async_method("t", |_, this, ()| async { | ___________________________________-------------_^ | | | | | | | return type of closure `{async block@$DIR/tests/compile/async_any_userdata_method.rs:9:49: 9:54}` contains a lifetime `'2` @@ -46,22 +50,28 @@ error: lifetime may not live long enough error[E0597]: `s` does not live long enough --> tests/compile/async_any_userdata_method.rs:8:21 | -7 | let s = String::new(); + 7 | let s = String::new(); | - binding `s` declared here -8 | let mut s = &s; + 8 | let mut s = &s; | ^^ borrowed value does not live long enough -9 | / reg.add_async_method("t", |_, this, ()| async { + 9 | / reg.add_async_method("t", |_, this, ()| async { 10 | | s = &*this; 11 | | Ok(()) 12 | | }); | |__________- argument requires that `s` is borrowed for `'static` 13 | }) | - `s` dropped here while still borrowed + | +note: requirement that the value outlives `'static` introduced here + --> src/userdata.rs + | + | M: Fn(Lua, UserDataRef, A) -> MR + MaybeSend + 'static, + | ^^^^^^^ error[E0373]: closure may outlive the current function, but it borrows `s`, which is owned by the current function --> tests/compile/async_any_userdata_method.rs:9:35 | -9 | reg.add_async_method("t", |_, this, ()| async { + 9 | reg.add_async_method("t", |_, this, ()| async { | ^^^^^^^^^^^^^ may outlive borrowed value `s` 10 | s = &*this; | - `s` is borrowed here @@ -69,12 +79,12 @@ error[E0373]: closure may outlive the current function, but it borrows `s`, whic note: function requires argument type to outlive `'static` --> tests/compile/async_any_userdata_method.rs:9:9 | -9 | / reg.add_async_method("t", |_, this, ()| async { + 9 | / reg.add_async_method("t", |_, this, ()| async { 10 | | s = &*this; 11 | | Ok(()) 12 | | }); | |__________^ help: to force the closure to take ownership of `s` (and any other referenced variables), use the `move` keyword | -9 | reg.add_async_method("t", move |_, this, ()| async { + 9 | reg.add_async_method("t", move |_, this, ()| async { | ++++ diff --git a/tests/compile/async_nonstatic_userdata.stderr b/tests/compile/async_nonstatic_userdata.stderr index 368e9f34..18412e4a 100644 --- a/tests/compile/async_nonstatic_userdata.stderr +++ b/tests/compile/async_nonstatic_userdata.stderr @@ -1,10 +1,10 @@ error: lifetime may not live long enough --> tests/compile/async_nonstatic_userdata.rs:9:13 | -7 | impl UserData for MyUserData<'_> { + 7 | impl UserData for MyUserData<'_> { | -- lifetime `'1` appears in the `impl`'s self type -8 | fn add_methods>(methods: &mut M) { -9 | / methods.add_async_method("print", |_, data, ()| async move { + 8 | fn add_methods>(methods: &mut M) { + 9 | / methods.add_async_method("print", |_, data, ()| async move { 10 | | println!("{}", data.0); 11 | | Ok(()) 12 | | }); diff --git a/tests/compile/lua_norefunwindsafe.stderr b/tests/compile/lua_norefunwindsafe.stderr index a482a8d7..814ae6b6 100644 --- a/tests/compile/lua_norefunwindsafe.stderr +++ b/tests/compile/lua_norefunwindsafe.stderr @@ -1,28 +1,32 @@ -error[E0277]: the type `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary +error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary --> tests/compile/lua_norefunwindsafe.rs:7:18 | 7 | catch_unwind(|| lua.create_table().unwrap()); - | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary | | | required by a bound introduced by this call | - = help: within `mlua::types::sync::inner::ReentrantMutex`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<*mut lua_State>` -note: required because it appears within the type `Cell<*mut lua_State>` - --> $RUST/core/src/cell.rs + = help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell` +note: required because it appears within the type `lock_api::remutex::ReentrantMutex` + --> $CARGO/lock_api-$VERSION/src/remutex.rs | - | pub struct Cell { - | ^^^^ -note: required because it appears within the type `mlua::state::raw::RawLua` - --> src/state/raw.rs + | pub struct ReentrantMutex { + | ^^^^^^^^^^^^^^ +note: required because it appears within the type `alloc::sync::ArcInner>` + --> $RUST/alloc/src/sync.rs + | + | struct ArcInner { + | ^^^^^^^^ +note: required because it appears within the type `PhantomData>>` + --> $RUST/core/src/marker.rs | - | pub struct RawLua { - | ^^^^^^ -note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex` - --> src/types/sync.rs + | pub struct PhantomData; + | ^^^^^^^^^^^ +note: required because it appears within the type `Arc>` + --> $RUST/alloc/src/sync.rs | - | pub(crate) struct ReentrantMutex(T); - | ^^^^^^^^^^^^^^ - = note: required for `Rc>` to implement `RefUnwindSafe` + | pub struct Arc< + | ^^^ note: required because it appears within the type `Lua` --> src/state.rs | @@ -40,27 +44,45 @@ note: required by a bound in `std::panic::catch_unwind` | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { | ^^^^^^^^^^ required by this bound in `catch_unwind` -error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary +error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary --> tests/compile/lua_norefunwindsafe.rs:7:18 | 7 | catch_unwind(|| lua.create_table().unwrap()); - | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary | | | required by a bound introduced by this call | - = help: the trait `RefUnwindSafe` is not implemented for `UnsafeCell` - = note: required for `Rc>` to implement `RefUnwindSafe` -note: required because it appears within the type `mlua::state::raw::RawLua` - --> src/state/raw.rs + = help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell` +note: required because it appears within the type `Cell` + --> $RUST/core/src/cell.rs | - | pub struct RawLua { - | ^^^^^^ -note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex` - --> src/types/sync.rs + | pub struct Cell { + | ^^^^ +note: required because it appears within the type `lock_api::remutex::RawReentrantMutex` + --> $CARGO/lock_api-$VERSION/src/remutex.rs + | + | pub struct RawReentrantMutex { + | ^^^^^^^^^^^^^^^^^ +note: required because it appears within the type `lock_api::remutex::ReentrantMutex` + --> $CARGO/lock_api-$VERSION/src/remutex.rs + | + | pub struct ReentrantMutex { + | ^^^^^^^^^^^^^^ +note: required because it appears within the type `alloc::sync::ArcInner>` + --> $RUST/alloc/src/sync.rs | - | pub(crate) struct ReentrantMutex(T); - | ^^^^^^^^^^^^^^ - = note: required for `Rc>` to implement `RefUnwindSafe` + | struct ArcInner { + | ^^^^^^^^ +note: required because it appears within the type `PhantomData>>` + --> $RUST/core/src/marker.rs + | + | pub struct PhantomData; + | ^^^^^^^^^^^ +note: required because it appears within the type `Arc>` + --> $RUST/alloc/src/sync.rs + | + | pub struct Arc< + | ^^^ note: required because it appears within the type `Lua` --> src/state.rs | diff --git a/tests/compile/ref_nounwindsafe.stderr b/tests/compile/ref_nounwindsafe.stderr index 048f9d32..49032555 100644 --- a/tests/compile/ref_nounwindsafe.stderr +++ b/tests/compile/ref_nounwindsafe.stderr @@ -1,25 +1,25 @@ -error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary +error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary --> tests/compile/ref_nounwindsafe.rs:8:18 | 8 | catch_unwind(move || table.set("a", "b").unwrap()); - | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary | | | required by a bound introduced by this call | - = help: within `rc::RcInner>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell` -note: required because it appears within the type `Cell` - --> $RUST/core/src/cell.rs + = help: within `alloc::sync::ArcInner>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell` +note: required because it appears within the type `lock_api::remutex::ReentrantMutex` + --> $CARGO/lock_api-$VERSION/src/remutex.rs | - | pub struct Cell { - | ^^^^ -note: required because it appears within the type `rc::RcInner>` - --> $RUST/alloc/src/rc.rs + | pub struct ReentrantMutex { + | ^^^^^^^^^^^^^^ +note: required because it appears within the type `alloc::sync::ArcInner>` + --> $RUST/alloc/src/sync.rs | - | struct RcInner { - | ^^^^^^^ - = note: required for `NonNull>>` to implement `UnwindSafe` -note: required because it appears within the type `std::rc::Weak>` - --> $RUST/alloc/src/rc.rs + | struct ArcInner { + | ^^^^^^^^ + = note: required for `NonNull>>` to implement `UnwindSafe` +note: required because it appears within the type `std::sync::Weak>` + --> $RUST/alloc/src/sync.rs | | pub struct Weak< | ^^^^ @@ -49,95 +49,38 @@ note: required by a bound in `std::panic::catch_unwind` | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { | ^^^^^^^^^^ required by this bound in `catch_unwind` -error[E0277]: the type `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary +error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary --> tests/compile/ref_nounwindsafe.rs:8:18 | 8 | catch_unwind(move || table.set("a", "b").unwrap()); - | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary + | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary | | | required by a bound introduced by this call | - = help: within `rc::RcInner>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<*mut lua_State>` -note: required because it appears within the type `Cell<*mut lua_State>` + = help: within `alloc::sync::ArcInner>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell` +note: required because it appears within the type `Cell` --> $RUST/core/src/cell.rs | | pub struct Cell { | ^^^^ -note: required because it appears within the type `mlua::state::raw::RawLua` - --> src/state/raw.rs - | - | pub struct RawLua { - | ^^^^^^ -note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex` - --> src/types/sync.rs - | - | pub(crate) struct ReentrantMutex(T); - | ^^^^^^^^^^^^^^ -note: required because it appears within the type `rc::RcInner>` - --> $RUST/alloc/src/rc.rs - | - | struct RcInner { - | ^^^^^^^ - = note: required for `NonNull>>` to implement `UnwindSafe` -note: required because it appears within the type `std::rc::Weak>` - --> $RUST/alloc/src/rc.rs - | - | pub struct Weak< - | ^^^^ -note: required because it appears within the type `WeakLua` - --> src/state.rs - | - | pub struct WeakLua(XWeak>); - | ^^^^^^^ -note: required because it appears within the type `mlua::types::value_ref::ValueRef` - --> src/types/value_ref.rs - | - | pub struct ValueRef { - | ^^^^^^^^ -note: required because it appears within the type `LuaTable` - --> src/table.rs - | - | pub struct Table(pub(crate) ValueRef); - | ^^^^^ -note: required because it's used within this closure - --> tests/compile/ref_nounwindsafe.rs:8:18 - | -8 | catch_unwind(move || table.set("a", "b").unwrap()); - | ^^^^^^^ -note: required by a bound in `std::panic::catch_unwind` - --> $RUST/std/src/panic.rs - | - | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { - | ^^^^^^^^^^ required by this bound in `catch_unwind` - -error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary - --> tests/compile/ref_nounwindsafe.rs:8:18 - | -8 | catch_unwind(move || table.set("a", "b").unwrap()); - | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary - | | - | required by a bound introduced by this call - | - = help: the trait `RefUnwindSafe` is not implemented for `UnsafeCell` - = note: required for `Rc>` to implement `RefUnwindSafe` -note: required because it appears within the type `mlua::state::raw::RawLua` - --> src/state/raw.rs - | - | pub struct RawLua { - | ^^^^^^ -note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex` - --> src/types/sync.rs - | - | pub(crate) struct ReentrantMutex(T); - | ^^^^^^^^^^^^^^ -note: required because it appears within the type `rc::RcInner>` - --> $RUST/alloc/src/rc.rs - | - | struct RcInner { - | ^^^^^^^ - = note: required for `NonNull>>` to implement `UnwindSafe` -note: required because it appears within the type `std::rc::Weak>` - --> $RUST/alloc/src/rc.rs +note: required because it appears within the type `lock_api::remutex::RawReentrantMutex` + --> $CARGO/lock_api-$VERSION/src/remutex.rs + | + | pub struct RawReentrantMutex { + | ^^^^^^^^^^^^^^^^^ +note: required because it appears within the type `lock_api::remutex::ReentrantMutex` + --> $CARGO/lock_api-$VERSION/src/remutex.rs + | + | pub struct ReentrantMutex { + | ^^^^^^^^^^^^^^ +note: required because it appears within the type `alloc::sync::ArcInner>` + --> $RUST/alloc/src/sync.rs + | + | struct ArcInner { + | ^^^^^^^^ + = note: required for `NonNull>>` to implement `UnwindSafe` +note: required because it appears within the type `std::sync::Weak>` + --> $RUST/alloc/src/sync.rs | | pub struct Weak< | ^^^^ diff --git a/tests/compile/scope_callback_capture.stderr b/tests/compile/scope_callback_capture.stderr index 94844dc9..7fa3a5e2 100644 --- a/tests/compile/scope_callback_capture.stderr +++ b/tests/compile/scope_callback_capture.stderr @@ -1,24 +1,24 @@ error[E0373]: closure may outlive the current function, but it borrows `inner`, which is owned by the current function --> tests/compile/scope_callback_capture.rs:7:43 | -5 | lua.scope(|scope| { + 5 | lua.scope(|scope| { | ----- has type `&'1 mlua::Scope<'1, '_>` -6 | let mut inner: Option
= None; -7 | let f = scope.create_function_mut(|_, t: Table| { + 6 | let mut inner: Option
= None; + 7 | let f = scope.create_function_mut(|_, t: Table| { | ^^^^^^^^^^^^^ may outlive borrowed value `inner` -8 | inner = Some(t); + 8 | inner = Some(t); | ----- `inner` is borrowed here | note: function requires argument type to outlive `'1` --> tests/compile/scope_callback_capture.rs:7:17 | -7 | let f = scope.create_function_mut(|_, t: Table| { + 7 | let f = scope.create_function_mut(|_, t: Table| { | _________________^ -8 | | inner = Some(t); -9 | | Ok(()) + 8 | | inner = Some(t); + 9 | | Ok(()) 10 | | })?; | |__________^ help: to force the closure to take ownership of `inner` (and any other referenced variables), use the `move` keyword | -7 | let f = scope.create_function_mut(move |_, t: Table| { + 7 | let f = scope.create_function_mut(move |_, t: Table| { | ++++ diff --git a/tests/compile/scope_invariance.stderr b/tests/compile/scope_invariance.stderr index a3f218df..8bad0c12 100644 --- a/tests/compile/scope_invariance.stderr +++ b/tests/compile/scope_invariance.stderr @@ -1,7 +1,7 @@ error[E0373]: closure may outlive the current function, but it borrows `test.field`, which is owned by the current function --> tests/compile/scope_invariance.rs:13:39 | -9 | lua.scope(|scope| { + 9 | lua.scope(|scope| { | ----- has type `&'1 mlua::Scope<'1, '_>` ... 13 | scope.create_function_mut(|_, ()| { diff --git a/tests/compile/scope_mutable_aliasing.stderr b/tests/compile/scope_mutable_aliasing.stderr index e6e57f13..d7724660 100644 --- a/tests/compile/scope_mutable_aliasing.stderr +++ b/tests/compile/scope_mutable_aliasing.stderr @@ -10,3 +10,9 @@ error[E0499]: cannot borrow `i` as mutable more than once at a time | argument requires that `i` is borrowed for `'1` 12 | let _b = scope.create_userdata(MyUserData(&mut i)).unwrap(); | ^^^^^^ second mutable borrow occurs here + | +note: requirement that the value outlives `'1` introduced here + --> src/scope.rs + | + | T: UserData + 'env, + | ^^^^ diff --git a/tests/compile/scope_userdata_borrow.stderr b/tests/compile/scope_userdata_borrow.stderr index 43025ddf..7aa771f2 100644 --- a/tests/compile/scope_userdata_borrow.stderr +++ b/tests/compile/scope_userdata_borrow.stderr @@ -13,3 +13,9 @@ error[E0597]: `ibad` does not live long enough | argument requires that `ibad` is borrowed for `'1` 16 | }; | - `ibad` dropped here while still borrowed + | +note: requirement that the value outlives `'1` introduced here + --> src/scope.rs + | + | T: UserData + 'env, + | ^^^^ From 7fb7e8685f45135679bcab31f82ee404ea009968 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 23 Jan 2026 15:37:18 +0000 Subject: [PATCH 587/635] Update spelling check --- .github/workflows/typos.yml | 15 ++++++++++----- src/luau/json.rs | 4 ++-- src/state.rs | 2 +- tests/luau.rs | 2 +- typos.toml | 8 ++++++++ 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index c904d668..1cd3c1c9 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -1,17 +1,22 @@ -name: Typos Check +name: Spelling Check on: pull_request: workflow_dispatch: +permissions: + contents: read + +env: + CLICOLOR: 1 + jobs: - run: + spelling: name: Spell Check with Typos runs-on: ubuntu-latest steps: - name: Checkout Actions Repository - uses: actions/checkout@v4 - + uses: actions/checkout@main - name: Check spelling - uses: crate-ci/typos@master + uses: crate-ci/typos@v1.42.1 with: config: ./typos.toml diff --git a/src/luau/json.rs b/src/luau/json.rs index 06b71606..ce17a20e 100644 --- a/src/luau/json.rs +++ b/src/luau/json.rs @@ -313,8 +313,8 @@ mod tests { fn test_error_cases() { assert!(parse("").is_err()); assert!(parse("nul").is_err()); - assert!(parse("tru").is_err()); - assert!(parse("fals").is_err()); + assert!(parse("tru").is_err()); // typos:ignore + assert!(parse("fals").is_err()); // typos:ignore assert!(parse(r#""unterminated"#).is_err()); assert!(parse("[1,2,]").is_err()); assert!(parse(r#"{"key""#).is_err()); diff --git a/src/state.rs b/src/state.rs index 0808ad2a..77ee199b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2150,7 +2150,7 @@ impl Lua { /// Suspends the current async function, returning the provided arguments to caller. /// - /// This function is similar to [`coroutine.yield`] but allow yeilding Rust functions + /// This function is similar to [`coroutine.yield`] but allow yielding Rust functions /// and passing values to the caller. /// Please note that you cannot cross [`Thread`] boundaries (e.g. calling `yield_with` on one /// thread and resuming on another). diff --git a/tests/luau.rs b/tests/luau.rs index 07495f95..2b597857 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -448,7 +448,7 @@ fn test_loadstring() -> Result<()> { assert_eq!(f.call::(())?, 123); let err = lua - .load(r#"loadstring("retur 123", "chunk")"#) + .load(r#"loadstring("retur 123", "chunk")"#) // typos:ignore .exec() .err() .unwrap(); diff --git a/typos.toml b/typos.toml index ef768641..7a8fc528 100644 --- a/typos.toml +++ b/typos.toml @@ -1,5 +1,13 @@ [default] extend-ignore-identifiers-re = ["2nd", "ser"] +extend-ignore-re = [ + # Custom ignore regex patterns: https://github.com/crate-ci/typos/blob/master/docs/reference.md#defaultextend-ignore-re + ".*(?:#|--|//|/*).*(?:spellchecker|typos):\\s?ignore[^\\n]*\\n", + ".*(?:spellchecker|typos):\\s?ignore-next-line[^\\n]*\\n[^\\n]*", +] + +[files] +extend-exclude = ["tests/compile/*.stderr"] [default.extend-words] thr = "thr" From fd245daa6f35b809f77a4d6d7c35a2b2fd472cc3 Mon Sep 17 00:00:00 2001 From: WASDetchan Date: Fri, 23 Jan 2026 19:19:10 +0300 Subject: [PATCH 588/635] Expose RawLua via Lua::exec_raw_lua (#670) --- src/state.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/state.rs b/src/state.rs index 77ee199b..c46b2694 100644 --- a/src/state.rs +++ b/src/state.rs @@ -337,6 +337,38 @@ impl Lua { R::from_stack_multi(nresults, &lua) } + /// Runs callback with the inner RawLua value. It can be used to manually push and get values on the stack. + /// + /// This function is safe because all unsafe actions with RawLua can only be done with unsafe + /// + /// # Example + /// ``` + /// # use mlua::{Lua, Result, FromLua, IntoLua}; + /// # fn main() -> Result<()> { + /// let lua = Lua::new(); + /// let n: i32 = { + /// let num = 11i32; + /// lua.exec_raw_lua(|lua| { + /// unsafe { + /// ::push_into_stack(num, lua)?; + /// } + /// + /// let n = unsafe { + /// ::from_stack(-1, lua)? + /// }; + /// Result::Ok(n) + /// }) + /// }?; + /// assert_eq!(n, 11); + /// # Ok(()) + /// # } + /// ``` + #[doc(hidden)] + pub fn exec_raw_lua(&self, f: impl FnOnce(&RawLua) -> R) -> R { + let lua = self.lock(); + f(&lua) + } + /// Loads the specified subset of the standard libraries into an existing Lua state. /// /// Use the [`StdLib`] flags to specify the libraries you want to load. From 8c1535c27b67e019911d29a483d423d4ed09072c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 27 Jan 2026 12:14:50 +0000 Subject: [PATCH 589/635] cargo fmt --- src/state.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/state.rs b/src/state.rs index c46b2694..b4a3c9a6 100644 --- a/src/state.rs +++ b/src/state.rs @@ -337,10 +337,11 @@ impl Lua { R::from_stack_multi(nresults, &lua) } - /// Runs callback with the inner RawLua value. It can be used to manually push and get values on the stack. + /// Runs callback with the inner RawLua value. It can be used to manually push and get values on + /// the stack. /// /// This function is safe because all unsafe actions with RawLua can only be done with unsafe - /// + /// /// # Example /// ``` /// # use mlua::{Lua, Result, FromLua, IntoLua}; From e67ae7f0de3b08dd46bd68a43cf4cf26326a31b0 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 27 Jan 2026 12:17:40 +0000 Subject: [PATCH 590/635] Make `RawLua::{push,push_value,pop_value}` public --- src/state/raw.rs | 7 ++++--- src/util/error.rs | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index a24fec35..8ab2d6e9 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -732,14 +732,14 @@ impl RawLua { /// /// Uses up to 2 stack spaces to push a single value, does not call `checkstack`. #[inline(always)] - pub(crate) unsafe fn push(&self, value: impl IntoLua) -> Result<()> { + pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> { value.push_into_stack(self) } /// Pushes a `Value` (by reference) onto the Lua stack. /// /// Uses 2 stack spaces, does not call `checkstack`. - pub(crate) unsafe fn push_value(&self, value: &Value) -> Result<()> { + pub unsafe fn push_value(&self, value: &Value) -> Result<()> { let state = self.state(); match value { Value::Nil => ffi::lua_pushnil(state), @@ -773,7 +773,8 @@ impl RawLua { /// Pops a value from the Lua stack. /// /// Uses up to 1 stack spaces, does not call `checkstack`. - pub(crate) unsafe fn pop_value(&self) -> Value { + #[inline] + pub unsafe fn pop_value(&self) -> Value { let value = self.stack_value(-1, None); ffi::lua_pop(self.state(), 1); value diff --git a/src/util/error.rs b/src/util/error.rs index 64c2481f..cba86e80 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -208,7 +208,7 @@ where F: FnOnce(*mut ffi::lua_State) -> R, R: Copy, { - let params = ffi::lua_touserdata(state, -1) as *mut Params; + let params = ffi::lua_tolightuserdata(state, -1) as *mut Params; ffi::lua_pop(state, 1); let f = (*params).function.take().unwrap(); @@ -239,7 +239,7 @@ where ffi::lua_pushlightuserdata(state, &mut params as *mut Params as *mut c_void); let ret = ffi::lua_pcall(state, nargs + 1, nresults, stack_start + 1); - ffi::lua_remove(state, stack_start + 1); + ffi::lua_remove(state, stack_start + 1); // remove error handler if ret == ffi::LUA_OK { // `LUA_OK` is only returned when the `do_call` function has completed successfully, so From 6bb7f099273c8d97d7bae8afcc78e8d5fea4464a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 27 Jan 2026 14:07:47 +0000 Subject: [PATCH 591/635] Don't use `luaL_typename` to get a static type name in Luau. In Luau this function returns heap-allocated string rather than static string, so accessing this value when Lua state is destroyed is UB. Fixes #674 --- src/conversion.rs | 3 ++- src/state/raw.rs | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index b02e861f..9ae5ce63 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -1211,7 +1211,8 @@ impl FromLua for Either { Err(_) => match R::from_stack(idx, lua).map(Either::Right) { Ok(r) => Ok(r), Err(_) => { - let value_type_name = CStr::from_ptr(ffi::luaL_typename(lua.state(), idx)); + let value_type_name = + CStr::from_ptr(ffi::lua_typename(lua.state(), ffi::lua_type(lua.state(), idx))); Err(Error::FromLuaConversionError { from: value_type_name.to_str().unwrap(), to: Self::type_name(), diff --git a/src/state/raw.rs b/src/state/raw.rs index 8ab2d6e9..13ce9d88 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -1219,7 +1219,10 @@ impl RawLua { Ok(type_id) => Ok(type_id), Err(Error::UserDataTypeMismatch) if ffi::lua_type(state, idx) != ffi::LUA_TUSERDATA => { // Report `FromLuaConversionError` instead - let idx_type_name = CStr::from_ptr(ffi::luaL_typename(state, idx)); + // In Luau `luaL_typename` return heap-allocated string that is valid only for + // the `state` lifetime. + // `lua_typename` is used instead to get a truly static string. + let idx_type_name = CStr::from_ptr(ffi::lua_typename(state, ffi::lua_type(state, idx))); let idx_type_name = idx_type_name.to_str().unwrap(); let message = format!("expected userdata of type '{}'", short_type_name::()); Err(Error::from_lua_conversion(idx_type_name, "userdata", message)) From 71757003c7a8b6e1e768c648cf273b81984f6822 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 27 Jan 2026 15:56:53 +0000 Subject: [PATCH 592/635] mlua-sys: v0.10.0 --- mlua-sys/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index dc82cdf2..3029566d 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.9.0" +version = "0.10.0" authors = ["Aleksandr Orlenko "] rust-version = "1.85" edition = "2024" From ec2ce3620f91230b9c279c7be24e908283558c6b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 27 Jan 2026 16:00:49 +0000 Subject: [PATCH 593/635] Some final Lua 5.5 updates --- Cargo.toml | 2 +- docs/release_notes/v0.9.md | 2 +- examples/module/Cargo.toml | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5873c9c9..fffd0e67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ with async/await features and support of writing native Lua modules in Rust. """ [package.metadata.docs.rs] -features = ["lua54", "vendored", "async", "send", "serde", "macros"] +features = ["lua55", "vendored", "async", "send", "serde", "macros"] rustdoc-args = ["--cfg", "docsrs"] [workspace] diff --git a/docs/release_notes/v0.9.md b/docs/release_notes/v0.9.md index cbc2d29b..7f3a0327 100644 --- a/docs/release_notes/v0.9.md +++ b/docs/release_notes/v0.9.md @@ -336,7 +336,7 @@ In previous mlua versions, building a Lua module for Windows requires having Lua In contrast, on Linux and macOS, modules can be built without any external dependencies using the `-undefined=dynamic_lookup` linker flag. With Rust 1.71+ it's now possible to lift this restriction for Windows as well. You can build modules normally and they will be linked with -`lua54.dll`/`lua53.dll`/`lua52.dll`/`lua51.dll` depending on the enabled Lua version. +`lua5x.dll` depending on the enabled Lua version. You still need to have the dll although, linked to application where the module will be loaded. diff --git a/examples/module/Cargo.toml b/examples/module/Cargo.toml index e15c4afd..588a4fea 100644 --- a/examples/module/Cargo.toml +++ b/examples/module/Cargo.toml @@ -10,6 +10,7 @@ crate-type = ["cdylib"] [workspace] [features] +lua55 = ["mlua/lua55"] lua54 = ["mlua/lua54"] lua53 = ["mlua/lua53"] lua52 = ["mlua/lua52"] From c10718ed2f236625fc70e356dcd196cf552d23c5 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 27 Jan 2026 16:04:33 +0000 Subject: [PATCH 594/635] Update CHANGELOG --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c3e1773..257ee8fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## v0.11.6 (Jan 27, 2026) + +- Added Lua 5.5 support (`lua55` feature flag) +- Luau updated to 0.705+ +- Added `AnyUserData::is_proxy` method to check if userdata is a proxy +- Added `num_params`, `num_upvalues`, `is_vararg` to `FunctionInfo` + ## v0.11.5 (Nov 22, 2025) - Luau updated to 0.701 From e7fa8d75bb3947648605562694d03c1ce4c82b20 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 27 Jan 2026 16:05:18 +0000 Subject: [PATCH 595/635] v0.11.6 --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fffd0e67..523ddb8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "mlua" -version = "0.11.5" # remember to update mlua_derive +version = "0.11.6" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] -rust-version = "1.80.0" +rust-version = "1.85.0" edition = "2021" repository = "https://github.com/mlua-rs/mlua" documentation = "https://docs.rs/mlua" @@ -64,7 +64,7 @@ anyhow = { version = "1.0", optional = true } rustversion = "1.0" libc = "0.2" -ffi = { package = "mlua-sys", version = "0.9.0", path = "mlua-sys" } +ffi = { package = "mlua-sys", version = "0.10.0", path = "mlua-sys" } [dev-dependencies] trybuild = "1.0" From a985dc7a37c18cbbf346da1100d7c62cd736af83 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 29 Jan 2026 10:07:05 +0000 Subject: [PATCH 596/635] Start 0.12.0-dev.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 523ddb8c..560ed962 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.11.6" # remember to update mlua_derive +version = "0.12.0-dev.1" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.85.0" edition = "2021" From 0c4206c97d78c3eef4e318c6248b2f14a65fba2b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 29 Jan 2026 10:13:04 +0000 Subject: [PATCH 597/635] Bump min Rust version to 1.88 --- Cargo.toml | 3 +-- mlua-sys/Cargo.toml | 2 +- src/memory.rs | 13 ------------- 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 560ed962..ccaff391 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "mlua" version = "0.12.0-dev.1" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] -rust-version = "1.85.0" +rust-version = "1.88" edition = "2021" repository = "https://github.com/mlua-rs/mlua" documentation = "https://docs.rs/mlua" @@ -61,7 +61,6 @@ erased-serde = { version = "0.4", optional = true } serde-value = { version = "0.7", optional = true } parking_lot = { version = "0.12", features = ["arc_lock"] } anyhow = { version = "1.0", optional = true } -rustversion = "1.0" libc = "0.2" ffi = { package = "mlua-sys", version = "0.10.0", path = "mlua-sys" } diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 3029566d..2230af75 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -2,7 +2,7 @@ name = "mlua-sys" version = "0.10.0" authors = ["Aleksandr Orlenko "] -rust-version = "1.85" +rust-version = "1.88" edition = "2024" repository = "https://github.com/mlua-rs/mlua" documentation = "https://docs.rs/mlua-sys" diff --git a/src/memory.rs b/src/memory.rs index e4c2ce6f..a484e277 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -28,9 +28,7 @@ impl MemoryState { } #[cfg(not(feature = "luau"))] - #[rustversion::since(1.85)] #[inline] - #[allow(clippy::incompatible_msrv)] pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { let mut mem_state = ptr::null_mut(); if !ptr::fn_addr_eq(ffi::lua_getallocf(state, &mut mem_state), ALLOCATOR) { @@ -39,17 +37,6 @@ impl MemoryState { mem_state as *mut MemoryState } - #[cfg(not(feature = "luau"))] - #[rustversion::before(1.85)] - #[inline] - pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { - let mut mem_state = ptr::null_mut(); - if ffi::lua_getallocf(state, &mut mem_state) != ALLOCATOR { - mem_state = ptr::null_mut(); - } - mem_state as *mut MemoryState - } - #[inline] pub(crate) fn used_memory(&self) -> usize { self.used_memory as usize From d9c139b55f9cb72479f65d10c8f435d002bbe3c4 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 29 Jan 2026 10:33:28 +0000 Subject: [PATCH 598/635] Rust 2024 --- Cargo.toml | 2 +- benches/benchmark.rs | 2 +- benches/serde.rs | 2 +- examples/async_http_client.rs | 2 +- examples/async_http_reqwest.rs | 2 +- examples/async_http_server.rs | 2 +- examples/async_tcp_server.rs | 2 +- examples/guided_tour.rs | 2 +- examples/userdata.rs | 2 +- src/buffer.rs | 6 +-- src/chunk.rs | 78 +++++++++++++++++----------------- src/debug.rs | 2 +- src/error.rs | 25 ++++++----- src/function.rs | 4 +- src/lib.rs | 3 +- src/luau/heap_dump.rs | 26 ++++++------ src/luau/mod.rs | 2 +- src/luau/require.rs | 2 +- src/luau/require/fs.rs | 8 ++-- src/multi.rs | 2 +- src/scope.rs | 2 +- src/serde/de.rs | 2 +- src/serde/ser.rs | 10 ++--- src/state.rs | 24 ++++------- src/state/extra.rs | 2 +- src/state/raw.rs | 22 +++++----- src/state/util.rs | 4 +- src/table.rs | 20 ++++----- src/thread.rs | 3 +- src/types/sync.rs | 2 +- src/types/value_ref.rs | 8 ++-- src/userdata.rs | 6 +-- src/userdata/cell.rs | 8 ++-- src/userdata/object.rs | 2 +- src/userdata/ref.rs | 2 +- src/userdata/registry.rs | 6 +-- src/util/error.rs | 6 +-- src/util/mod.rs | 16 +++---- src/util/path.rs | 2 +- src/util/userdata.rs | 2 +- src/value.rs | 2 +- tests/conversion.rs | 28 ++++++------ tests/luau.rs | 2 +- tests/luau/require.rs | 18 +++++--- tests/tests.rs | 25 ++++++----- tests/userdata.rs | 7 +-- tests/value.rs | 8 ++-- 47 files changed, 212 insertions(+), 203 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ccaff391..7eceb7f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "mlua" version = "0.12.0-dev.1" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.88" -edition = "2021" +edition = "2024" repository = "https://github.com/mlua-rs/mlua" documentation = "https://docs.rs/mlua" readme = "README.md" diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 297bb1d6..3e3bcb96 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -1,7 +1,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::Duration; -use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; +use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; use tokio::runtime::Runtime; use tokio::task; diff --git a/benches/serde.rs b/benches/serde.rs index 1dc26c08..002061ac 100644 --- a/benches/serde.rs +++ b/benches/serde.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; +use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; use mlua::prelude::*; diff --git a/examples/async_http_client.rs b/examples/async_http_client.rs index 86568127..a2d9ed83 100644 --- a/examples/async_http_client.rs +++ b/examples/async_http_client.rs @@ -5,7 +5,7 @@ use hyper::body::Incoming; use hyper_util::client::legacy::Client as HyperClient; use hyper_util::rt::TokioExecutor; -use mlua::{chunk, ExternalResult, Lua, Result, UserData, UserDataMethods}; +use mlua::{ExternalResult, Lua, Result, UserData, UserDataMethods, chunk}; struct BodyReader(Incoming); diff --git a/examples/async_http_reqwest.rs b/examples/async_http_reqwest.rs index 2021e849..91206d4a 100644 --- a/examples/async_http_reqwest.rs +++ b/examples/async_http_reqwest.rs @@ -1,4 +1,4 @@ -use mlua::{chunk, ExternalResult, Lua, LuaSerdeExt, Result, Value}; +use mlua::{ExternalResult, Lua, LuaSerdeExt, Result, Value, chunk}; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { diff --git a/examples/async_http_server.rs b/examples/async_http_server.rs index 244a8e4e..f5057ed6 100644 --- a/examples/async_http_server.rs +++ b/examples/async_http_server.rs @@ -11,7 +11,7 @@ use hyper::{Request, Response}; use hyper_util::rt::TokioIo; use tokio::net::TcpListener; -use mlua::{chunk, Error as LuaError, Function, Lua, String as LuaString, Table, UserData, UserDataMethods}; +use mlua::{Error as LuaError, Function, Lua, String as LuaString, Table, UserData, UserDataMethods, chunk}; /// Wrapper around incoming request that implements UserData struct LuaRequest(SocketAddr, Request); diff --git a/examples/async_tcp_server.rs b/examples/async_tcp_server.rs index 7d96dbe2..c78d9d8e 100644 --- a/examples/async_tcp_server.rs +++ b/examples/async_tcp_server.rs @@ -4,7 +4,7 @@ use std::net::SocketAddr; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream}; -use mlua::{chunk, BString, Function, Lua, UserData, UserDataMethods}; +use mlua::{BString, Function, Lua, UserData, UserDataMethods, chunk}; struct LuaTcpStream(TcpStream); diff --git a/examples/guided_tour.rs b/examples/guided_tour.rs index dd4c649a..ba8b3ac4 100644 --- a/examples/guided_tour.rs +++ b/examples/guided_tour.rs @@ -1,7 +1,7 @@ use std::f32; use std::iter::FromIterator; -use mlua::{chunk, FromLua, Function, Lua, MetaMethod, Result, UserData, UserDataMethods, Value, Variadic}; +use mlua::{FromLua, Function, Lua, MetaMethod, Result, UserData, UserDataMethods, Value, Variadic, chunk}; fn main() -> Result<()> { // You can create a new Lua state with `Lua::new()`. This loads the default Lua std library diff --git a/examples/userdata.rs b/examples/userdata.rs index a5cc8ba4..6a21e90b 100644 --- a/examples/userdata.rs +++ b/examples/userdata.rs @@ -1,4 +1,4 @@ -use mlua::{chunk, Lua, MetaMethod, Result, UserData}; +use mlua::{Lua, MetaMethod, Result, UserData, chunk}; #[derive(Default)] struct Rectangle { diff --git a/src/buffer.rs b/src/buffer.rs index f0977ccf..070391fe 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -97,7 +97,7 @@ struct BufferCursor(Buffer, usize); impl io::Read for BufferCursor { fn read(&mut self, buf: &mut [u8]) -> io::Result { - let lua = self.0 .0.lua.lock(); + let lua = self.0.0.lua.lock(); let data = self.0.as_slice(&lua); if self.1 == data.len() { return Ok(0); @@ -111,7 +111,7 @@ impl io::Read for BufferCursor { impl io::Write for BufferCursor { fn write(&mut self, buf: &[u8]) -> io::Result { - let lua = self.0 .0.lua.lock(); + let lua = self.0.0.lua.lock(); let data = self.0.as_slice_mut(&lua); if self.1 == data.len() { return Ok(0); @@ -129,7 +129,7 @@ impl io::Write for BufferCursor { impl io::Seek for BufferCursor { fn seek(&mut self, pos: io::SeekFrom) -> io::Result { - let lua = self.0 .0.lua.lock(); + let lua = self.0.0.lua.lock(); let data = self.0.as_slice(&lua); let new_offset = match pos { io::SeekFrom::Start(offset) => offset as i64, diff --git a/src/chunk.rs b/src/chunk.rs index 5dd92640..23616771 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -477,11 +477,11 @@ impl Compiler { options.mutableGlobals = mutable_globals_ptr; options.userdataTypes = userdata_types_ptr; options.librariesWithKnownMembers = libraries_with_known_members_ptr; - if let Some(map) = self.library_constants.as_ref() { - if !self.libraries_with_known_members.is_empty() { - LIBRARY_MEMBER_CONSTANT_MAP.with_borrow_mut(|gmap| *gmap = map.clone()); - options.libraryMemberConstantCallback = Some(library_member_constant_callback); - } + if let Some(map) = self.library_constants.as_ref() + && !self.libraries_with_known_members.is_empty() + { + LIBRARY_MEMBER_CONSTANT_MAP.with_borrow_mut(|gmap| *gmap = map.clone()); + options.libraryMemberConstantCallback = Some(library_member_constant_callback); } options.disabledBuiltins = disabled_builtins_ptr; ffi::luau_compile(source.as_ref(), options) @@ -662,19 +662,19 @@ impl Chunk<'_> { /// /// It does nothing if the chunk is already binary or invalid. fn compile(&mut self) { - if let Ok(ref source) = self.source { - if self.detect_mode() == ChunkMode::Text { - #[cfg(feature = "luau")] - if let Ok(data) = self.compiler.get_or_insert_with(Default::default).compile(source) { - self.source = Ok(Cow::Owned(data)); - self.mode = Some(ChunkMode::Binary); - } - #[cfg(not(feature = "luau"))] - if let Ok(func) = self.lua.lock().load_chunk(None, None, None, source.as_ref()) { - let data = func.dump(false); - self.source = Ok(Cow::Owned(data)); - self.mode = Some(ChunkMode::Binary); - } + if let Ok(ref source) = self.source + && self.detect_mode() == ChunkMode::Text + { + #[cfg(feature = "luau")] + if let Ok(data) = self.compiler.get_or_insert_with(Default::default).compile(source) { + self.source = Ok(Cow::Owned(data)); + self.mode = Some(ChunkMode::Binary); + } + #[cfg(not(feature = "luau"))] + if let Ok(func) = self.lua.lock().load_chunk(None, None, None, source.as_ref()) { + let data = func.dump(false); + self.source = Ok(Cow::Owned(data)); + self.mode = Some(ChunkMode::Binary); } } } @@ -687,33 +687,33 @@ impl Chunk<'_> { // Try to fetch compiled chunk from cache let mut text_source = None; - if let Ok(ref source) = self.source { - if self.detect_mode() == ChunkMode::Text { - let lua = self.lua.lock(); - if let Some(cache) = lua.priv_app_data_ref::() { - if let Some(data) = cache.0.get(source.as_ref()) { - self.source = Ok(Cow::Owned(data.clone())); - self.mode = Some(ChunkMode::Binary); - return self; - } - } - text_source = Some(source.as_ref().to_vec()); + if let Ok(ref source) = self.source + && self.detect_mode() == ChunkMode::Text + { + let lua = self.lua.lock(); + if let Some(cache) = lua.priv_app_data_ref::() + && let Some(data) = cache.0.get(source.as_ref()) + { + self.source = Ok(Cow::Owned(data.clone())); + self.mode = Some(ChunkMode::Binary); + return self; } + text_source = Some(source.as_ref().to_vec()); } // Compile and cache the chunk if let Some(text_source) = text_source { self.compile(); - if let Ok(ref binary_source) = self.source { - if self.detect_mode() == ChunkMode::Binary { - let lua = self.lua.lock(); - if let Some(mut cache) = lua.priv_app_data_mut::() { - cache.0.insert(text_source, binary_source.to_vec()); - } else { - let mut cache = ChunksCache(HashMap::new()); - cache.0.insert(text_source, binary_source.to_vec()); - lua.set_priv_app_data(cache); - }; + if let Ok(ref binary_source) = self.source + && self.detect_mode() == ChunkMode::Binary + { + let lua = self.lua.lock(); + if let Some(mut cache) = lua.priv_app_data_mut::() { + cache.0.insert(text_source, binary_source.to_vec()); + } else { + let mut cache = ChunksCache(HashMap::new()); + cache.0.insert(text_source, binary_source.to_vec()); + lua.set_priv_app_data(cache); } } } diff --git a/src/debug.rs b/src/debug.rs index e37b0cf4..6c0ba6bb 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -5,7 +5,7 @@ use ffi::{lua_Debug, lua_State}; use crate::function::Function; use crate::state::RawLua; -use crate::util::{assert_stack, linenumber_to_usize, ptr_to_lossy_str, ptr_to_str, StackGuard}; +use crate::util::{StackGuard, assert_stack, linenumber_to_usize, ptr_to_lossy_str, ptr_to_str}; /// Contains information about currently executing Lua code. /// diff --git a/src/error.rs b/src/error.rs index c20a39ef..29ee9fcb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -225,7 +225,7 @@ impl fmt::Display for Error { } Error::SafetyError(msg) => { write!(fmt, "safety error: {msg}") - }, + } Error::MemoryControlNotAvailable => { write!(fmt, "memory control is not available") } @@ -238,10 +238,7 @@ impl fmt::Display for Error { fmt, "out of Lua stack, too many arguments to a Lua function or too many return values from a callback" ), - Error::BindError => write!( - fmt, - "too many arguments to Function::bind" - ), + Error::BindError => write!(fmt, "too many arguments to Function::bind"), Error::BadArgument { to, pos, name, cause } => { if let Some(name) = name { write!(fmt, "bad argument `{name}`")?; @@ -252,7 +249,7 @@ impl fmt::Display for Error { write!(fmt, " to `{to}`")?; } write!(fmt, ": {cause}") - }, + } Error::ToLuaConversionError { from, to, message } => { write!(fmt, "error converting {from} to Lua {to}")?; match message { @@ -273,7 +270,11 @@ impl fmt::Display for Error { Error::UserDataBorrowError => write!(fmt, "error borrowing userdata"), Error::UserDataBorrowMutError => write!(fmt, "error mutably borrowing userdata"), Error::MetaMethodRestricted(method) => write!(fmt, "metamethod {method} is restricted"), - Error::MetaMethodTypeError { method, type_name, message } => { + Error::MetaMethodTypeError { + method, + type_name, + message, + } => { write!(fmt, "metamethod {method} has unsupported type {type_name}")?; match message { None => Ok(()), @@ -286,7 +287,11 @@ impl fmt::Display for Error { Error::CallbackError { cause, traceback } => { // Trace errors down to the root let (mut cause, mut full_traceback) = (cause, None); - while let Error::CallbackError { cause: cause2, traceback: traceback2 } = &**cause { + while let Error::CallbackError { + cause: cause2, + traceback: traceback2, + } = &**cause + { cause = cause2; full_traceback = Some(traceback2); } @@ -312,11 +317,11 @@ impl fmt::Display for Error { #[cfg(feature = "serde")] Error::SerializeError(err) => { write!(fmt, "serialize error: {err}") - }, + } #[cfg(feature = "serde")] Error::DeserializeError(err) => { write!(fmt, "deserialize error: {err}") - }, + } Error::ExternalError(err) => err.fmt(fmt), Error::WithContext { context, cause } => { writeln!(fmt, "{context}")?; diff --git a/src/function.rs b/src/function.rs index f967ca84..24fc34de 100644 --- a/src/function.rs +++ b/src/function.rs @@ -8,7 +8,7 @@ use crate::table::Table; use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut}; use crate::types::{Callback, LuaType, MaybeSend, ValueRef}; use crate::util::{ - assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str, StackGuard, + StackGuard, assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str, }; use crate::value::Value; @@ -18,7 +18,7 @@ use { crate::traits::LuaNativeAsyncFn, crate::types::AsyncCallback, std::future::{self, Future}, - std::pin::{pin, Pin}, + std::pin::{Pin, pin}, std::task::{Context, Poll}, }; diff --git a/src/lib.rs b/src/lib.rs index 61df03af..01c68b9e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,6 @@ // warnings at all. #![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(not(send), allow(clippy::arc_with_non_send_sync))] -#![allow(clippy::ptr_eq)] #![allow(unsafe_op_in_unsafe_fn)] #[macro_use] @@ -143,7 +142,7 @@ pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn}; #[cfg(feature = "serde")] #[doc(inline)] pub use crate::{ - serde::{de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt}, + serde::{LuaSerdeExt, de::Options as DeserializeOptions, ser::Options as SerializeOptions}, value::SerializableValue, }; diff --git a/src/luau/heap_dump.rs b/src/luau/heap_dump.rs index 6920cad6..0189845c 100644 --- a/src/luau/heap_dump.rs +++ b/src/luau/heap_dump.rs @@ -79,10 +79,10 @@ impl HeapDump { let mut size_by_type = HashMap::new(); let objects = self.data["objects"].as_object()?; for obj in objects.values() { - if let Some(cat_id) = category_id { - if obj["cat"].as_i64()? != cat_id { - continue; - } + if let Some(cat_id) = category_id + && obj["cat"].as_i64()? != cat_id + { + continue; } update_size(&mut size_by_type, obj["type"].as_str()?, obj["size"].as_u64()?); } @@ -123,18 +123,18 @@ impl HeapDump { if obj["type"] != "userdata" { continue; } - if let Some(cat_id) = category_id { - if obj["cat"].as_i64()? != cat_id { - continue; - } + if let Some(cat_id) = category_id + && obj["cat"].as_i64()? != cat_id + { + continue; } // Determine userdata type from metatable let mut ud_type = "unknown"; - if let Some(metatable_addr) = obj["metatable"].as_str() { - if let Some(t) = get_key(objects, &objects[metatable_addr], "__type") { - ud_type = t; - } + if let Some(metatable_addr) = obj["metatable"].as_str() + && let Some(t) = get_key(objects, &objects[metatable_addr], "__type") + { + ud_type = t; } update_size(&mut size_by_userdata, ud_type, obj["size"].as_u64()?); } @@ -155,7 +155,7 @@ impl HeapDump { /// Updates the size mapping for a given key. fn update_size(size_type: &mut HashMap, key: K, size: u64) { - let (ref mut count, ref mut total_size) = size_type.entry(key).or_insert((0, 0)); + let (count, total_size) = size_type.entry(key).or_insert((0, 0)); *count += 1; *total_size += size; } diff --git a/src/luau/mod.rs b/src/luau/mod.rs index 88113e81..4015a7c4 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -5,7 +5,7 @@ use std::ptr; use crate::chunk::ChunkMode; use crate::error::{Error, Result}; use crate::function::Function; -use crate::state::{callback_error_ext, ExtraData, Lua}; +use crate::state::{ExtraData, Lua, callback_error_ext}; use crate::traits::{FromLuaMulti, IntoLua}; use crate::types::MaybeSend; diff --git a/src/luau/require.rs b/src/luau/require.rs index 27dd8b4b..86b23a12 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -8,7 +8,7 @@ use std::{fmt, mem, ptr}; use crate::error::{Error, Result}; use crate::function::Function; -use crate::state::{callback_error_ext, Lua}; +use crate::state::{Lua, callback_error_ext}; use crate::table::Table; use crate::types::MaybeSend; diff --git a/src/luau/require/fs.rs b/src/luau/require/fs.rs index 4e7261bf..6588b02c 100644 --- a/src/luau/require/fs.rs +++ b/src/luau/require/fs.rs @@ -42,10 +42,10 @@ impl TextRequirer { } fn normalize_chunk_name(chunk_name: &str) -> &str { - if let Some((path, line)) = chunk_name.rsplit_once(':') { - if line.parse::().is_ok() { - return path; - } + if let Some((path, line)) = chunk_name.rsplit_once(':') + && line.parse::().is_ok() + { + return path; } chunk_name } diff --git a/src/multi.rs b/src/multi.rs index f67b7119..f82f4cb5 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -1,4 +1,4 @@ -use std::collections::{vec_deque, VecDeque}; +use std::collections::{VecDeque, vec_deque}; use std::iter::FromIterator; use std::mem; use std::ops::{Deref, DerefMut}; diff --git a/src/scope.rs b/src/scope.rs index c56647a4..fa8cafaf 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -8,7 +8,7 @@ use crate::state::{Lua, LuaGuard, RawLua}; use crate::traits::{FromLuaMulti, IntoLuaMulti}; use crate::types::{Callback, CallbackUpvalue, ScopedCallback, ValueRef}; use crate::userdata::{AnyUserData, UserData, UserDataRegistry, UserDataStorage}; -use crate::util::{self, check_stack, get_metatable_ptr, get_userdata, take_userdata, StackGuard}; +use crate::util::{self, StackGuard, check_stack, get_metatable_ptr, get_userdata, take_userdata}; /// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and /// callbacks that are not required to be `Send` or `'static`. diff --git a/src/serde/de.rs b/src/serde/de.rs index f1ae4062..d9163608 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -250,7 +250,7 @@ impl<'de> serde::Deserializer<'de> for Deserializer { return Err(de::Error::invalid_value( de::Unexpected::Map, &"map with a single key", - )) + )); } }; diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 7b40dfc3..d14a7189 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -1,6 +1,6 @@ //! Serialize a Rust data structure into Lua value. -use serde::{ser, Serialize}; +use serde::{Serialize, ser}; use super::LuaSerdeExt; use crate::error::{Error, Result}; @@ -531,10 +531,10 @@ impl ser::SerializeStruct for SerializeStruct<'_> { Some(table @ Value::Table(_)) => Ok(table), Some(value @ Value::String(_)) if self.options.detect_serde_json_arbitrary_precision => { let number_s = value.to_string()?; - if number_s.contains(['.', 'e', 'E']) { - if let Ok(number) = number_s.parse().map(Value::Number) { - return Ok(number); - } + if number_s.contains(['.', 'e', 'E']) + && let Ok(number) = number_s.parse().map(Value::Number) + { + return Ok(number); } Ok(number_s .parse() diff --git a/src/state.rs b/src/state.rs index b4a3c9a6..e8e67f81 100644 --- a/src/state.rs +++ b/src/state.rs @@ -24,7 +24,7 @@ use crate::types::{ ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak, }; use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataStorage}; -use crate::util::{assert_stack, check_stack, protect_lua_closure, push_string, rawset_field, StackGuard}; +use crate::util::{StackGuard, assert_stack, check_stack, protect_lua_closure, push_string, rawset_field}; use crate::value::{Nil, Value}; #[cfg(not(feature = "luau"))] @@ -1127,7 +1127,7 @@ impl Lua { #[cfg(not(feature = "luau"))] let _ = step_size; // Ignored - return GCMode::Incremental; + GCMode::Incremental } #[cfg(feature = "lua55")] @@ -1205,10 +1205,10 @@ impl Lua { #[doc(hidden)] #[allow(clippy::result_unit_err)] pub fn set_fflag(name: &str, enabled: bool) -> StdResult<(), ()> { - if let Ok(name) = std::ffi::CString::new(name) { - if unsafe { ffi::luau_setfflag(name.as_ptr(), enabled as c_int) != 0 } { - return Ok(()); - } + if let Ok(name) = std::ffi::CString::new(name) + && unsafe { ffi::luau_setfflag(name.as_ptr(), enabled as c_int) != 0 } + { + return Ok(()); } Err(()) } @@ -1785,11 +1785,7 @@ impl Lua { lua.push_value(&v)?; let mut isint = 0; let i = ffi::lua_tointegerx(state, -1, &mut isint); - if isint == 0 { - None - } else { - Some(i) - } + if isint == 0 { None } else { Some(i) } }, }) } @@ -1811,11 +1807,7 @@ impl Lua { lua.push_value(&v)?; let mut isnum = 0; let n = ffi::lua_tonumberx(state, -1, &mut isnum); - if isnum == 0 { - None - } else { - Some(n) - } + if isnum == 0 { None } else { Some(n) } }, }) } diff --git a/src/state/extra.rs b/src/state/extra.rs index d021ca45..d761c9cb 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -14,7 +14,7 @@ use crate::state::RawLua; use crate::stdlib::StdLib; use crate::types::{AppData, ReentrantMutex, XRc}; use crate::userdata::RawUserDataRegistry; -use crate::util::{get_internal_metatable, push_internal_userdata, TypeKey, WrappedFailure}; +use crate::util::{TypeKey, WrappedFailure, get_internal_metatable, push_internal_userdata}; #[cfg(any(feature = "luau", doc))] use crate::chunk::Compiler; diff --git a/src/state/raw.rs b/src/state/raw.rs index 13ce9d88..d3bc70ca 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -10,7 +10,7 @@ use std::sync::Arc; use crate::chunk::ChunkMode; use crate::error::{Error, Result}; use crate::function::Function; -use crate::memory::{MemoryState, ALLOCATOR}; +use crate::memory::{ALLOCATOR, MemoryState}; use crate::state::util::callback_error_ext; use crate::stdlib::StdLib; use crate::string::String; @@ -22,14 +22,14 @@ use crate::types::{ LuaType, MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc, }; use crate::userdata::{ - init_userdata_metatable, AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, - UserDataStorage, + AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, UserDataStorage, + init_userdata_metatable, }; use crate::util::{ - assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state, - get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, pop_error, - push_internal_userdata, push_string, push_table, push_userdata, rawset_field, safe_pcall, safe_xpcall, - short_type_name, StackGuard, WrappedFailure, + StackGuard, WrappedFailure, assert_stack, check_stack, get_destructed_userdata_metatable, + get_internal_userdata, get_main_state, get_metatable_ptr, get_userdata, init_error_registry, + init_internal_metatable, pop_error, push_internal_userdata, push_string, push_table, push_userdata, + rawset_field, safe_pcall, safe_xpcall, short_type_name, }; use crate::value::{Nil, Value}; @@ -681,10 +681,10 @@ impl RawLua { #[cfg(feature = "async")] pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) { let extra = &mut *self.extra.get(); - if extra.thread_pool.len() < extra.thread_pool.capacity() { - if let Some(index) = thread.0.index_count.take() { - extra.thread_pool.push(index); - } + if extra.thread_pool.len() < extra.thread_pool.capacity() + && let Some(index) = thread.0.index_count.take() + { + extra.thread_pool.push(index); } } diff --git a/src/state/util.rs b/src/state/util.rs index 5c8a0afb..fdbc5cf4 100644 --- a/src/state/util.rs +++ b/src/state/util.rs @@ -1,11 +1,11 @@ use std::os::raw::c_int; -use std::panic::{catch_unwind, AssertUnwindSafe}; +use std::panic::{AssertUnwindSafe, catch_unwind}; use std::ptr; use std::sync::Arc; use crate::error::{Error, Result}; use crate::state::{ExtraData, RawLua}; -use crate::util::{self, get_internal_metatable, WrappedFailure}; +use crate::util::{self, WrappedFailure, get_internal_metatable}; struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State); diff --git a/src/table.rs b/src/table.rs index 4b184838..f7dcb2c8 100644 --- a/src/table.rs +++ b/src/table.rs @@ -9,7 +9,7 @@ use crate::function::Function; use crate::state::{LuaGuard, RawLua, WeakLua}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike}; use crate::types::{Integer, ValueRef}; -use crate::util::{assert_stack, check_stack, get_metatable_ptr, StackGuard}; +use crate::util::{StackGuard, assert_stack, check_stack, get_metatable_ptr}; use crate::value::{Nil, Value}; #[cfg(feature = "async")] @@ -226,15 +226,15 @@ impl Table { // Compare using `__eq` metamethod if exists // First, check the self for the metamethod. // If self does not define it, then check the other table. - if let Some(mt) = self.metatable() { - if mt.contains_key("__eq")? { - return mt.get::("__eq")?.call((self, other)); - } + if let Some(mt) = self.metatable() + && let Some(eq_func) = mt.get::>("__eq")? + { + return eq_func.call((self, other)); } - if let Some(mt) = other.metatable() { - if mt.contains_key("__eq")? { - return mt.get::("__eq")?.call((self, other)); - } + if let Some(mt) = other.metatable() + && let Some(eq_func) = mt.get::>("__eq")? + { + return eq_func.call((self, other)); } Ok(false) @@ -1070,7 +1070,7 @@ impl Serialize for SerializableTable<'_> { where S: Serializer, { - use crate::serde::de::{check_value_for_skip, MapPairs, RecursionGuard}; + use crate::serde::de::{MapPairs, RecursionGuard, check_value_for_skip}; use crate::value::SerializableValue; let convert_result = |res: Result<()>, serialize_err: Option| match res { diff --git a/src/thread.rs b/src/thread.rs index ee2f98c9..6941dc65 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -6,7 +6,7 @@ use crate::function::Function; use crate::state::RawLua; use crate::traits::{FromLuaMulti, IntoLuaMulti}; use crate::types::{LuaType, ValueRef}; -use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard}; +use crate::util::{StackGuard, check_stack, error_traceback_thread, pop_error}; #[cfg(not(feature = "luau"))] use crate::{ @@ -523,6 +523,7 @@ impl AsyncThread { #[cfg(feature = "async")] impl Drop for AsyncThread { fn drop(&mut self) { + #[allow(clippy::collapsible_if)] if self.recycle { if let Some(lua) = self.thread.0.lua.try_lock() { unsafe { diff --git a/src/types/sync.rs b/src/types/sync.rs index 9aa3b4ff..753755cf 100644 --- a/src/types/sync.rs +++ b/src/types/sync.rs @@ -69,7 +69,7 @@ mod inner { #[inline(always)] fn deref(&self) -> &Self::Target { - &self.0 .0 + &self.0.0 } } } diff --git a/src/types/value_ref.rs b/src/types/value_ref.rs index b88a391c..564c28f0 100644 --- a/src/types/value_ref.rs +++ b/src/types/value_ref.rs @@ -55,10 +55,10 @@ impl Drop for ValueRef { if let Some(ValueRefIndex(index)) = self.index_count.take() { // It's guaranteed that the inner value returns exactly once. // This means in particular that the value is not dropped. - if XRc::into_inner(index).is_some() { - if let Some(lua) = self.lua.try_lock() { - unsafe { lua.drop_ref(self) }; - } + if XRc::into_inner(index).is_some() + && let Some(lua) = self.lua.try_lock() + { + unsafe { lua.drop_ref(self) } } } } diff --git a/src/userdata.rs b/src/userdata.rs index 493e04b1..98fdbbbf 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -12,7 +12,7 @@ use crate::string::String; use crate::table::{Table, TablePairs}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::types::{MaybeSend, ValueRef}; -use crate::util::{check_stack, get_userdata, push_string, short_type_name, take_userdata, StackGuard}; +use crate::util::{StackGuard, check_stack, get_userdata, push_string, short_type_name, take_userdata}; use crate::value::Value; #[cfg(feature = "async")] @@ -30,8 +30,8 @@ pub use r#ref::{UserDataRef, UserDataRefMut}; pub use registry::UserDataRegistry; pub(crate) use registry::{RawUserDataRegistry, UserDataProxy}; pub(crate) use util::{ - borrow_userdata_scoped, borrow_userdata_scoped_mut, collect_userdata, init_userdata_metatable, - TypeIdHints, + TypeIdHints, borrow_userdata_scoped, borrow_userdata_scoped_mut, collect_userdata, + init_userdata_metatable, }; /// Kinds of metamethods that can be overridden. diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index f70399cd..58f72465 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -173,10 +173,10 @@ pub(crate) enum ScopedUserDataVariant { impl Drop for ScopedUserDataVariant { #[inline] fn drop(&mut self) { - if let Self::Boxed(value) = self { - if let Ok(value) = value.try_borrow_mut() { - unsafe { drop(Box::from_raw(*value)) }; - } + if let Self::Boxed(value) = self + && let Ok(value) = value.try_borrow_mut() + { + unsafe { drop(Box::from_raw(*value)) } } } } diff --git a/src/userdata/object.rs b/src/userdata/object.rs index cc9543eb..e16e312d 100644 --- a/src/userdata/object.rs +++ b/src/userdata/object.rs @@ -1,12 +1,12 @@ use std::string::String as StdString; +use crate::Function; use crate::error::{Error, Result}; use crate::state::WeakLua; use crate::table::Table; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike}; use crate::userdata::AnyUserData; use crate::value::Value; -use crate::Function; #[cfg(feature = "async")] use crate::function::AsyncCallFuture; diff --git a/src/userdata/ref.rs b/src/userdata/ref.rs index dde89c2b..0661c129 100644 --- a/src/userdata/ref.rs +++ b/src/userdata/ref.rs @@ -1,4 +1,4 @@ -use std::any::{type_name, TypeId}; +use std::any::{TypeId, type_name}; use std::ops::{Deref, DerefMut}; use std::os::raw::c_int; use std::{fmt, mem}; diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index b405a49b..b37aa71d 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -11,8 +11,8 @@ use crate::state::{Lua, LuaGuard}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::types::{Callback, MaybeSend}; use crate::userdata::{ - borrow_userdata_scoped, borrow_userdata_scoped_mut, AnyUserData, MetaMethod, TypeIdHints, UserData, - UserDataFields, UserDataMethods, UserDataStorage, + AnyUserData, MetaMethod, TypeIdHints, UserData, UserDataFields, UserDataMethods, UserDataStorage, + borrow_userdata_scoped, borrow_userdata_scoped_mut, }; use crate::util::short_type_name; use crate::value::Value; @@ -368,7 +368,7 @@ impl UserDataRegistry { method: name.to_string(), type_name: value.type_name(), message: Some("expected nil, table or function".to_string()), - }) + }); } } } diff --git a/src/util/error.rs b/src/util/error.rs index cba86e80..297754d0 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -2,15 +2,15 @@ use std::any::Any; use std::fmt::Write as _; use std::mem::MaybeUninit; use std::os::raw::{c_int, c_void}; -use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe}; +use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind}; use std::ptr; use std::sync::Arc; use crate::error::{Error, Result}; use crate::memory::MemoryState; use crate::util::{ - check_stack, get_internal_userdata, init_internal_metatable, push_internal_userdata, push_string, - push_table, rawset_field, to_string, TypeKey, DESTRUCTED_USERDATA_METATABLE, + DESTRUCTED_USERDATA_METATABLE, TypeKey, check_stack, get_internal_userdata, init_internal_metatable, + push_internal_userdata, push_string, push_table, rawset_field, to_string, }; static WRAPPED_FAILURE_TYPE_KEY: u8 = 0; diff --git a/src/util/mod.rs b/src/util/mod.rs index 89057084..0741f12c 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -6,16 +6,16 @@ use std::{ptr, slice, str}; use crate::error::{Error, Result}; pub(crate) use error::{ - error_traceback, error_traceback_thread, init_error_registry, pop_error, protect_lua_call, - protect_lua_closure, WrappedFailure, + WrappedFailure, error_traceback, error_traceback_thread, init_error_registry, pop_error, + protect_lua_call, protect_lua_closure, }; pub(crate) use path::parse_path as parse_lookup_path; pub(crate) use short_names::short_type_name; pub(crate) use types::TypeKey; pub(crate) use userdata::{ - get_destructed_userdata_metatable, get_internal_metatable, get_internal_userdata, get_userdata, - init_internal_metatable, push_internal_userdata, push_userdata, take_userdata, - DESTRUCTED_USERDATA_METATABLE, + DESTRUCTED_USERDATA_METATABLE, get_destructed_userdata_metatable, get_internal_metatable, + get_internal_userdata, get_userdata, init_internal_metatable, push_internal_userdata, push_userdata, + take_userdata, }; #[cfg(not(feature = "luau"))] @@ -264,11 +264,7 @@ pub(crate) unsafe fn get_main_state(state: *mut ffi::lua_State) -> Option<*mut f // Check the current state first let is_main_state = ffi::lua_pushthread(state) == 1; ffi::lua_pop(state, 1); - if is_main_state { - Some(state) - } else { - None - } + if is_main_state { Some(state) } else { None } } #[cfg(feature = "luau")] Some(ffi::lua_mainthread(state)) diff --git a/src/util/path.rs b/src/util/path.rs index 35cd1f21..b1381e12 100644 --- a/src/util/path.rs +++ b/src/util/path.rs @@ -196,7 +196,7 @@ fn unquote_string<'a>(path: &'a str, chars: &mut Peekable>) -> R #[cfg(test)] mod tests { - use super::{parse_path, PathKey}; + use super::{PathKey, parse_path}; #[test] fn test_parse_path() { diff --git a/src/util/userdata.rs b/src/util/userdata.rs index e55fc8c8..76a9507c 100644 --- a/src/util/userdata.rs +++ b/src/util/userdata.rs @@ -3,7 +3,7 @@ use std::{mem, ptr}; use crate::error::Result; use crate::userdata::collect_userdata; -use crate::util::{check_stack, get_metatable_ptr, push_table, rawset_field, TypeKey}; +use crate::util::{TypeKey, check_stack, get_metatable_ptr, push_table, rawset_field}; // Pushes the userdata and attaches a metatable with __gc method. // Internally uses 3 stack spaces, does not call checkstack. diff --git a/src/value.rs b/src/value.rs index ff3ed3a4..22f7ce8d 100644 --- a/src/value.rs +++ b/src/value.rs @@ -13,7 +13,7 @@ use crate::table::Table; use crate::thread::Thread; use crate::types::{Integer, LightUserData, Number, ValueRef}; use crate::userdata::AnyUserData; -use crate::util::{check_stack, StackGuard}; +use crate::util::{StackGuard, check_stack}; #[cfg(feature = "serde")] use { diff --git a/tests/conversion.rs b/tests/conversion.rs index 73228532..1931e3b5 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -708,9 +708,10 @@ fn test_either_from_lua() -> Result<()> { }, err => panic!("expected `Error::BadArgument`, got {err:?}"), } - assert!(err - .to_string() - .starts_with("bad argument #1: error converting Lua string to Either"),); + assert!( + err.to_string() + .starts_with("bad argument #1: error converting Lua string to Either"), + ); } err => panic!("expected `Error::CallbackError`, got {err:?}"), } @@ -736,15 +737,18 @@ fn test_char_from_lua() -> Result<()> { assert_eq!(lua.convert::("A")?, 'A'); assert_eq!(lua.convert::(65)?, 'A'); assert_eq!(lua.convert::(128175)?, '💯'); - assert!(lua - .convert::(5456324) - .is_err_and(|e| e.to_string().contains("integer out of range"))); - assert!(lua - .convert::("hello") - .is_err_and(|e| e.to_string().contains("expected string to have exactly one char"))); - assert!(lua - .convert::(HashMap::::new()) - .is_err_and(|e| e.to_string().contains("expected string or integer"))); + assert!( + lua.convert::(5456324) + .is_err_and(|e| e.to_string().contains("integer out of range")) + ); + assert!( + lua.convert::("hello") + .is_err_and(|e| e.to_string().contains("expected string to have exactly one char")) + ); + assert!( + lua.convert::(HashMap::::new()) + .is_err_and(|e| e.to_string().contains("expected string or integer")) + ); Ok(()) } diff --git a/tests/luau.rs b/tests/luau.rs index 2b597857..8f745768 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -3,8 +3,8 @@ use std::cell::Cell; use std::fmt::Debug; use std::os::raw::c_void; -use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering}; use mlua::{ Compiler, Error, Function, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, Vector, VmState, diff --git a/tests/luau/require.rs b/tests/luau/require.rs index 7dfd7a86..ba79fb6a 100644 --- a/tests/luau/require.rs +++ b/tests/luau/require.rs @@ -42,8 +42,10 @@ fn test_require_errors() { // Pass non-string to require let res = run_require(&lua, true); assert!(res.is_err()); - assert!((res.unwrap_err().to_string()) - .contains("bad argument #1 to 'require' (string expected, got boolean)")); + assert!( + (res.unwrap_err().to_string()) + .contains("bad argument #1 to 'require' (string expected, got boolean)") + ); // Require from loadstring let res = lua @@ -169,8 +171,10 @@ fn test_require_without_config() { "./tests/luau/require/without_config/ambiguous_file_requirer", ); assert!(res.is_err()); - assert!((res.unwrap_err().to_string()) - .contains("could not resolve child component \"dependency\" (ambiguous)")); + assert!( + (res.unwrap_err().to_string()) + .contains("could not resolve child component \"dependency\" (ambiguous)") + ); // RequireWithDirectoryAmbiguity let res = run_require( @@ -178,8 +182,10 @@ fn test_require_without_config() { "./tests/luau/require/without_config/ambiguous_directory_requirer", ); assert!(res.is_err()); - assert!((res.unwrap_err().to_string()) - .contains("could not resolve child component \"dependency\" (ambiguous)")); + assert!( + (res.unwrap_err().to_string()) + .contains("could not resolve child component \"dependency\" (ambiguous)") + ); // CheckCachedResult let res = run_require(&lua, "./tests/luau/require/without_config/validate_cache").unwrap(); diff --git a/tests/tests.rs b/tests/tests.rs index 41a6aa97..7a0c43f5 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,14 +1,14 @@ use std::collections::HashMap; #[cfg(not(target_arch = "wasm32"))] use std::iter::FromIterator; -use std::panic::{catch_unwind, AssertUnwindSafe}; +use std::panic::{AssertUnwindSafe, catch_unwind}; use std::string::String as StdString; use std::sync::Arc; use std::{error, f32, f64, fmt}; use mlua::{ - ffi, ChunkMode, Error, ExternalError, Function, Lua, LuaOptions, Nil, Result, StdLib, String, Table, - UserData, Value, Variadic, + ChunkMode, Error, ExternalError, Function, Lua, LuaOptions, Nil, Result, StdLib, String, Table, UserData, + Value, Variadic, ffi, }; #[test] @@ -684,10 +684,12 @@ fn test_pcall_xpcall() -> Result<()> { ))] assert_eq!(globals.get::("xpcall_error")?, "testerror"); #[cfg(feature = "lua51")] - assert!(globals - .get::("xpcall_error")? - .to_str()? - .ends_with(": testerror")); + assert!( + globals + .get::("xpcall_error")? + .to_str()? + .ends_with(": testerror") + ); // Make sure that weird xpcall error recursion at least doesn't cause unsafety or panics. lua.load( @@ -1070,10 +1072,11 @@ fn test_ref_stack_exhaustion() { Ok(()) })) { Ok(_) => panic!("no panic was detected"), - Err(p) => assert!(p - .downcast::() - .unwrap() - .starts_with("cannot create a Lua reference, out of auxiliary stack space")), + Err(p) => assert!( + p.downcast::() + .unwrap() + .starts_with("cannot create a Lua reference, out of auxiliary stack space") + ), } } diff --git a/tests/userdata.rs b/tests/userdata.rs index dc8ca8b1..ce1b7d16 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -291,8 +291,8 @@ fn test_gc_userdata() -> Result<()> { let lua = Lua::new(); lua.globals().set("userdata", MyUserdata { id: 123 })?; - assert!(lua - .load( + assert!( + lua.load( r#" local tbl = setmetatable({ userdata = userdata @@ -308,7 +308,8 @@ fn test_gc_userdata() -> Result<()> { "# ) .exec() - .is_err()); + .is_err() + ); Ok(()) } diff --git a/tests/value.rs b/tests/value.rs index ebe753dd..a8f282ad 100644 --- a/tests/value.rs +++ b/tests/value.rs @@ -259,9 +259,11 @@ fn test_value_conversions() -> Result<()> { assert!(Value::Table(lua.create_table()?).is_table()); assert!(Value::Table(lua.create_table()?).as_table().is_some()); assert!(Value::Function(lua.create_function(|_, ()| Ok(())).unwrap()).is_function()); - assert!(Value::Function(lua.create_function(|_, ()| Ok(())).unwrap()) - .as_function() - .is_some()); + assert!( + Value::Function(lua.create_function(|_, ()| Ok(())).unwrap()) + .as_function() + .is_some() + ); assert!(Value::Thread(lua.create_thread(lua.load("function() end").eval()?)?).is_thread()); assert!( Value::Thread(lua.create_thread(lua.load("function() end").eval()?)?) From c1ffd4e790da6db2b9652d799519ecf9648e4782 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 29 Jan 2026 10:46:58 +0000 Subject: [PATCH 599/635] Replace `get_or_insert_with` with `get_or_insert_default` --- src/chunk.rs | 4 ++-- src/state/raw.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index 23616771..28e5a78e 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -366,7 +366,7 @@ impl Compiler { self.libraries_with_known_members.push(lib.clone()); } self.library_constants - .get_or_insert_with(HashMap::new) + .get_or_insert_default() .insert((lib, member), r#const.into()); self } @@ -666,7 +666,7 @@ impl Chunk<'_> { && self.detect_mode() == ChunkMode::Text { #[cfg(feature = "luau")] - if let Ok(data) = self.compiler.get_or_insert_with(Default::default).compile(source) { + if let Ok(data) = self.compiler.get_or_insert_default().compile(source) { self.source = Ok(Cow::Owned(data)); self.mode = Some(ChunkMode::Binary); } diff --git a/src/state/raw.rs b/src/state/raw.rs index d3bc70ca..c6919a9e 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -1126,7 +1126,7 @@ impl RawLua { #[cfg(feature = "luau")] if registry.enable_namecall { let map: &mut rustc_hash::FxHashMap<_, crate::types::CallbackPtr> = - methods_map.get_or_insert_with(Default::default); + methods_map.get_or_insert_default(); for (k, m) in ®istry.methods { map.insert(k.as_bytes().to_vec(), &**m); } From 2ace89261395052cb37d0cc64bb7d50a4e2d81e0 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 29 Jan 2026 18:45:41 +0000 Subject: [PATCH 600/635] Rename `string::String` to `LuaString` --- src/chunk.rs | 55 ++++++++++++++-------------- src/conversion.rs | 25 +++++++------ src/error.rs | 33 +++++++++-------- src/lib.rs | 2 +- src/prelude.rs | 4 +-- src/serde/de.rs | 5 ++- src/state.rs | 17 +++++---- src/state/raw.rs | 16 ++++----- src/string.rs | 77 ++++++++++++++++++++-------------------- src/table.rs | 7 ++-- src/traits.rs | 5 ++- src/userdata.rs | 61 ++++++++++++++++--------------- src/userdata/object.rs | 3 +- src/userdata/registry.rs | 47 ++++++++++++------------ src/value.rs | 29 ++++++++------- tests/async.rs | 5 ++- tests/conversion.rs | 2 +- tests/function.rs | 4 +-- tests/multi.rs | 4 +-- tests/scope.rs | 15 ++++---- tests/send.rs | 5 ++- tests/string.rs | 20 +++++------ tests/tests.rs | 27 +++++++------- tests/types.rs | 2 +- tests/userdata.rs | 33 +++++++++-------- tests/value.rs | 7 ++-- 26 files changed, 246 insertions(+), 264 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index 28e5a78e..02bf474e 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -4,7 +4,6 @@ use std::ffi::CString; use std::io::Result as IoResult; use std::panic::Location; use std::path::{Path, PathBuf}; -use std::string::String as StdString; use crate::error::{Error, Result}; use crate::function::Function; @@ -20,7 +19,7 @@ pub trait AsChunk { /// Returns optional chunk name /// /// See [`Chunk::set_name`] for possible name prefixes. - fn name(&self) -> Option { + fn name(&self) -> Option { None } @@ -52,13 +51,13 @@ impl AsChunk for &str { } } -impl AsChunk for StdString { +impl AsChunk for String { fn source<'a>(&self) -> IoResult> { Ok(Cow::Owned(self.clone().into_bytes())) } } -impl AsChunk for &StdString { +impl AsChunk for &String { fn source<'a>(&self) -> IoResult> where Self: 'a, @@ -92,7 +91,7 @@ impl AsChunk for &Vec { } impl AsChunk for &Path { - fn name(&self) -> Option { + fn name(&self) -> Option { Some(format!("@{}", self.display())) } @@ -102,7 +101,7 @@ impl AsChunk for &Path { } impl AsChunk for PathBuf { - fn name(&self) -> Option { + fn name(&self) -> Option { Some(format!("@{}", self.display())) } @@ -112,7 +111,7 @@ impl AsChunk for PathBuf { } impl AsChunk for Box { - fn name(&self) -> Option { + fn name(&self) -> Option { (**self).name() } @@ -136,7 +135,7 @@ impl AsChunk for Box { #[must_use = "`Chunk`s do nothing unless one of `exec`, `eval`, `call`, or `into_function` are called on them"] pub struct Chunk<'a> { pub(crate) lua: WeakLua, - pub(crate) name: StdString, + pub(crate) name: String, pub(crate) env: Result>, pub(crate) mode: Option, pub(crate) source: IoResult>, @@ -160,7 +159,7 @@ pub enum CompileConstant { Boolean(bool), Number(crate::Number), Vector(crate::Vector), - String(StdString), + String(String), } #[cfg(any(feature = "luau", doc))] @@ -192,7 +191,7 @@ impl From<&str> for CompileConstant { } #[cfg(any(feature = "luau", doc))] -type LibraryMemberConstantMap = HashMap<(StdString, StdString), CompileConstant>; +type LibraryMemberConstantMap = HashMap<(String, String), CompileConstant>; /// Luau compiler #[cfg(any(feature = "luau", doc))] @@ -203,14 +202,14 @@ pub struct Compiler { debug_level: u8, type_info_level: u8, coverage_level: u8, - vector_lib: Option, - vector_ctor: Option, - vector_type: Option, - mutable_globals: Vec, - userdata_types: Vec, - libraries_with_known_members: Vec, + vector_lib: Option, + vector_ctor: Option, + vector_type: Option, + mutable_globals: Vec, + userdata_types: Vec, + libraries_with_known_members: Vec, library_constants: Option, - disabled_builtins: Vec, + disabled_builtins: Vec, } #[cfg(any(feature = "luau", doc))] @@ -294,7 +293,7 @@ impl Compiler { /// To set the library and method name, use the `lib.ctor` format. #[doc(hidden)] #[must_use] - pub fn set_vector_ctor(mut self, ctor: impl Into) -> Self { + pub fn set_vector_ctor(mut self, ctor: impl Into) -> Self { let ctor = ctor.into(); let lib_ctor = ctor.split_once('.'); self.vector_lib = lib_ctor.as_ref().map(|&(lib, _)| lib.to_owned()); @@ -307,7 +306,7 @@ impl Compiler { /// Sets alternative vector type name for type tables, in addition to default type `vector`. #[doc(hidden)] #[must_use] - pub fn set_vector_type(mut self, r#type: impl Into) -> Self { + pub fn set_vector_type(mut self, r#type: impl Into) -> Self { self.vector_type = Some(r#type.into()); self } @@ -316,7 +315,7 @@ impl Compiler { /// /// It disables the import optimization for fields accessed through it. #[must_use] - pub fn add_mutable_global(mut self, global: impl Into) -> Self { + pub fn add_mutable_global(mut self, global: impl Into) -> Self { self.mutable_globals.push(global.into()); self } @@ -325,21 +324,21 @@ impl Compiler { /// /// It disables the import optimization for fields accessed through these. #[must_use] - pub fn set_mutable_globals>(mut self, globals: impl IntoIterator) -> Self { + pub fn set_mutable_globals>(mut self, globals: impl IntoIterator) -> Self { self.mutable_globals = globals.into_iter().map(|s| s.into()).collect(); self } /// Adds a userdata type to the list that will be included in the type information. #[must_use] - pub fn add_userdata_type(mut self, r#type: impl Into) -> Self { + pub fn add_userdata_type(mut self, r#type: impl Into) -> Self { self.userdata_types.push(r#type.into()); self } /// Sets a list of userdata types that will be included in the type information. #[must_use] - pub fn set_userdata_types>(mut self, types: impl IntoIterator) -> Self { + pub fn set_userdata_types>(mut self, types: impl IntoIterator) -> Self { self.userdata_types = types.into_iter().map(|s| s.into()).collect(); self } @@ -373,14 +372,14 @@ impl Compiler { /// Adds a builtin that should be disabled. #[must_use] - pub fn add_disabled_builtin(mut self, builtin: impl Into) -> Self { + pub fn add_disabled_builtin(mut self, builtin: impl Into) -> Self { self.disabled_builtins.push(builtin.into()); self } /// Sets a list of builtins that should be disabled. #[must_use] - pub fn set_disabled_builtins>( + pub fn set_disabled_builtins>( mut self, builtins: impl IntoIterator, ) -> Self { @@ -490,7 +489,7 @@ impl Compiler { if bytecode.first() == Some(&0) { // The rest of the bytecode is the error message starting with `:` // See https://github.com/luau-lang/luau/blob/0.640/Compiler/src/Compiler.cpp#L4336 - let message = StdString::from_utf8_lossy(&bytecode[2..]).into_owned(); + let message = String::from_utf8_lossy(&bytecode[2..]).into_owned(); return Err(Error::SyntaxError { incomplete_input: message.ends_with(""), message, @@ -513,7 +512,7 @@ impl Chunk<'_> { /// - `@` - file path (when truncation is needed, the end of the file path is kept, as this is /// more useful for identifying the file) /// - `=` - custom chunk name (when truncation is needed, the beginning of the name is kept) - pub fn set_name(mut self, name: impl Into) -> Self { + pub fn set_name(mut self, name: impl Into) -> Self { self.name = name.into(); self } @@ -761,7 +760,7 @@ impl Chunk<'_> { ChunkMode::Text } - fn convert_name(name: StdString) -> Result { + fn convert_name(name: String) -> Result { CString::new(name).map_err(|err| Error::runtime(format!("invalid name: {err}"))) } diff --git a/src/conversion.rs b/src/conversion.rs index 9ae5ce63..58642771 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -4,7 +4,6 @@ use std::ffi::{CStr, CString, OsStr, OsString}; use std::hash::{BuildHasher, Hash}; use std::os::raw::c_int; use std::path::{Path, PathBuf}; -use std::string::String as StdString; use std::{mem, slice, str}; use bstr::{BStr, BString, ByteSlice, ByteVec}; @@ -13,7 +12,7 @@ use num_traits::cast; use crate::error::{Error, Result}; use crate::function::Function; use crate::state::{Lua, RawLua}; -use crate::string::{BorrowedBytes, BorrowedStr, String}; +use crate::string::{BorrowedBytes, BorrowedStr, LuaString}; use crate::table::Table; use crate::thread::Thread; use crate::traits::{FromLua, IntoLua, ShortTypeName as _}; @@ -47,14 +46,14 @@ impl FromLua for Value { } } -impl IntoLua for String { +impl IntoLua for LuaString { #[inline] fn into_lua(self, _: &Lua) -> Result { Ok(Value::String(self)) } } -impl IntoLua for &String { +impl IntoLua for &LuaString { #[inline] fn into_lua(self, _: &Lua) -> Result { Ok(Value::String(self.clone())) @@ -67,9 +66,9 @@ impl IntoLua for &String { } } -impl FromLua for String { +impl FromLua for LuaString { #[inline] - fn from_lua(value: Value, lua: &Lua) -> Result { + fn from_lua(value: Value, lua: &Lua) -> Result { let ty = value.type_name(); lua.coerce_string(value)? .ok_or_else(|| Error::FromLuaConversionError { @@ -84,7 +83,7 @@ impl FromLua for String { let type_id = ffi::lua_type(state, idx); if type_id == ffi::LUA_TSTRING { ffi::lua_xpush(state, lua.ref_thread(), idx); - return Ok(String(lua.pop_ref_thread())); + return Ok(LuaString(lua.pop_ref_thread())); } // Fallback to default Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua()) @@ -119,7 +118,7 @@ impl IntoLua for &BorrowedStr<'_> { impl FromLua for BorrowedStr<'_> { fn from_lua(value: Value, lua: &Lua) -> Result { - let s = String::from_lua(value, lua)?; + let s = LuaString::from_lua(value, lua)?; let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?; let buf = unsafe { mem::transmute::<&str, &'static str>(buf) }; let borrow = Cow::Owned(s); @@ -127,7 +126,7 @@ impl FromLua for BorrowedStr<'_> { } unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - let s = String::from_stack(idx, lua)?; + let s = LuaString::from_stack(idx, lua)?; let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?; let buf = unsafe { mem::transmute::<&str, &'static str>(buf) }; let borrow = Cow::Owned(s); @@ -163,7 +162,7 @@ impl IntoLua for &BorrowedBytes<'_> { impl FromLua for BorrowedBytes<'_> { fn from_lua(value: Value, lua: &Lua) -> Result { - let s = String::from_lua(value, lua)?; + let s = LuaString::from_lua(value, lua)?; let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s); let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) }; let borrow = Cow::Owned(s); @@ -171,7 +170,7 @@ impl FromLua for BorrowedBytes<'_> { } unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - let s = String::from_stack(idx, lua)?; + let s = LuaString::from_stack(idx, lua)?; let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s); let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) }; let borrow = Cow::Owned(s); @@ -497,7 +496,7 @@ impl FromLua for crate::Buffer { } } -impl IntoLua for StdString { +impl IntoLua for String { #[inline] fn into_lua(self, lua: &Lua) -> Result { #[cfg(feature = "lua55")] @@ -519,7 +518,7 @@ impl IntoLua for StdString { } } -impl FromLua for StdString { +impl FromLua for String { #[inline] fn from_lua(value: Value, lua: &Lua) -> Result { let ty = value.type_name(); diff --git a/src/error.rs b/src/error.rs index 29ee9fcb..092fcb90 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,7 +4,6 @@ use std::io::Error as IoError; use std::net::AddrParseError; use std::result::Result as StdResult; use std::str::Utf8Error; -use std::string::String as StdString; use std::sync::Arc; use crate::private::Sealed; @@ -22,7 +21,7 @@ pub enum Error { /// Syntax error while parsing Lua source code. SyntaxError { /// The error message as returned by Lua. - message: StdString, + message: String, /// `true` if the error can likely be fixed by appending more input to the source code. /// /// This is useful for implementing REPLs as they can query the user for more input if this @@ -34,20 +33,20 @@ pub enum Error { /// The Lua VM returns this error when a builtin operation is performed on incompatible types. /// Among other things, this includes invoking operators on wrong types (such as calling or /// indexing a `nil` value). - RuntimeError(StdString), + RuntimeError(String), /// Lua memory error, aka `LUA_ERRMEM` /// /// The Lua VM returns this error when the allocator does not return the requested memory, aka /// it is an out-of-memory error. - MemoryError(StdString), + MemoryError(String), /// Lua garbage collector error, aka `LUA_ERRGCMM`. /// /// The Lua VM returns this error when there is an error running a `__gc` metamethod. #[cfg(any(feature = "lua53", feature = "lua52", doc))] #[cfg_attr(docsrs, doc(cfg(any(feature = "lua53", feature = "lua52"))))] - GarbageCollectorError(StdString), + GarbageCollectorError(String), /// Potentially unsafe action in safe mode. - SafetyError(StdString), + SafetyError(String), /// Memory control is not available. /// /// This error can only happen when Lua state was not created by us and does not have the @@ -80,11 +79,11 @@ pub enum Error { /// (which is stored in the corresponding field). BadArgument { /// Function that was called. - to: Option, + to: Option, /// Argument position (usually starts from 1). pos: usize, /// Argument name. - name: Option, + name: Option, /// Underlying error returned when converting argument to a Lua value. cause: Arc, }, @@ -95,7 +94,7 @@ pub enum Error { /// Name of the Lua type that could not be created. to: &'static str, /// A message indicating why the conversion failed in more detail. - message: Option, + message: Option, }, /// A Lua value could not be converted to the expected Rust type. FromLuaConversionError { @@ -104,7 +103,7 @@ pub enum Error { /// Name of the Rust type that could not be created. to: String, /// A string containing more detailed error information. - message: Option, + message: Option, }, /// [`Thread::resume`] was called on an unresumable coroutine. /// @@ -154,17 +153,17 @@ pub enum Error { /// A [`MetaMethod`] operation is restricted (typically for `__gc` or `__metatable`). /// /// [`MetaMethod`]: crate::MetaMethod - MetaMethodRestricted(StdString), + MetaMethodRestricted(String), /// A [`MetaMethod`] (eg. `__index` or `__newindex`) has invalid type. /// /// [`MetaMethod`]: crate::MetaMethod MetaMethodTypeError { /// Name of the metamethod. - method: StdString, + method: String, /// Passed value type. type_name: &'static str, /// A string containing more detailed error information. - message: Option, + message: Option, }, /// A [`RegistryKey`] produced from a different Lua state was used. /// @@ -173,7 +172,7 @@ pub enum Error { /// A Rust callback returned `Err`, raising the contained `Error` as a Lua error. CallbackError { /// Lua call stack backtrace. - traceback: StdString, + traceback: String, /// Original error returned by the Rust code. cause: Arc, }, @@ -185,11 +184,11 @@ pub enum Error { /// Serialization error. #[cfg(feature = "serde")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] - SerializeError(StdString), + SerializeError(String), /// Deserialization error. #[cfg(feature = "serde")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] - DeserializeError(StdString), + DeserializeError(String), /// A custom error. /// /// This can be used for returning user-defined errors from callbacks. @@ -201,7 +200,7 @@ pub enum Error { /// An error with additional context. WithContext { /// A string containing additional context. - context: StdString, + context: String, /// Underlying error. cause: Arc, }, diff --git a/src/lib.rs b/src/lib.rs index 01c68b9e..46f17371 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,7 +107,7 @@ pub use crate::multi::{MultiValue, Variadic}; pub use crate::scope::Scope; pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua}; pub use crate::stdlib::StdLib; -pub use crate::string::{BorrowedBytes, BorrowedStr, String}; +pub use crate::string::{BorrowedBytes, BorrowedStr, LuaString, LuaString as String}; pub use crate::table::{Table, TablePairs, TableSequence}; pub use crate::thread::{Thread, ThreadStatus}; pub use crate::traits::{ diff --git a/src/prelude.rs b/src/prelude.rs index fca5af51..5c4db648 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -7,9 +7,9 @@ pub use crate::{ ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti, Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, Integer as LuaInteger, IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions, - MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, + LuaString, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, - String as LuaString, Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, + Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, diff --git a/src/serde/de.rs b/src/serde/de.rs index d9163608..4c5eb1b6 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -4,7 +4,6 @@ use std::cell::RefCell; use std::os::raw::c_void; use std::rc::Rc; use std::result::Result as StdResult; -use std::string::String as StdString; use rustc_hash::FxHashSet; use serde::de::{self, IntoDeserializer}; @@ -243,7 +242,7 @@ impl<'de> serde::Deserializer<'de> for Deserializer { Value::Table(table) => { let _guard = RecursionGuard::new(&table, &self.visited); - let mut iter = table.pairs::(); + let mut iter = table.pairs::(); let (variant, value) = match iter.next() { Some(v) => v?, None => { @@ -621,7 +620,7 @@ impl<'de> de::MapAccess<'de> for MapDeserializer<'_> { } struct EnumDeserializer { - variant: StdString, + variant: String, value: Option, options: Options, visited: Rc>>, diff --git a/src/state.rs b/src/state.rs index e8e67f81..9c364a2e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -15,7 +15,7 @@ use crate::memory::MemoryState; use crate::multi::MultiValue; use crate::scope::Scope; use crate::stdlib::StdLib; -use crate::string::String; +use crate::string::LuaString; use crate::table::Table; use crate::thread::Thread; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; @@ -855,7 +855,6 @@ impl Lua { { use std::ffi::CStr; use std::os::raw::{c_char, c_void}; - use std::string::String as StdString; unsafe extern "C-unwind" fn warn_proc(ud: *mut c_void, msg: *const c_char, tocont: c_int) { let extra = ud as *mut ExtraData; @@ -865,7 +864,7 @@ impl Lua { if XRc::strong_count(&warn_callback) > 2 { return Ok(()); } - let msg = StdString::from_utf8_lossy(CStr::from_ptr(msg).to_bytes()); + let msg = String::from_utf8_lossy(CStr::from_ptr(msg).to_bytes()); warn_callback((*extra).lua(), &msg, tocont != 0) }); } @@ -936,7 +935,7 @@ impl Lua { /// /// The `msg` parameter, if provided, is added at the beginning of the traceback. /// The `level` parameter works the same way as in [`Lua::inspect_stack`]. - pub fn traceback(&self, msg: Option<&str>, level: usize) -> Result { + pub fn traceback(&self, msg: Option<&str>, level: usize) -> Result { let lua = self.lock(); unsafe { check_stack(lua.state(), 3)?; @@ -948,7 +947,7 @@ impl Lua { // `protect_lua` adds it's own call frame, so we need to increase level by 1 ffi::luaL_traceback(state, state, msg, (level + 1) as c_int); })?; - Ok(String(lua.pop_ref())) + Ok(LuaString(lua.pop_ref())) } } @@ -1248,7 +1247,7 @@ impl Lua { /// Lua strings can be arbitrary `[u8]` data including embedded nulls, so in addition to `&str` /// and `&String`, you can also pass plain `&[u8]` here. #[inline] - pub fn create_string(&self, s: impl AsRef<[u8]>) -> Result { + pub fn create_string(&self, s: impl AsRef<[u8]>) -> Result { unsafe { self.lock().create_string(s.as_ref()) } } @@ -1259,7 +1258,7 @@ impl Lua { #[cfg(feature = "lua55")] #[cfg_attr(docsrs, doc(cfg(feature = "lua55")))] #[inline] - pub fn create_external_string(&self, s: impl Into>) -> Result { + pub fn create_external_string(&self, s: impl Into>) -> Result { unsafe { self.lock().create_external_string(s.into()) } } @@ -1741,7 +1740,7 @@ impl Lua { /// /// To succeed, the value must be a string (in which case this is a no-op), an integer, or a /// number. - pub fn coerce_string(&self, v: Value) -> Result> { + pub fn coerce_string(&self, v: Value) -> Result> { Ok(match v { Value::String(s) => Some(s), v => unsafe { @@ -1759,7 +1758,7 @@ impl Lua { })? }; if !res.is_null() { - Some(String(lua.pop_ref())) + Some(LuaString(lua.pop_ref())) } else { None } diff --git a/src/state/raw.rs b/src/state/raw.rs index c6919a9e..7e1421a7 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -13,7 +13,7 @@ use crate::function::Function; use crate::memory::{ALLOCATOR, MemoryState}; use crate::state::util::callback_error_ext; use crate::stdlib::StdLib; -use crate::string::String; +use crate::string::LuaString; use crate::table::Table; use crate::thread::Thread; use crate::traits::IntoLua; @@ -516,34 +516,34 @@ impl RawLua { } /// See [`Lua::create_string`] - pub(crate) unsafe fn create_string(&self, s: &[u8]) -> Result { + pub(crate) unsafe fn create_string(&self, s: &[u8]) -> Result { let state = self.state(); if self.unlikely_memory_error() { push_string(state, s, false)?; - return Ok(String(self.pop_ref())); + return Ok(LuaString(self.pop_ref())); } let _sg = StackGuard::new(state); check_stack(state, 3)?; push_string(state, s, true)?; - Ok(String(self.pop_ref())) + Ok(LuaString(self.pop_ref())) } /// Creates an external string, that is, a string that uses memory not managed by Lua. /// /// Modifies the input data to add `\0` terminator. #[cfg(feature = "lua55")] - pub(crate) unsafe fn create_external_string(&self, bytes: Vec) -> Result { + pub(crate) unsafe fn create_external_string(&self, bytes: Vec) -> Result { let state = self.state(); if self.unlikely_memory_error() { crate::util::push_external_string(state, bytes, false)?; - return Ok(String(self.pop_ref())); + return Ok(LuaString(self.pop_ref())); } let _sg = StackGuard::new(state); check_stack(state, 3)?; crate::util::push_external_string(state, bytes, true)?; - Ok(String(self.pop_ref())) + Ok(LuaString(self.pop_ref())) } #[cfg(feature = "luau")] @@ -824,7 +824,7 @@ impl RawLua { ffi::LUA_TSTRING => { ffi::lua_xpush(state, self.ref_thread(), idx); - Value::String(String(self.pop_ref_thread())) + Value::String(LuaString(self.pop_ref_thread())) } ffi::LUA_TTABLE => { diff --git a/src/string.rs b/src/string.rs index eeb08ed4..9f2b4e95 100644 --- a/src/string.rs +++ b/src/string.rs @@ -2,7 +2,6 @@ use std::borrow::{Borrow, Cow}; use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::os::raw::{c_int, c_void}; -use std::string::String as StdString; use std::{cmp, fmt, slice, str}; use crate::error::{Error, Result}; @@ -21,23 +20,23 @@ use { /// /// Unlike Rust strings, Lua strings may not be valid UTF-8. #[derive(Clone)] -pub struct String(pub(crate) ValueRef); +pub struct LuaString(pub(crate) ValueRef); -impl String { +impl LuaString { /// Get a [`BorrowedStr`] if the Lua string is valid UTF-8. /// /// # Examples /// /// ``` - /// # use mlua::{Lua, Result, String}; + /// # use mlua::{Lua, LuaString, Result}; /// # fn main() -> Result<()> { /// # let lua = Lua::new(); /// let globals = lua.globals(); /// - /// let version: String = globals.get("_VERSION")?; + /// let version: LuaString = globals.get("_VERSION")?; /// assert!(version.to_str()?.contains("Lua")); /// - /// let non_utf8: String = lua.load(r#" "test\255" "#).eval()?; + /// let non_utf8: LuaString = lua.load(r#" "test\255" "#).eval()?; /// assert!(non_utf8.to_str().is_err()); /// # Ok(()) /// # } @@ -47,11 +46,11 @@ impl String { BorrowedStr::try_from(self) } - /// Converts this string to a [`StdString`]. + /// Converts this Lua string to a [`String`]. /// /// Any non-Unicode sequences are replaced with [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD]. /// - /// This method returns [`StdString`] instead of [`Cow<'_, str>`] because lifetime cannot be + /// This method returns [`String`] instead of [`Cow<'_, str>`] because lifetime cannot be /// bound to a weak Lua object. /// /// [U+FFFD]: std::char::REPLACEMENT_CHARACTER @@ -70,11 +69,11 @@ impl String { /// # } /// ``` #[inline] - pub fn to_string_lossy(&self) -> StdString { - StdString::from_utf8_lossy(&self.as_bytes()).into_owned() + pub fn to_string_lossy(&self) -> String { + String::from_utf8_lossy(&self.as_bytes()).into_owned() } - /// Returns an object that implements [`Display`] for safely printing a Lua [`String`] that may + /// Returns an object that implements [`Display`] for safely printing a [`LuaString`] that may /// contain non-Unicode data. /// /// This may perform lossy conversion. @@ -92,10 +91,10 @@ impl String { /// # Examples /// /// ``` - /// # use mlua::{Lua, Result, String}; + /// # use mlua::{Lua, LuaString, Result}; /// # fn main() -> Result<()> { /// # let lua = Lua::new(); - /// let non_utf8: String = lua.load(r#" "test\255" "#).eval()?; + /// let non_utf8: LuaString = lua.load(r#" "test\255" "#).eval()?; /// assert!(non_utf8.to_str().is_err()); // oh no :( /// assert_eq!(non_utf8.as_bytes(), &b"test\xff"[..]); /// # Ok(()) @@ -135,7 +134,7 @@ impl String { (slice, lua) } - /// Converts this string to a generic C pointer. + /// Converts this Lua string to a generic C pointer. /// /// There is no way to convert the pointer back to its original value. /// @@ -146,7 +145,7 @@ impl String { } } -impl fmt::Debug for String { +impl fmt::Debug for LuaString { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let bytes = self.as_bytes(); // Check if the string is valid utf8 @@ -162,12 +161,12 @@ impl fmt::Debug for String { // Lua strings are basically `&[u8]` slices, so implement `PartialEq` for anything resembling that. // -// This makes our `String` comparable with `Vec`, `[u8]`, `&str` and `String`. +// This makes our `LuaString` comparable with `Vec`, `[u8]`, `&str` and `String`. // // The only downside is that this disallows a comparison with `Cow`, as that only implements // `AsRef`, which collides with this impl. Requiring `AsRef` would fix that, but limit us // in other ways. -impl PartialEq for String +impl PartialEq for LuaString where T: AsRef<[u8]> + ?Sized, { @@ -176,43 +175,43 @@ where } } -impl PartialEq for String { - fn eq(&self, other: &String) -> bool { +impl PartialEq for LuaString { + fn eq(&self, other: &LuaString) -> bool { self.as_bytes() == other.as_bytes() } } -impl Eq for String {} +impl Eq for LuaString {} -impl PartialOrd for String +impl PartialOrd for LuaString where T: AsRef<[u8]> + ?Sized, { fn partial_cmp(&self, other: &T) -> Option { - self.as_bytes().partial_cmp(&other.as_ref()) + <[u8]>::partial_cmp(&self.as_bytes(), other.as_ref()) } } -impl PartialOrd for String { - fn partial_cmp(&self, other: &String) -> Option { +impl PartialOrd for LuaString { + fn partial_cmp(&self, other: &LuaString) -> Option { Some(self.cmp(other)) } } -impl Ord for String { - fn cmp(&self, other: &String) -> cmp::Ordering { +impl Ord for LuaString { + fn cmp(&self, other: &LuaString) -> cmp::Ordering { self.as_bytes().cmp(&other.as_bytes()) } } -impl Hash for String { +impl Hash for LuaString { fn hash(&self, state: &mut H) { self.as_bytes().hash(state); } } #[cfg(feature = "serde")] -impl Serialize for String { +impl Serialize for LuaString { fn serialize(&self, serializer: S) -> StdResult where S: Serializer, @@ -224,7 +223,7 @@ impl Serialize for String { } } -struct Display<'a>(&'a String); +struct Display<'a>(&'a LuaString); impl fmt::Display for Display<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -237,7 +236,7 @@ impl fmt::Display for Display<'_> { pub struct BorrowedStr<'a> { // `buf` points to a readonly memory managed by Lua pub(crate) buf: &'a str, - pub(crate) borrow: Cow<'a, String>, + pub(crate) borrow: Cow<'a, LuaString>, pub(crate) _lua: Lua, } @@ -302,11 +301,11 @@ impl Ord for BorrowedStr<'_> { } } -impl<'a> TryFrom<&'a String> for BorrowedStr<'a> { +impl<'a> TryFrom<&'a LuaString> for BorrowedStr<'a> { type Error = Error; #[inline] - fn try_from(value: &'a String) -> Result { + fn try_from(value: &'a LuaString) -> Result { let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(value); let buf = str::from_utf8(buf).map_err(|e| Error::FromLuaConversionError { from: "string", @@ -321,7 +320,7 @@ impl<'a> TryFrom<&'a String> for BorrowedStr<'a> { pub struct BorrowedBytes<'a> { // `buf` points to a readonly memory managed by Lua pub(crate) buf: &'a [u8], - pub(crate) borrow: Cow<'a, String>, + pub(crate) borrow: Cow<'a, LuaString>, pub(crate) _lua: Lua, } @@ -389,9 +388,9 @@ impl<'a> IntoIterator for &'a BorrowedBytes<'_> { } } -impl<'a> From<&'a String> for BorrowedBytes<'a> { +impl<'a> From<&'a LuaString> for BorrowedBytes<'a> { #[inline] - fn from(value: &'a String) -> Self { + fn from(value: &'a LuaString) -> Self { let (buf, _lua) = unsafe { value.to_slice() }; let borrow = Cow::Borrowed(value); Self { buf, borrow, _lua } @@ -400,7 +399,7 @@ impl<'a> From<&'a String> for BorrowedBytes<'a> { struct WrappedString>(T); -impl String { +impl LuaString { /// Wraps bytes, returning an opaque type that implements [`IntoLua`] trait. /// /// This function uses [`Lua::create_string`] under the hood. @@ -415,7 +414,7 @@ impl> IntoLua for WrappedString { } } -impl LuaType for String { +impl LuaType for LuaString { const TYPE_ID: c_int = ffi::LUA_TSTRING; } @@ -424,9 +423,9 @@ mod assertions { use super::*; #[cfg(not(feature = "send"))] - static_assertions::assert_not_impl_any!(String: Send); + static_assertions::assert_not_impl_any!(LuaString: Send); #[cfg(feature = "send")] - static_assertions::assert_impl_all!(String: Send, Sync); + static_assertions::assert_impl_all!(LuaString: Send, Sync); #[cfg(feature = "send")] static_assertions::assert_impl_all!(BorrowedBytes: Send, Sync); #[cfg(feature = "send")] diff --git a/src/table.rs b/src/table.rs index f7dcb2c8..c53fc550 100644 --- a/src/table.rs +++ b/src/table.rs @@ -2,7 +2,6 @@ use std::collections::HashSet; use std::fmt; use std::marker::PhantomData; use std::os::raw::c_void; -use std::string::String as StdString; use crate::error::{Error, Result}; use crate::function::Function; @@ -1008,7 +1007,7 @@ impl ObjectLike for Table { } #[inline] - fn to_string(&self) -> Result { + fn to_string(&self) -> Result { Value::Table(Table(self.0.clone())).to_string() } @@ -1098,7 +1097,7 @@ impl Serialize for SerializableTable<'_> { seq.serialize_element(&SerializableValue::new(&value, options, Some(visited))) .map_err(|err| { serialize_err = Some(err); - Error::SerializeError(StdString::new()) + Error::SerializeError(String::new()) }) }); convert_result(res, serialize_err)?; @@ -1123,7 +1122,7 @@ impl Serialize for SerializableTable<'_> { ) .map_err(|err| { serialize_err = Some(err); - Error::SerializeError(StdString::new()) + Error::SerializeError(String::new()) }) }; diff --git a/src/traits.rs b/src/traits.rs index 231d1512..93429108 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,5 +1,4 @@ use std::os::raw::c_int; -use std::string::String as StdString; use std::sync::Arc; use crate::error::{Error, Result}; @@ -236,7 +235,7 @@ pub trait ObjectLike: Sealed { /// Converts the object to a string in a human-readable format. /// /// This might invoke the `__tostring` metamethod. - fn to_string(&self) -> Result; + fn to_string(&self) -> Result; /// Converts the object to a Lua value. fn to_value(&self) -> Value; @@ -339,7 +338,7 @@ impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); pub(crate) trait ShortTypeName { #[inline(always)] - fn type_name() -> StdString { + fn type_name() -> String { short_type_name::() } } diff --git a/src/userdata.rs b/src/userdata.rs index 98fdbbbf..c8d8c19b 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -3,12 +3,11 @@ use std::ffi::CStr; use std::fmt; use std::hash::Hash; use std::os::raw::{c_char, c_void}; -use std::string::String as StdString; use crate::error::{Error, Result}; use crate::function::Function; use crate::state::Lua; -use crate::string::String; +use crate::string::LuaString; use crate::table::{Table, TablePairs}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::types::{MaybeSend, ValueRef}; @@ -185,7 +184,7 @@ impl PartialEq for &str { } } -impl PartialEq for StdString { +impl PartialEq for String { fn eq(&self, other: &MetaMethod) -> bool { self == other.name() } @@ -279,7 +278,7 @@ impl AsRef for MetaMethod { } } -impl From for StdString { +impl From for String { #[inline] fn from(method: MetaMethod) -> Self { method.name().to_owned() @@ -295,7 +294,7 @@ pub trait UserDataMethods { /// /// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will /// be used as a fall-back if no regular method is found. - fn add_method(&mut self, name: impl Into, method: M) + fn add_method(&mut self, name: impl Into, method: M) where M: Fn(&Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -306,7 +305,7 @@ pub trait UserDataMethods { /// Refer to [`add_method`] for more information about the implementation. /// /// [`add_method`]: UserDataMethods::add_method - fn add_method_mut(&mut self, name: impl Into, method: M) + fn add_method_mut(&mut self, name: impl Into, method: M) where M: FnMut(&Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -320,7 +319,7 @@ pub trait UserDataMethods { /// The method can be called only once per userdata instance, subsequent calls will result in a /// [`Error::UserDataDestructed`] error. #[doc(hidden)] - fn add_method_once(&mut self, name: impl Into, method: M) + fn add_method_once(&mut self, name: impl Into, method: M) where T: 'static, M: Fn(&Lua, T, A) -> Result + MaybeSend + 'static, @@ -342,7 +341,7 @@ pub trait UserDataMethods { /// [`add_method`]: UserDataMethods::add_method #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn add_async_method(&mut self, name: impl Into, method: M) + fn add_async_method(&mut self, name: impl Into, method: M) where T: 'static, M: Fn(Lua, UserDataRef, A) -> MR + MaybeSend + 'static, @@ -357,7 +356,7 @@ pub trait UserDataMethods { /// [`add_method`]: UserDataMethods::add_method #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn add_async_method_mut(&mut self, name: impl Into, method: M) + fn add_async_method_mut(&mut self, name: impl Into, method: M) where T: 'static, M: Fn(Lua, UserDataRefMut, A) -> MR + MaybeSend + 'static, @@ -375,7 +374,7 @@ pub trait UserDataMethods { #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[doc(hidden)] - fn add_async_method_once(&mut self, name: impl Into, method: M) + fn add_async_method_once(&mut self, name: impl Into, method: M) where T: 'static, M: Fn(Lua, T, A) -> MR + MaybeSend + 'static, @@ -398,7 +397,7 @@ pub trait UserDataMethods { /// The first argument will be a [`AnyUserData`] of type `T` if the method is called with Lua /// method syntax: `my_userdata:my_method(arg1, arg2)`, or it is passed in as the first /// argument: `my_userdata.my_method(my_userdata, arg1, arg2)`. - fn add_function(&mut self, name: impl Into, function: F) + fn add_function(&mut self, name: impl Into, function: F) where F: Fn(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -409,7 +408,7 @@ pub trait UserDataMethods { /// This is a version of [`add_function`] that accepts a `FnMut` argument. /// /// [`add_function`]: UserDataMethods::add_function - fn add_function_mut(&mut self, name: impl Into, function: F) + fn add_function_mut(&mut self, name: impl Into, function: F) where F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -423,7 +422,7 @@ pub trait UserDataMethods { /// [`add_function`]: UserDataMethods::add_function #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn add_async_function(&mut self, name: impl Into, function: F) + fn add_async_function(&mut self, name: impl Into, function: F) where F: Fn(Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, @@ -438,7 +437,7 @@ pub trait UserDataMethods { /// side has a metatable. To prevent this, use [`add_meta_function`]. /// /// [`add_meta_function`]: UserDataMethods::add_meta_function - fn add_meta_method(&mut self, name: impl Into, method: M) + fn add_meta_method(&mut self, name: impl Into, method: M) where M: Fn(&Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -452,7 +451,7 @@ pub trait UserDataMethods { /// side has a metatable. To prevent this, use [`add_meta_function`]. /// /// [`add_meta_function`]: UserDataMethods::add_meta_function - fn add_meta_method_mut(&mut self, name: impl Into, method: M) + fn add_meta_method_mut(&mut self, name: impl Into, method: M) where M: FnMut(&Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -468,7 +467,7 @@ pub trait UserDataMethods { docsrs, doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))) )] - fn add_async_meta_method(&mut self, name: impl Into, method: M) + fn add_async_meta_method(&mut self, name: impl Into, method: M) where T: 'static, M: Fn(Lua, UserDataRef, A) -> MR + MaybeSend + 'static, @@ -484,7 +483,7 @@ pub trait UserDataMethods { /// [`add_meta_method_mut`]: UserDataMethods::add_meta_method_mut #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn add_async_meta_method_mut(&mut self, name: impl Into, method: M) + fn add_async_meta_method_mut(&mut self, name: impl Into, method: M) where T: 'static, M: Fn(Lua, UserDataRefMut, A) -> MR + MaybeSend + 'static, @@ -497,7 +496,7 @@ pub trait UserDataMethods { /// Metamethods for binary operators can be triggered if either the left or right argument to /// the binary operator has a metatable, so the first argument here is not necessarily a /// userdata of type `T`. - fn add_meta_function(&mut self, name: impl Into, function: F) + fn add_meta_function(&mut self, name: impl Into, function: F) where F: Fn(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -508,7 +507,7 @@ pub trait UserDataMethods { /// This is a version of [`add_meta_function`] that accepts a `FnMut` argument. /// /// [`add_meta_function`]: UserDataMethods::add_meta_function - fn add_meta_function_mut(&mut self, name: impl Into, function: F) + fn add_meta_function_mut(&mut self, name: impl Into, function: F) where F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -524,7 +523,7 @@ pub trait UserDataMethods { docsrs, doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))) )] - fn add_async_meta_function(&mut self, name: impl Into, function: F) + fn add_async_meta_function(&mut self, name: impl Into, function: F) where F: Fn(Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, @@ -543,7 +542,7 @@ pub trait UserDataFields { /// /// If `add_meta_method` is used to set the `__index` metamethod, it will /// be used as a fall-back if no regular field or method are found. - fn add_field(&mut self, name: impl Into, value: V) + fn add_field(&mut self, name: impl Into, value: V) where V: IntoLua + 'static; @@ -554,7 +553,7 @@ pub trait UserDataFields { /// /// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will /// be used as a fall-back if no regular field or method are found. - fn add_field_method_get(&mut self, name: impl Into, method: M) + fn add_field_method_get(&mut self, name: impl Into, method: M) where M: Fn(&Lua, &T) -> Result + MaybeSend + 'static, R: IntoLua; @@ -567,21 +566,21 @@ pub trait UserDataFields { /// /// If `add_meta_method` is used to set the `__newindex` metamethod, the `__newindex` metamethod /// will be used as a fall-back if no regular field is found. - fn add_field_method_set(&mut self, name: impl Into, method: M) + fn add_field_method_set(&mut self, name: impl Into, method: M) where M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static, A: FromLua; /// Add a regular field getter as a function which accepts a generic [`AnyUserData`] of type `T` /// argument. - fn add_field_function_get(&mut self, name: impl Into, function: F) + fn add_field_function_get(&mut self, name: impl Into, function: F) where F: Fn(&Lua, AnyUserData) -> Result + MaybeSend + 'static, R: IntoLua; /// Add a regular field setter as a function which accepts a generic [`AnyUserData`] of type `T` /// first argument. - fn add_field_function_set(&mut self, name: impl Into, function: F) + fn add_field_function_set(&mut self, name: impl Into, function: F) where F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static, A: FromLua; @@ -594,7 +593,7 @@ pub trait UserDataFields { /// /// `mlua` will trigger an error on an attempt to define a protected metamethod, /// like `__gc` or `__metatable`. - fn add_meta_field(&mut self, name: impl Into, value: V) + fn add_meta_field(&mut self, name: impl Into, value: V) where V: IntoLua + 'static; @@ -606,7 +605,7 @@ pub trait UserDataFields { /// /// `mlua` will trigger an error on an attempt to define a protected metamethod, /// like `__gc` or `__metatable`. - fn add_meta_field_with(&mut self, name: impl Into, f: F) + fn add_meta_field_with(&mut self, name: impl Into, f: F) where F: FnOnce(&Lua) -> Result + 'static, R: IntoLua; @@ -1022,7 +1021,7 @@ impl AnyUserData { /// Returns a type name of this userdata (from a metatable field). /// /// If no type name is set, returns `None`. - pub fn type_name(&self) -> Result> { + pub fn type_name(&self) -> Result> { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { @@ -1039,7 +1038,7 @@ impl AnyUserData { ffi::luaL_getmetafield(state, -1, MetaMethod::Type.as_cstr().as_ptr()) }; match name_type { - ffi::LUA_TSTRING => Ok(Some(String(lua.pop_ref()).to_str()?.to_owned())), + ffi::LUA_TSTRING => Ok(Some(LuaString(lua.pop_ref()).to_str()?.to_owned())), _ => Ok(None), } } @@ -1126,13 +1125,13 @@ impl UserDataMetatable { /// It skips restricted metamethods, such as `__gc` or `__metatable`. /// /// This struct is created by the [`UserDataMetatable::pairs`] method. -pub struct UserDataMetatablePairs<'a, V>(TablePairs<'a, StdString, V>); +pub struct UserDataMetatablePairs<'a, V>(TablePairs<'a, String, V>); impl Iterator for UserDataMetatablePairs<'_, V> where V: FromLua, { - type Item = Result<(StdString, V)>; + type Item = Result<(String, V)>; fn next(&mut self) -> Option { loop { diff --git a/src/userdata/object.rs b/src/userdata/object.rs index e16e312d..35f11e61 100644 --- a/src/userdata/object.rs +++ b/src/userdata/object.rs @@ -1,4 +1,3 @@ -use std::string::String as StdString; use crate::Function; use crate::error::{Error, Result}; @@ -88,7 +87,7 @@ impl ObjectLike for AnyUserData { } #[inline] - fn to_string(&self) -> Result { + fn to_string(&self) -> Result { Value::UserData(self.clone()).to_string() } diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index b37aa71d..e16bec6d 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -4,7 +4,6 @@ use std::any::TypeId; use std::cell::RefCell; use std::marker::PhantomData; use std::os::raw::c_void; -use std::string::String as StdString; use crate::error::{Error, Result}; use crate::state::{Lua, LuaGuard}; @@ -55,7 +54,7 @@ pub(crate) struct RawUserDataRegistry { pub(crate) destructor: ffi::lua_CFunction, pub(crate) type_id: Option, - pub(crate) type_name: StdString, + pub(crate) type_name: String, #[cfg(feature = "luau")] pub(crate) enable_namecall: bool, @@ -382,12 +381,12 @@ impl UserDataRegistry { } // Returns function name for the type `T`, without the module path -fn get_function_name(name: &str) -> StdString { +fn get_function_name(name: &str) -> String { format!("{}.{name}", short_type_name::()) } impl UserDataFields for UserDataRegistry { - fn add_field(&mut self, name: impl Into, value: V) + fn add_field(&mut self, name: impl Into, value: V) where V: IntoLua + 'static, { @@ -395,7 +394,7 @@ impl UserDataFields for UserDataRegistry { self.raw.fields.push((name, value.into_lua(self.lua.lua()))); } - fn add_field_method_get(&mut self, name: impl Into, method: M) + fn add_field_method_get(&mut self, name: impl Into, method: M) where M: Fn(&Lua, &T) -> Result + MaybeSend + 'static, R: IntoLua, @@ -405,7 +404,7 @@ impl UserDataFields for UserDataRegistry { self.raw.field_getters.push((name, callback)); } - fn add_field_method_set(&mut self, name: impl Into, method: M) + fn add_field_method_set(&mut self, name: impl Into, method: M) where M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static, A: FromLua, @@ -415,7 +414,7 @@ impl UserDataFields for UserDataRegistry { self.raw.field_setters.push((name, callback)); } - fn add_field_function_get(&mut self, name: impl Into, function: F) + fn add_field_function_get(&mut self, name: impl Into, function: F) where F: Fn(&Lua, AnyUserData) -> Result + MaybeSend + 'static, R: IntoLua, @@ -425,7 +424,7 @@ impl UserDataFields for UserDataRegistry { self.raw.field_getters.push((name, callback)); } - fn add_field_function_set(&mut self, name: impl Into, mut function: F) + fn add_field_function_set(&mut self, name: impl Into, mut function: F) where F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static, A: FromLua, @@ -435,7 +434,7 @@ impl UserDataFields for UserDataRegistry { self.raw.field_setters.push((name, callback)); } - fn add_meta_field(&mut self, name: impl Into, value: V) + fn add_meta_field(&mut self, name: impl Into, value: V) where V: IntoLua + 'static, { @@ -445,7 +444,7 @@ impl UserDataFields for UserDataRegistry { self.raw.meta_fields.push((name, field)); } - fn add_meta_field_with(&mut self, name: impl Into, f: F) + fn add_meta_field_with(&mut self, name: impl Into, f: F) where F: FnOnce(&Lua) -> Result + 'static, R: IntoLua, @@ -458,7 +457,7 @@ impl UserDataFields for UserDataRegistry { } impl UserDataMethods for UserDataRegistry { - fn add_method(&mut self, name: impl Into, method: M) + fn add_method(&mut self, name: impl Into, method: M) where M: Fn(&Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -469,7 +468,7 @@ impl UserDataMethods for UserDataRegistry { self.raw.methods.push((name, callback)); } - fn add_method_mut(&mut self, name: impl Into, method: M) + fn add_method_mut(&mut self, name: impl Into, method: M) where M: FnMut(&Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -481,7 +480,7 @@ impl UserDataMethods for UserDataRegistry { } #[cfg(feature = "async")] - fn add_async_method(&mut self, name: impl Into, method: M) + fn add_async_method(&mut self, name: impl Into, method: M) where T: 'static, M: Fn(Lua, UserDataRef, A) -> MR + MaybeSend + 'static, @@ -495,7 +494,7 @@ impl UserDataMethods for UserDataRegistry { } #[cfg(feature = "async")] - fn add_async_method_mut(&mut self, name: impl Into, method: M) + fn add_async_method_mut(&mut self, name: impl Into, method: M) where T: 'static, M: Fn(Lua, UserDataRefMut, A) -> MR + MaybeSend + 'static, @@ -508,7 +507,7 @@ impl UserDataMethods for UserDataRegistry { self.raw.async_methods.push((name, callback)); } - fn add_function(&mut self, name: impl Into, function: F) + fn add_function(&mut self, name: impl Into, function: F) where F: Fn(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -519,7 +518,7 @@ impl UserDataMethods for UserDataRegistry { self.raw.methods.push((name, callback)); } - fn add_function_mut(&mut self, name: impl Into, function: F) + fn add_function_mut(&mut self, name: impl Into, function: F) where F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -531,7 +530,7 @@ impl UserDataMethods for UserDataRegistry { } #[cfg(feature = "async")] - fn add_async_function(&mut self, name: impl Into, function: F) + fn add_async_function(&mut self, name: impl Into, function: F) where F: Fn(Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, @@ -543,7 +542,7 @@ impl UserDataMethods for UserDataRegistry { self.raw.async_methods.push((name, callback)); } - fn add_meta_method(&mut self, name: impl Into, method: M) + fn add_meta_method(&mut self, name: impl Into, method: M) where M: Fn(&Lua, &T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -554,7 +553,7 @@ impl UserDataMethods for UserDataRegistry { self.raw.meta_methods.push((name, callback)); } - fn add_meta_method_mut(&mut self, name: impl Into, method: M) + fn add_meta_method_mut(&mut self, name: impl Into, method: M) where M: FnMut(&Lua, &mut T, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -566,7 +565,7 @@ impl UserDataMethods for UserDataRegistry { } #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] - fn add_async_meta_method(&mut self, name: impl Into, method: M) + fn add_async_meta_method(&mut self, name: impl Into, method: M) where T: 'static, M: Fn(Lua, UserDataRef, A) -> MR + MaybeSend + 'static, @@ -580,7 +579,7 @@ impl UserDataMethods for UserDataRegistry { } #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] - fn add_async_meta_method_mut(&mut self, name: impl Into, method: M) + fn add_async_meta_method_mut(&mut self, name: impl Into, method: M) where T: 'static, M: Fn(Lua, UserDataRefMut, A) -> MR + MaybeSend + 'static, @@ -593,7 +592,7 @@ impl UserDataMethods for UserDataRegistry { self.raw.async_meta_methods.push((name, callback)); } - fn add_meta_function(&mut self, name: impl Into, function: F) + fn add_meta_function(&mut self, name: impl Into, function: F) where F: Fn(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -604,7 +603,7 @@ impl UserDataMethods for UserDataRegistry { self.raw.meta_methods.push((name, callback)); } - fn add_meta_function_mut(&mut self, name: impl Into, function: F) + fn add_meta_function_mut(&mut self, name: impl Into, function: F) where F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, A: FromLuaMulti, @@ -616,7 +615,7 @@ impl UserDataMethods for UserDataRegistry { } #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] - fn add_async_meta_function(&mut self, name: impl Into, function: F) + fn add_async_meta_function(&mut self, name: impl Into, function: F) where F: Fn(Lua, A) -> FR + MaybeSend + 'static, A: FromLuaMulti, diff --git a/src/value.rs b/src/value.rs index 22f7ce8d..1250a3f7 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,14 +1,13 @@ use std::cmp::Ordering; use std::collections::HashSet; use std::os::raw::c_void; -use std::string::String as StdString; use std::{fmt, ptr, str}; use num_traits::FromPrimitive; use crate::error::{Error, Result}; use crate::function::Function; -use crate::string::{BorrowedStr, String}; +use crate::string::{BorrowedStr, LuaString}; use crate::table::Table; use crate::thread::Thread; use crate::types::{Integer, LightUserData, Number, ValueRef}; @@ -50,7 +49,7 @@ pub enum Value { /// An interned string, managed by Lua. /// /// Unlike Rust strings, Lua strings may not be valid UTF-8. - String(String), + String(LuaString), /// Reference to a Lua table. Table(Table), /// Reference to a Lua function (or closure). @@ -129,7 +128,7 @@ impl Value { #[inline] pub fn to_pointer(&self) -> *const c_void { match self { - Value::String(String(vref)) => { + Value::String(LuaString(vref)) => { // In Lua < 5.4 (excluding Luau), string pointers are NULL // Use alternative approach let lua = vref.lua.lock(); @@ -151,8 +150,8 @@ impl Value { /// /// This might invoke the `__tostring` metamethod for non-primitive types (eg. tables, /// functions). - pub fn to_string(&self) -> Result { - unsafe fn invoke_to_string(vref: &ValueRef) -> Result { + pub fn to_string(&self) -> Result { + unsafe fn invoke_to_string(vref: &ValueRef) -> Result { let lua = vref.lua.lock(); let state = lua.state(); let _guard = StackGuard::new(state); @@ -162,7 +161,7 @@ impl Value { protect_lua!(state, 1, 1, fn(state) { ffi::luaL_tolstring(state, -1, ptr::null_mut()); })?; - Ok(String(lua.pop_ref()).to_str()?.to_string()) + Ok(LuaString(lua.pop_ref()).to_str()?.to_string()) } match self { @@ -336,17 +335,17 @@ impl Value { self.as_number() } - /// Returns `true` if the value is a Lua [`String`]. + /// Returns `true` if the value is a [`LuaString`]. #[inline] pub fn is_string(&self) -> bool { self.as_string().is_some() } - /// Cast the value to Lua [`String`]. + /// Cast the value to a [`LuaString`]. /// - /// If the value is a Lua [`String`], returns it or `None` otherwise. + /// If the value is a [`LuaString`], returns it or `None` otherwise. #[inline] - pub fn as_string(&self) -> Option<&String> { + pub fn as_string(&self) -> Option<&LuaString> { match self { Value::String(s) => Some(s), _ => None, @@ -355,7 +354,7 @@ impl Value { /// Cast the value to [`BorrowedStr`]. /// - /// If the value is a Lua [`String`], try to convert it to [`BorrowedStr`] or return `None` + /// If the value is a [`LuaString`], try to convert it to [`BorrowedStr`] or return `None` /// otherwise. #[deprecated( since = "0.11.0", @@ -366,15 +365,15 @@ impl Value { self.as_string().and_then(|s| s.to_str().ok()) } - /// Cast the value to [`StdString`]. + /// Cast the value to [`String`]. /// - /// If the value is a Lua [`String`], converts it to [`StdString`] or returns `None` otherwise. + /// If the value is a [`LuaString`], converts it to [`String`] or returns `None` otherwise. #[deprecated( since = "0.11.0", note = "This method does not follow Rust naming convention. Use `as_string().map(|s| s.to_string_lossy())` instead." )] #[inline] - pub fn as_string_lossy(&self) -> Option { + pub fn as_string_lossy(&self) -> Option { self.as_string().map(|s| s.to_string_lossy()) } diff --git a/tests/async.rs b/tests/async.rs index de6e9fea..22df2ab3 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -1,6 +1,5 @@ #![cfg(feature = "async")] -use std::string::String as StdString; use std::sync::Arc; use std::time::Duration; @@ -40,7 +39,7 @@ async fn test_async_function() -> Result<()> { async fn test_async_function_wrap() -> Result<()> { let lua = Lua::new(); - let f = Function::wrap_async(|s: StdString| async move { + let f = Function::wrap_async(|s: String| async move { tokio::task::yield_now().await; Ok(s) }); @@ -68,7 +67,7 @@ async fn test_async_function_wrap() -> Result<()> { async fn test_async_function_wrap_raw() -> Result<()> { let lua = Lua::new(); - let f = Function::wrap_raw_async(|s: StdString| async move { + let f = Function::wrap_raw_async(|s: String| async move { tokio::task::yield_now().await; s }); diff --git a/tests/conversion.rs b/tests/conversion.rs index 1931e3b5..ca16327e 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -49,7 +49,7 @@ fn test_string_from_lua() -> Result<()> { let lua = Lua::new(); // From stack - let f = lua.create_function(|_, s: mlua::String| Ok(s))?; + let f = lua.create_function(|_, s: mlua::LuaString| Ok(s))?; let s = f.call::("hello, world!")?; assert_eq!(s, "hello, world!"); diff --git a/tests/function.rs b/tests/function.rs index d9970898..c5eb9f27 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -1,4 +1,4 @@ -use mlua::{Error, Function, Lua, Result, String, Table, Variadic}; +use mlua::{Error, Function, Lua, LuaString, Result, Table, Variadic}; #[test] fn test_function_call() -> Result<()> { @@ -343,7 +343,7 @@ fn test_function_deep_clone() -> Result<()> { fn test_function_wrap() -> Result<()> { let lua = Lua::new(); - let f = Function::wrap(|s: String, n| Ok(s.to_str().unwrap().repeat(n))); + let f = Function::wrap(|s: LuaString, n| Ok(s.to_str().unwrap().repeat(n))); lua.globals().set("f", f)?; lua.load(r#"assert(f("hello", 2) == "hellohello")"#) .exec() diff --git a/tests/multi.rs b/tests/multi.rs index 7468c49d..4b71fd40 100644 --- a/tests/multi.rs +++ b/tests/multi.rs @@ -1,4 +1,4 @@ -use mlua::{Error, ExternalError, Integer, IntoLuaMulti, Lua, MultiValue, Result, String, Value, Variadic}; +use mlua::{Error, ExternalError, Integer, IntoLuaMulti, Lua, LuaString, MultiValue, Result, Value, Variadic}; #[test] fn test_result_conversions() -> Result<()> { @@ -81,7 +81,7 @@ fn test_multivalue_by_ref() -> Result<()> { Value::Boolean(true), ]); - let f = lua.create_function(|_, (i, s, b): (i32, String, bool)| { + let f = lua.create_function(|_, (i, s, b): (i32, LuaString, bool)| { assert_eq!(i, 3); assert_eq!(s.to_str()?, "hello"); assert_eq!(b, true); diff --git a/tests/scope.rs b/tests/scope.rs index c8ca041f..9b16fcdd 100644 --- a/tests/scope.rs +++ b/tests/scope.rs @@ -1,10 +1,9 @@ use std::cell::Cell; use std::rc::Rc; -use std::string::String as StdString; use std::sync::Arc; use mlua::{ - AnyUserData, Error, Function, Lua, MetaMethod, ObjectLike, Result, String, UserData, UserDataFields, + AnyUserData, Error, Function, Lua, LuaString, MetaMethod, ObjectLike, Result, UserData, UserDataFields, UserDataMethods, UserDataRegistry, }; @@ -437,15 +436,15 @@ fn test_scope_userdata_ref_mut() -> Result<()> { fn test_scope_any_userdata() -> Result<()> { let lua = Lua::new(); - fn register(reg: &mut UserDataRegistry<&mut StdString>) { - reg.add_method_mut("push", |_, this, s: String| { + fn register(reg: &mut UserDataRegistry<&mut String>) { + reg.add_method_mut("push", |_, this, s: LuaString| { this.push_str(&s.to_str()?); Ok(()) }); reg.add_meta_method("__tostring", |_, data, ()| Ok((*data).clone())); } - let mut data = StdString::from("foo"); + let mut data = String::from("foo"); lua.scope(|scope| { let ud = scope.create_any_userdata(&mut data, register)?; lua.globals().set("ud", ud)?; @@ -527,11 +526,11 @@ fn test_scope_any_userdata_ref_mut() -> Result<()> { fn test_scope_destructors() -> Result<()> { let lua = Lua::new(); - lua.register_userdata_type::>(|reg| { + lua.register_userdata_type::>(|reg| { reg.add_meta_method("__tostring", |_, data, ()| Ok(data.to_string())); })?; - let arc_str = Arc::new(StdString::from("foo")); + let arc_str = Arc::new(String::from("foo")); let ud = lua.create_any_userdata(arc_str.clone())?; lua.scope(|scope| { @@ -544,7 +543,7 @@ fn test_scope_destructors() -> Result<()> { // Try destructing the userdata while it's borrowed let ud = lua.create_any_userdata(arc_str.clone())?; - ud.borrow_scoped::, _>(|arc_str| { + ud.borrow_scoped::, _>(|arc_str| { assert_eq!(arc_str.as_str(), "foo"); lua.scope(|scope| { scope.add_destructor(|| { diff --git a/tests/send.rs b/tests/send.rs index 6aa81003..f9803f5b 100644 --- a/tests/send.rs +++ b/tests/send.rs @@ -2,7 +2,6 @@ use std::cell::UnsafeCell; use std::marker::PhantomData; -use std::string::String as StdString; use mlua::{AnyUserData, Error, Lua, ObjectLike, Result, UserData, UserDataMethods, UserDataRef}; use static_assertions::{assert_impl_all, assert_not_impl_all}; @@ -12,7 +11,7 @@ fn test_userdata_multithread_access_send_only() -> Result<()> { let lua = Lua::new(); // This type is `Send` but not `Sync`. - struct MyUserData(StdString, PhantomData>); + struct MyUserData(String, PhantomData>); assert_impl_all!(MyUserData: Send); assert_not_impl_all!(MyUserData: Sync); @@ -52,7 +51,7 @@ fn test_userdata_multithread_access_sync() -> Result<()> { let lua = Lua::new(); // This type is `Send` and `Sync`. - struct MyUserData(StdString); + struct MyUserData(String); assert_impl_all!(MyUserData: Send, Sync); impl UserData for MyUserData { diff --git a/tests/string.rs b/tests/string.rs index 7f61b67b..158a5b5f 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -1,11 +1,11 @@ use std::borrow::Cow; use std::collections::HashSet; -use mlua::{Lua, Result, String}; +use mlua::{Lua, LuaString, Result}; #[test] fn test_string_compare() { - fn with_str(s: &str, f: F) { + fn with_str(s: &str, f: F) { f(Lua::new().create_string(s).unwrap()); } @@ -42,9 +42,9 @@ fn test_string_views() -> Result<()> { .exec()?; let globals = lua.globals(); - let ok: String = globals.get("ok")?; - let err: String = globals.get("err")?; - let empty: String = globals.get("empty")?; + let ok: LuaString = globals.get("ok")?; + let err: LuaString = globals.get("err")?; + let empty: LuaString = globals.get("empty")?; assert_eq!(ok.to_str()?, "null bytes are valid utf-8, wh\0 knew?"); assert_eq!(ok.to_string_lossy(), "null bytes are valid utf-8, wh\0 knew?"); @@ -74,7 +74,7 @@ fn test_string_from_bytes() -> Result<()> { fn test_string_hash() -> Result<()> { let lua = Lua::new(); - let set: HashSet = lua.load(r#"{"hello", "world", "abc", 321}"#).eval()?; + let set: HashSet = lua.load(r#"{"hello", "world", "abc", 321}"#).eval()?; assert_eq!(set.len(), 4); assert!(set.contains(&lua.create_string("hello")?)); assert!(set.contains(&lua.create_string("world")?)); @@ -133,13 +133,13 @@ fn test_string_display() -> Result<()> { fn test_string_wrap() -> Result<()> { let lua = Lua::new(); - let s = String::wrap("hello, world"); + let s = LuaString::wrap("hello, world"); lua.globals().set("s", s)?; - assert_eq!(lua.globals().get::("s")?, "hello, world"); + assert_eq!(lua.globals().get::("s")?, "hello, world"); - let s2 = String::wrap("hello, world (owned)".to_string()); + let s2 = LuaString::wrap("hello, world (owned)".to_string()); lua.globals().set("s2", s2)?; - assert_eq!(lua.globals().get::("s2")?, "hello, world (owned)"); + assert_eq!(lua.globals().get::("s2")?, "hello, world (owned)"); Ok(()) } diff --git a/tests/tests.rs b/tests/tests.rs index 7a0c43f5..9f8c7cfb 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -2,12 +2,11 @@ use std::collections::HashMap; #[cfg(not(target_arch = "wasm32"))] use std::iter::FromIterator; use std::panic::{AssertUnwindSafe, catch_unwind}; -use std::string::String as StdString; use std::sync::Arc; use std::{error, f32, f64, fmt}; use mlua::{ - ChunkMode, Error, ExternalError, Function, Lua, LuaOptions, Nil, Result, StdLib, String, Table, UserData, + ChunkMode, Error, ExternalError, Function, Lua, LuaOptions, Nil, Result, StdLib, Table, UserData, Value, Variadic, ffi, }; @@ -155,7 +154,7 @@ fn test_replace_globals() -> Result<()> { globals.set("foo", "bar")?; lua.set_globals(globals.clone())?; - let val = lua.load("return foo").eval::()?; + let val = lua.load("return foo").eval::()?; assert_eq!(val, "bar"); // Updating globals in sandboxed Lua state is not allowed @@ -398,7 +397,7 @@ fn test_error() -> Result<()> { fn test_panic() -> Result<()> { fn make_lua(options: LuaOptions) -> Result { let lua = Lua::new_with(StdLib::ALL_SAFE, options)?; - let rust_panic_function = lua.create_function(|_, msg: Option| -> Result<()> { + let rust_panic_function = lua.create_function(|_, msg: Option| -> Result<()> { if let Some(msg) = msg { panic!("{}", msg) } @@ -496,7 +495,7 @@ fn test_panic() -> Result<()> { .exec() }) { Ok(r) => panic!("no panic was detected: {:?}", r), - Err(p) => assert!(*p.downcast::().unwrap() == "rust panic from lua"), + Err(p) => assert!(*p.downcast::().unwrap() == "rust panic from lua"), } // Test disabling `catch_rust_panics` option / xpcall correctness @@ -520,7 +519,7 @@ fn test_panic() -> Result<()> { .exec() }) { Ok(r) => panic!("no panic was detected: {:?}", r), - Err(p) => assert!(*p.downcast::().unwrap() == "rust panic from lua"), + Err(p) => assert!(*p.downcast::().unwrap() == "rust panic from lua"), } Ok(()) @@ -686,7 +685,7 @@ fn test_pcall_xpcall() -> Result<()> { #[cfg(feature = "lua51")] assert!( globals - .get::("xpcall_error")? + .get::("xpcall_error")? .to_str()? .ends_with(": testerror") ); @@ -1073,7 +1072,7 @@ fn test_ref_stack_exhaustion() { })) { Ok(_) => panic!("no panic was detected"), Err(p) => assert!( - p.downcast::() + p.downcast::() .unwrap() .starts_with("cannot create a Lua reference, out of auxiliary stack space") ), @@ -1221,7 +1220,7 @@ fn test_context_thread_51() -> Result<()> { fn test_jit_version() -> Result<()> { let lua = Lua::new(); let jit: Table = lua.globals().get("jit")?; - assert!(jit.get::("version")?.to_str()?.contains("LuaJIT")); + assert!(jit.get::("version")?.to_str()?.contains("LuaJIT")); Ok(()) } @@ -1321,7 +1320,7 @@ fn test_inspect_stack() -> Result<()> { // Not inside any function assert!(lua.inspect_stack(0, |_| ()).is_none()); - let logline = lua.create_function(|lua, msg: StdString| { + let logline = lua.create_function(|lua, msg: String| { let r = lua .inspect_stack(1, |debug| { let source = debug.source().short_src; @@ -1425,7 +1424,7 @@ fn test_traceback() -> Result<()> { assert!(traceback.contains("stack traceback:")); // Test traceback inside a function - let get_traceback = lua.create_function(|lua, (msg, level): (Option, usize)| { + let get_traceback = lua.create_function(|lua, (msg, level): (Option, usize)| { lua.traceback(msg.as_deref(), level) })?; lua.globals().set("get_traceback", get_traceback)?; @@ -1507,10 +1506,10 @@ fn test_multi_states() -> Result<()> { #[cfg(any(feature = "lua55", feature = "lua54"))] fn test_warnings() -> Result<()> { let lua = Lua::new(); - lua.set_app_data::>(Vec::new()); + lua.set_app_data::>(Vec::new()); lua.set_warning_function(|lua, msg, incomplete| { - lua.app_data_mut::>() + lua.app_data_mut::>() .unwrap() .push((msg.to_string(), incomplete)); Ok(()) @@ -1524,7 +1523,7 @@ fn test_warnings() -> Result<()> { lua.remove_warning_function(); lua.warning("one more warning", false); - let messages = lua.app_data_ref::>().unwrap(); + let messages = lua.app_data_ref::>().unwrap(); assert_eq!( *messages, vec![ diff --git a/tests/types.rs b/tests/types.rs index 6c7a0185..0cd775bc 100644 --- a/tests/types.rs +++ b/tests/types.rs @@ -1,6 +1,6 @@ use std::os::raw::c_void; -use mlua::{Function, LightUserData, Lua, Number, Result, String as LuaString, Thread}; +use mlua::{Function, LightUserData, Lua, LuaString, Number, Result, Thread}; #[test] fn test_lightuserdata() -> Result<()> { diff --git a/tests/userdata.rs b/tests/userdata.rs index ce1b7d16..12bf5668 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -1,13 +1,12 @@ use std::any::TypeId; use std::collections::HashMap; -use std::string::String as StdString; use std::sync::Arc; #[cfg(any(feature = "lua55", feature = "lua54"))] use std::sync::atomic::{AtomicI64, Ordering}; use mlua::{ - AnyUserData, Error, ExternalError, Function, Lua, MetaMethod, Nil, ObjectLike, Result, String, UserData, + AnyUserData, Error, ExternalError, Function, Lua, LuaString, MetaMethod, Nil, ObjectLike, Result, UserData, UserDataFields, UserDataMethods, UserDataRef, UserDataRegistry, Value, Variadic, }; @@ -131,7 +130,7 @@ fn test_metamethods() -> Result<()> { MetaMethod::Eq, |_, (lhs, rhs): (UserDataRef, UserDataRef)| Ok(lhs.0 == rhs.0), ); - methods.add_meta_method(MetaMethod::Index, |_, data, index: String| { + methods.add_meta_method(MetaMethod::Index, |_, data, index: LuaString| { if index.to_str()? == "inner" { Ok(data.0) } else { @@ -492,8 +491,8 @@ fn test_user_values() -> Result<()> { ud.set_nth_user_value(1, "hello")?; ud.set_nth_user_value(2, "world")?; ud.set_nth_user_value(65535, 321)?; - assert_eq!(ud.nth_user_value::(1)?, "hello"); - assert_eq!(ud.nth_user_value::(2)?, "world"); + assert_eq!(ud.nth_user_value::(1)?, "hello"); + assert_eq!(ud.nth_user_value::(2)?, "world"); assert_eq!(ud.nth_user_value::(3)?, Value::Nil); assert_eq!(ud.nth_user_value::(65535)?, 321); @@ -583,8 +582,8 @@ fn test_fields() -> Result<()> { }); // Use userdata "uservalue" storage - fields.add_field_function_get("uval", |_, ud| ud.user_value::>()); - fields.add_field_function_set("uval", |_, ud, s: Option| ud.set_user_value(s)); + fields.add_field_function_get("uval", |_, ud| ud.user_value::>()); + fields.add_field_function_set("uval", |_, ud, s: Option| ud.set_user_value(s)); fields.add_meta_field(MetaMethod::Index, HashMap::from([("f", 321)])); fields.add_meta_field_with(MetaMethod::NewIndex, |lua| { @@ -631,7 +630,7 @@ fn test_fields() -> Result<()> { } fn add_methods>(methods: &mut M) { - methods.add_meta_method(MetaMethod::Index, |_, _, name: StdString| match &*name { + methods.add_meta_method(MetaMethod::Index, |_, _, name: LuaString| match name.to_str()?.as_ref() { "y" => Ok(Some(-1)), _ => Ok(None), }); @@ -660,7 +659,7 @@ fn test_metatable() -> Result<()> { fn add_methods>(methods: &mut M) { methods.add_function("my_type_name", |_, data: AnyUserData| { let metatable = data.metatable()?; - metatable.get::(MetaMethod::Type) + metatable.get::(MetaMethod::Type) }); } } @@ -724,7 +723,7 @@ fn test_metatable() -> Result<()> { let ud = lua.create_userdata(MyUserData3)?; let metatable = ud.metatable()?; - assert_eq!(metatable.get::(MetaMethod::Type)?.to_str()?, "CustomName"); + assert_eq!(metatable.get::(MetaMethod::Type)?.to_str()?, "CustomName"); Ok(()) } @@ -777,16 +776,16 @@ fn test_userdata_proxy() -> Result<()> { fn test_any_userdata() -> Result<()> { let lua = Lua::new(); - lua.register_userdata_type::(|reg| { + lua.register_userdata_type::(|reg| { reg.add_method("get", |_, this, ()| Ok(this.clone())); - reg.add_method_mut("concat", |_, this, s: String| { + reg.add_method_mut("concat", |_, this, s: LuaString| { this.push_str(&s.to_string_lossy()); Ok(()) }); })?; let ud = lua.create_any_userdata("hello".to_string())?; - assert_eq!(&*ud.borrow::()?, "hello"); + assert_eq!(&*ud.borrow::()?, "hello"); lua.globals().set("ud", ud)?; lua.load( @@ -806,7 +805,7 @@ fn test_any_userdata() -> Result<()> { fn test_any_userdata_wrap() -> Result<()> { let lua = Lua::new(); - lua.register_userdata_type::(|reg| { + lua.register_userdata_type::(|reg| { reg.add_method("get", |_, this, ()| Ok(this.clone())); })?; @@ -858,7 +857,7 @@ fn test_userdata_object_like() -> Result<()> { r => panic!("expected RuntimeError, got {r:?}"), } - assert_eq!(ud.call::(())?, "called"); + assert_eq!(ud.call::(())?, "called"); ud.call_method::<()>("add", 2)?; assert_eq!(ud.get::("n")?, 323); @@ -1376,7 +1375,7 @@ fn test_userdata_namecall() -> Result<()> { registry.add_method("method", |_, _, ()| Ok("method called")); registry.add_field_method_get("field", |_, _| Ok("field value")); - registry.add_meta_method(MetaMethod::Index, |_, _, key: StdString| Ok(key)); + registry.add_meta_method(MetaMethod::Index, |_, _, key: LuaString| Ok(key)); registry.enable_namecall(); } @@ -1414,7 +1413,7 @@ fn test_userdata_get_path() -> Result<()> { } let ud = lua.create_userdata(MyUd)?; - assert_eq!(ud.get_path::(".value")?, "userdata_value"); + assert_eq!(ud.get_path::(".value")?, "userdata_value"); Ok(()) } diff --git a/tests/value.rs b/tests/value.rs index a8f282ad..43ec708f 100644 --- a/tests/value.rs +++ b/tests/value.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use std::os::raw::c_void; use std::ptr; -use std::string::String as StdString; use mlua::{Error, LightUserData, Lua, MultiValue, Result, UserData, UserDataMethods, Value}; @@ -178,7 +177,7 @@ fn test_value_to_string() -> Result<()> { assert!(thread.to_string()?.starts_with("thread:")); assert_eq!(thread.type_name(), "thread"); - lua.register_userdata_type::(|reg| { + lua.register_userdata_type::(|reg| { reg.add_meta_method("__tostring", |_, this, ()| Ok(this.clone())); })?; let ud: Value = Value::UserData(lua.create_any_userdata(String::from("string userdata"))?); @@ -213,9 +212,9 @@ fn test_value_to_string() -> Result<()> { fn test_debug_format() -> Result<()> { let lua = Lua::new(); - lua.register_userdata_type::>(|_| {})?; + lua.register_userdata_type::>(|_| {})?; let ud = lua - .create_any_userdata::>(HashMap::new()) + .create_any_userdata::>(HashMap::new()) .map(Value::UserData)?; assert!(format!("{ud:#?}").starts_with("HashMap:")); From c79b5e9cdbfbc29bb53d053945d9ef0ad53ab341 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 29 Jan 2026 23:03:56 +0000 Subject: [PATCH 601/635] cargo fmt --- src/chunk.rs | 5 +---- src/prelude.rs | 12 ++++++------ src/userdata/object.rs | 1 - tests/multi.rs | 4 +++- tests/tests.rs | 15 +++++++++------ tests/userdata.rs | 17 +++++++++++------ 6 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index 02bf474e..3aedfb43 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -379,10 +379,7 @@ impl Compiler { /// Sets a list of builtins that should be disabled. #[must_use] - pub fn set_disabled_builtins>( - mut self, - builtins: impl IntoIterator, - ) -> Self { + pub fn set_disabled_builtins>(mut self, builtins: impl IntoIterator) -> Self { self.disabled_builtins = builtins.into_iter().map(|s| s.into()).collect(); self } diff --git a/src/prelude.rs b/src/prelude.rs index 5c4db648..1f3ba217 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -9,12 +9,12 @@ pub use crate::{ IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions, LuaString, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, - Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, - Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData, - UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, - UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, - UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, - Variadic as LuaVariadic, VmState as LuaVmState, WeakLua, + Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, Thread as LuaThread, + ThreadStatus as LuaThreadStatus, UserData as LuaUserData, UserDataFields as LuaUserDataFields, + UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, + UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut, + UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, Variadic as LuaVariadic, + VmState as LuaVmState, WeakLua, }; #[cfg(not(feature = "luau"))] diff --git a/src/userdata/object.rs b/src/userdata/object.rs index 35f11e61..12019e01 100644 --- a/src/userdata/object.rs +++ b/src/userdata/object.rs @@ -1,4 +1,3 @@ - use crate::Function; use crate::error::{Error, Result}; use crate::state::WeakLua; diff --git a/tests/multi.rs b/tests/multi.rs index 4b71fd40..9fe43bfc 100644 --- a/tests/multi.rs +++ b/tests/multi.rs @@ -1,4 +1,6 @@ -use mlua::{Error, ExternalError, Integer, IntoLuaMulti, Lua, LuaString, MultiValue, Result, Value, Variadic}; +use mlua::{ + Error, ExternalError, Integer, IntoLuaMulti, Lua, LuaString, MultiValue, Result, Value, Variadic, +}; #[test] fn test_result_conversions() -> Result<()> { diff --git a/tests/tests.rs b/tests/tests.rs index 9f8c7cfb..47151387 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -6,8 +6,8 @@ use std::sync::Arc; use std::{error, f32, f64, fmt}; use mlua::{ - ChunkMode, Error, ExternalError, Function, Lua, LuaOptions, Nil, Result, StdLib, Table, UserData, - Value, Variadic, ffi, + ChunkMode, Error, ExternalError, Function, Lua, LuaOptions, Nil, Result, StdLib, Table, UserData, Value, + Variadic, ffi, }; #[test] @@ -1220,7 +1220,11 @@ fn test_context_thread_51() -> Result<()> { fn test_jit_version() -> Result<()> { let lua = Lua::new(); let jit: Table = lua.globals().get("jit")?; - assert!(jit.get::("version")?.to_str()?.contains("LuaJIT")); + assert!( + jit.get::("version")? + .to_str()? + .contains("LuaJIT") + ); Ok(()) } @@ -1424,9 +1428,8 @@ fn test_traceback() -> Result<()> { assert!(traceback.contains("stack traceback:")); // Test traceback inside a function - let get_traceback = lua.create_function(|lua, (msg, level): (Option, usize)| { - lua.traceback(msg.as_deref(), level) - })?; + let get_traceback = lua + .create_function(|lua, (msg, level): (Option, usize)| lua.traceback(msg.as_deref(), level))?; lua.globals().set("get_traceback", get_traceback)?; lua.load( diff --git a/tests/userdata.rs b/tests/userdata.rs index 12bf5668..df5ed1f4 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -6,8 +6,8 @@ use std::sync::Arc; use std::sync::atomic::{AtomicI64, Ordering}; use mlua::{ - AnyUserData, Error, ExternalError, Function, Lua, LuaString, MetaMethod, Nil, ObjectLike, Result, UserData, - UserDataFields, UserDataMethods, UserDataRef, UserDataRegistry, Value, Variadic, + AnyUserData, Error, ExternalError, Function, Lua, LuaString, MetaMethod, Nil, ObjectLike, Result, + UserData, UserDataFields, UserDataMethods, UserDataRef, UserDataRegistry, Value, Variadic, }; #[test] @@ -630,9 +630,11 @@ fn test_fields() -> Result<()> { } fn add_methods>(methods: &mut M) { - methods.add_meta_method(MetaMethod::Index, |_, _, name: LuaString| match name.to_str()?.as_ref() { - "y" => Ok(Some(-1)), - _ => Ok(None), + methods.add_meta_method(MetaMethod::Index, |_, _, name: LuaString| { + match name.to_str()?.as_ref() { + "y" => Ok(Some(-1)), + _ => Ok(None), + } }); } } @@ -723,7 +725,10 @@ fn test_metatable() -> Result<()> { let ud = lua.create_userdata(MyUserData3)?; let metatable = ud.metatable()?; - assert_eq!(metatable.get::(MetaMethod::Type)?.to_str()?, "CustomName"); + assert_eq!( + metatable.get::(MetaMethod::Type)?.to_str()?, + "CustomName" + ); Ok(()) } From 2fbd266da6985bc5516bb9d4a14cbd0ae51d9e59 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 29 Jan 2026 21:03:09 +0000 Subject: [PATCH 602/635] Remove `Error::ToLuaConversionError` This variant used only once and not practically useful. --- src/conversion.rs | 17 ++++++++++------- src/error.rs | 16 ---------------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index 58642771..eb622d54 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -6,7 +6,7 @@ use std::os::raw::c_int; use std::path::{Path, PathBuf}; use std::{mem, slice, str}; -use bstr::{BStr, BString, ByteSlice, ByteVec}; +use bstr::{BStr, BString, ByteVec}; use num_traits::cast; use crate::error::{Error, Result}; @@ -730,14 +730,17 @@ impl FromLua for OsString { } impl IntoLua for &OsStr { + #[cfg(unix)] #[inline] fn into_lua(self, lua: &Lua) -> Result { - let s = <[u8]>::from_os_str(self).ok_or_else(|| Error::ToLuaConversionError { - from: "OsStr".into(), - to: "string", - message: Some("invalid utf-8 encoding".into()), - })?; - Ok(Value::String(lua.create_string(s)?)) + use std::os::unix::ffi::OsStrExt; + Ok(Value::String(lua.create_string(self.as_bytes())?)) + } + + #[cfg(not(unix))] + #[inline] + fn into_lua(self, lua: &Lua) -> Result { + self.display().to_string().into_lua(lua) } } diff --git a/src/error.rs b/src/error.rs index 092fcb90..7d20a5ab 100644 --- a/src/error.rs +++ b/src/error.rs @@ -87,15 +87,6 @@ pub enum Error { /// Underlying error returned when converting argument to a Lua value. cause: Arc, }, - /// A Rust value could not be converted to a Lua value. - ToLuaConversionError { - /// Name of the Rust type that could not be converted. - from: String, - /// Name of the Lua type that could not be created. - to: &'static str, - /// A message indicating why the conversion failed in more detail. - message: Option, - }, /// A Lua value could not be converted to the expected Rust type. FromLuaConversionError { /// Name of the Lua type that could not be converted. @@ -249,13 +240,6 @@ impl fmt::Display for Error { } write!(fmt, ": {cause}") } - Error::ToLuaConversionError { from, to, message } => { - write!(fmt, "error converting {from} to Lua {to}")?; - match message { - None => Ok(()), - Some(message) => write!(fmt, " ({message})"), - } - } Error::FromLuaConversionError { from, to, message } => { write!(fmt, "error converting Lua {from} to {to}")?; match message { From 613748ec16c65341bf5b4d2b48d36c9432069124 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 29 Jan 2026 20:47:06 +0000 Subject: [PATCH 603/635] Use `Error::from_lua_conversion` helper --- src/conversion.rs | 272 ++++++++++++++++---------------------------- src/error.rs | 1 + src/state/raw.rs | 10 +- src/string.rs | 7 +- src/userdata/ref.rs | 10 +- 5 files changed, 108 insertions(+), 192 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index eb622d54..b74343db 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -71,11 +71,7 @@ impl FromLua for LuaString { fn from_lua(value: Value, lua: &Lua) -> Result { let ty = value.type_name(); lua.coerce_string(value)? - .ok_or_else(|| Error::FromLuaConversionError { - from: ty, - to: "string".to_string(), - message: Some("expected string or number".to_string()), - }) + .ok_or_else(|| Error::from_lua_conversion(ty, "string", "expected string or number".to_string())) } unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { @@ -203,11 +199,7 @@ impl FromLua for Table { fn from_lua(value: Value, _: &Lua) -> Result
{ match value { Value::Table(table) => Ok(table), - _ => Err(Error::FromLuaConversionError { - from: value.type_name(), - to: "table".to_string(), - message: None, - }), + _ => Err(Error::from_lua_conversion(value.type_name(), "table", None)), } } } @@ -237,11 +229,7 @@ impl FromLua for Function { fn from_lua(value: Value, _: &Lua) -> Result { match value { Value::Function(table) => Ok(table), - _ => Err(Error::FromLuaConversionError { - from: value.type_name(), - to: "function".to_string(), - message: None, - }), + _ => Err(Error::from_lua_conversion(value.type_name(), "function", None)), } } } @@ -271,11 +259,7 @@ impl FromLua for Thread { fn from_lua(value: Value, _: &Lua) -> Result { match value { Value::Thread(t) => Ok(t), - _ => Err(Error::FromLuaConversionError { - from: value.type_name(), - to: "thread".to_string(), - message: None, - }), + _ => Err(Error::from_lua_conversion(value.type_name(), "thread", None)), } } } @@ -305,11 +289,7 @@ impl FromLua for AnyUserData { fn from_lua(value: Value, _: &Lua) -> Result { match value { Value::UserData(ud) => Ok(ud), - _ => Err(Error::FromLuaConversionError { - from: value.type_name(), - to: "userdata".to_string(), - message: None, - }), + _ => Err(Error::from_lua_conversion(value.type_name(), "userdata", None)), } } } @@ -427,11 +407,11 @@ impl FromLua for LightUserData { fn from_lua(value: Value, _: &Lua) -> Result { match value { Value::LightUserData(ud) => Ok(ud), - _ => Err(Error::FromLuaConversionError { - from: value.type_name(), - to: "lightuserdata".to_string(), - message: None, - }), + _ => Err(Error::from_lua_conversion( + value.type_name(), + "lightuserdata", + None, + )), } } } @@ -450,11 +430,7 @@ impl FromLua for crate::Vector { fn from_lua(value: Value, _: &Lua) -> Result { match value { Value::Vector(v) => Ok(v), - _ => Err(Error::FromLuaConversionError { - from: value.type_name(), - to: "vector".to_string(), - message: None, - }), + _ => Err(Error::from_lua_conversion(value.type_name(), "vector", None)), } } } @@ -487,11 +463,7 @@ impl FromLua for crate::Buffer { fn from_lua(value: Value, _: &Lua) -> Result { match value { Value::Buffer(buf) => Ok(buf), - _ => Err(Error::FromLuaConversionError { - from: value.type_name(), - to: "buffer".to_string(), - message: None, - }), + _ => Err(Error::from_lua_conversion(value.type_name(), "buffer", None)), } } } @@ -524,10 +496,8 @@ impl FromLua for String { let ty = value.type_name(); Ok(lua .coerce_string(value)? - .ok_or_else(|| Error::FromLuaConversionError { - from: ty, - to: Self::type_name(), - message: Some("expected string or number".to_string()), + .ok_or_else(|| { + Error::from_lua_conversion(ty, Self::type_name(), "expected string or number".to_string()) })? .to_str()? .to_owned()) @@ -543,11 +513,7 @@ impl FromLua for String { let bytes = slice::from_raw_parts(data as *const u8, size); return str::from_utf8(bytes) .map(|s| s.to_owned()) - .map_err(|e| Error::FromLuaConversionError { - from: "string", - to: Self::type_name(), - message: Some(e.to_string()), - }); + .map_err(|e| Error::from_lua_conversion("string", Self::type_name(), e.to_string())); } // Fallback to default Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua()) @@ -586,10 +552,8 @@ impl FromLua for Box { let ty = value.type_name(); Ok(lua .coerce_string(value)? - .ok_or_else(|| Error::FromLuaConversionError { - from: ty, - to: Self::type_name(), - message: Some("expected string or number".to_string()), + .ok_or_else(|| { + Error::from_lua_conversion(ty, Self::type_name(), "expected string or number".to_string()) })? .to_str()? .to_owned() @@ -613,21 +577,12 @@ impl FromLua for CString { #[inline] fn from_lua(value: Value, lua: &Lua) -> Result { let ty = value.type_name(); - let string = lua - .coerce_string(value)? - .ok_or_else(|| Error::FromLuaConversionError { - from: ty, - to: Self::type_name(), - message: Some("expected string or number".to_string()), - })?; - + let string = lua.coerce_string(value)?.ok_or_else(|| { + Error::from_lua_conversion(ty, Self::type_name(), "expected string or number".to_string()) + })?; match CStr::from_bytes_with_nul(&string.as_bytes_with_nul()) { Ok(s) => Ok(s.into()), - Err(_) => Err(Error::FromLuaConversionError { - from: ty, - to: Self::type_name(), - message: Some("invalid C-style string".to_string()), - }), + Err(err) => Err(Error::from_lua_conversion(ty, Self::type_name(), err.to_string())), } } } @@ -667,10 +622,8 @@ impl FromLua for BString { Value::Buffer(buf) => Ok(buf.to_vec().into()), _ => Ok((*lua .coerce_string(value)? - .ok_or_else(|| Error::FromLuaConversionError { - from: ty, - to: Self::type_name(), - message: Some("expected string or number".to_string()), + .ok_or_else(|| { + Error::from_lua_conversion(ty, Self::type_name(), "expected string or number".to_string()) })? .as_bytes()) .into()), @@ -721,11 +674,7 @@ impl FromLua for OsString { let bs = BString::from_lua(value, lua)?; Vec::from(bs) .into_os_string() - .map_err(|err| Error::FromLuaConversionError { - from: ty, - to: "OsString".into(), - message: Some(err.to_string()), - }) + .map_err(|err| Error::from_lua_conversion(ty, "OsString", err.to_string())) } } @@ -778,34 +727,25 @@ impl FromLua for char { fn from_lua(value: Value, _lua: &Lua) -> Result { let ty = value.type_name(); match value { - Value::Integer(i) => { - cast(i) - .and_then(char::from_u32) - .ok_or_else(|| Error::FromLuaConversionError { - from: ty, - to: "char".to_string(), - message: Some("integer out of range when converting to char".to_string()), - }) - } + Value::Integer(i) => cast(i).and_then(char::from_u32).ok_or_else(|| { + let msg = "integer out of range when converting to char"; + Error::from_lua_conversion(ty, "char", msg.to_string()) + }), Value::String(s) => { let str = s.to_str()?; let mut str_iter = str.chars(); match (str_iter.next(), str_iter.next()) { (Some(char), None) => Ok(char), - _ => Err(Error::FromLuaConversionError { - from: ty, - to: "char".to_string(), - message: Some( - "expected string to have exactly one char when converting to char".to_string(), - ), - }), + _ => { + let msg = "expected string to have exactly one char when converting to char"; + Err(Error::from_lua_conversion(ty, "char", msg.to_string())) + } } } - _ => Err(Error::FromLuaConversionError { - from: ty, - to: Self::type_name(), - message: Some("expected string or integer".to_string()), - }), + _ => { + let msg = "expected string or integer"; + Err(Error::from_lua_conversion(ty, Self::type_name(), msg.to_string())) + } } } } @@ -856,24 +796,14 @@ macro_rules! lua_convert_int { if let Some(i) = lua.coerce_integer(value.clone())? { cast(i) } else { - cast( - lua.coerce_number(value)? - .ok_or_else(|| Error::FromLuaConversionError { - from: ty, - to: stringify!($x).to_string(), - message: Some( - "expected number or string coercible to number".to_string(), - ), - })?, - ) + cast(lua.coerce_number(value)?.ok_or_else(|| { + let msg = "expected number or string coercible to number"; + Error::from_lua_conversion(ty, stringify!($x), msg.to_string()) + })?) } } }) - .ok_or_else(|| Error::FromLuaConversionError { - from: ty, - to: stringify!($x).to_string(), - message: Some("out of range".to_owned()), - }) + .ok_or_else(|| Error::from_lua_conversion(ty, stringify!($x), "out of range".to_string())) } unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { @@ -883,10 +813,8 @@ macro_rules! lua_convert_int { let mut ok = 0; let i = ffi::lua_tointegerx(state, idx, &mut ok); if ok != 0 { - return cast(i).ok_or_else(|| Error::FromLuaConversionError { - from: "integer", - to: stringify!($x).to_string(), - message: Some("out of range".to_owned()), + return cast(i).ok_or_else(|| { + Error::from_lua_conversion("integer", stringify!($x), "out of range".to_string()) }); } } @@ -923,13 +851,10 @@ macro_rules! lua_convert_float { #[inline] 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()), - }) + lua.coerce_number(value)?.map(|n| n as $x).ok_or_else(|| { + let msg = "expected number or string coercible to number"; + Error::from_lua_conversion(ty, stringify!($x), msg.to_string()) + }) } unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { @@ -989,18 +914,16 @@ where }, Value::Table(table) => { let vec = table.sequence_values().collect::>>()?; - vec.try_into() - .map_err(|vec: Vec| Error::FromLuaConversionError { - from: "table", - to: Self::type_name(), - message: Some(format!("expected table of length {N}, got {}", vec.len())), - }) + vec.try_into().map_err(|vec: Vec| { + let msg = format!("expected table of length {N}, got {}", vec.len()); + Error::from_lua_conversion("table", Self::type_name(), msg) + }) + } + _ => { + let msg = format!("expected table of length {N}"); + let err = Error::from_lua_conversion(value.type_name(), Self::type_name(), msg.to_string()); + Err(err) } - _ => Err(Error::FromLuaConversionError { - from: value.type_name(), - to: Self::type_name(), - message: Some("expected table".to_string()), - }), } } } @@ -1031,11 +954,11 @@ impl FromLua for Vec { fn from_lua(value: Value, _lua: &Lua) -> Result { match value { Value::Table(table) => table.sequence_values().collect(), - _ => Err(Error::FromLuaConversionError { - from: value.type_name(), - to: Self::type_name(), - message: Some("expected table".to_string()), - }), + _ => Err(Error::from_lua_conversion( + value.type_name(), + Self::type_name(), + "expected table".to_string(), + )), } } } @@ -1050,14 +973,13 @@ impl IntoLua for HashMap FromLua for HashMap { #[inline] fn from_lua(value: Value, _: &Lua) -> Result { - if let Value::Table(table) = value { - table.pairs().collect() - } else { - Err(Error::FromLuaConversionError { - from: value.type_name(), - to: Self::type_name(), - message: Some("expected table".to_string()), - }) + match value { + Value::Table(table) => table.pairs().collect(), + _ => Err(Error::from_lua_conversion( + value.type_name(), + Self::type_name(), + "expected table".to_string(), + )), } } } @@ -1072,14 +994,13 @@ impl IntoLua for BTreeMap { impl FromLua for BTreeMap { #[inline] fn from_lua(value: Value, _: &Lua) -> Result { - if let Value::Table(table) = value { - table.pairs().collect() - } else { - Err(Error::FromLuaConversionError { - from: value.type_name(), - to: Self::type_name(), - message: Some("expected table".to_string()), - }) + match value { + Value::Table(table) => table.pairs().collect(), + _ => Err(Error::from_lua_conversion( + value.type_name(), + Self::type_name(), + "expected table".to_string(), + )), } } } @@ -1099,11 +1020,11 @@ impl FromLua for HashSet match value { Value::Table(table) if table.raw_len() > 0 => table.sequence_values().collect(), Value::Table(table) => table.pairs::().map(|res| res.map(|(k, _)| k)).collect(), - _ => Err(Error::FromLuaConversionError { - from: value.type_name(), - to: Self::type_name(), - message: Some("expected table".to_string()), - }), + _ => Err(Error::from_lua_conversion( + value.type_name(), + Self::type_name(), + "expected table".to_string(), + )), } } } @@ -1123,11 +1044,11 @@ impl FromLua for BTreeSet { match value { Value::Table(table) if table.raw_len() > 0 => table.sequence_values().collect(), Value::Table(table) => table.pairs::().map(|res| res.map(|(k, _)| k)).collect(), - _ => Err(Error::FromLuaConversionError { - from: value.type_name(), - to: Self::type_name(), - message: Some("expected table".to_string()), - }), + _ => Err(Error::from_lua_conversion( + value.type_name(), + Self::type_name(), + "expected table".to_string(), + )), } } } @@ -1197,11 +1118,11 @@ impl FromLua for Either { // Try the right type Err(_) => match R::from_lua(value, lua).map(Either::Right) { Ok(r) => Ok(r), - Err(_) => Err(Error::FromLuaConversionError { - from: value_type_name, - to: Self::type_name(), - message: None, - }), + Err(_) => Err(Error::from_lua_conversion( + value_type_name, + Self::type_name(), + None, + )), }, } } @@ -1213,13 +1134,12 @@ impl FromLua for Either { Err(_) => match R::from_stack(idx, lua).map(Either::Right) { Ok(r) => Ok(r), Err(_) => { - let value_type_name = - CStr::from_ptr(ffi::lua_typename(lua.state(), ffi::lua_type(lua.state(), idx))); - Err(Error::FromLuaConversionError { - from: value_type_name.to_str().unwrap(), - to: Self::type_name(), - message: None, - }) + let state = lua.state(); + let from_type_name = CStr::from_ptr(ffi::lua_typename(state, ffi::lua_type(state, idx))) + .to_str() + .unwrap_or("unknown"); + let err = Error::from_lua_conversion(from_type_name, Self::type_name(), None); + Err(err) } }, } diff --git a/src/error.rs b/src/error.rs index 7d20a5ab..c5b5e150 100644 --- a/src/error.rs +++ b/src/error.rs @@ -382,6 +382,7 @@ impl Error { } } + #[inline] pub(crate) fn from_lua_conversion( from: &'static str, to: impl ToString, diff --git a/src/state/raw.rs b/src/state/raw.rs index 7e1421a7..4b2fa2da 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -1219,13 +1219,11 @@ impl RawLua { Ok(type_id) => Ok(type_id), Err(Error::UserDataTypeMismatch) if ffi::lua_type(state, idx) != ffi::LUA_TUSERDATA => { // Report `FromLuaConversionError` instead - // In Luau `luaL_typename` return heap-allocated string that is valid only for - // the `state` lifetime. - // `lua_typename` is used instead to get a truly static string. - let idx_type_name = CStr::from_ptr(ffi::lua_typename(state, ffi::lua_type(state, idx))); - let idx_type_name = idx_type_name.to_str().unwrap(); + let type_name = CStr::from_ptr(ffi::lua_typename(state, ffi::lua_type(state, idx))) + .to_str() + .unwrap_or("unknown"); let message = format!("expected userdata of type '{}'", short_type_name::()); - Err(Error::from_lua_conversion(idx_type_name, "userdata", message)) + Err(Error::from_lua_conversion(type_name, "userdata", message)) } Err(err) => Err(err), } diff --git a/src/string.rs b/src/string.rs index 9f2b4e95..0a1eff91 100644 --- a/src/string.rs +++ b/src/string.rs @@ -307,11 +307,8 @@ impl<'a> TryFrom<&'a LuaString> for BorrowedStr<'a> { #[inline] fn try_from(value: &'a LuaString) -> Result { let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(value); - let buf = str::from_utf8(buf).map_err(|e| Error::FromLuaConversionError { - from: "string", - to: "&str".to_string(), - message: Some(e.to_string()), - })?; + let buf = + str::from_utf8(buf).map_err(|e| Error::from_lua_conversion("string", "&str", e.to_string()))?; Ok(Self { buf, borrow, _lua }) } } diff --git a/src/userdata/ref.rs b/src/userdata/ref.rs index 0661c129..48f67c28 100644 --- a/src/userdata/ref.rs +++ b/src/userdata/ref.rs @@ -446,11 +446,11 @@ impl DerefMut for UserDataRefMutInner { fn try_value_to_userdata(value: Value) -> Result { match value { Value::UserData(ud) => Ok(ud), - _ => Err(Error::FromLuaConversionError { - from: value.type_name(), - to: "userdata".to_string(), - message: Some(format!("expected userdata of type {}", type_name::())), - }), + _ => Err(Error::from_lua_conversion( + value.type_name(), + "userdata", + format!("expected userdata of type {}", type_name::()), + )), } } From c8436e2b8046d7e9ffa60fbe3ed36b1352265119 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 30 Jan 2026 12:30:30 +0000 Subject: [PATCH 604/635] Make `function` module public Reduce number of function-specific types exported to the mlua root and keep them inside the module. --- src/function.rs | 83 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 5 ++- src/prelude.rs | 13 ++++---- tests/function.rs | 8 ++--- 4 files changed, 95 insertions(+), 14 deletions(-) diff --git a/src/function.rs b/src/function.rs index 24fc34de..9d1d7c9d 100644 --- a/src/function.rs +++ b/src/function.rs @@ -1,3 +1,84 @@ +//! Lua function handling. +//! +//! This module provides types for working with Lua functions from Rust, including +//! both Lua-defined functions and native Rust callbacks. +//! +//! # Main Types +//! +//! - [`Function`] - A handle to a Lua function that can be called from Rust. +//! - [`FunctionInfo`] - Debug information about a function (name, source, line numbers, etc.). +//! - [`CoverageInfo`] - Code coverage data for Luau functions (requires `luau` feature). +//! +//! # Calling Functions +//! +//! Use [`Function::call`] to invoke a Lua function synchronously: +//! +//! ``` +//! # use mlua::{Function, Lua, Result}; +//! # fn main() -> Result<()> { +//! let lua = Lua::new(); +//! +//! // Get a built-in function +//! let print: Function = lua.globals().get("print")?; +//! print.call::<()>("Hello from Rust!")?; +//! +//! // Call a function that returns values +//! let tonumber: Function = lua.globals().get("tonumber")?; +//! let n: i32 = tonumber.call("42")?; +//! assert_eq!(n, 42); +//! # Ok(()) +//! # } +//! ``` +//! +//! For asynchronous execution, use `Function::call_async` (requires `async` feature): +//! +//! ```ignore +//! let result: String = my_async_func.call_async(args).await?; +//! ``` +//! +//! # Creating Functions +//! +//! Functions can be created from Rust closures using [`Lua::create_function`]: +//! +//! ``` +//! # use mlua::{Lua, Result}; +//! # fn main() -> Result<()> { +//! let lua = Lua::new(); +//! +//! let greet = lua.create_function(|_, name: String| { +//! Ok(format!("Hello, {}!", name)) +//! })?; +//! +//! lua.globals().set("greet", greet)?; +//! let result: String = lua.load(r#"greet("World")"#).eval()?; +//! assert_eq!(result, "Hello, World!"); +//! # Ok(()) +//! # } +//! ``` +//! +//! For simpler cases, use [`Function::wrap`] or [`Function::wrap_raw`] to convert a Rust function +//! directly: +//! +//! ``` +//! # use mlua::{Function, Lua, Result}; +//! # fn main() -> Result<()> { +//! let lua = Lua::new(); +//! +//! fn add(a: i32, b: i32) -> i32 { a + b } +//! +//! lua.globals().set("add", Function::wrap_raw(add))?; +//! let sum: i32 = lua.load("add(2, 3)").eval()?; +//! assert_eq!(sum, 5); +//! # Ok(()) +//! # } +//! ``` +//! +//! # Function Environments +//! +//! Lua functions have an associated environment table that determines how global +//! variables are resolved. Use [`Function::environment`] and [`Function::set_environment`] +//! to inspect or modify this environment. + use std::cell::RefCell; use std::os::raw::{c_int, c_void}; use std::{mem, ptr, slice}; @@ -681,7 +762,9 @@ impl LuaType for Function { const TYPE_ID: c_int = ffi::LUA_TFUNCTION; } +/// Future for asynchronous function calls. #[cfg(feature = "async")] +#[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct AsyncCallFuture(Result>); diff --git a/src/lib.rs b/src/lib.rs index 46f17371..6b9c6bb6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,7 +76,6 @@ mod chunk; mod conversion; mod debug; mod error; -mod function; #[cfg(any(feature = "luau", doc))] mod luau; mod memory; @@ -94,6 +93,7 @@ mod util; mod value; mod vector; +pub mod function; pub mod prelude; pub use bstr::BString; @@ -102,7 +102,7 @@ pub use ffi::{self, lua_CFunction, lua_State}; pub use crate::chunk::{AsChunk, Chunk, ChunkMode}; pub use crate::debug::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack}; pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result}; -pub use crate::function::{Function, FunctionInfo}; +pub use crate::function::Function; pub use crate::multi::{MultiValue, Variadic}; pub use crate::scope::Scope; pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua}; @@ -130,7 +130,6 @@ pub use crate::debug::HookTriggers; pub use crate::{ buffer::Buffer, chunk::{CompileConstant, Compiler}, - function::CoverageInfo, luau::{HeapDump, NavigateError, Require, TextRequirer}, vector::Vector, }; diff --git a/src/prelude.rs b/src/prelude.rs index 1f3ba217..05b01cb7 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -5,16 +5,16 @@ pub use crate::{ AnyUserData as LuaAnyUserData, BorrowedBytes as LuaBorrowedBytes, BorrowedStr as LuaBorrowedStr, Chunk as LuaChunk, Either as LuaEither, Error as LuaError, ErrorContext as LuaErrorContext, ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti, - Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, Integer as LuaInteger, - IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions, - LuaString, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, + Function as LuaFunction, GCMode as LuaGCMode, Integer as LuaInteger, IntoLua, IntoLuaMulti, + LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions, LuaString, + MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, Variadic as LuaVariadic, - VmState as LuaVmState, WeakLua, + VmState as LuaVmState, WeakLua, function::FunctionInfo as LuaFunctionInfo, }; #[cfg(not(feature = "luau"))] @@ -24,9 +24,8 @@ pub use crate::HookTriggers as LuaHookTriggers; #[cfg(feature = "luau")] #[doc(no_inline)] pub use crate::{ - CompileConstant as LuaCompileConstant, CoverageInfo as LuaCoverageInfo, - NavigateError as LuaNavigateError, Require as LuaRequire, TextRequirer as LuaTextRequirer, - Vector as LuaVector, + CompileConstant as LuaCompileConstant, NavigateError as LuaNavigateError, Require as LuaRequire, + TextRequirer as LuaTextRequirer, Vector as LuaVector, }; #[cfg(feature = "async")] diff --git a/tests/function.rs b/tests/function.rs index c5eb9f27..d01e4fa9 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -267,7 +267,7 @@ fn test_function_coverage() -> Result<()> { assert_eq!( report[0], - mlua::CoverageInfo { + mlua::function::CoverageInfo { function: None, line_defined: 1, depth: 0, @@ -276,7 +276,7 @@ fn test_function_coverage() -> Result<()> { ); assert_eq!( report[1], - mlua::CoverageInfo { + mlua::function::CoverageInfo { function: Some("abc".into()), line_defined: 4, depth: 1, @@ -285,7 +285,7 @@ fn test_function_coverage() -> Result<()> { ); assert_eq!( report[2], - mlua::CoverageInfo { + mlua::function::CoverageInfo { function: None, line_defined: 12, depth: 1, @@ -294,7 +294,7 @@ fn test_function_coverage() -> Result<()> { ); assert_eq!( report[3], - mlua::CoverageInfo { + mlua::function::CoverageInfo { function: None, line_defined: 13, depth: 2, From 88063e756f823a27d10339be9597558ea6ea9bd0 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 30 Jan 2026 13:18:46 +0000 Subject: [PATCH 605/635] Make `table` module public --- src/lib.rs | 4 +- src/prelude.rs | 12 ++-- src/table.rs | 159 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6b9c6bb6..9ce9860f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,7 +84,6 @@ mod scope; mod state; mod stdlib; mod string; -mod table; mod thread; mod traits; mod types; @@ -95,6 +94,7 @@ mod vector; pub mod function; pub mod prelude; +pub mod table; pub use bstr::BString; pub use ffi::{self, lua_CFunction, lua_State}; @@ -108,7 +108,7 @@ pub use crate::scope::Scope; pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua}; pub use crate::stdlib::StdLib; pub use crate::string::{BorrowedBytes, BorrowedStr, LuaString, LuaString as String}; -pub use crate::table::{Table, TablePairs, TableSequence}; +pub use crate::table::Table; pub use crate::thread::{Thread, ThreadStatus}; pub use crate::traits::{ FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut, ObjectLike, diff --git a/src/prelude.rs b/src/prelude.rs index 05b01cb7..23cdf85d 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -9,12 +9,12 @@ pub use crate::{ LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions, LuaString, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, - Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, Thread as LuaThread, - ThreadStatus as LuaThreadStatus, UserData as LuaUserData, UserDataFields as LuaUserDataFields, - UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, - UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut, - UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, Variadic as LuaVariadic, - VmState as LuaVmState, WeakLua, function::FunctionInfo as LuaFunctionInfo, + Table as LuaTable, Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData, + UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, + UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, + UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, + Variadic as LuaVariadic, VmState as LuaVmState, WeakLua, function::FunctionInfo as LuaFunctionInfo, + table::TablePairs as LuaTablePairs, table::TableSequence as LuaTableSequence, }; #[cfg(not(feature = "luau"))] diff --git a/src/table.rs b/src/table.rs index c53fc550..694e9927 100644 --- a/src/table.rs +++ b/src/table.rs @@ -1,3 +1,162 @@ +//! Lua table handling. +//! +//! Tables are Lua's primary data structure, used for arrays, dictionaries, objects, modules, +//! and more. This module provides types for creating and manipulating Lua tables from Rust. +//! +//! # Main Types +//! +//! - [`Table`] - A handle to a Lua table. +//! - [`TablePairs`] - An iterator over key-value pairs in a table. +//! - [`TableSequence`] - An iterator over the array (sequence) portion of a table. +//! +//! # Basic Operations +//! +//! Tables support key-value access similar to Rust's `HashMap`: +//! +//! ``` +//! # use mlua::{Lua, Result}; +//! # fn main() -> Result<()> { +//! let lua = Lua::new(); +//! let table = lua.create_table()?; +//! +//! // Set and get values +//! table.set("key", "value")?; +//! let value: String = table.get("key")?; +//! assert_eq!(value, "value"); +//! +//! // Keys and values can be any Lua-compatible type +//! table.set(1, "first")?; +//! table.set("nested", lua.create_table()?)?; +//! # Ok(()) +//! # } +//! ``` +//! +//! # Array Operations +//! +//! Tables can be used as arrays with 1-based indexing: +//! +//! ``` +//! # use mlua::{Lua, Result}; +//! # fn main() -> Result<()> { +//! let lua = Lua::new(); +//! let array = lua.create_table()?; +//! +//! // Push values to the end (like Vec::push) +//! array.push("first")?; +//! array.push("second")?; +//! array.push("third")?; +//! +//! // Pop from the end +//! let last: String = array.pop()?; +//! assert_eq!(last, "third"); +//! +//! // Get length +//! assert_eq!(array.raw_len(), 2); +//! # Ok(()) +//! # } +//! ``` +//! +//! # Iteration +//! +//! Iterate over all key-value pairs with [`Table::pairs`]: +//! +//! ``` +//! # use mlua::{Lua, Result, Value}; +//! # fn main() -> Result<()> { +//! let lua = Lua::new(); +//! let table = lua.create_table()?; +//! table.set("a", 1)?; +//! table.set("b", 2)?; +//! +//! for pair in table.pairs::() { +//! let (key, value) = pair?; +//! println!("{key} = {value}"); +//! } +//! # Ok(()) +//! # } +//! ``` +//! +//! For array portions, use [`Table::sequence_values`]: +//! +//! ``` +//! # use mlua::{Lua, Result}; +//! # fn main() -> Result<()> { +//! let lua = Lua::new(); +//! let array = lua.create_sequence_from(["a", "b", "c"])?; +//! +//! for value in array.sequence_values::() { +//! println!("{}", value?); +//! } +//! # Ok(()) +//! # } +//! ``` +//! +//! # Raw vs Normal Access +//! +//! Methods prefixed with `raw_` (like [`Table::raw_get`], [`Table::raw_set`]) bypass +//! metamethods, directly accessing the table's contents. Normal methods may trigger +//! `__index`, `__newindex`, and other metamethods: +//! +//! ``` +//! # use mlua::{Lua, Result}; +//! # fn main() -> Result<()> { +//! let lua = Lua::new(); +//! +//! // raw_set bypasses __newindex metamethod +//! let t = lua.create_table()?; +//! t.raw_set("key", "value")?; +//! +//! // raw_get bypasses __index metamethod +//! let v: String = t.raw_get("key")?; +//! # Ok(()) +//! # } +//! ``` +//! +//! # Metatables +//! +//! Tables can have metatables that customize their behavior: +//! +//! ``` +//! # use mlua::{Lua, Result}; +//! # fn main() -> Result<()> { +//! let lua = Lua::new(); +//! +//! let table = lua.create_table()?; +//! let metatable = lua.create_table()?; +//! +//! // Set a default value via __index +//! metatable.set("__index", lua.create_function(|_, _: ()| Ok("default"))?)?; +//! table.set_metatable(Some(metatable))?; +//! +//! // Accessing missing keys returns "default" +//! let value: String = table.get("missing")?; +//! assert_eq!(value, "default"); +//! # Ok(()) +//! # } +//! ``` +//! +//! # Global Table +//! +//! The Lua global environment is itself a table, accessible via [`Lua::globals`]: +//! +//! ``` +//! # use mlua::{Lua, Result}; +//! # fn main() -> Result<()> { +//! let lua = Lua::new(); +//! let globals = lua.globals(); +//! +//! // Set a global variable +//! globals.set("my_var", 42)?; +//! +//! // Now accessible from Lua code +//! let result: i32 = lua.load("my_var + 8").eval()?; +//! assert_eq!(result, 50); +//! # Ok(()) +//! # } +//! ``` +//! +//! [`Lua::globals`]: crate::Lua::globals + use std::collections::HashSet; use std::fmt; use std::marker::PhantomData; From 0e489901a5e43c45ca4743e3e879062ca0c18ca1 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 3 Feb 2026 10:33:05 +0000 Subject: [PATCH 606/635] Update `debug` module Move debug types from root to new new module. --- src/debug.rs | 79 +++++++++++++++++++++------------------------------- src/lib.rs | 3 +- 2 files changed, 32 insertions(+), 50 deletions(-) diff --git a/src/debug.rs b/src/debug.rs index 6c0ba6bb..a8527e7b 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,3 +1,9 @@ +//! Lua debugging interface. +//! +//! This module provides access to the Lua debug interface, allowing inspection of the call stack, +//! and function information. The main types are [`Debug`] for accessing debug information and +//! [`HookTriggers`] for configuring debug hooks. + use std::borrow::Cow; use std::os::raw::c_int; @@ -133,12 +139,6 @@ impl<'a> Debug<'a> { } } - #[doc(hidden)] - #[deprecated(note = "Use `current_line` instead")] - pub fn curr_line(&self) -> i32 { - self.current_line().map(|n| n as i32).unwrap_or(-1) - } - /// Corresponds to the `l` "what" mask. Returns the current line. pub fn current_line(&self) -> Option { unsafe { @@ -190,15 +190,15 @@ impl<'a> Debug<'a> { #[cfg(not(feature = "luau"))] let stack = DebugStack { - num_ups: (*self.ar).nups as _, - #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))] + num_upvalues: (*self.ar).nups as _, + #[cfg(not(any(feature = "lua51", feature = "luajit")))] num_params: (*self.ar).nparams as _, - #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))] + #[cfg(not(any(feature = "lua51", feature = "luajit")))] is_vararg: (*self.ar).isvararg != 0, }; #[cfg(feature = "luau")] let stack = DebugStack { - num_ups: (*self.ar).nupvals, + num_upvalues: (*self.ar).nupvals, num_params: (*self.ar).nparams, is_vararg: (*self.ar).isvararg != 0, }; @@ -208,6 +208,8 @@ impl<'a> Debug<'a> { } /// Represents a specific event that triggered the hook. +#[cfg(not(feature = "luau"))] +#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum DebugEvent { Call, @@ -218,6 +220,9 @@ pub enum DebugEvent { Unknown(c_int), } +/// Contains the name information of a function in the call stack. +/// +/// Returned by the [`Debug::names`] method. #[derive(Clone, Debug)] pub struct DebugNames<'a> { /// A (reasonable) name of the function (`None` if the name cannot be found). @@ -228,6 +233,9 @@ pub struct DebugNames<'a> { pub name_what: Option<&'static str>, } +/// Contains the source information of a function in the call stack. +/// +/// Returned by the [`Debug::source`] method. #[derive(Clone, Debug)] pub struct DebugSource<'a> { /// Source of the chunk that created the function. @@ -243,47 +251,20 @@ pub struct DebugSource<'a> { pub what: &'static str, } +/// Contains stack information about a function in the call stack. +/// +/// Returned by the [`Debug::stack`] method. #[derive(Copy, Clone, Debug)] pub struct DebugStack { - /// Number of upvalues. - pub num_ups: u8, - /// Number of parameters. - #[cfg(any( - feature = "lua55", - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luau" - ))] - #[cfg_attr( - docsrs, - doc(cfg(any( - feature = "lua55", - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luau" - ))) - )] + /// The number of upvalues of the function. + pub num_upvalues: u8, + /// The number of parameters of the function (always 0 for C). + #[cfg(any(not(any(feature = "lua51", feature = "luajit")), doc))] + #[cfg_attr(docsrs, doc(cfg(not(any(feature = "lua51", feature = "luajit")))))] pub num_params: u8, - /// Whether the function is a vararg function. - #[cfg(any( - feature = "lua55", - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luau" - ))] - #[cfg_attr( - docsrs, - doc(cfg(any( - feature = "lua55", - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luau" - ))) - )] + /// Whether the function is a variadic function (always true for C). + #[cfg(any(not(any(feature = "lua51", feature = "luajit")), doc))] + #[cfg_attr(docsrs, doc(cfg(not(any(feature = "lua51", feature = "luajit")))))] pub is_vararg: bool, } @@ -361,6 +342,7 @@ impl HookTriggers { } // Compute the mask to pass to `lua_sethook`. + #[cfg(not(feature = "luau"))] pub(crate) const fn mask(&self) -> c_int { let mut mask: c_int = 0; if self.on_calls { @@ -380,6 +362,7 @@ impl HookTriggers { // Returns the `count` parameter to pass to `lua_sethook`, if applicable. Otherwise, zero is // returned. + #[cfg(not(feature = "luau"))] pub(crate) const fn count(&self) -> c_int { match self.every_nth_instruction { Some(n) => n as c_int, diff --git a/src/lib.rs b/src/lib.rs index 9ce9860f..fa3bd1d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,7 +74,6 @@ mod macros; mod buffer; mod chunk; mod conversion; -mod debug; mod error; #[cfg(any(feature = "luau", doc))] mod luau; @@ -92,6 +91,7 @@ mod util; mod value; mod vector; +pub mod debug; pub mod function; pub mod prelude; pub mod table; @@ -100,7 +100,6 @@ pub use bstr::BString; pub use ffi::{self, lua_CFunction, lua_State}; pub use crate::chunk::{AsChunk, Chunk, ChunkMode}; -pub use crate::debug::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack}; pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result}; pub use crate::function::Function; pub use crate::multi::{MultiValue, Variadic}; From 497d84828aba4d742db6d67b706c5148d5054fef Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 3 Feb 2026 11:45:13 +0000 Subject: [PATCH 607/635] Add CI to build dev docs --- .github/workflows/docs.yml | 68 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..c3a2ad77 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,68 @@ +name: Documentation (dev) + +on: + push: + branches: [dev] + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + name: Build Documentation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@main + - uses: dtolnay/rust-toolchain@nightly + - uses: Swatinem/rust-cache@v2 + + - name: Build documentation + env: + RUSTDOCFLAGS: "--cfg docsrs" + run: | + cargo +nightly doc --no-deps \ + --features "lua55,vendored,async,send,serde,macros,anyhow,userdata-wrappers" + + - name: Create index redirect + run: | + echo ' + + + + Redirecting to mlua documentation + + + + + + + ' > target/doc/index.html + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v4 + with: + path: target/doc + + deploy: + name: Deploy to GitHub Pages + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 From 29af448ad9799b1791a7660f6b0446a3ecd9a4c2 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 12 Feb 2026 15:27:30 +0000 Subject: [PATCH 608/635] Update README to indicate dev status --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c2e2eba2..fa462e10 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ [Benchmarks]: https://github.com/khvzak/script-bench-rs [FAQ]: FAQ.md +## The main branch is the development version of `mlua`. Please see the [v0.11](https://github.com/mlua-rs/mlua/tree/v0.11) branch for the stable versions of `mlua`. + `mlua` is a set of bindings to the [Lua](https://www.lua.org) programming language for Rust with a goal of providing a _safe_ (as much as possible), high level, easy to use, practical and flexible API. From f19c6aac3b04889ea0b52e84183cc11a3b15d933 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 12 Feb 2026 15:59:55 +0000 Subject: [PATCH 609/635] Fix tests --- tests/hooks.rs | 3 ++- tests/tests.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/hooks.rs b/tests/hooks.rs index c5d7da32..f1b72445 100644 --- a/tests/hooks.rs +++ b/tests/hooks.rs @@ -3,7 +3,8 @@ use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::{Arc, Mutex}; -use mlua::{DebugEvent, Error, HookTriggers, Lua, Result, ThreadStatus, Value, VmState}; +use mlua::debug::DebugEvent; +use mlua::{Error, HookTriggers, Lua, Result, ThreadStatus, Value, VmState}; #[test] fn test_hook_triggers() { diff --git a/tests/tests.rs b/tests/tests.rs index 47151387..99716f63 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1374,7 +1374,7 @@ fn test_inspect_stack() -> Result<()> { local function baz(a, b, c, ...) return stack_info() end - assert(baz() == 'DebugStack { num_ups: 1, num_params: 3, is_vararg: true }') + assert(baz() == 'DebugStack { num_upvalues: 1, num_params: 3, is_vararg: true }') "#, ) .exec()?; @@ -1387,7 +1387,7 @@ fn test_inspect_stack() -> Result<()> { local function baz(a, b, c, ...) return stack_info() end - assert(baz() == 'DebugStack { num_ups: 1 }') + assert(baz() == 'DebugStack { num_upvalues: 1 }') "#, ) .exec()?; From 7f3ec63ab5898bf3a8145a065bc24393dce916a4 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 20 Feb 2026 18:59:53 +0000 Subject: [PATCH 610/635] Support `__todebugstring` for pretty userdata debug output Close #681 --- src/userdata.rs | 6 ++++++ src/value.rs | 44 ++++++++++++++++++++++++++++++++------------ tests/value.rs | 24 +++++++++++++++++++++++- 3 files changed, 61 insertions(+), 13 deletions(-) diff --git a/src/userdata.rs b/src/userdata.rs index c8d8c19b..4719533e 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -123,6 +123,11 @@ pub enum MetaMethod { /// /// This is not an operator, but will be called by methods such as `tostring` and `print`. ToString, + /// The `__todebugstring` metamethod for debug purposes. + /// + /// This is an mlua-specific metamethod that can be used to provide debug representation for + /// userdata. + ToDebugString, /// The `__pairs` metamethod. /// /// This is not an operator, but it will be called by the built-in `pairs` function. @@ -232,6 +237,7 @@ impl MetaMethod { MetaMethod::NewIndex => "__newindex", MetaMethod::Call => "__call", MetaMethod::ToString => "__tostring", + MetaMethod::ToDebugString => "__todebugstring", #[cfg(any( feature = "lua55", diff --git a/src/value.rs b/src/value.rs index 1250a3f7..623fe001 100644 --- a/src/value.rs +++ b/src/value.rs @@ -151,7 +151,7 @@ impl Value { /// This might invoke the `__tostring` metamethod for non-primitive types (eg. tables, /// functions). pub fn to_string(&self) -> Result { - unsafe fn invoke_to_string(vref: &ValueRef) -> Result { + unsafe fn invoke_tostring(vref: &ValueRef) -> Result { let lua = vref.lua.lock(); let state = lua.state(); let _guard = StackGuard::new(state); @@ -178,9 +178,9 @@ impl Value { | Value::Function(Function(vref)) | Value::Thread(Thread(vref, ..)) | Value::UserData(AnyUserData(vref)) - | Value::Other(vref) => unsafe { invoke_to_string(vref) }, + | Value::Other(vref) => unsafe { invoke_tostring(vref) }, #[cfg(feature = "luau")] - Value::Buffer(crate::Buffer(vref)) => unsafe { invoke_to_string(vref) }, + Value::Buffer(crate::Buffer(vref)) => unsafe { invoke_tostring(vref) }, Value::Error(err) => Ok(err.to_string()), } } @@ -545,6 +545,24 @@ impl Value { ident: usize, visited: &mut HashSet<*const c_void>, ) -> fmt::Result { + unsafe fn invoke_tostring_dbg(vref: &ValueRef) -> Result> { + let lua = vref.lua.lock(); + let state = lua.state(); + let _guard = StackGuard::new(state); + check_stack(state, 3)?; + + lua.push_ref(vref); + protect_lua!(state, 1, 1, fn(state) { + // Try `__todebugstring` metamethod first, then `__tostring` + if ffi::luaL_callmeta(state, -1, cstr!("__todebugstring")) == 0 { + if ffi::luaL_callmeta(state, -1, cstr!("__tostring")) == 0 { + ffi::lua_pushnil(state); + } + } + })?; + Ok(lua.pop_value().as_string().map(|s| s.to_string_lossy())) + } + match self { Value::Nil => write!(fmt, "nil"), Value::Boolean(b) => write!(fmt, "{b}"), @@ -561,15 +579,17 @@ impl Value { t @ Value::Table(_) => write!(fmt, "table: {:?}", t.to_pointer()), f @ Value::Function(_) => write!(fmt, "function: {:?}", f.to_pointer()), t @ Value::Thread(_) => write!(fmt, "thread: {:?}", t.to_pointer()), - u @ Value::UserData(ud) => { - // Try `__name/__type` first then `__tostring` - let name = ud.type_name().ok().flatten(); - let s = name - .map(|name| format!("{name}: {:?}", u.to_pointer())) - .or_else(|| u.to_string().ok()) - .unwrap_or_else(|| format!("userdata: {:?}", u.to_pointer())); - write!(fmt, "{s}") - } + u @ Value::UserData(ud) => unsafe { + // Try converting to a (debug) string first, with fallback to `__name/__type` + match invoke_tostring_dbg(&ud.0) { + Ok(Some(s)) => write!(fmt, "{s}"), + _ => { + let name = ud.type_name().ok().flatten(); + let name = name.as_deref().unwrap_or("userdata"); + write!(fmt, "{name}: {:?}", u.to_pointer()) + } + } + }, #[cfg(feature = "luau")] buf @ Value::Buffer(_) => write!(fmt, "buffer: {:?}", buf.to_pointer()), Value::Error(e) if recursive => write!(fmt, "{e:?}"), diff --git a/tests/value.rs b/tests/value.rs index 43ec708f..31bc4e9d 100644 --- a/tests/value.rs +++ b/tests/value.rs @@ -2,7 +2,9 @@ use std::collections::HashMap; use std::os::raw::c_void; use std::ptr; -use mlua::{Error, LightUserData, Lua, MultiValue, Result, UserData, UserDataMethods, Value}; +use mlua::{ + Error, LightUserData, Lua, MultiValue, Result, UserData, UserDataMethods, UserDataRegistry, Value, +}; #[test] fn test_value_eq() -> Result<()> { @@ -218,6 +220,26 @@ fn test_debug_format() -> Result<()> { .map(Value::UserData)?; assert!(format!("{ud:#?}").starts_with("HashMap:")); + struct ToDebugUserData; + impl UserData for ToDebugUserData { + fn register(registry: &mut UserDataRegistry) { + registry.add_meta_method("__tostring", |_, _, ()| Ok("regular-string")); + registry.add_meta_method("__todebugstring", |_, _, ()| Ok("debug-string")); + } + } + let debug_ud = Value::UserData(lua.create_userdata(ToDebugUserData)?); + assert_eq!(debug_ud.to_string()?, "regular-string"); + assert_eq!(format!("{debug_ud:#?}"), "debug-string"); + + struct ToStringUserData; + impl UserData for ToStringUserData { + fn register(registry: &mut UserDataRegistry) { + registry.add_meta_method("__tostring", |_, _, ()| Ok("to-string-only")); + } + } + let tostring_only_ud = Value::UserData(lua.create_userdata(ToStringUserData)?); + assert_eq!(format!("{tostring_only_ud:#?}"), "tostring-only"); + Ok(()) } From 151adc0e8725f97d2bdaab2e1c24deaefd6dcf4e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 20 Feb 2026 20:31:29 +0000 Subject: [PATCH 611/635] Implement pretty debug format for `AnyUserData` similar to `Value::UserData`. --- src/userdata.rs | 41 ++++++++++++++++++++++++++++++++++++++++- src/value.rs | 30 +----------------------------- tests/value.rs | 12 +++++++++--- 3 files changed, 50 insertions(+), 33 deletions(-) diff --git a/src/userdata.rs b/src/userdata.rs index 4719533e..9a507d70 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -711,7 +711,7 @@ pub trait UserData: Sized { /// /// [`is`]: crate::AnyUserData::is /// [`borrow`]: crate::AnyUserData::borrow -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, PartialEq)] pub struct AnyUserData(pub(crate) ValueRef); impl AnyUserData { @@ -1081,6 +1081,45 @@ impl AnyUserData { }; is_serializable().unwrap_or(false) } + + unsafe fn invoke_tostring_dbg(&self) -> Result> { + let lua = self.0.lua.lock(); + let state = lua.state(); + let _guard = StackGuard::new(state); + check_stack(state, 3)?; + + lua.push_ref(&self.0); + protect_lua!(state, 1, 1, fn(state) { + // Try `__todebugstring` metamethod first, then `__tostring` + if ffi::luaL_callmeta(state, -1, cstr!("__todebugstring")) == 0 { + if ffi::luaL_callmeta(state, -1, cstr!("__tostring")) == 0 { + ffi::lua_pushnil(state); + } + } + })?; + Ok(lua.pop_value().as_string().map(|s| s.to_string_lossy())) + } + + pub(crate) fn fmt_pretty(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + // Try converting to a (debug) string first, with fallback to `__name/__type` + match unsafe { self.invoke_tostring_dbg() } { + Ok(Some(s)) => write!(fmt, "{s}"), + _ => { + let name = self.type_name().ok().flatten(); + let name = name.as_deref().unwrap_or("userdata"); + write!(fmt, "{name}: {:?}", self.to_pointer()) + } + } + } +} + +impl fmt::Debug for AnyUserData { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + if fmt.alternate() { + return self.fmt_pretty(fmt); + } + fmt.debug_tuple("AnyUserData").field(&self.0).finish() + } } /// Handle to a [`AnyUserData`] metatable. diff --git a/src/value.rs b/src/value.rs index 623fe001..8d178e04 100644 --- a/src/value.rs +++ b/src/value.rs @@ -545,24 +545,6 @@ impl Value { ident: usize, visited: &mut HashSet<*const c_void>, ) -> fmt::Result { - unsafe fn invoke_tostring_dbg(vref: &ValueRef) -> Result> { - let lua = vref.lua.lock(); - let state = lua.state(); - let _guard = StackGuard::new(state); - check_stack(state, 3)?; - - lua.push_ref(vref); - protect_lua!(state, 1, 1, fn(state) { - // Try `__todebugstring` metamethod first, then `__tostring` - if ffi::luaL_callmeta(state, -1, cstr!("__todebugstring")) == 0 { - if ffi::luaL_callmeta(state, -1, cstr!("__tostring")) == 0 { - ffi::lua_pushnil(state); - } - } - })?; - Ok(lua.pop_value().as_string().map(|s| s.to_string_lossy())) - } - match self { Value::Nil => write!(fmt, "nil"), Value::Boolean(b) => write!(fmt, "{b}"), @@ -579,17 +561,7 @@ impl Value { t @ Value::Table(_) => write!(fmt, "table: {:?}", t.to_pointer()), f @ Value::Function(_) => write!(fmt, "function: {:?}", f.to_pointer()), t @ Value::Thread(_) => write!(fmt, "thread: {:?}", t.to_pointer()), - u @ Value::UserData(ud) => unsafe { - // Try converting to a (debug) string first, with fallback to `__name/__type` - match invoke_tostring_dbg(&ud.0) { - Ok(Some(s)) => write!(fmt, "{s}"), - _ => { - let name = ud.type_name().ok().flatten(); - let name = name.as_deref().unwrap_or("userdata"); - write!(fmt, "{name}: {:?}", u.to_pointer()) - } - } - }, + Value::UserData(ud) => ud.fmt_pretty(fmt), #[cfg(feature = "luau")] buf @ Value::Buffer(_) => write!(fmt, "buffer: {:?}", buf.to_pointer()), Value::Error(e) if recursive => write!(fmt, "{e:?}"), diff --git a/tests/value.rs b/tests/value.rs index 31bc4e9d..506aaa0e 100644 --- a/tests/value.rs +++ b/tests/value.rs @@ -3,7 +3,8 @@ use std::os::raw::c_void; use std::ptr; use mlua::{ - Error, LightUserData, Lua, MultiValue, Result, UserData, UserDataMethods, UserDataRegistry, Value, + AnyUserData, Error, LightUserData, Lua, MultiValue, Result, UserData, UserDataMethods, UserDataRegistry, + Value, }; #[test] @@ -234,11 +235,16 @@ fn test_debug_format() -> Result<()> { struct ToStringUserData; impl UserData for ToStringUserData { fn register(registry: &mut UserDataRegistry) { - registry.add_meta_method("__tostring", |_, _, ()| Ok("to-string-only")); + registry.add_meta_method("__tostring", |_, _, ()| Ok("regular-string")); } } let tostring_only_ud = Value::UserData(lua.create_userdata(ToStringUserData)?); - assert_eq!(format!("{tostring_only_ud:#?}"), "tostring-only"); + assert_eq!(format!("{tostring_only_ud:#?}"), "regular-string"); + + // Check that `AnyUsedata` pretty debug format is same as for `Value::UserData` + let any_ud: AnyUserData = lua.create_userdata(ToDebugUserData)?; + let value_ud = Value::UserData(any_ud.clone()); + assert_eq!(format!("{any_ud:#?}"), format!("{value_ud:#?}")); Ok(()) } From 63a255bbc918bf0925650313872608fdc838a57e Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 21 Feb 2026 15:05:55 +0000 Subject: [PATCH 612/635] Replace `is_sync` specialization trick with `MaybeSync` trait bound. The `is_sync::()` runtime check relied on implicit specialization via `Copy`/`Clone` array behavior, which has changed in Rust 1.86+. `UserDataRef` always taking an exclusive lock even for `Sync` userdata, preventing concurrent shared borrows. With the `send` feature flag enabled, userdata types must now be `Send + Sync`. This is a breaking change, `T: Send + !Sync` userdata types can be wrapped in a `Mutex` or used inside a `Scope` where this restriction is lifted. --- src/conversion.rs | 4 +-- src/lib.rs | 3 ++- src/state.rs | 12 ++++----- src/types.rs | 12 +++++++++ src/userdata.rs | 6 ++--- src/userdata/cell.rs | 49 ++++++++++++++----------------------- src/userdata/ref.rs | 10 +++----- src/userdata/registry.rs | 6 +++++ src/userdata/util.rs | 31 ----------------------- tests/send.rs | 53 +++------------------------------------- 10 files changed, 57 insertions(+), 129 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index b74343db..8de53467 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -16,7 +16,7 @@ use crate::string::{BorrowedBytes, BorrowedStr, LuaString}; use crate::table::Table; use crate::thread::Thread; use crate::traits::{FromLua, IntoLua, ShortTypeName as _}; -use crate::types::{Either, LightUserData, MaybeSend, RegistryKey}; +use crate::types::{Either, LightUserData, MaybeSend, MaybeSync, RegistryKey}; use crate::userdata::{AnyUserData, UserData}; use crate::value::{Nil, Value}; @@ -294,7 +294,7 @@ impl FromLua for AnyUserData { } } -impl IntoLua for T { +impl IntoLua for T { #[inline] fn into_lua(self, lua: &Lua) -> Result { Ok(Value::UserData(lua.create_userdata(self)?)) diff --git a/src/lib.rs b/src/lib.rs index fa3bd1d0..787baa3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -113,7 +113,8 @@ pub use crate::traits::{ FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut, ObjectLike, }; pub use crate::types::{ - AppDataRef, AppDataRefMut, Either, Integer, LightUserData, MaybeSend, Number, RegistryKey, VmState, + AppDataRef, AppDataRefMut, Either, Integer, LightUserData, MaybeSend, MaybeSync, Number, RegistryKey, + VmState, }; pub use crate::userdata::{ AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef, diff --git a/src/state.rs b/src/state.rs index 9c364a2e..543c0063 100644 --- a/src/state.rs +++ b/src/state.rs @@ -20,8 +20,8 @@ use crate::table::Table; use crate::thread::Thread; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::types::{ - AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LuaType, MaybeSend, Number, ReentrantMutex, - ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak, + AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LuaType, MaybeSend, MaybeSync, Number, + ReentrantMutex, ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak, }; use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataStorage}; use crate::util::{StackGuard, assert_stack, check_stack, protect_lua_closure, push_string, rawset_field}; @@ -1492,7 +1492,7 @@ impl Lua { #[inline] pub fn create_userdata(&self, data: T) -> Result where - T: UserData + MaybeSend + 'static, + T: UserData + MaybeSend + MaybeSync + 'static, { unsafe { self.lock().make_userdata(UserDataStorage::new(data)) } } @@ -1503,7 +1503,7 @@ impl Lua { #[inline] pub fn create_ser_userdata(&self, data: T) -> Result where - T: UserData + Serialize + MaybeSend + 'static, + T: UserData + Serialize + MaybeSend + MaybeSync + 'static, { unsafe { self.lock().make_userdata(UserDataStorage::new_ser(data)) } } @@ -1518,7 +1518,7 @@ impl Lua { #[inline] pub fn create_any_userdata(&self, data: T) -> Result where - T: MaybeSend + 'static, + T: MaybeSend + MaybeSync + 'static, { unsafe { self.lock().make_any_userdata(UserDataStorage::new(data)) } } @@ -1531,7 +1531,7 @@ impl Lua { #[inline] pub fn create_ser_any_userdata(&self, data: T) -> Result where - T: Serialize + MaybeSend + 'static, + T: Serialize + MaybeSend + MaybeSync + 'static, { unsafe { (self.lock()).make_any_userdata(UserDataStorage::new_ser(data)) } } diff --git a/src/types.rs b/src/types.rs index d05d35c2..a6a91030 100644 --- a/src/types.rs +++ b/src/types.rs @@ -128,6 +128,18 @@ pub trait MaybeSend {} #[cfg(not(feature = "send"))] impl MaybeSend for T {} +/// A trait that adds `Sync` requirement if `send` feature is enabled. +#[cfg(feature = "send")] +pub trait MaybeSync: Sync {} +#[cfg(feature = "send")] +impl MaybeSync for T {} + +/// A trait that adds `Sync` requirement if `send` feature is enabled. +#[cfg(not(feature = "send"))] +pub trait MaybeSync {} +#[cfg(not(feature = "send"))] +impl MaybeSync for T {} + pub(crate) struct DestructedUserdata; pub(crate) trait LuaType { diff --git a/src/userdata.rs b/src/userdata.rs index 9a507d70..51ad5178 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -10,7 +10,7 @@ use crate::state::Lua; use crate::string::LuaString; use crate::table::{Table, TablePairs}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; -use crate::types::{MaybeSend, ValueRef}; +use crate::types::{MaybeSend, MaybeSync, ValueRef}; use crate::util::{StackGuard, check_stack, get_userdata, push_string, short_type_name, take_userdata}; use crate::value::Value; @@ -1216,7 +1216,7 @@ impl AnyUserData { /// Wraps any Rust type, returning an opaque type that implements [`IntoLua`] trait. /// /// This function uses [`Lua::create_any_userdata`] under the hood. - pub fn wrap(data: T) -> impl IntoLua { + pub fn wrap(data: T) -> impl IntoLua { WrappedUserdata(move |lua| lua.create_any_userdata(data)) } @@ -1226,7 +1226,7 @@ impl AnyUserData { /// This function uses [`Lua::create_ser_any_userdata`] under the hood. #[cfg(feature = "serde")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] - pub fn wrap_ser(data: T) -> impl IntoLua { + pub fn wrap_ser(data: T) -> impl IntoLua { WrappedUserdata(move |lua| lua.create_ser_any_userdata(data)) } } diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index 58f72465..5bd382f8 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -25,7 +25,7 @@ pub(crate) enum UserDataStorage { pub(crate) enum UserDataVariant { Default(XRc>), #[cfg(feature = "serde")] - Serializable(XRc>>, bool), // bool is `is_sync` + Serializable(XRc>>), } impl Clone for UserDataVariant { @@ -34,7 +34,7 @@ impl Clone for UserDataVariant { match self { Self::Default(inner) => Self::Default(XRc::clone(inner)), #[cfg(feature = "serde")] - Self::Serializable(inner, is_sync) => Self::Serializable(XRc::clone(inner), *is_sync), + Self::Serializable(inner) => Self::Serializable(XRc::clone(inner)), } } } @@ -42,10 +42,12 @@ impl Clone for UserDataVariant { impl UserDataVariant { #[inline(always)] pub(super) fn try_borrow_scoped(&self, f: impl FnOnce(&T) -> R) -> Result { - // We don't need to check for `T: Sync` because when this method is used (internally), - // Lua mutex is already locked. - // If non-`Sync` userdata is already borrowed by another thread (via `UserDataRef`), it will be - // exclusively locked. + // Shared (read) lock is always correct for in-place borrows: + // - this method is called internally while the Lua mutex is held, ensuring exclusive Lua-level + // access per call frame + // - with `send` feature, all owned userdata satisfies `T: Sync`, so simultaneous shared references + // from multiple threads are sound + // - without `send` feature, single-threaded execution makes shared lock safe for any `T` let _guard = (self.raw_lock().try_lock_shared_guarded()).map_err(|_| Error::UserDataBorrowError)?; Ok(f(unsafe { &*self.as_ptr() })) } @@ -80,7 +82,7 @@ impl UserDataVariant { Ok(match self { Self::Default(inner) => XRc::into_inner(inner).unwrap().value.into_inner(), #[cfg(feature = "serde")] - Self::Serializable(inner, _) => unsafe { + Self::Serializable(inner) => unsafe { let raw = Box::into_raw(XRc::into_inner(inner).unwrap().value.into_inner()); *Box::from_raw(raw as *mut T) }, @@ -92,7 +94,7 @@ impl UserDataVariant { match self { Self::Default(inner) => XRc::strong_count(inner), #[cfg(feature = "serde")] - Self::Serializable(inner, _) => XRc::strong_count(inner), + Self::Serializable(inner) => XRc::strong_count(inner), } } @@ -101,7 +103,7 @@ impl UserDataVariant { match self { Self::Default(inner) => &inner.raw_lock, #[cfg(feature = "serde")] - Self::Serializable(inner, _) => &inner.raw_lock, + Self::Serializable(inner) => &inner.raw_lock, } } @@ -110,7 +112,7 @@ impl UserDataVariant { match self { Self::Default(inner) => inner.value.get(), #[cfg(feature = "serde")] - Self::Serializable(inner, _) => unsafe { &mut **(inner.value.get() as *mut Box) }, + Self::Serializable(inner) => unsafe { &mut **(inner.value.get() as *mut Box) }, } } } @@ -119,24 +121,10 @@ impl UserDataVariant { impl Serialize for UserDataStorage<()> { fn serialize(&self, serializer: S) -> std::result::Result { match self { - Self::Owned(variant @ UserDataVariant::Serializable(inner, is_sync)) => unsafe { - #[cfg(feature = "send")] - if *is_sync { - let _guard = (variant.raw_lock().try_lock_shared_guarded()) - .map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?; - (*inner.value.get()).serialize(serializer) - } else { - let _guard = (variant.raw_lock().try_lock_exclusive_guarded()) - .map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?; - (*inner.value.get()).serialize(serializer) - } - #[cfg(not(feature = "send"))] - { - let _ = is_sync; - let _guard = (variant.raw_lock().try_lock_shared_guarded()) - .map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?; - (*inner.value.get()).serialize(serializer) - } + Self::Owned(variant @ UserDataVariant::Serializable(inner)) => unsafe { + let _guard = (variant.raw_lock().try_lock_shared_guarded()) + .map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?; + (*inner.value.get()).serialize(serializer) }, _ => Err(serde::ser::Error::custom("cannot serialize ")), } @@ -201,11 +189,10 @@ impl UserDataStorage { #[inline(always)] pub(crate) fn new_ser(data: T) -> Self where - T: Serialize + crate::types::MaybeSend, + T: Serialize + crate::types::MaybeSend + crate::types::MaybeSync, { let data = Box::new(data) as Box; - let is_sync = super::util::is_sync::(); - let variant = UserDataVariant::Serializable(XRc::new(UserDataCell::new(data)), is_sync); + let variant = UserDataVariant::Serializable(XRc::new(UserDataCell::new(data))); Self::Owned(variant) } diff --git a/src/userdata/ref.rs b/src/userdata/ref.rs index 48f67c28..3cc59ef4 100644 --- a/src/userdata/ref.rs +++ b/src/userdata/ref.rs @@ -12,7 +12,6 @@ use crate::value::Value; use super::cell::{UserDataStorage, UserDataVariant}; use super::lock::{LockGuard, RawLock, UserDataLock}; -use super::util::is_sync; #[cfg(feature = "userdata-wrappers")] use { @@ -63,11 +62,10 @@ impl TryFrom> for UserDataRef { #[inline] fn try_from(variant: UserDataVariant) -> Result { - let guard = if cfg!(not(feature = "send")) || is_sync::() { - variant.raw_lock().try_lock_shared_guarded() - } else { - variant.raw_lock().try_lock_exclusive_guarded() - }; + // Shared (read) lock is always correct: + // - with `send` feature, `T: Sync` is guaranteed by the `MaybeSync` bound on userdata creation + // - without `send` feature, single-threaded access makes shared lock safe for any `T` + let guard = variant.raw_lock().try_lock_shared_guarded(); let guard = guard.map_err(|_| Error::UserDataBorrowError)?; let guard = unsafe { mem::transmute::, LockGuard<'static, _>>(guard) }; Ok(UserDataRef::from_parts(UserDataRefInner::Default(variant), guard)) diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index e16bec6d..c5138140 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -654,6 +654,12 @@ macro_rules! lua_userdata_impl { // A special proxy object for UserData pub(crate) struct UserDataProxy(pub(crate) PhantomData); +// `UserDataProxy` holds no real `T` value, only a type marker, so it is always safe to send/share. +#[cfg(feature = "send")] +unsafe impl Send for UserDataProxy {} +#[cfg(feature = "send")] +unsafe impl Sync for UserDataProxy {} + lua_userdata_impl!(UserDataProxy); #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] diff --git a/src/userdata/util.rs b/src/userdata/util.rs index d42eaaed..6c5f0f8f 100644 --- a/src/userdata/util.rs +++ b/src/userdata/util.rs @@ -1,6 +1,4 @@ use std::any::TypeId; -use std::cell::Cell; -use std::marker::PhantomData; use std::os::raw::c_int; use std::ptr; @@ -11,35 +9,6 @@ use crate::error::{Error, Result}; use crate::types::CallbackPtr; use crate::util::{get_userdata, rawget_field, rawset_field, take_userdata}; -// This is a trick to check if a type is `Sync` or not. -// It uses leaked specialization feature from stdlib. -struct IsSync<'a, T> { - is_sync: &'a Cell, - _marker: PhantomData, -} - -impl Clone for IsSync<'_, T> { - fn clone(&self) -> Self { - self.is_sync.set(false); - IsSync { - is_sync: self.is_sync, - _marker: PhantomData, - } - } -} - -impl Copy for IsSync<'_, T> {} - -pub(crate) fn is_sync() -> bool { - let is_sync = Cell::new(true); - let _ = [IsSync:: { - is_sync: &is_sync, - _marker: PhantomData, - }] - .clone(); - is_sync.get() -} - // Userdata type hints, used to match types of wrapped userdata #[derive(Clone, Copy)] pub(crate) struct TypeIdHints { diff --git a/tests/send.rs b/tests/send.rs index f9803f5b..2f10466a 100644 --- a/tests/send.rs +++ b/tests/send.rs @@ -1,50 +1,7 @@ #![cfg(feature = "send")] -use std::cell::UnsafeCell; -use std::marker::PhantomData; - -use mlua::{AnyUserData, Error, Lua, ObjectLike, Result, UserData, UserDataMethods, UserDataRef}; -use static_assertions::{assert_impl_all, assert_not_impl_all}; - -#[test] -fn test_userdata_multithread_access_send_only() -> Result<()> { - let lua = Lua::new(); - - // This type is `Send` but not `Sync`. - struct MyUserData(String, PhantomData>); - assert_impl_all!(MyUserData: Send); - assert_not_impl_all!(MyUserData: Sync); - - impl UserData for MyUserData { - fn add_methods>(methods: &mut M) { - methods.add_method("method", |lua, this, ()| { - let ud = lua.globals().get::("ud")?; - assert_eq!(ud.call_method::("method2", ())?, "method2"); - Ok(this.0.clone()) - }); - - methods.add_method("method2", |_, _, ()| Ok("method2")); - } - } - - lua.globals() - .set("ud", MyUserData("hello".to_string(), PhantomData))?; - - // We acquired the exclusive reference. - let ud = lua.globals().get::>("ud")?; - - std::thread::scope(|s| { - s.spawn(|| { - let res = lua.globals().get::>("ud"); - assert!(matches!(res, Err(Error::UserDataBorrowError))); - }); - }); - - drop(ud); - lua.load("ud:method()").exec().unwrap(); - - Ok(()) -} +use mlua::{AnyUserData, Lua, ObjectLike, Result, UserData, UserDataMethods, UserDataRef}; +use static_assertions::assert_impl_all; #[test] fn test_userdata_multithread_access_sync() -> Result<()> { @@ -74,13 +31,11 @@ fn test_userdata_multithread_access_sync() -> Result<()> { std::thread::scope(|s| { s.spawn(|| { // Getting another shared reference for `Sync` type is allowed. - // FIXME: does not work due to https://github.com/rust-lang/rust/pull/135634 - // let _ = lua.globals().get::>("ud").unwrap(); + let _ = lua.globals().get::>("ud").unwrap(); }); }); - // FIXME: does not work due to https://github.com/rust-lang/rust/pull/135634 - // lua.load("ud:method()").exec().unwrap(); + lua.load("ud:method()").exec().unwrap(); Ok(()) } From 452dc8be888cbb99e04c3773d0f1ff9d1bf9c07c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 21 Feb 2026 15:31:48 +0000 Subject: [PATCH 613/635] clippy --- src/userdata.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/userdata.rs b/src/userdata.rs index 51ad5178..acea3108 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -1091,6 +1091,7 @@ impl AnyUserData { lua.push_ref(&self.0); protect_lua!(state, 1, 1, fn(state) { // Try `__todebugstring` metamethod first, then `__tostring` + #[allow(clippy::collapsible_if)] if ffi::luaL_callmeta(state, -1, cstr!("__todebugstring")) == 0 { if ffi::luaL_callmeta(state, -1, cstr!("__tostring")) == 0 { ffi::lua_pushnil(state); From 8fcb6a841673a1820645c1b80d8fcf8d99e97d9a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 21 Feb 2026 15:38:00 +0000 Subject: [PATCH 614/635] Update dependencies --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7eceb7f4..29c4b8f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,9 +77,9 @@ static_assertions = "1.0" hyper = { version = "1.2", features = ["full"] } hyper-util = { version = "0.1.3", features = ["full"] } http-body-util = "0.1.1" -reqwest = { version = "0.12", features = ["json"] } +reqwest = { version = "0.13", features = ["json"] } tempfile = "3" -criterion = { version = "0.7", features = ["async_tokio"] } +criterion = { version = "0.8", features = ["async_tokio"] } rustyline = "17.0" tokio = { version = "1.0", features = ["full"] } From 943c3aed5811c61cd78f57ea6d6141dd2f38a89f Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 21 Feb 2026 21:19:42 +0000 Subject: [PATCH 615/635] Some minor fixes in userdata cell --- src/userdata/cell.rs | 2 +- src/userdata/lock.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index 5bd382f8..40025fa4 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -13,7 +13,7 @@ use super::r#ref::{UserDataRef, UserDataRefMut}; type DynSerialize = dyn erased_serde::Serialize; #[cfg(all(feature = "serde", feature = "send"))] -type DynSerialize = dyn erased_serde::Serialize + Send; +type DynSerialize = dyn erased_serde::Serialize + Send + Sync; pub(crate) enum UserDataStorage { Owned(UserDataVariant), diff --git a/src/userdata/lock.rs b/src/userdata/lock.rs index e0e5d1af..b749ed71 100644 --- a/src/userdata/lock.rs +++ b/src/userdata/lock.rs @@ -72,7 +72,7 @@ mod lock_impl { #[inline(always)] fn try_lock_shared(&self) -> bool { - let flag = self.get().wrapping_add(1); + let flag = self.get().checked_add(1).expect("userdata lock count overflow"); if flag <= UNUSED { return false; } From 5776c722089745a72afedc591d9bd9599b2b9155 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 21 Feb 2026 23:15:48 +0000 Subject: [PATCH 616/635] Use `parking_lot::RwLock` in `UserDataCell` container in "send" mode. In non-send mode, mimic the `RwLock` API (using `Cell` counter). We're continue manually operating the underlying `RawRwLock` for flexibility. --- src/userdata/cell.rs | 55 +++++++++++++++++++++++---------------- src/userdata/lock.rs | 61 +++++++++++++++++++++++++++++++------------- 2 files changed, 76 insertions(+), 40 deletions(-) diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index 40025fa4..e954b613 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -1,4 +1,4 @@ -use std::cell::{RefCell, UnsafeCell}; +use std::cell::RefCell; #[cfg(feature = "serde")] use serde::ser::{Serialize, Serializer}; @@ -6,7 +6,7 @@ use serde::ser::{Serialize, Serializer}; use crate::error::{Error, Result}; use crate::types::XRc; -use super::lock::{RawLock, UserDataLock}; +use super::lock::{RawLock, RwLock, UserDataLock}; use super::r#ref::{UserDataRef, UserDataRefMut}; #[cfg(all(feature = "serde", not(feature = "send")))] @@ -80,10 +80,12 @@ impl UserDataVariant { return Err(Error::UserDataBorrowMutError); } Ok(match self { - Self::Default(inner) => XRc::into_inner(inner).unwrap().value.into_inner(), + Self::Default(inner) => XRc::into_inner(inner).unwrap().into_value(), #[cfg(feature = "serde")] Self::Serializable(inner) => unsafe { - let raw = Box::into_raw(XRc::into_inner(inner).unwrap().value.into_inner()); + // The serde variant erases `T` to `Box`, so we + // must cast the raw pointer back to recover the concrete type. + let raw = Box::into_raw(XRc::into_inner(inner).unwrap().into_value()); *Box::from_raw(raw as *mut T) }, }) @@ -101,18 +103,18 @@ impl UserDataVariant { #[inline(always)] pub(super) fn raw_lock(&self) -> &RawLock { match self { - Self::Default(inner) => &inner.raw_lock, + Self::Default(inner) => unsafe { inner.raw_lock() }, #[cfg(feature = "serde")] - Self::Serializable(inner) => &inner.raw_lock, + Self::Serializable(inner) => unsafe { inner.raw_lock() }, } } #[inline(always)] pub(super) fn as_ptr(&self) -> *mut T { match self { - Self::Default(inner) => inner.value.get(), + Self::Default(inner) => inner.as_ptr(), #[cfg(feature = "serde")] - Self::Serializable(inner) => unsafe { &mut **(inner.value.get() as *mut Box) }, + Self::Serializable(inner) => unsafe { (&mut **inner.as_ptr()) as *mut DynSerialize as *mut T }, } } } @@ -124,7 +126,7 @@ impl Serialize for UserDataStorage<()> { Self::Owned(variant @ UserDataVariant::Serializable(inner)) => unsafe { let _guard = (variant.raw_lock().try_lock_shared_guarded()) .map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?; - (*inner.value.get()).serialize(serializer) + (*inner.as_ptr()).serialize(serializer) }, _ => Err(serde::ser::Error::custom("cannot serialize ")), } @@ -132,23 +134,32 @@ impl Serialize for UserDataStorage<()> { } /// A type that provides interior mutability for a userdata value (thread-safe). -pub(crate) struct UserDataCell { - raw_lock: RawLock, - value: UnsafeCell, -} - -#[cfg(feature = "send")] -unsafe impl Send for UserDataCell {} -#[cfg(feature = "send")] -unsafe impl Sync for UserDataCell {} +pub(crate) struct UserDataCell(RwLock); impl UserDataCell { #[inline(always)] fn new(value: T) -> Self { - UserDataCell { - raw_lock: RawLock::INIT, - value: UnsafeCell::new(value), - } + UserDataCell(RwLock::new(value)) + } + + /// Returns a reference to the underlying raw lock. + #[inline(always)] + pub(super) unsafe fn raw_lock(&self) -> &RawLock { + self.0.raw() + } + + /// Returns a raw pointer to the wrapped value. + /// + /// The caller is responsible for ensuring the appropriate lock is held. + #[inline(always)] + pub(super) fn as_ptr(&self) -> *mut T { + self.0.data_ptr() + } + + /// Consumes the cell and returns the inner value. + #[inline(always)] + pub(super) fn into_value(self) -> T { + self.0.into_inner() } } diff --git a/src/userdata/lock.rs b/src/userdata/lock.rs index b749ed71..901a557b 100644 --- a/src/userdata/lock.rs +++ b/src/userdata/lock.rs @@ -1,6 +1,4 @@ pub(crate) trait UserDataLock { - const INIT: Self; - fn is_locked(&self) -> bool; fn try_lock_shared(&self) -> bool; fn try_lock_exclusive(&self) -> bool; @@ -48,12 +46,12 @@ impl Drop for LockGuard<'_, L> { } } -pub(crate) use lock_impl::RawLock; +pub(crate) use lock_impl::{RawLock, RwLock}; #[cfg(not(feature = "send"))] #[cfg(not(tarpaulin_include))] mod lock_impl { - use std::cell::Cell; + use std::cell::{Cell, UnsafeCell}; // Positive values represent the number of read references. // Negative values represent the number of write references (only one allowed). @@ -62,9 +60,6 @@ mod lock_impl { const UNUSED: isize = 0; impl super::UserDataLock for RawLock { - #[allow(clippy::declare_interior_mutable_const)] - const INIT: Self = Cell::new(UNUSED); - #[inline(always)] fn is_locked(&self) -> bool { self.get() != UNUSED @@ -104,41 +99,71 @@ mod lock_impl { self.set(flag + 1); } } + + /// A cheap single-threaded read-write lock pairing a `parking_lot::RwLock` type. + pub(crate) struct RwLock { + lock: RawLock, + data: UnsafeCell, + } + + impl RwLock { + /// Creates a new `RwLock` containing the given value. + #[inline(always)] + pub(crate) fn new(value: T) -> Self { + RwLock { + lock: RawLock::new(UNUSED), + data: UnsafeCell::new(value), + } + } + + /// Returns a reference to the underlying raw lock. + #[inline(always)] + pub(crate) unsafe fn raw(&self) -> &RawLock { + &self.lock + } + + /// Returns a raw pointer to the underlying data. + #[inline(always)] + pub(crate) fn data_ptr(&self) -> *mut T { + self.data.get() + } + + /// Consumes this `RwLock`, returning the underlying data. + #[inline(always)] + pub(crate) fn into_inner(self) -> T { + self.data.into_inner() + } + } } #[cfg(feature = "send")] mod lock_impl { - use parking_lot::lock_api::RawRwLock; - - pub(crate) type RawLock = parking_lot::RawRwLock; + pub(crate) use parking_lot::{RawRwLock as RawLock, RwLock}; impl super::UserDataLock for RawLock { - #[allow(clippy::declare_interior_mutable_const)] - const INIT: Self = ::INIT; - #[inline(always)] fn is_locked(&self) -> bool { - RawRwLock::is_locked(self) + parking_lot::lock_api::RawRwLock::is_locked(self) } #[inline(always)] fn try_lock_shared(&self) -> bool { - RawRwLock::try_lock_shared(self) + parking_lot::lock_api::RawRwLock::try_lock_shared(self) } #[inline(always)] fn try_lock_exclusive(&self) -> bool { - RawRwLock::try_lock_exclusive(self) + parking_lot::lock_api::RawRwLock::try_lock_exclusive(self) } #[inline(always)] unsafe fn unlock_shared(&self) { - RawRwLock::unlock_shared(self) + parking_lot::lock_api::RawRwLock::unlock_shared(self) } #[inline(always)] unsafe fn unlock_exclusive(&self) { - RawRwLock::unlock_exclusive(self) + parking_lot::lock_api::RawRwLock::unlock_exclusive(self) } } } From 30cf4bef5817221b7ace93e5ddcff53c4a2f4052 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 22 Feb 2026 00:13:19 +0000 Subject: [PATCH 617/635] Use RwLock directly instead of UserDataCell --- src/userdata/cell.rs | 52 ++++++++++---------------------------------- 1 file changed, 11 insertions(+), 41 deletions(-) diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index e954b613..f0058fd8 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -23,9 +23,9 @@ pub(crate) enum UserDataStorage { // A enum for storing userdata values. // It's stored inside a Lua VM and protected by the outer `ReentrantMutex`. pub(crate) enum UserDataVariant { - Default(XRc>), + Default(XRc>), #[cfg(feature = "serde")] - Serializable(XRc>>), + Serializable(XRc>>), } impl Clone for UserDataVariant { @@ -80,12 +80,12 @@ impl UserDataVariant { return Err(Error::UserDataBorrowMutError); } Ok(match self { - Self::Default(inner) => XRc::into_inner(inner).unwrap().into_value(), + Self::Default(inner) => XRc::into_inner(inner).unwrap().into_inner(), #[cfg(feature = "serde")] Self::Serializable(inner) => unsafe { // The serde variant erases `T` to `Box`, so we // must cast the raw pointer back to recover the concrete type. - let raw = Box::into_raw(XRc::into_inner(inner).unwrap().into_value()); + let raw = Box::into_raw(XRc::into_inner(inner).unwrap().into_inner()); *Box::from_raw(raw as *mut T) }, }) @@ -103,18 +103,18 @@ impl UserDataVariant { #[inline(always)] pub(super) fn raw_lock(&self) -> &RawLock { match self { - Self::Default(inner) => unsafe { inner.raw_lock() }, + Self::Default(inner) => unsafe { inner.raw() }, #[cfg(feature = "serde")] - Self::Serializable(inner) => unsafe { inner.raw_lock() }, + Self::Serializable(inner) => unsafe { inner.raw() }, } } #[inline(always)] pub(super) fn as_ptr(&self) -> *mut T { match self { - Self::Default(inner) => inner.as_ptr(), + Self::Default(inner) => inner.data_ptr(), #[cfg(feature = "serde")] - Self::Serializable(inner) => unsafe { (&mut **inner.as_ptr()) as *mut DynSerialize as *mut T }, + Self::Serializable(inner) => unsafe { (&mut **inner.data_ptr()) as *mut DynSerialize as *mut T }, } } } @@ -126,43 +126,13 @@ impl Serialize for UserDataStorage<()> { Self::Owned(variant @ UserDataVariant::Serializable(inner)) => unsafe { let _guard = (variant.raw_lock().try_lock_shared_guarded()) .map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?; - (*inner.as_ptr()).serialize(serializer) + (*inner.data_ptr()).serialize(serializer) }, _ => Err(serde::ser::Error::custom("cannot serialize ")), } } } -/// A type that provides interior mutability for a userdata value (thread-safe). -pub(crate) struct UserDataCell(RwLock); - -impl UserDataCell { - #[inline(always)] - fn new(value: T) -> Self { - UserDataCell(RwLock::new(value)) - } - - /// Returns a reference to the underlying raw lock. - #[inline(always)] - pub(super) unsafe fn raw_lock(&self) -> &RawLock { - self.0.raw() - } - - /// Returns a raw pointer to the wrapped value. - /// - /// The caller is responsible for ensuring the appropriate lock is held. - #[inline(always)] - pub(super) fn as_ptr(&self) -> *mut T { - self.0.data_ptr() - } - - /// Consumes the cell and returns the inner value. - #[inline(always)] - pub(super) fn into_value(self) -> T { - self.0.into_inner() - } -} - pub(crate) enum ScopedUserDataVariant { Ref(*const T), RefMut(RefCell<*mut T>), @@ -183,7 +153,7 @@ impl Drop for ScopedUserDataVariant { impl UserDataStorage { #[inline(always)] pub(crate) fn new(data: T) -> Self { - Self::Owned(UserDataVariant::Default(XRc::new(UserDataCell::new(data)))) + Self::Owned(UserDataVariant::Default(XRc::new(RwLock::new(data)))) } #[inline(always)] @@ -203,7 +173,7 @@ impl UserDataStorage { T: Serialize + crate::types::MaybeSend + crate::types::MaybeSync, { let data = Box::new(data) as Box; - let variant = UserDataVariant::Serializable(XRc::new(UserDataCell::new(data))); + let variant = UserDataVariant::Serializable(XRc::new(RwLock::new(data))); Self::Owned(variant) } From 79d438aaad9593ff8e5eebb83b87e7ca45ddb5f3 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 22 Feb 2026 13:28:48 +0000 Subject: [PATCH 618/635] Build CI docs on main branch --- .github/workflows/docs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c3a2ad77..662a04a3 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,8 +1,8 @@ -name: Documentation (dev) +name: Documentation (main) on: push: - branches: [dev] + branches: [main] workflow_dispatch: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages From 0f3fdb05394e104da14cfc6f4d55e7d0759b0bf0 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 22 Feb 2026 14:33:38 +0000 Subject: [PATCH 619/635] Make `string` module public --- src/lib.rs | 7 +++++-- src/string.rs | 10 ++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 787baa3d..19792ebd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,7 +82,6 @@ mod multi; mod scope; mod state; mod stdlib; -mod string; mod thread; mod traits; mod types; @@ -94,6 +93,7 @@ mod vector; pub mod debug; pub mod function; pub mod prelude; +pub mod string; pub mod table; pub use bstr::BString; @@ -106,7 +106,7 @@ pub use crate::multi::{MultiValue, Variadic}; pub use crate::scope::Scope; pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua}; pub use crate::stdlib::StdLib; -pub use crate::string::{BorrowedBytes, BorrowedStr, LuaString, LuaString as String}; +pub use crate::string::{BorrowedBytes, BorrowedStr, LuaString}; pub use crate::table::Table; pub use crate::thread::{Thread, ThreadStatus}; pub use crate::traits::{ @@ -122,6 +122,9 @@ pub use crate::userdata::{ }; pub use crate::value::{Nil, Value}; +#[doc(hidden)] +pub use crate::string::LuaString as String; + #[cfg(not(feature = "luau"))] pub use crate::debug::HookTriggers; diff --git a/src/string.rs b/src/string.rs index 0a1eff91..711685bf 100644 --- a/src/string.rs +++ b/src/string.rs @@ -1,3 +1,13 @@ +//! Lua string handling. +//! +//! This module provides types for working with Lua strings from Rust. +//! +//! # Main Types +//! +//! - [`LuaString`] - A handle to an internal Lua string (may not be valid UTF-8). +//! - [`BorrowedStr`] - A borrowed `&str` view of a Lua string that holds a strong reference to the Lua state. +//! - [`BorrowedBytes`] - A borrowed `&[u8]` view of a Lua string that holds a strong reference to the Lua state. + use std::borrow::{Borrow, Cow}; use std::hash::{Hash, Hasher}; use std::ops::Deref; From 33bf3ffde7c0fc363c09f9bc85e1c59b1e61ab6b Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 22 Feb 2026 14:37:23 +0000 Subject: [PATCH 620/635] Fix doc warnings --- src/debug.rs | 4 ++-- src/state.rs | 4 ++-- src/string.rs | 6 ++++-- src/thread.rs | 2 ++ 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/debug.rs b/src/debug.rs index a8527e7b..89d8c501 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,8 +1,8 @@ //! Lua debugging interface. //! //! This module provides access to the Lua debug interface, allowing inspection of the call stack, -//! and function information. The main types are [`Debug`] for accessing debug information and -//! [`HookTriggers`] for configuring debug hooks. +//! and function information. The main types are [`struct@Debug`] for accessing debug information +//! and [`HookTriggers`] for configuring debug hooks. use std::borrow::Cow; use std::os::raw::c_int; diff --git a/src/state.rs b/src/state.rs index 543c0063..6da2eefb 100644 --- a/src/state.rs +++ b/src/state.rs @@ -909,8 +909,8 @@ impl Lua { /// Gets information about the interpreter runtime stack at the given level. /// - /// This function calls callback `f`, passing the [`Debug`] structure that can be used to get - /// information about the function executing at a given level. + /// This function calls callback `f`, passing the [`struct@Debug`] structure that can be used to + /// get information about the function executing at a given level. /// Level `0` is the current running function, whereas level `n+1` is the function that has /// called level `n` (except for tail calls, which do not count in the stack). pub fn inspect_stack(&self, level: usize, f: impl FnOnce(&Debug) -> R) -> Option { diff --git a/src/string.rs b/src/string.rs index 711685bf..3428ce69 100644 --- a/src/string.rs +++ b/src/string.rs @@ -5,8 +5,10 @@ //! # Main Types //! //! - [`LuaString`] - A handle to an internal Lua string (may not be valid UTF-8). -//! - [`BorrowedStr`] - A borrowed `&str` view of a Lua string that holds a strong reference to the Lua state. -//! - [`BorrowedBytes`] - A borrowed `&[u8]` view of a Lua string that holds a strong reference to the Lua state. +//! - [`BorrowedStr`] - A borrowed `&str` view of a Lua string that holds a strong reference to the +//! Lua state. +//! - [`BorrowedBytes`] - A borrowed `&[u8]` view of a Lua string that holds a strong reference to +//! the Lua state. use std::borrow::{Borrow, Cow}; use std::hash::{Hash, Hasher}; diff --git a/src/thread.rs b/src/thread.rs index 6941dc65..03ac4137 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -449,6 +449,8 @@ impl Thread { /// Please note that Luau links environment table with chunk when loading it into Lua state. /// Therefore you need to load chunks into a thread to link with the thread environment. /// + /// [`Lua::sandbox`]: crate::Lua::sandbox + /// /// # Examples /// /// ``` From eb76db59da141eea6ba4262ff9633bb8f5b7e8d2 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 22 Feb 2026 19:52:57 +0000 Subject: [PATCH 621/635] Make `userdata` module public --- src/lib.rs | 13 ++++++++----- src/userdata.rs | 16 ++++++++++++++++ tests/userdata.rs | 2 +- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 19792ebd..d34b78b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,7 +85,6 @@ mod stdlib; mod thread; mod traits; mod types; -mod userdata; mod util; mod value; mod vector; @@ -95,6 +94,7 @@ pub mod function; pub mod prelude; pub mod string; pub mod table; +pub mod userdata; pub use bstr::BString; pub use ffi::{self, lua_CFunction, lua_State}; @@ -116,14 +116,17 @@ pub use crate::types::{ AppDataRef, AppDataRefMut, Either, Integer, LightUserData, MaybeSend, MaybeSync, Number, RegistryKey, VmState, }; -pub use crate::userdata::{ - AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef, - UserDataRefMut, UserDataRegistry, -}; +pub use crate::userdata::AnyUserData; pub use crate::value::{Nil, Value}; +// Re-export some types to keep backward compatibility and avoid breaking changes in the public API. #[doc(hidden)] pub use crate::string::LuaString as String; +#[doc(hidden)] +pub use crate::userdata::{ + MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef, UserDataRefMut, + UserDataRegistry, +}; #[cfg(not(feature = "luau"))] pub use crate::debug::HookTriggers; diff --git a/src/userdata.rs b/src/userdata.rs index acea3108..6e5211eb 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -1,3 +1,19 @@ +//! Lua userdata handling. +//! +//! This module provides types for creating and working with Lua userdata from Rust. +//! +//! # Main Types +//! +//! - [`AnyUserData`] - A handle to a Lua userdata value of any Rust type. +//! - [`UserData`] - Trait to implement for types that should be exposed to Lua as userdata. +//! - [`UserDataFields`] - Trait for registering fields on userdata types. +//! - [`UserDataMethods`] - Trait for registering methods on userdata types. +//! - [`UserDataRegistry`] - Registry for userdata methods and fields. +//! - [`UserDataMetatable`] - A handle to the metatable of a userdata type. +//! - [`UserDataRef`] - A borrowed reference to a userdata value. +//! - [`UserDataRefMut`] - A mutably borrowed reference to a userdata value. +//! - [`MetaMethod`] - Metamethod names for customizing Lua operators. + use std::any::TypeId; use std::ffi::CStr; use std::fmt; diff --git a/tests/userdata.rs b/tests/userdata.rs index df5ed1f4..4af59814 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -1376,7 +1376,7 @@ fn test_userdata_namecall() -> Result<()> { struct MyUserData; impl UserData for MyUserData { - fn register(registry: &mut mlua::UserDataRegistry) { + fn register(registry: &mut UserDataRegistry) { registry.add_method("method", |_, _, ()| Ok("method called")); registry.add_field_method_get("field", |_, _| Ok("field value")); From 35294359adb4d5a4a6d854662663899d5c1c2946 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 22 Feb 2026 19:58:28 +0000 Subject: [PATCH 622/635] Inline doc for some types --- src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index d34b78b1..10a6d3a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,12 +101,15 @@ pub use ffi::{self, lua_CFunction, lua_State}; pub use crate::chunk::{AsChunk, Chunk, ChunkMode}; pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result}; +#[doc(inline)] pub use crate::function::Function; pub use crate::multi::{MultiValue, Variadic}; pub use crate::scope::Scope; pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua}; pub use crate::stdlib::StdLib; +#[doc(inline)] pub use crate::string::{BorrowedBytes, BorrowedStr, LuaString}; +#[doc(inline)] pub use crate::table::Table; pub use crate::thread::{Thread, ThreadStatus}; pub use crate::traits::{ @@ -116,6 +119,7 @@ pub use crate::types::{ AppDataRef, AppDataRefMut, Either, Integer, LightUserData, MaybeSend, MaybeSync, Number, RegistryKey, VmState, }; +#[doc(inline)] pub use crate::userdata::AnyUserData; pub use crate::value::{Nil, Value}; @@ -129,6 +133,7 @@ pub use crate::userdata::{ }; #[cfg(not(feature = "luau"))] +#[doc(inline)] pub use crate::debug::HookTriggers; #[cfg(any(feature = "luau", doc))] From 88177203620db5e75a1d131dba4329db97a0b191 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 23 Feb 2026 09:51:24 +0000 Subject: [PATCH 623/635] Re-export (hidden) `TablePairs` and `TableSequence` --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 10a6d3a8..d12101bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,6 +127,8 @@ pub use crate::value::{Nil, Value}; #[doc(hidden)] pub use crate::string::LuaString as String; #[doc(hidden)] +pub use crate::table::{TablePairs, TableSequence}; +#[doc(hidden)] pub use crate::userdata::{ MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef, UserDataRefMut, UserDataRegistry, From bf0c96908f06ce7491497b7e392835054f8dc0ce Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 23 Feb 2026 10:24:21 +0000 Subject: [PATCH 624/635] Remove lifetime from `BorrowedStr` and `BorrowedBytes` The underlying `ValueRef` is cheap to clone as only increases reference count, instead of allocating a new Lua stack slot. --- src/conversion.rs | 50 ++++++++++----------------- src/string.rs | 88 +++++++++++++++++++++++++---------------------- src/value.rs | 2 +- 3 files changed, 67 insertions(+), 73 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index 8de53467..f2f7ca5a 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -4,7 +4,7 @@ use std::ffi::{CStr, CString, OsStr, OsString}; use std::hash::{BuildHasher, Hash}; use std::os::raw::c_int; use std::path::{Path, PathBuf}; -use std::{mem, slice, str}; +use std::{slice, str}; use bstr::{BStr, BString, ByteVec}; use num_traits::cast; @@ -86,91 +86,79 @@ impl FromLua for LuaString { } } -impl IntoLua for BorrowedStr<'_> { +impl IntoLua for BorrowedStr { #[inline] fn into_lua(self, _: &Lua) -> Result { - Ok(Value::String(self.borrow.into_owned())) + Ok(Value::String(LuaString(self.vref))) } #[inline] unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - lua.push_ref(&self.borrow.0); + lua.push_ref(&self.vref); Ok(()) } } -impl IntoLua for &BorrowedStr<'_> { +impl IntoLua for &BorrowedStr { #[inline] fn into_lua(self, _: &Lua) -> Result { - Ok(Value::String(self.borrow.clone().into_owned())) + Ok(Value::String(LuaString(self.vref.clone()))) } #[inline] unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - lua.push_ref(&self.borrow.0); + lua.push_ref(&self.vref); Ok(()) } } -impl FromLua for BorrowedStr<'_> { +impl FromLua for BorrowedStr { fn from_lua(value: Value, lua: &Lua) -> Result { let s = LuaString::from_lua(value, lua)?; - let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?; - let buf = unsafe { mem::transmute::<&str, &'static str>(buf) }; - let borrow = Cow::Owned(s); - Ok(Self { buf, borrow, _lua }) + BorrowedStr::try_from(&s) } unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { let s = LuaString::from_stack(idx, lua)?; - let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?; - let buf = unsafe { mem::transmute::<&str, &'static str>(buf) }; - let borrow = Cow::Owned(s); - Ok(Self { buf, borrow, _lua }) + BorrowedStr::try_from(&s) } } -impl IntoLua for BorrowedBytes<'_> { +impl IntoLua for BorrowedBytes { #[inline] fn into_lua(self, _: &Lua) -> Result { - Ok(Value::String(self.borrow.into_owned())) + Ok(Value::String(LuaString(self.vref))) } #[inline] unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - lua.push_ref(&self.borrow.0); + lua.push_ref(&self.vref); Ok(()) } } -impl IntoLua for &BorrowedBytes<'_> { +impl IntoLua for &BorrowedBytes { #[inline] fn into_lua(self, _: &Lua) -> Result { - Ok(Value::String(self.borrow.clone().into_owned())) + Ok(Value::String(LuaString(self.vref.clone()))) } #[inline] unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - lua.push_ref(&self.borrow.0); + lua.push_ref(&self.vref); Ok(()) } } -impl FromLua for BorrowedBytes<'_> { +impl FromLua for BorrowedBytes { fn from_lua(value: Value, lua: &Lua) -> Result { let s = LuaString::from_lua(value, lua)?; - let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s); - let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) }; - let borrow = Cow::Owned(s); - Ok(Self { buf, borrow, _lua }) + Ok(BorrowedBytes::from(&s)) } unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { let s = LuaString::from_stack(idx, lua)?; - let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s); - let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) }; - let borrow = Cow::Owned(s); - Ok(Self { buf, borrow, _lua }) + Ok(BorrowedBytes::from(&s)) } } diff --git a/src/string.rs b/src/string.rs index 3428ce69..3093c99c 100644 --- a/src/string.rs +++ b/src/string.rs @@ -10,11 +10,11 @@ //! - [`BorrowedBytes`] - A borrowed `&[u8]` view of a Lua string that holds a strong reference to //! the Lua state. -use std::borrow::{Borrow, Cow}; +use std::borrow::Borrow; use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::os::raw::{c_int, c_void}; -use std::{cmp, fmt, slice, str}; +use std::{cmp, fmt, mem, slice, str}; use crate::error::{Error, Result}; use crate::state::Lua; @@ -37,6 +37,9 @@ pub struct LuaString(pub(crate) ValueRef); impl LuaString { /// Get a [`BorrowedStr`] if the Lua string is valid UTF-8. /// + /// The returned `BorrowedStr` holds a strong reference to the Lua state to guarantee the + /// validity of the underlying data. + /// /// # Examples /// /// ``` @@ -54,7 +57,7 @@ impl LuaString { /// # } /// ``` #[inline] - pub fn to_str(&self) -> Result> { + pub fn to_str(&self) -> Result { BorrowedStr::try_from(self) } @@ -97,8 +100,9 @@ impl LuaString { /// Get the bytes that make up this string. /// - /// The returned slice will not contain the terminating null byte, but will contain any null - /// bytes embedded into the Lua string. + /// The returned `BorrowedStr` holds a strong reference to the Lua state to guarantee the + /// validity of the underlying data. The data will not contain the terminating null byte, but + /// will contain any null bytes embedded into the Lua string. /// /// # Examples /// @@ -113,16 +117,16 @@ impl LuaString { /// # } /// ``` #[inline] - pub fn as_bytes(&self) -> BorrowedBytes<'_> { + pub fn as_bytes(&self) -> BorrowedBytes { BorrowedBytes::from(self) } /// Get the bytes that make up this string, including the trailing null byte. - pub fn as_bytes_with_nul(&self) -> BorrowedBytes<'_> { - let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self); + pub fn as_bytes_with_nul(&self) -> BorrowedBytes { + let BorrowedBytes { buf, vref, _lua } = BorrowedBytes::from(self); // Include the trailing null byte (it's always present but excluded by default) let buf = unsafe { slice::from_raw_parts((*buf).as_ptr(), (*buf).len() + 1) }; - BorrowedBytes { buf, borrow, _lua } + BorrowedBytes { buf, vref, _lua } } // Does not return the terminating null byte @@ -245,14 +249,14 @@ impl fmt::Display for Display<'_> { } /// A borrowed string (`&str`) that holds a strong reference to the Lua state. -pub struct BorrowedStr<'a> { +pub struct BorrowedStr { // `buf` points to a readonly memory managed by Lua - pub(crate) buf: &'a str, - pub(crate) borrow: Cow<'a, LuaString>, + pub(crate) buf: &'static str, + pub(crate) vref: ValueRef, pub(crate) _lua: Lua, } -impl Deref for BorrowedStr<'_> { +impl Deref for BorrowedStr { type Target = str; #[inline(always)] @@ -261,33 +265,33 @@ impl Deref for BorrowedStr<'_> { } } -impl Borrow for BorrowedStr<'_> { +impl Borrow for BorrowedStr { #[inline(always)] fn borrow(&self) -> &str { self.buf } } -impl AsRef for BorrowedStr<'_> { +impl AsRef for BorrowedStr { #[inline(always)] fn as_ref(&self) -> &str { self.buf } } -impl fmt::Display for BorrowedStr<'_> { +impl fmt::Display for BorrowedStr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.buf.fmt(f) } } -impl fmt::Debug for BorrowedStr<'_> { +impl fmt::Debug for BorrowedStr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.buf.fmt(f) } } -impl PartialEq for BorrowedStr<'_> +impl PartialEq for BorrowedStr where T: AsRef, { @@ -296,9 +300,9 @@ where } } -impl Eq for BorrowedStr<'_> {} +impl Eq for BorrowedStr {} -impl PartialOrd for BorrowedStr<'_> +impl PartialOrd for BorrowedStr where T: AsRef, { @@ -307,33 +311,33 @@ where } } -impl Ord for BorrowedStr<'_> { +impl Ord for BorrowedStr { fn cmp(&self, other: &Self) -> cmp::Ordering { self.buf.cmp(other.buf) } } -impl<'a> TryFrom<&'a LuaString> for BorrowedStr<'a> { +impl TryFrom<&LuaString> for BorrowedStr { type Error = Error; #[inline] - fn try_from(value: &'a LuaString) -> Result { - let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(value); + fn try_from(value: &LuaString) -> Result { + let BorrowedBytes { buf, vref, _lua } = BorrowedBytes::from(value); let buf = str::from_utf8(buf).map_err(|e| Error::from_lua_conversion("string", "&str", e.to_string()))?; - Ok(Self { buf, borrow, _lua }) + Ok(Self { buf, vref, _lua }) } } /// A borrowed byte slice (`&[u8]`) that holds a strong reference to the Lua state. -pub struct BorrowedBytes<'a> { +pub struct BorrowedBytes { // `buf` points to a readonly memory managed by Lua - pub(crate) buf: &'a [u8], - pub(crate) borrow: Cow<'a, LuaString>, + pub(crate) buf: &'static [u8], + pub(crate) vref: ValueRef, pub(crate) _lua: Lua, } -impl Deref for BorrowedBytes<'_> { +impl Deref for BorrowedBytes { type Target = [u8]; #[inline(always)] @@ -342,27 +346,27 @@ impl Deref for BorrowedBytes<'_> { } } -impl Borrow<[u8]> for BorrowedBytes<'_> { +impl Borrow<[u8]> for BorrowedBytes { #[inline(always)] fn borrow(&self) -> &[u8] { self.buf } } -impl AsRef<[u8]> for BorrowedBytes<'_> { +impl AsRef<[u8]> for BorrowedBytes { #[inline(always)] fn as_ref(&self) -> &[u8] { self.buf } } -impl fmt::Debug for BorrowedBytes<'_> { +impl fmt::Debug for BorrowedBytes { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.buf.fmt(f) } } -impl PartialEq for BorrowedBytes<'_> +impl PartialEq for BorrowedBytes where T: AsRef<[u8]>, { @@ -371,9 +375,9 @@ where } } -impl Eq for BorrowedBytes<'_> {} +impl Eq for BorrowedBytes {} -impl PartialOrd for BorrowedBytes<'_> +impl PartialOrd for BorrowedBytes where T: AsRef<[u8]>, { @@ -382,13 +386,13 @@ where } } -impl Ord for BorrowedBytes<'_> { +impl Ord for BorrowedBytes { fn cmp(&self, other: &Self) -> cmp::Ordering { self.buf.cmp(other.buf) } } -impl<'a> IntoIterator for &'a BorrowedBytes<'_> { +impl<'a> IntoIterator for &'a BorrowedBytes { type Item = &'a u8; type IntoIter = slice::Iter<'a, u8>; @@ -397,12 +401,14 @@ impl<'a> IntoIterator for &'a BorrowedBytes<'_> { } } -impl<'a> From<&'a LuaString> for BorrowedBytes<'a> { +impl From<&LuaString> for BorrowedBytes { #[inline] - fn from(value: &'a LuaString) -> Self { + fn from(value: &LuaString) -> Self { let (buf, _lua) = unsafe { value.to_slice() }; - let borrow = Cow::Borrowed(value); - Self { buf, borrow, _lua } + let vref = value.0.clone(); + // SAFETY: The `buf` is valid for the lifetime of the Lua state and occupied slot index + let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) }; + Self { buf, vref, _lua } } } diff --git a/src/value.rs b/src/value.rs index 8d178e04..10d4c4d0 100644 --- a/src/value.rs +++ b/src/value.rs @@ -361,7 +361,7 @@ impl Value { note = "This method does not follow Rust naming convention. Use `as_string().and_then(|s| s.to_str().ok())` instead." )] #[inline] - pub fn as_str(&self) -> Option> { + pub fn as_str(&self) -> Option { self.as_string().and_then(|s| s.to_str().ok()) } From 47e6a37323cd7df96dcdc869d773c79b81cf9e9d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 28 Feb 2026 11:46:05 +0000 Subject: [PATCH 625/635] Add shortcuts to check thread status (`Thread::is_resumable()`, `Thread::is_finished()` etc) --- src/thread.rs | 25 +++++++++++++++++++++++++ tests/async.rs | 4 ++-- tests/hooks.rs | 8 ++++---- tests/luau.rs | 8 +++----- tests/thread.rs | 36 ++++++++++++++++++------------------ 5 files changed, 52 insertions(+), 29 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index 03ac4137..74bced65 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -259,6 +259,31 @@ impl Thread { } } + /// Returns `true` if this thread is resumable (meaning it can be resumed by calling + /// [`Thread::resume`]). + #[inline(always)] + pub fn is_resumable(&self) -> bool { + self.status() == ThreadStatus::Resumable + } + + /// Returns `true` if this thread is currently running. + #[inline(always)] + pub fn is_running(&self) -> bool { + self.status() == ThreadStatus::Running + } + + /// Returns `true` if this thread has finished executing. + #[inline(always)] + pub fn is_finished(&self) -> bool { + self.status() == ThreadStatus::Finished + } + + /// Returns `true` if this thread has raised a Lua error during execution. + #[inline(always)] + pub fn is_error(&self) -> bool { + self.status() == ThreadStatus::Error + } + /// Sets a hook function that will periodically be called as Lua code executes. /// /// This function is similar or [`Lua::set_hook`] except that it sets for the thread. diff --git a/tests/async.rs b/tests/async.rs index 22df2ab3..16ddd9ee 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -7,7 +7,7 @@ use futures_util::stream::TryStreamExt; use tokio::sync::Mutex; use mlua::{ - Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, ThreadStatus, UserData, + Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, UserData, UserDataMethods, UserDataRef, Value, }; @@ -714,7 +714,7 @@ fn test_async_yield_with() -> Result<()> { assert_eq!(thread.resume::<(i32, i32)>((10, 11))?, (21, 110)); assert_eq!(thread.resume::<(i32, i32)>((11, 12))?, (23, 132)); assert_eq!(thread.resume::<(i32, i32)>((12, 13))?, (0, 0)); - assert_eq!(thread.status(), ThreadStatus::Finished); + assert!(thread.is_finished()); Ok(()) } diff --git a/tests/hooks.rs b/tests/hooks.rs index f1b72445..9d68c84b 100644 --- a/tests/hooks.rs +++ b/tests/hooks.rs @@ -4,7 +4,7 @@ use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::{Arc, Mutex}; use mlua::debug::DebugEvent; -use mlua::{Error, HookTriggers, Lua, Result, ThreadStatus, Value, VmState}; +use mlua::{Error, HookTriggers, Lua, Result, Value, VmState}; #[test] fn test_hook_triggers() { @@ -281,14 +281,14 @@ fn test_hook_yield() -> Result<()> { assert!(co.resume::<()>(()).is_ok()); assert!(co.resume::<()>(()).is_ok()); assert!(co.resume::<()>(()).is_ok()); - assert!(co.status() == ThreadStatus::Finished); + assert!(co.is_finished()); } #[cfg(any(feature = "lua51", feature = "lua52", feature = "luajit"))] { assert!( matches!(co.resume::<()>(()), Err(Error::RuntimeError(err)) if err.contains("attempt to yield from a hook")) ); - assert!(co.status() == ThreadStatus::Error); + assert!(co.is_error()); } Ok(()) @@ -321,7 +321,7 @@ fn test_global_hook() -> Result<()> { thread.resume::<()>(()).unwrap(); lua.remove_global_hook(); thread.resume::<()>(()).unwrap(); - assert_eq!(thread.status(), ThreadStatus::Finished); + assert!(thread.is_finished()); assert_eq!(counter.load(Ordering::Relaxed), 3); Ok(()) diff --git a/tests/luau.rs b/tests/luau.rs index 8f745768..5143e7ac 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -6,9 +6,7 @@ use std::os::raw::c_void; use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering}; -use mlua::{ - Compiler, Error, Function, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, Vector, VmState, -}; +use mlua::{Compiler, Error, Function, Lua, LuaOptions, Result, StdLib, Table, Value, Vector, VmState}; #[test] fn test_version() -> Result<()> { @@ -324,11 +322,11 @@ fn test_interrupts() -> Result<()> { .into_function()?, )?; co.resume::<()>(())?; - assert_eq!(co.status(), ThreadStatus::Resumable); + assert!(co.is_resumable()); let result: i32 = co.resume(())?; assert_eq!(result, 6); assert_eq!(yield_count.load(Ordering::Relaxed), 7); - assert_eq!(co.status(), ThreadStatus::Finished); + assert!(co.is_finished()); // Test no yielding at non-yieldable points yield_count.store(0, Ordering::Relaxed); diff --git a/tests/thread.rs b/tests/thread.rs index 71eb24c9..98b861f8 100644 --- a/tests/thread.rs +++ b/tests/thread.rs @@ -1,6 +1,6 @@ use std::panic::catch_unwind; -use mlua::{Error, Function, IntoLua, Lua, Result, Thread, ThreadStatus, Value}; +use mlua::{Error, Function, IntoLua, Lua, Result, Thread, Value}; #[test] fn test_thread() -> Result<()> { @@ -21,17 +21,17 @@ fn test_thread() -> Result<()> { .eval()?, )?; - assert_eq!(thread.status(), ThreadStatus::Resumable); + assert!(thread.is_resumable()); assert_eq!(thread.resume::(0)?, 0); - assert_eq!(thread.status(), ThreadStatus::Resumable); + assert!(thread.is_resumable()); assert_eq!(thread.resume::(1)?, 1); - assert_eq!(thread.status(), ThreadStatus::Resumable); + assert!(thread.is_resumable()); assert_eq!(thread.resume::(2)?, 3); - assert_eq!(thread.status(), ThreadStatus::Resumable); + assert!(thread.is_resumable()); assert_eq!(thread.resume::(3)?, 6); - assert_eq!(thread.status(), ThreadStatus::Resumable); + assert!(thread.is_resumable()); assert_eq!(thread.resume::(4)?, 10); - assert_eq!(thread.status(), ThreadStatus::Finished); + assert!(thread.is_finished()); let accumulate = lua.create_thread( lua.load( @@ -50,9 +50,9 @@ fn test_thread() -> Result<()> { accumulate.resume::<()>(i)?; } assert_eq!(accumulate.resume::(4)?, 10); - assert_eq!(accumulate.status(), ThreadStatus::Resumable); + assert!(accumulate.is_resumable()); assert!(accumulate.resume::<()>("error").is_err()); - assert_eq!(accumulate.status(), ThreadStatus::Error); + assert!(accumulate.is_error()); let thread = lua .load( @@ -65,7 +65,7 @@ fn test_thread() -> Result<()> { "#, ) .eval::()?; - assert_eq!(thread.status(), ThreadStatus::Resumable); + assert!(thread.is_resumable()); assert_eq!(thread.resume::(())?, 42); let thread: Thread = lua @@ -92,7 +92,7 @@ fn test_thread() -> Result<()> { // Already running thread must be unresumable let thread = lua.create_thread(lua.create_function(|lua, ()| { - assert_eq!(lua.current_thread().status(), ThreadStatus::Running); + assert!(lua.current_thread().is_running()); let result = lua.current_thread().resume::<()>(()); assert!( matches!(result, Err(Error::CoroutineUnresumable)), @@ -123,12 +123,12 @@ fn test_thread_reset() -> Result<()> { assert!(thread.reset(func.clone()).is_ok()); for _ in 0..2 { - assert_eq!(thread.status(), ThreadStatus::Resumable); + assert!(thread.is_resumable()); let _ = thread.resume::(MyUserData(arc.clone()))?; - assert_eq!(thread.status(), ThreadStatus::Resumable); + assert!(thread.is_resumable()); assert_eq!(Arc::strong_count(&arc), 2); thread.resume::<()>(())?; - assert_eq!(thread.status(), ThreadStatus::Finished); + assert!(thread.is_finished()); thread.reset(func.clone())?; lua.gc_collect()?; assert_eq!(Arc::strong_count(&arc), 1); @@ -138,21 +138,21 @@ fn test_thread_reset() -> Result<()> { let func: Function = lua.load(r#"function(ud) error("test error") end"#).eval()?; let thread = lua.create_thread(func.clone())?; let _ = thread.resume::(MyUserData(arc.clone())); - assert_eq!(thread.status(), ThreadStatus::Error); + assert!(thread.is_error()); assert_eq!(Arc::strong_count(&arc), 2); #[cfg(any(feature = "lua55", feature = "lua54"))] { assert!(thread.reset(func.clone()).is_err()); // Reset behavior has changed in Lua v5.4.4 // It's became possible to force reset thread by popping error object - assert!(matches!(thread.status(), ThreadStatus::Finished)); + assert!(thread.is_finished()); assert!(thread.reset(func.clone()).is_ok()); - assert_eq!(thread.status(), ThreadStatus::Resumable); + assert!(thread.is_resumable()); } #[cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))] { assert!(thread.reset(func.clone()).is_ok()); - assert_eq!(thread.status(), ThreadStatus::Resumable); + assert!(thread.is_resumable()); } // Try reset running thread From f1a97e4193e3d617ac743da176ab61fd72b3212c Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 28 Feb 2026 11:48:59 +0000 Subject: [PATCH 626/635] Open `Thread::state()` that returns `*mut lua_State` pointer. --- src/thread.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/thread.rs b/src/thread.rs index 74bced65..86e8223b 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -92,7 +92,6 @@ pub struct AsyncThread { impl Thread { /// Returns reference to the Lua state that this thread is associated with. - #[doc(hidden)] #[inline(always)] pub fn state(&self) -> *mut ffi::lua_State { self.1 From a45fe9bb936909adf5e2a25b2bef80640de835cb Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 28 Feb 2026 12:03:37 +0000 Subject: [PATCH 627/635] Bump luau-src to 0.19 (Luau 0.710) --- mlua-sys/Cargo.toml | 2 +- mlua-sys/src/luau/lua.rs | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 2230af75..c6804892 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -43,7 +43,7 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 550.0.0, < 550.1.0", optional = true } luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true } -luau0-src = { version = "0.18.0", optional = true } +luau0-src = { version = "0.19.0", optional = true } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 98ee5bc7..15862331 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -501,6 +501,12 @@ pub type lua_Coverage = unsafe extern "C-unwind" fn( size: usize, ); +pub type lua_CounterFunction = + unsafe extern "C-unwind" fn(context: *mut c_void, function: *const c_char, linedefined: c_int); + +pub type lua_CounterValue = + unsafe extern "C-unwind" fn(context: *mut c_void, kind: c_int, line: c_int, hits: u64); + unsafe extern "C-unwind" { pub fn lua_stackdepth(L: *mut lua_State) -> c_int; pub fn lua_getinfo(L: *mut lua_State, level: c_int, what: *const c_char, ar: *mut lua_Debug) -> c_int; @@ -515,6 +521,14 @@ unsafe extern "C-unwind" { pub fn lua_getcoverage(L: *mut lua_State, funcindex: c_int, context: *mut c_void, callback: lua_Coverage); + pub fn lua_getcounters( + L: *mut lua_State, + funcindex: c_int, + context: *mut c_void, + functionvisit: lua_CounterFunction, + countervisit: lua_CounterValue, + ); + pub fn lua_debugtrace(L: *mut lua_State) -> *const c_char; } @@ -552,7 +566,7 @@ pub struct lua_Callbacks { /// gets called when L is created (LP == parent) or destroyed (LP == NULL) pub userthread: Option, /// gets called when a string is created; returned atom can be retrieved via tostringatom - pub useratom: Option i16>, + pub useratom: Option i16>, /// gets called when BREAK instruction is encountered pub debugbreak: Option, From c91066006f5796ab644e04cc6852237935391c99 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 28 Feb 2026 14:44:50 +0000 Subject: [PATCH 628/635] Make `thread` module public --- src/lib.rs | 10 +++++++--- src/prelude.rs | 13 +++++++------ src/thread.rs | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d12101bf..5573db90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,6 +61,7 @@ //! [`Future`]: std::future::Future //! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html //! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html +//! [`AsyncThread`]: crate::thread::AsyncThread // Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any* // warnings at all. @@ -82,7 +83,6 @@ mod multi; mod scope; mod state; mod stdlib; -mod thread; mod traits; mod types; mod util; @@ -94,6 +94,7 @@ pub mod function; pub mod prelude; pub mod string; pub mod table; +pub mod thread; pub mod userdata; pub use bstr::BString; @@ -111,7 +112,8 @@ pub use crate::stdlib::StdLib; pub use crate::string::{BorrowedBytes, BorrowedStr, LuaString}; #[doc(inline)] pub use crate::table::Table; -pub use crate::thread::{Thread, ThreadStatus}; +#[doc(inline)] +pub use crate::thread::Thread; pub use crate::traits::{ FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut, ObjectLike, }; @@ -133,6 +135,8 @@ pub use crate::userdata::{ MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef, UserDataRefMut, UserDataRegistry, }; +#[doc(hidden)] +pub use thread::ThreadStatus; #[cfg(not(feature = "luau"))] #[doc(inline)] @@ -149,7 +153,7 @@ pub use crate::{ #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] -pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn}; +pub use crate::traits::LuaNativeAsyncFn; #[cfg(feature = "serde")] #[doc(inline)] diff --git a/src/prelude.rs b/src/prelude.rs index 23cdf85d..f5d670a2 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -9,12 +9,13 @@ pub use crate::{ LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions, LuaString, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, - Table as LuaTable, Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData, - UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, - UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, - UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, - Variadic as LuaVariadic, VmState as LuaVmState, WeakLua, function::FunctionInfo as LuaFunctionInfo, + Table as LuaTable, Thread as LuaThread, UserData as LuaUserData, UserDataFields as LuaUserDataFields, + UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, + UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut, + UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, Variadic as LuaVariadic, + VmState as LuaVmState, WeakLua, function::FunctionInfo as LuaFunctionInfo, table::TablePairs as LuaTablePairs, table::TableSequence as LuaTableSequence, + thread::ThreadStatus as LuaThreadStatus, }; #[cfg(not(feature = "luau"))] @@ -30,7 +31,7 @@ pub use crate::{ #[cfg(feature = "async")] #[doc(no_inline)] -pub use crate::{AsyncThread as LuaAsyncThread, LuaNativeAsyncFn}; +pub use crate::{LuaNativeAsyncFn, thread::AsyncThread as LuaAsyncThread}; #[cfg(feature = "serde")] #[doc(no_inline)] diff --git a/src/thread.rs b/src/thread.rs index 86e8223b..561c0bb8 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -1,3 +1,40 @@ +//! Lua thread (coroutine) handling. +//! +//! This module provides types for creating and working with Lua coroutines from Rust. +//! Coroutines allow cooperative multitasking within a single Lua state by suspending and +//! resuming execution at well-defined yield points. +//! +//! # Basic Usage +//! +//! Threads are created via [`Lua::create_thread`] and driven by calling [`Thread::resume`]: +//! +//! ```rust +//! # use mlua::{Lua, Result, Thread}; +//! # fn main() -> Result<()> { +//! let lua = Lua::new(); +//! let thread: Thread = lua.load(r#" +//! coroutine.create(function(a, b) +//! coroutine.yield(a + b) +//! return a * b +//! end) +//! "#).eval()?; +//! +//! assert_eq!(thread.resume::((3, 4))?, 7); +//! assert_eq!(thread.resume::(())?, 12); +//! # Ok(()) +//! # } +//! ``` +//! +//! # Async Support +//! +//! When the `async` feature is enabled, a [`Thread`] can be converted into an [`AsyncThread`] +//! via [`Thread::into_async`], which implements both [`Future`] and [`Stream`]. +//! This integrates Lua coroutines naturally with Rust async runtimes such as Tokio. +//! +//! [`Lua::create_thread`]: crate::Lua::create_thread +//! [`Future`]: std::future::Future +//! [`Stream`]: futures_util::stream::Stream + use std::fmt; use std::os::raw::{c_int, c_void}; From efd085603367ef969cb85b7b0ad37333226027e7 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 28 Feb 2026 14:52:59 +0000 Subject: [PATCH 629/635] Derive `PartialEq` for `Thread` --- src/thread.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index 561c0bb8..935f4546 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -106,7 +106,7 @@ impl ThreadStatusInner { } /// Handle to an internal Lua thread (coroutine). -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct Thread(pub(crate) ValueRef, pub(crate) *mut ffi::lua_State); #[cfg(feature = "send")] @@ -565,12 +565,6 @@ impl fmt::Debug for Thread { } } -impl PartialEq for Thread { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - impl LuaType for Thread { const TYPE_ID: c_int = ffi::LUA_TTHREAD; } From a959b98d3062856d1737e927f387eaf3df95c2fe Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 1 Mar 2026 16:11:38 +0000 Subject: [PATCH 630/635] Add chunk module doc and update prelude re-exports --- src/chunk.rs | 7 +++++++ src/lib.rs | 4 ++-- src/prelude.rs | 12 ++++++------ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/chunk.rs b/src/chunk.rs index 3aedfb43..5426411b 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -1,3 +1,10 @@ +//! Lua chunk loading and execution. +//! +//! This module provides types for loading Lua source code or bytecode into a [`Chunk`], +//! configuring how it is compiled and executed, and converting it into a callable [`Function`]. +//! +//! Chunks can be loaded from strings, byte slices, or files via the [`AsChunk`] trait. + use std::borrow::Cow; use std::collections::HashMap; use std::ffi::CString; diff --git a/src/lib.rs b/src/lib.rs index 5573db90..97a2669b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,12 +131,12 @@ pub use crate::string::LuaString as String; #[doc(hidden)] pub use crate::table::{TablePairs, TableSequence}; #[doc(hidden)] +pub use crate::thread::ThreadStatus; +#[doc(hidden)] pub use crate::userdata::{ MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef, UserDataRefMut, UserDataRegistry, }; -#[doc(hidden)] -pub use thread::ThreadStatus; #[cfg(not(feature = "luau"))] #[doc(inline)] diff --git a/src/prelude.rs b/src/prelude.rs index f5d670a2..a10ab987 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -3,10 +3,10 @@ #[doc(no_inline)] pub use crate::{ AnyUserData as LuaAnyUserData, BorrowedBytes as LuaBorrowedBytes, BorrowedStr as LuaBorrowedStr, - Chunk as LuaChunk, Either as LuaEither, Error as LuaError, ErrorContext as LuaErrorContext, - ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti, - Function as LuaFunction, GCMode as LuaGCMode, Integer as LuaInteger, IntoLua, IntoLuaMulti, - LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions, LuaString, + Chunk as LuaChunk, ChunkMode as LuaChunkMode, Either as LuaEither, Error as LuaError, + ErrorContext as LuaErrorContext, ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, + FromLua, FromLuaMulti, Function as LuaFunction, GCMode as LuaGCMode, Integer as LuaInteger, IntoLua, + IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions, LuaString, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, Table as LuaTable, Thread as LuaThread, UserData as LuaUserData, UserDataFields as LuaUserDataFields, @@ -25,8 +25,8 @@ pub use crate::HookTriggers as LuaHookTriggers; #[cfg(feature = "luau")] #[doc(no_inline)] pub use crate::{ - CompileConstant as LuaCompileConstant, NavigateError as LuaNavigateError, Require as LuaRequire, - TextRequirer as LuaTextRequirer, Vector as LuaVector, + CompileConstant as LuaCompileConstant, Compiler as LuaCompiler, NavigateError as LuaNavigateError, + Require as LuaRequire, TextRequirer as LuaTextRequirer, Vector as LuaVector, }; #[cfg(feature = "async")] From 81ae8e1393b1def3d6e9758d6131929b6638a60d Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 1 Mar 2026 16:17:11 +0000 Subject: [PATCH 631/635] Make `Chunk::wrap` public --- src/chunk.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/chunk.rs b/src/chunk.rs index 5426411b..cd1b0597 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -786,7 +786,6 @@ impl Chunk<'_> { /// /// The resulted `IntoLua` implementation will convert the chunk into a Lua function without /// executing it. - #[doc(hidden)] #[track_caller] pub fn wrap(chunk: impl AsChunk) -> impl IntoLua { WrappedChunk { From a9604c4946532482920458e8026c8a586000ce38 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 1 Mar 2026 16:26:09 +0000 Subject: [PATCH 632/635] Rename Luau's TextRequirer to FsRequirer --- src/lib.rs | 2 +- src/luau/mod.rs | 4 ++-- src/luau/require.rs | 3 +-- src/luau/require/fs.rs | 12 ++++++------ src/prelude.rs | 4 ++-- tests/luau/require.rs | 8 +++----- 6 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 97a2669b..14e247d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,7 +147,7 @@ pub use crate::debug::HookTriggers; pub use crate::{ buffer::Buffer, chunk::{CompileConstant, Compiler}, - luau::{HeapDump, NavigateError, Require, TextRequirer}, + luau::{FsRequirer, HeapDump, NavigateError, Require}, vector::Vector, }; diff --git a/src/luau/mod.rs b/src/luau/mod.rs index 4015a7c4..701d015c 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -10,7 +10,7 @@ use crate::traits::{FromLuaMulti, IntoLua}; use crate::types::MaybeSend; pub use heap_dump::HeapDump; -pub use require::{NavigateError, Require, TextRequirer}; +pub use require::{FsRequirer, NavigateError, Require}; // Since Luau has some missing standard functions, we re-implement them here @@ -86,7 +86,7 @@ impl Lua { } // Enable default `require` implementation - let require = self.create_require_function(require::TextRequirer::new())?; + let require = self.create_require_function(FsRequirer::new())?; self.globals().raw_set("require", require)?; Ok(()) diff --git a/src/luau/require.rs b/src/luau/require.rs index 86b23a12..3aee6f27 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -12,8 +12,7 @@ use crate::state::{Lua, callback_error_ext}; use crate::table::Table; use crate::types::MaybeSend; -// TODO: Rename to FsRequirer -pub use fs::TextRequirer; +pub use fs::FsRequirer; /// An error that can occur during navigation in the Luau `require-by-string` system. #[derive(Debug, Clone)] diff --git a/src/luau/require/fs.rs b/src/luau/require/fs.rs index 6588b02c..f6373434 100644 --- a/src/luau/require/fs.rs +++ b/src/luau/require/fs.rs @@ -12,7 +12,7 @@ use super::{NavigateError, Require}; /// The standard implementation of Luau `require-by-string` navigation. #[derive(Default, Debug)] -pub struct TextRequirer { +pub struct FsRequirer { /// An absolute path to the current Luau module (not mapped to a physical file) abs_path: PathBuf, /// A relative path to the current Luau module (not mapped to a physical file) @@ -22,7 +22,7 @@ pub struct TextRequirer { resolved_path: Option, } -impl TextRequirer { +impl FsRequirer { /// The prefix used for chunk names in the require system. /// Only chunk names starting with this prefix are allowed to be used in `require`. const CHUNK_PREFIX: &str = "@"; @@ -36,7 +36,7 @@ impl TextRequirer { /// The filename for the Luau configuration file. const LUAU_CONFIG_FILENAME: &str = ".config.luau"; - /// Creates a new `TextRequirer` instance. + /// Creates a new `FsRequirer` instance. pub fn new() -> Self { Self::default() } @@ -114,7 +114,7 @@ impl TextRequirer { } } -impl Require for TextRequirer { +impl Require for FsRequirer { fn is_require_allowed(&self, chunk_name: &str) -> bool { chunk_name.starts_with(Self::CHUNK_PREFIX) } @@ -231,7 +231,7 @@ impl Require for TextRequirer { mod tests { use std::path::Path; - use super::TextRequirer; + use super::FsRequirer; #[test] fn test_path_normalize() { @@ -267,7 +267,7 @@ mod tests { // '..' disappears if path is absolute and component is non-erasable ("/../", "/"), ] { - let path = TextRequirer::normalize_path(input.as_ref()); + let path = FsRequirer::normalize_path(input.as_ref()); assert_eq!( &path, expected.as_ref() as &Path, diff --git a/src/prelude.rs b/src/prelude.rs index a10ab987..f142ad40 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -25,8 +25,8 @@ pub use crate::HookTriggers as LuaHookTriggers; #[cfg(feature = "luau")] #[doc(no_inline)] pub use crate::{ - CompileConstant as LuaCompileConstant, Compiler as LuaCompiler, NavigateError as LuaNavigateError, - Require as LuaRequire, TextRequirer as LuaTextRequirer, Vector as LuaVector, + CompileConstant as LuaCompileConstant, Compiler as LuaCompiler, FsRequirer as LuaFsRequirer, + NavigateError as LuaNavigateError, Require as LuaRequire, Vector as LuaVector, }; #[cfg(feature = "async")] diff --git a/tests/luau/require.rs b/tests/luau/require.rs index ba79fb6a..36649c9b 100644 --- a/tests/luau/require.rs +++ b/tests/luau/require.rs @@ -1,7 +1,7 @@ use std::io::Result as IoResult; use std::result::Result as StdResult; -use mlua::{Error, FromLua, IntoLua, Lua, MultiValue, NavigateError, Require, Result, TextRequirer, Value}; +use mlua::{Error, FromLua, FsRequirer, IntoLua, Lua, MultiValue, NavigateError, Require, Result, Value}; fn run_require(lua: &Lua, path: impl IntoLua) -> Result { lua.load(r#"return require(...)"#).call(path) @@ -65,7 +65,7 @@ fn test_require_errors() { assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias")); // Test throwing mlua::Error - struct MyRequire(TextRequirer); + struct MyRequire(FsRequirer); impl Require for MyRequire { fn is_require_allowed(&self, chunk_name: &str) -> bool { @@ -109,9 +109,7 @@ fn test_require_errors() { } } - let require = lua - .create_require_function(MyRequire(TextRequirer::new())) - .unwrap(); + let require = lua.create_require_function(MyRequire(FsRequirer::new())).unwrap(); lua.globals().set("require", require).unwrap(); let res = lua.load(r#"return require('./a/relative/path')"#).exec(); assert!((res.unwrap_err().to_string()).contains("test error")); From a2d8b219643929d3d32271617308008767f1d2d3 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 1 Mar 2026 16:37:02 +0000 Subject: [PATCH 633/635] Make `luau` module public --- src/function.rs | 2 +- src/lib.rs | 6 +++--- src/luau/mod.rs | 16 ++++++++++++++++ src/prelude.rs | 7 +++++-- src/state.rs | 2 +- tests/luau/require.rs | 3 ++- 6 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/function.rs b/src/function.rs index 9d1d7c9d..736b7ce4 100644 --- a/src/function.rs +++ b/src/function.rs @@ -246,7 +246,7 @@ impl Function { /// # } /// ``` /// - /// [`AsyncThread`]: crate::AsyncThread + /// [`AsyncThread`]: crate::thread::AsyncThread #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] pub fn call_async(&self, args: impl IntoLuaMulti) -> AsyncCallFuture diff --git a/src/lib.rs b/src/lib.rs index 14e247d3..32aede6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,8 +76,6 @@ mod buffer; mod chunk; mod conversion; mod error; -#[cfg(any(feature = "luau", doc))] -mod luau; mod memory; mod multi; mod scope; @@ -91,6 +89,9 @@ mod vector; pub mod debug; pub mod function; +#[cfg(any(feature = "luau", doc))] +#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] +pub mod luau; pub mod prelude; pub mod string; pub mod table; @@ -147,7 +148,6 @@ pub use crate::debug::HookTriggers; pub use crate::{ buffer::Buffer, chunk::{CompileConstant, Compiler}, - luau::{FsRequirer, HeapDump, NavigateError, Require}, vector::Vector, }; diff --git a/src/luau/mod.rs b/src/luau/mod.rs index 701d015c..bcda08c3 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -1,3 +1,19 @@ +//! Luau-specific extensions and types. +//! +//! This module provides Luau-specific functionality including custom `require` implementations, +//! heap memory analysis, and Luau VM integration utilities. +//! +//! # Overview +//! +//! - [`Require`] — trait for implementing custom module loaders used with +//! [`Lua::create_require_function`] +//! - [`FsRequirer`] — default filesystem-based [`Require`] implementation +//! - [`NavigateError`] — error type returned when navigating the module path +//! - [`HeapDump`] — snapshot of Luau heap memory usage, obtained via [`Lua::heap_dump`] +//! +//! [`Lua::create_require_function`]: crate::Lua::create_require_function +//! [`Lua::heap_dump`]: crate::Lua::heap_dump + use std::ffi::{CStr, CString}; use std::os::raw::c_int; use std::ptr; diff --git a/src/prelude.rs b/src/prelude.rs index f142ad40..1c65951a 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -25,8 +25,11 @@ pub use crate::HookTriggers as LuaHookTriggers; #[cfg(feature = "luau")] #[doc(no_inline)] pub use crate::{ - CompileConstant as LuaCompileConstant, Compiler as LuaCompiler, FsRequirer as LuaFsRequirer, - NavigateError as LuaNavigateError, Require as LuaRequire, Vector as LuaVector, + CompileConstant as LuaCompileConstant, Compiler as LuaCompiler, Vector as LuaVector, + luau::{ + FsRequirer as LuaFsRequirer, HeapDump as LuaHeapDump, NavigateError as LuaNavigateError, + Require as LuaRequire, + }, }; #[cfg(feature = "async")] diff --git a/src/state.rs b/src/state.rs index 6da2eefb..9566f48d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1456,7 +1456,7 @@ impl Lua { /// } /// ``` /// - /// [`AsyncThread`]: crate::AsyncThread + /// [`AsyncThread`]: crate::thread::AsyncThread #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] pub fn create_async_function(&self, func: F) -> Result diff --git a/tests/luau/require.rs b/tests/luau/require.rs index 36649c9b..eace354c 100644 --- a/tests/luau/require.rs +++ b/tests/luau/require.rs @@ -1,7 +1,8 @@ use std::io::Result as IoResult; use std::result::Result as StdResult; -use mlua::{Error, FromLua, FsRequirer, IntoLua, Lua, MultiValue, NavigateError, Require, Result, Value}; +use mlua::luau::{FsRequirer, NavigateError, Require}; +use mlua::{Error, FromLua, IntoLua, Lua, MultiValue, Result, Value}; fn run_require(lua: &Lua, path: impl IntoLua) -> Result { lua.load(r#"return require(...)"#).call(path) From d5d66abe424e5d27820f82115163f62133f2610a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 7 Mar 2026 23:33:52 +0000 Subject: [PATCH 634/635] Refactor GC control API - Replace `gc_inc/gc_gen` with `gc_set_mode` - Add `GcIncParams` and `GcGenParams` for GC tuning - Remove `gc_step_kbytes` (it's very rare needed and Lua 5.5 has changed the input param from kbytes to bytes) --- src/lib.rs | 5 +- src/prelude.rs | 22 +-- src/state.rs | 357 +++++++++++++++++++++++++++++------------------- tests/memory.rs | 26 +++- 4 files changed, 258 insertions(+), 152 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 32aede6b..180804b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,7 +107,10 @@ pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Resul pub use crate::function::Function; pub use crate::multi::{MultiValue, Variadic}; pub use crate::scope::Scope; -pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua}; +#[cfg(any(feature = "lua54", feature = "lua55"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua55"))))] +pub use crate::state::GcGenParams; +pub use crate::state::{GcIncParams, GcMode, Lua, LuaOptions, WeakLua}; pub use crate::stdlib::StdLib; #[doc(inline)] pub use crate::string::{BorrowedBytes, BorrowedStr, LuaString}; diff --git a/src/prelude.rs b/src/prelude.rs index 1c65951a..ac3e6cdc 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -5,15 +5,15 @@ pub use crate::{ AnyUserData as LuaAnyUserData, BorrowedBytes as LuaBorrowedBytes, BorrowedStr as LuaBorrowedStr, Chunk as LuaChunk, ChunkMode as LuaChunkMode, Either as LuaEither, Error as LuaError, ErrorContext as LuaErrorContext, ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, - FromLua, FromLuaMulti, Function as LuaFunction, GCMode as LuaGCMode, Integer as LuaInteger, IntoLua, - IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions, LuaString, - MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, - ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, - Table as LuaTable, Thread as LuaThread, UserData as LuaUserData, UserDataFields as LuaUserDataFields, - UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, - UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut, - UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, Variadic as LuaVariadic, - VmState as LuaVmState, WeakLua, function::FunctionInfo as LuaFunctionInfo, + FromLua, FromLuaMulti, Function as LuaFunction, GcIncParams as LuaGcIncParams, GcMode as LuaGcMode, + Integer as LuaInteger, IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, + LuaNativeFnMut, LuaOptions, LuaString, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, + Nil as LuaNil, Number as LuaNumber, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, + Result as LuaResult, StdLib as LuaStdLib, Table as LuaTable, Thread as LuaThread, + UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, + UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, + UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, + Variadic as LuaVariadic, VmState as LuaVmState, WeakLua, function::FunctionInfo as LuaFunctionInfo, table::TablePairs as LuaTablePairs, table::TableSequence as LuaTableSequence, thread::ThreadStatus as LuaThreadStatus, }; @@ -22,6 +22,10 @@ pub use crate::{ #[doc(no_inline)] pub use crate::HookTriggers as LuaHookTriggers; +#[cfg(any(feature = "lua54", feature = "lua55"))] +#[doc(no_inline)] +pub use crate::GcGenParams as LuaGcGenParams; + #[cfg(feature = "luau")] #[doc(no_inline)] pub use crate::{ diff --git a/src/state.rs b/src/state.rs index 9566f48d..0bc4dc6a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -62,20 +62,126 @@ pub struct WeakLua(XWeak>); pub(crate) struct LuaGuard(ArcReentrantMutexGuard); -/// Mode of the Lua garbage collector (GC). +/// Tuning parameters for the incremental GC collector. /// -/// In Lua 5.4 GC can work in two modes: incremental and generational. -/// Previous Lua versions support only incremental GC. +/// More information can be found in the Lua [documentation]. +/// +/// [documentation]: https://www.lua.org/manual/5.5/manual.html#2.5.1 +#[non_exhaustive] +#[derive(Clone, Copy, Debug, Default)] +pub struct GcIncParams { + /// Pause between successive GC cycles, expressed as a percentage of live memory. + #[cfg(not(feature = "luau"))] + #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] + pub pause: Option, + + /// Target heap size as a percentage of live data, controlling how aggressively + /// the GC reclaims memory (`LUA_GCSETGOAL`). + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub goal: Option, + + /// GC work performed per unit of memory allocated. + pub step_multiplier: Option, + + /// Granularity of each GC step in kilobytes. + #[cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))))] + pub step_size: Option, +} + +impl GcIncParams { + /// Sets the `pause` parameter. + #[cfg(not(feature = "luau"))] + #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] + pub fn pause(mut self, v: c_int) -> Self { + self.pause = Some(v); + self + } + + /// Sets the `goal` parameter. + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn goal(mut self, v: c_int) -> Self { + self.goal = Some(v); + self + } + + /// Sets the `step_multiplier` parameter. + pub fn step_multiplier(mut self, v: c_int) -> Self { + self.step_multiplier = Some(v); + self + } + + /// Sets the `step_size` parameter. + #[cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))))] + pub fn step_size(mut self, v: c_int) -> Self { + self.step_size = Some(v); + self + } +} + +/// Tuning parameters for the generational GC collector (Lua 5.4+). /// /// More information can be found in the Lua [documentation]. /// -/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum GCMode { - Incremental, +/// [documentation]: https://www.lua.org/manual/5.5/manual.html#2.5.2 +#[cfg(any(feature = "lua55", feature = "lua54"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, Default)] +pub struct GcGenParams { + /// Frequency of minor (young-generation) collection steps. + pub minor_multiplier: Option, + + /// Threshold controlling how large the young generation can grow before triggering + /// a shift from minor to major collection. + pub minor_to_major: Option, + + /// Threshold controlling how much the major collection must shrink the heap before + /// switching back to minor (young-generation) collection. + #[cfg(feature = "lua55")] + #[cfg_attr(docsrs, doc(cfg(feature = "lua55")))] + pub major_to_minor: Option, +} + +#[cfg(any(feature = "lua55", feature = "lua54"))] +impl GcGenParams { + /// Sets the `minor_multiplier` parameter. + pub fn minor_multiplier(mut self, v: c_int) -> Self { + self.minor_multiplier = Some(v); + self + } + + /// Sets the `minor_to_major` threshold. + pub fn minor_to_major(mut self, v: c_int) -> Self { + self.minor_to_major = Some(v); + self + } + + /// Sets the `major_to_minor` parameter. + #[cfg(feature = "lua55")] + #[cfg_attr(docsrs, doc(cfg(feature = "lua55")))] + pub fn major_to_minor(mut self, v: c_int) -> Self { + self.major_to_minor = Some(v); + self + } +} + +/// Lua garbage collector (GC) operating mode. +/// +/// Use [`Lua::gc_set_mode`] to switch the collector mode and/or tune its parameters. +#[non_exhaustive] +#[derive(Clone, Debug)] +pub enum GcMode { + /// Incremental mark-and-sweep + Incremental(GcIncParams), + + /// Generational #[cfg(any(feature = "lua55", feature = "lua54"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))] - Generational, + Generational(GcGenParams), } /// Controls Lua interpreter behavior such as Rust panics handling. @@ -998,19 +1104,19 @@ impl Lua { unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCISRUNNING, 0) != 0 } } - /// Stop the Lua GC from running + /// Stops the Lua GC from running. pub fn gc_stop(&self) { let lua = self.lock(); unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCSTOP, 0) }; } - /// Restarts the Lua GC if it is not running + /// Restarts the Lua GC if it is not running. pub fn gc_restart(&self) { let lua = self.lock(); unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCRESTART, 0) }; } - /// Perform a full garbage-collection cycle. + /// Performs a full garbage-collection cycle. /// /// It may be necessary to call this function twice to collect all currently unreachable /// objects. Once to finish the current gc cycle, and once to start and finish the next cycle. @@ -1023,153 +1129,128 @@ impl Lua { } } - /// Steps the garbage collector one indivisible step. + /// Performs a basic step of garbage collection. /// - /// Returns `true` if this has finished a collection cycle. - pub fn gc_step(&self) -> Result { - self.gc_step_kbytes(0) - } - - /// Steps the garbage collector as though memory had been allocated. + /// In incremental mode, a basic step corresponds to the current step size. In generational + /// mode, a basic step performs a full minor collection or an incremental step, if the collector + /// has scheduled one. /// - /// if `kbytes` is 0, then this is the same as calling `gc_step`. Returns true if this step has - /// finished a collection cycle. - pub fn gc_step_kbytes(&self, kbytes: c_int) -> Result { + /// In incremental mode, returns `true` if this step has finished a collection cycle. + /// In generational mode, returns `true` if the step finished a major collection. + pub fn gc_step(&self) -> Result { let lua = self.lock(); let state = lua.main_state(); unsafe { check_stack(state, 3)?; protect_lua!(state, 0, 0, |state| { - ffi::lua_gc(state, ffi::LUA_GCSTEP, kbytes) != 0 + ffi::lua_gc(state, ffi::LUA_GCSTEP, 0) != 0 }) } } - /// Sets the `pause` value of the collector. - /// - /// Returns the previous value of `pause`. More information can be found in the Lua - /// [documentation]. - /// - /// For Luau this parameter sets GC goal - /// - /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 - pub fn gc_set_pause(&self, pause: c_int) -> c_int { - let lua = self.lock(); - let state = lua.main_state(); - unsafe { - #[cfg(feature = "lua55")] - return ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, pause); - - #[cfg(not(any(feature = "lua55", feature = "luau")))] - return ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause); - - #[cfg(feature = "luau")] - return ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause); - } - } - - /// Sets the `step multiplier` value of the collector. + /// Switches the GC to the given mode with the provided parameters. /// - /// Returns the previous value of the `step multiplier`. More information can be found in the - /// Lua [documentation]. + /// Returns the previous [`GcMode`]. The returned value's parameter fields are always + /// `None` because Lua's C API does not provide a way to read back current parameter values + /// without changing them. /// - /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 - pub fn gc_set_step_multiplier(&self, step_multiplier: c_int) -> c_int { - let lua = self.lock(); - unsafe { - #[cfg(feature = "lua55")] - return ffi::lua_gc( - lua.main_state(), - ffi::LUA_GCPARAM, - ffi::LUA_GCPSTEPMUL, - step_multiplier, - ); - - #[cfg(not(feature = "lua55"))] - return ffi::lua_gc(lua.main_state(), ffi::LUA_GCSETSTEPMUL, step_multiplier); - } - } - - /// Changes the collector to incremental mode with the given parameters. + /// # Examples /// - /// Returns the previous mode (always `GCMode::Incremental` in Lua < 5.4). - /// More information can be found in the Lua [documentation]. + /// Switch to generational mode (Lua 5.4+): + /// ```ignore + /// let prev = lua.gc_set_mode(GcMode::Generational(GcGenParams::default())); + /// ``` /// - /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5.1 - pub fn gc_inc(&self, pause: c_int, step_multiplier: c_int, step_size: c_int) -> GCMode { + /// Switch to incremental mode with custom parameters: + /// ```ignore + /// lua.gc_set_mode(GcMode::Incremental( + /// GcIncParams::default().pause(200).step_multiplier(100) + /// )); + /// ``` + pub fn gc_set_mode(&self, mode: GcMode) -> GcMode { let lua = self.lock(); let state = lua.main_state(); - #[cfg(any( - feature = "lua53", - feature = "lua52", - feature = "lua51", - feature = "luajit", - feature = "luau" - ))] - unsafe { - if pause > 0 { - #[cfg(not(feature = "luau"))] - ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause); - #[cfg(feature = "luau")] - ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause); - } - - if step_multiplier > 0 { - ffi::lua_gc(state, ffi::LUA_GCSETSTEPMUL, step_multiplier); - } - + match mode { + #[cfg(feature = "lua55")] + GcMode::Incremental(params) => unsafe { + if let Some(v) = params.pause { + ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, v); + } + if let Some(v) = params.step_multiplier { + ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPMUL, v); + } + if let Some(v) = params.step_size { + ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPSIZE, v); + } + match ffi::lua_gc(state, ffi::LUA_GCINC) { + ffi::LUA_GCINC => GcMode::Incremental(GcIncParams::default()), + ffi::LUA_GCGEN => GcMode::Generational(GcGenParams::default()), + _ => unreachable!(), + } + }, + #[cfg(feature = "lua54")] + GcMode::Incremental(params) => unsafe { + let pause = params.pause.unwrap_or(0); + let step_mul = params.step_multiplier.unwrap_or(0); + let step_size = params.step_size.unwrap_or(0); + match ffi::lua_gc(state, ffi::LUA_GCINC, pause, step_mul, step_size) { + ffi::LUA_GCINC => GcMode::Incremental(GcIncParams::default()), + ffi::LUA_GCGEN => GcMode::Generational(GcGenParams::default()), + _ => unreachable!(), + } + }, + #[cfg(any(feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit"))] + GcMode::Incremental(params) => unsafe { + if let Some(v) = params.pause { + ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, v); + } + if let Some(v) = params.step_multiplier { + ffi::lua_gc(state, ffi::LUA_GCSETSTEPMUL, v); + } + GcMode::Incremental(GcIncParams::default()) + }, #[cfg(feature = "luau")] - if step_size > 0 { - ffi::lua_gc(state, ffi::LUA_GCSETSTEPSIZE, step_size); - } - #[cfg(not(feature = "luau"))] - let _ = step_size; // Ignored - - GCMode::Incremental - } - - #[cfg(feature = "lua55")] - let prev_mode = unsafe { - ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, pause); - ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPMUL, step_multiplier); - ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPSIZE, step_size); - ffi::lua_gc(state, ffi::LUA_GCINC) - }; - #[cfg(feature = "lua54")] - let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCINC, pause, step_multiplier, step_size) }; - #[cfg(any(feature = "lua55", feature = "lua54"))] - match prev_mode { - ffi::LUA_GCINC => GCMode::Incremental, - ffi::LUA_GCGEN => GCMode::Generational, - _ => unreachable!(), - } - } + GcMode::Incremental(params) => unsafe { + if let Some(v) = params.goal { + ffi::lua_gc(state, ffi::LUA_GCSETGOAL, v); + } + if let Some(v) = params.step_multiplier { + ffi::lua_gc(state, ffi::LUA_GCSETSTEPMUL, v); + } + if let Some(v) = params.step_size { + ffi::lua_gc(state, ffi::LUA_GCSETSTEPSIZE, v); + } + GcMode::Incremental(GcIncParams::default()) + }, - /// Changes the collector to generational mode with the given parameters. - /// - /// Returns the previous mode. More information about the generational GC - /// can be found in the Lua 5.4 [documentation][lua_doc]. - /// - /// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#2.5.2 - #[cfg(any(feature = "lua55", feature = "lua54"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))] - pub fn gc_gen(&self, minor_multiplier: c_int, major_multiplier: c_int) -> GCMode { - let lua = self.lock(); - let state = lua.main_state(); - #[cfg(feature = "lua55")] - let prev_mode = unsafe { - ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMUL, minor_multiplier); - ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMAJOR, major_multiplier); - // TODO: LUA_GCPMAJORMINOR - ffi::lua_gc(state, ffi::LUA_GCGEN) - }; - #[cfg(not(feature = "lua55"))] - let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCGEN, minor_multiplier, major_multiplier) }; - match prev_mode { - ffi::LUA_GCGEN => GCMode::Generational, - ffi::LUA_GCINC => GCMode::Incremental, - _ => unreachable!(), + #[cfg(feature = "lua55")] + GcMode::Generational(params) => unsafe { + if let Some(v) = params.minor_multiplier { + ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMUL, v); + } + if let Some(v) = params.minor_to_major { + ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMAJOR, v); + } + if let Some(v) = params.major_to_minor { + ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMAJORMINOR, v); + } + match ffi::lua_gc(state, ffi::LUA_GCGEN) { + ffi::LUA_GCGEN => GcMode::Generational(GcGenParams::default()), + ffi::LUA_GCINC => GcMode::Incremental(GcIncParams::default()), + _ => unreachable!(), + } + }, + #[cfg(feature = "lua54")] + GcMode::Generational(params) => unsafe { + let minor = params.minor_multiplier.unwrap_or(0); + let minor_to_major = params.minor_to_major.unwrap_or(0); + match ffi::lua_gc(state, ffi::LUA_GCGEN, minor, minor_to_major) { + ffi::LUA_GCGEN => GcMode::Generational(GcGenParams::default()), + ffi::LUA_GCINC => GcMode::Incremental(GcIncParams::default()), + _ => unreachable!(), + } + }, } } diff --git a/tests/memory.rs b/tests/memory.rs index 930aa95d..d3aac5a3 100644 --- a/tests/memory.rs +++ b/tests/memory.rs @@ -1,6 +1,8 @@ use std::sync::Arc; -use mlua::{Error, GCMode, Lua, Result, UserData}; +#[cfg(any(feature = "lua54", feature = "lua55"))] +use mlua::GcGenParams; +use mlua::{Error, GcIncParams, GcMode, Lua, Result, UserData}; #[test] fn test_memory_limit() -> Result<()> { @@ -74,8 +76,14 @@ fn test_gc_control() -> Result<()> { #[cfg(any(feature = "lua55", feature = "lua54"))] { - assert_eq!(lua.gc_gen(0, 0), GCMode::Incremental); - assert_eq!(lua.gc_inc(0, 0, 0), GCMode::Generational); + assert!(matches!( + lua.gc_set_mode(GcMode::Generational(GcGenParams::default())), + GcMode::Incremental(_) + )); + assert!(matches!( + lua.gc_set_mode(GcMode::Incremental(GcIncParams::default())), + GcMode::Generational(_) + )); } #[cfg(any( @@ -93,7 +101,17 @@ fn test_gc_control() -> Result<()> { assert!(lua.gc_is_running()); } - assert_eq!(lua.gc_inc(200, 100, 13), GCMode::Incremental); + assert!(matches!( + lua.gc_set_mode(GcMode::Incremental({ + let p = GcIncParams::default().step_multiplier(100); + #[cfg(not(feature = "luau"))] + let p = p.pause(200); + #[cfg(feature = "luau")] + let p = p.goal(200); + p + })), + GcMode::Incremental(_) + )); struct MyUserdata(#[allow(unused)] Arc<()>); impl UserData for MyUserdata {} From 56c227fd7e099335284fe0ec9d5038e5c485fa0a Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 8 Mar 2026 00:06:11 +0000 Subject: [PATCH 635/635] Make `state` module public --- src/lib.rs | 8 +++----- src/prelude.rs | 24 ++++++++++++------------ src/state.rs | 6 ++++++ tests/memory.rs | 6 ++++-- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 180804b4..78d4227b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,7 +79,6 @@ mod error; mod memory; mod multi; mod scope; -mod state; mod stdlib; mod traits; mod types; @@ -93,6 +92,7 @@ pub mod function; #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub mod luau; pub mod prelude; +pub mod state; pub mod string; pub mod table; pub mod thread; @@ -107,10 +107,8 @@ pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Resul pub use crate::function::Function; pub use crate::multi::{MultiValue, Variadic}; pub use crate::scope::Scope; -#[cfg(any(feature = "lua54", feature = "lua55"))] -#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua55"))))] -pub use crate::state::GcGenParams; -pub use crate::state::{GcIncParams, GcMode, Lua, LuaOptions, WeakLua}; +#[doc(inline)] +pub use crate::state::{Lua, LuaOptions, WeakLua}; pub use crate::stdlib::StdLib; #[doc(inline)] pub use crate::string::{BorrowedBytes, BorrowedStr, LuaString}; diff --git a/src/prelude.rs b/src/prelude.rs index ac3e6cdc..4eae1763 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -5,17 +5,17 @@ pub use crate::{ AnyUserData as LuaAnyUserData, BorrowedBytes as LuaBorrowedBytes, BorrowedStr as LuaBorrowedStr, Chunk as LuaChunk, ChunkMode as LuaChunkMode, Either as LuaEither, Error as LuaError, ErrorContext as LuaErrorContext, ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, - FromLua, FromLuaMulti, Function as LuaFunction, GcIncParams as LuaGcIncParams, GcMode as LuaGcMode, - Integer as LuaInteger, IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, - LuaNativeFnMut, LuaOptions, LuaString, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, - Nil as LuaNil, Number as LuaNumber, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, - Result as LuaResult, StdLib as LuaStdLib, Table as LuaTable, Thread as LuaThread, - UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, - UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, - UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, - Variadic as LuaVariadic, VmState as LuaVmState, WeakLua, function::FunctionInfo as LuaFunctionInfo, - table::TablePairs as LuaTablePairs, table::TableSequence as LuaTableSequence, - thread::ThreadStatus as LuaThreadStatus, + FromLua, FromLuaMulti, Function as LuaFunction, Integer as LuaInteger, IntoLua, IntoLuaMulti, + LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions, LuaString, + MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, + ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, + Table as LuaTable, Thread as LuaThread, UserData as LuaUserData, UserDataFields as LuaUserDataFields, + UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, + UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut, + UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, Variadic as LuaVariadic, + VmState as LuaVmState, WeakLua, function::FunctionInfo as LuaFunctionInfo, + state::GcIncParams as LuaGcIncParams, state::GcMode as LuaGcMode, table::TablePairs as LuaTablePairs, + table::TableSequence as LuaTableSequence, thread::ThreadStatus as LuaThreadStatus, }; #[cfg(not(feature = "luau"))] @@ -24,7 +24,7 @@ pub use crate::HookTriggers as LuaHookTriggers; #[cfg(any(feature = "lua54", feature = "lua55"))] #[doc(no_inline)] -pub use crate::GcGenParams as LuaGcGenParams; +pub use crate::state::GcGenParams as LuaGcGenParams; #[cfg(feature = "luau")] #[doc(no_inline)] diff --git a/src/state.rs b/src/state.rs index 0bc4dc6a..7513d7b2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,3 +1,8 @@ +//! Lua state management. +//! +//! This module provides the main [`Lua`] state handle together with state-specific +//! configuration and garbage collector controls. + use std::any::TypeId; use std::cell::{BorrowError, BorrowMutError, RefCell}; use std::marker::PhantomData; @@ -44,6 +49,7 @@ use { use serde::Serialize; pub(crate) use extra::ExtraData; +#[doc(hidden)] pub use raw::RawLua; pub(crate) use util::callback_error_ext; diff --git a/tests/memory.rs b/tests/memory.rs index d3aac5a3..4f91221e 100644 --- a/tests/memory.rs +++ b/tests/memory.rs @@ -1,8 +1,10 @@ use std::sync::Arc; +use mlua::state::{GcIncParams, GcMode}; +use mlua::{Error, Lua, Result, UserData}; + #[cfg(any(feature = "lua54", feature = "lua55"))] -use mlua::GcGenParams; -use mlua::{Error, GcIncParams, GcMode, Lua, Result, UserData}; +use mlua::state::GcGenParams; #[test] fn test_memory_limit() -> Result<()> {

Redirecting to mlua documentation...