Skip to main content

lua_rs_runtime/
lib.rs

1//! Embedding helper for lua-rs.
2//!
3//! This crate sits above `lua-vm`, `lua-stdlib`, and `lua-parse` and exposes a
4//! handle-based embedding API: a [`Lua`] state, typed [`Value`] / [`Table`] /
5//! [`Function`] handles that root themselves via RAII, [`UserData`] for binding
6//! Rust types, and a typed [`LuaError`]. It also provides the common setup
7//! sequence (state, parser hook, host hooks, stdlib).
8//!
9//! # Userdata model
10//!
11//! Userdata behavior in lua-rs runs through real Lua metatables, exactly as in
12//! reference Lua 5.4. The runtime builds the metatable for a type once, on the
13//! first [`Lua::create_userdata`] for that `TypeId`, permanently roots it on
14//! the state, and shares it across every later value of the type. This keeps
15//! `getmetatable`, `setmetatable`, `rawget`, `debug.setmetatable`, and every
16//! other reflective Lua operation behaving as in C Lua, which is what lets
17//! lua-rs pass the upstream 5.4 test suite and stand in for C Lua in real
18//! embedders.
19//!
20//! Fields and methods both live on that single metatable. Register fields with
21//! [`UserDataMethods::add_field_method_get`] / `add_field_method_set` and
22//! methods with [`UserDataMethods::add_method`] / `add_method_mut`. The runtime
23//! composes a single `__index` whose lookup order is field, then method, then
24//! a raw `add_meta_method(MetaMethod::Index, ...)` if you registered one as an
25//! escape hatch, with the symmetric composition on `__newindex`.
26//!
27//! # Derive
28//!
29//! Enable the `derive` feature for `#[derive(LuaUserData)]`, `#[lua_methods]`,
30//! and `#[lua_impl(Display, PartialEq, PartialOrd)]`. The derive targets the
31//! field API above; `#[lua_methods]` exposes each `pub fn(&self / &mut self,
32//! ...)` as `obj:method(args)`; `#[lua_impl(...)]` wires `__tostring`, `__eq`,
33//! `__lt`, and `__le` from the type's Rust trait impls.
34//!
35//! ```ignore
36//! use lua_rs_runtime::{lua_methods, Lua, LuaUserData};
37//!
38//! #[derive(LuaUserData, PartialEq, PartialOrd)]
39//! #[lua(methods)]
40//! #[lua_impl(Display, PartialEq, PartialOrd)]
41//! struct Vec2 { pub x: f64, pub y: f64 }
42//!
43//! #[lua_methods]
44//! impl Vec2 {
45//!     pub fn length(&self) -> f64 { (self.x * self.x + self.y * self.y).sqrt() }
46//!     pub fn scale(&mut self, k: f64) { self.x *= k; self.y *= k; }
47//! }
48//! ```
49//!
50//! # Scope: lending non-`'static` borrows to Lua
51//!
52//! [`Lua::create_userdata`] takes its value by ownership, so the type must be
53//! `'static`. When you instead want to lend Lua a value that lives on the Rust
54//! stack for the duration of one call (the classic case is a game engine's
55//! `&mut World`), use [`Lua::scope`]. A scope hands Lua a borrow that is
56//! invalidated the moment the scope closure returns: any Lua reference that
57//! escaped the scope fails with a clean runtime error on next use instead of
58//! touching freed memory.
59//!
60//! ```
61//! use lua_rs_runtime::{Lua, UserData, UserDataMethods};
62//!
63//! struct Counter { value: i64 }
64//! impl UserData for Counter {
65//!     fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
66//!         m.add_method_mut("inc", |_, this, by: i64| { this.value += by; Ok(this.value) });
67//!     }
68//! }
69//!
70//! let lua = Lua::new();
71//! let mut counter = Counter { value: 0 };
72//! lua.scope(|s| {
73//!     let ud = s.create_userdata_ref_mut(&lua, &mut counter)?;
74//!     lua.globals().set("c", &ud)?;
75//!     lua.load("c:inc(5); c:inc(7)").exec()
76//! }).unwrap();
77//! assert_eq!(counter.value, 12);
78//! ```
79//!
80//! [`Scope::create_function`] / [`Scope::create_function_mut`] do the same for
81//! closures that capture non-`'static` borrows. And
82//! [`AnyUserData::delegate`] builds a *sub-userdata* that re-borrows a field of
83//! its parent on every call (`world:entity(id)` returning a live `&mut Entity`),
84//! so an `App -> World -> Component` chain stays a chain of short borrows rather
85//! than one long-held `&mut`. See [`Lua::scope`] for the full contract.
86//!
87//! # Known limitations and planned work
88//!
89//! - `#[lua_methods]` does not yet special-case methods that return
90//!   `Result<T, E>`, associated functions and constructors (`Type::new`), or
91//!   `Option<T>` parameters and returns.
92//! - The derive does not yet handle enums (a `register_enum::<T>()` path) or
93//!   the iteration, `__close`, and arithmetic metamethods. The runtime already
94//!   supports adding these as ordinary `add_meta_method` registrations today.
95
96use std::any::{Any, TypeId};
97use std::cell::{Cell, Ref, RefCell, RefMut};
98use std::collections::HashMap;
99use std::ffi::c_void;
100use std::fmt;
101use std::hash::Hash;
102use std::ops::{Deref, DerefMut};
103use std::panic::{catch_unwind, AssertUnwindSafe};
104use std::ptr::NonNull;
105use std::rc::Rc;
106
107use lua_stdlib::auxlib::load_buffer;
108use lua_stdlib::init::open_libs;
109use lua_types::closure::{LuaCClosure as RawLuaCClosure, LuaClosure as RawLuaClosure, LuaLClosure};
110use lua_types::gc::GcRef;
111use lua_types::string::LuaString as RawLuaString;
112use lua_types::upval::UpVal;
113use lua_types::userdata::LuaUserData as RawLuaUserData;
114use lua_types::value::{LuaTable as RawLuaTable, LuaValue as RawLuaValue};
115use lua_vm::state::{
116    new_state, CpuClockHook, DynLibLoadHook, DynLibSymbolHook, DynLibUnloadHook, EntropyHook,
117    EnvHook, ExternalRootKey, FileLoaderHook, FileOpenHook, FileRemoveHook, FileRenameHook,
118    InputHook, LuaCallable, LuaRustFunction, LuaState, OsExecuteHook, OutputHook, PopenHook,
119    TempNameHook, UnixTimeHook,
120};
121
122pub use lua_types::{LuaError, LuaFileHandle};
123pub use lua_vm::state::{DynLibId, DynamicSymbol, OsExecuteReason, OsExecuteResult};
124
125#[cfg(feature = "derive")]
126pub use lua_rs_derive::{lua_methods, LuaUserData};
127
128pub type Error = LuaError;
129pub type Result<T> = std::result::Result<T, Error>;
130
131/// Host capabilities exposed to Lua stdlib.
132///
133/// Every field is optional. Missing file, process, and dynamic-loading hooks
134/// produce Lua errors or Lua failure tuples. On bare `wasm32-unknown-unknown`,
135/// missing stdio/time/env/temp hooks avoid unsupported Rust `std` stubs and fail
136/// at the Lua boundary. Native builds may still use compatibility fallbacks for
137/// some stdio and OS functions when hooks are absent.
138#[derive(Clone, Copy, Default)]
139pub struct HostHooks {
140    pub file_loader_hook: Option<FileLoaderHook>,
141    pub file_open_hook: Option<FileOpenHook>,
142    pub stdin_hook: Option<InputHook>,
143    pub stdout_hook: Option<OutputHook>,
144    pub stderr_hook: Option<OutputHook>,
145    pub env_hook: Option<EnvHook>,
146    pub unix_time_hook: Option<UnixTimeHook>,
147    pub cpu_clock_hook: Option<CpuClockHook>,
148    pub entropy_hook: Option<EntropyHook>,
149    pub temp_name_hook: Option<TempNameHook>,
150    pub popen_hook: Option<PopenHook>,
151    pub file_remove_hook: Option<FileRemoveHook>,
152    pub file_rename_hook: Option<FileRenameHook>,
153    pub os_execute_hook: Option<OsExecuteHook>,
154    pub dynlib_load_hook: Option<DynLibLoadHook>,
155    pub dynlib_symbol_hook: Option<DynLibSymbolHook>,
156    pub dynlib_unload_hook: Option<DynLibUnloadHook>,
157}
158
159impl HostHooks {
160    pub fn new() -> Self {
161        Self::default()
162    }
163
164    pub fn file_loader(mut self, hook: FileLoaderHook) -> Self {
165        self.file_loader_hook = Some(hook);
166        self
167    }
168
169    pub fn file_open(mut self, hook: FileOpenHook) -> Self {
170        self.file_open_hook = Some(hook);
171        self
172    }
173
174    pub fn stdin(mut self, hook: InputHook) -> Self {
175        self.stdin_hook = Some(hook);
176        self
177    }
178
179    pub fn stdout(mut self, hook: OutputHook) -> Self {
180        self.stdout_hook = Some(hook);
181        self
182    }
183
184    pub fn stderr(mut self, hook: OutputHook) -> Self {
185        self.stderr_hook = Some(hook);
186        self
187    }
188
189    pub fn env(mut self, hook: EnvHook) -> Self {
190        self.env_hook = Some(hook);
191        self
192    }
193
194    pub fn unix_time(mut self, hook: UnixTimeHook) -> Self {
195        self.unix_time_hook = Some(hook);
196        self
197    }
198
199    pub fn cpu_clock(mut self, hook: CpuClockHook) -> Self {
200        self.cpu_clock_hook = Some(hook);
201        self
202    }
203
204    pub fn entropy(mut self, hook: EntropyHook) -> Self {
205        self.entropy_hook = Some(hook);
206        self
207    }
208
209    pub fn temp_name(mut self, hook: TempNameHook) -> Self {
210        self.temp_name_hook = Some(hook);
211        self
212    }
213
214    pub fn popen(mut self, hook: PopenHook) -> Self {
215        self.popen_hook = Some(hook);
216        self
217    }
218
219    pub fn file_remove(mut self, hook: FileRemoveHook) -> Self {
220        self.file_remove_hook = Some(hook);
221        self
222    }
223
224    pub fn file_rename(mut self, hook: FileRenameHook) -> Self {
225        self.file_rename_hook = Some(hook);
226        self
227    }
228
229    pub fn os_execute(mut self, hook: OsExecuteHook) -> Self {
230        self.os_execute_hook = Some(hook);
231        self
232    }
233
234    pub fn dynlib_load(mut self, hook: DynLibLoadHook) -> Self {
235        self.dynlib_load_hook = Some(hook);
236        self
237    }
238
239    pub fn dynlib_symbol(mut self, hook: DynLibSymbolHook) -> Self {
240        self.dynlib_symbol_hook = Some(hook);
241        self
242    }
243
244    pub fn dynlib_unload(mut self, hook: DynLibUnloadHook) -> Self {
245        self.dynlib_unload_hook = Some(hook);
246        self
247    }
248
249    pub fn install(self, state: &mut LuaState) {
250        let global = &mut *state.global_mut();
251        global.file_loader_hook = self.file_loader_hook;
252        global.file_open_hook = self.file_open_hook;
253        global.stdin_hook = self.stdin_hook;
254        global.stdout_hook = self.stdout_hook;
255        global.stderr_hook = self.stderr_hook;
256        global.env_hook = self.env_hook;
257        global.unix_time_hook = self.unix_time_hook;
258        global.cpu_clock_hook = self.cpu_clock_hook;
259        global.entropy_hook = self.entropy_hook;
260        global.temp_name_hook = self.temp_name_hook;
261        global.popen_hook = self.popen_hook;
262        global.file_remove_hook = self.file_remove_hook;
263        global.file_rename_hook = self.file_rename_hook;
264        global.os_execute_hook = self.os_execute_hook;
265        global.dynlib_load_hook = self.dynlib_load_hook;
266        global.dynlib_symbol_hook = self.dynlib_symbol_hook;
267        global.dynlib_unload_hook = self.dynlib_unload_hook;
268    }
269}
270
271/// Primary owned embedding handle.
272///
273/// `Lua` is intentionally cheap to clone and single-threaded. State access is
274/// borrowed at the embedding boundary only; opcode dispatch still runs with
275/// direct `&mut LuaState` access. Captured Rust callbacks will need a call-path
276/// adapter that releases this boundary borrow before invoking user code.
277#[derive(Clone)]
278pub struct Lua {
279    inner: Rc<LuaInner>,
280}
281
282struct LuaInner {
283    state: RefCell<LuaState>,
284    active_state: Cell<*mut LuaState>,
285    pending_external_unroots: RefCell<Vec<ExternalRootKey>>,
286    /// One metatable per `UserData` type, built on first `create_userdata::<T>`
287    /// and reused for every later value of that type. Each entry is permanently
288    /// rooted in the state's external-root set, so it survives even when no
289    /// instance currently exists, and frees with the state.
290    userdata_metatables: RefCell<HashMap<TypeId, GcRef<RawLuaTable>>>,
291    /// Same shape as `userdata_metatables` but for the `Scope::create_userdata`
292    /// path: the method closures here downcast `host_value` to
293    /// `Rc<ScopedCell<T>>` and check the cell's validity flag before
294    /// dereferencing the pointer it holds.
295    userdata_scoped_metatables: RefCell<HashMap<TypeId, GcRef<RawLuaTable>>>,
296}
297
298struct UserDataCell<T> {
299    value: RefCell<T>,
300}
301
302// ---------------------------------------------------------------------------
303// Scope: pass non-`'static` borrows into Lua safely.
304//
305// `Scope::create_userdata::<T>(&mut data)` stores a raw pointer to `data` in a
306// `ScopedCell<T>` and registers the cell with the scope. While the scope is
307// alive the cell's pointer is dereferenced (validity-checked) on every method
308// call from Lua. When the scope drops, every registered cell's pointer is set
309// to `None`, so any leaked userdata calls return a clean Lua error instead of
310// using-after-the-borrow-ended.
311//
312// Safety model:
313// - The raw pointer's borrow originates from `&mut data`, whose lifetime is
314//   tied to the scope's lifetime via `&'scope mut T`. The borrow checker holds
315//   the borrow alive for the full scope body.
316// - Re-entrant access (a Lua callback that fires another callback on the same
317//   userdata) is rejected at runtime via `ScopedCell::borrow`'s shared/exclusive
318//   counter, mirroring `RefCell`.
319// - On scope drop, callbacks have already returned (they run synchronously
320//   inside the scope body), so `invalidate` only nulls the pointer; no
321//   concurrent dereference can be in progress.
322
323/// Holder for a borrowed Rust value passed into Lua via [`Scope::create_userdata`].
324///
325/// Generic over `T: 'static` so it satisfies the existing `UserData: 'static`
326/// requirement and `Any`-based downcast lookup; the actual borrow lifetime is
327/// erased into a raw pointer and re-checked on every access.
328struct ScopedCell<T: 'static> {
329    ptr: Cell<Option<NonNull<T>>>,
330    /// Same encoding as `RefCell`: positive = shared borrows, negative = one
331    /// exclusive borrow, zero = unborrowed.
332    borrow: Cell<isize>,
333}
334
335impl<T: 'static> ScopedCell<T> {
336    fn new(data: &mut T) -> Self {
337        Self {
338            ptr: Cell::new(Some(NonNull::from(data))),
339            borrow: Cell::new(0),
340        }
341    }
342
343    fn try_borrow(&self) -> Result<ScopedRef<'_, T>> {
344        let b = self.borrow.get();
345        if b < 0 {
346            return Err(LuaError::runtime(format_args!(
347                "scoped userdata is already mutably borrowed"
348            )));
349        }
350        let ptr = self.ptr.get().ok_or_else(|| {
351            LuaError::runtime(format_args!(
352                "scoped userdata is no longer valid (its scope has ended)"
353            ))
354        })?;
355        self.borrow.set(b + 1);
356        Ok(ScopedRef { cell: self, ptr })
357    }
358
359    fn try_borrow_mut(&self) -> Result<ScopedRefMut<'_, T>> {
360        let b = self.borrow.get();
361        if b != 0 {
362            return Err(LuaError::runtime(format_args!(
363                "scoped userdata is already borrowed"
364            )));
365        }
366        let ptr = self.ptr.get().ok_or_else(|| {
367            LuaError::runtime(format_args!(
368                "scoped userdata is no longer valid (its scope has ended)"
369            ))
370        })?;
371        self.borrow.set(-1);
372        Ok(ScopedRefMut { cell: self, ptr })
373    }
374}
375
376/// Trait-object handle a `Scope` uses to invalidate any cell type on drop
377/// without knowing its `T`.
378trait ScopeInvalidate {
379    fn invalidate(&self);
380}
381
382impl<T: 'static> ScopeInvalidate for ScopedCell<T> {
383    fn invalidate(&self) {
384        // Safe only because callbacks have all returned by the time `Scope`
385        // drops: they run synchronously inside the closure body. If a callback
386        // is somehow mid-execution, its `ScopedRef`/`ScopedRefMut` guard still
387        // has the raw pointer copied locally and dereferences it; the next
388        // `try_borrow*` after invalidate sees `ptr = None` and errors cleanly.
389        self.ptr.set(None);
390    }
391}
392
393struct ScopedRef<'a, T: 'static> {
394    cell: &'a ScopedCell<T>,
395    ptr: NonNull<T>,
396}
397
398impl<'a, T: 'static> Drop for ScopedRef<'a, T> {
399    fn drop(&mut self) {
400        self.cell.borrow.set(self.cell.borrow.get() - 1);
401    }
402}
403
404impl<'a, T: 'static> Deref for ScopedRef<'a, T> {
405    type Target = T;
406    fn deref(&self) -> &T {
407        // SAFETY: pointer was obtained from a live `&mut T` (or `&mut T`-derived)
408        // value whose lifetime spans the scope. Re-entrant borrow conflicts are
409        // rejected by `borrow` above. The pointer is set to `None` only when
410        // `invalidate` runs, which can only happen after `Scope` drops; by then
411        // no `ScopedRef` can exist because callbacks have returned.
412        unsafe { self.ptr.as_ref() }
413    }
414}
415
416struct ScopedRefMut<'a, T: 'static> {
417    cell: &'a ScopedCell<T>,
418    ptr: NonNull<T>,
419}
420
421impl<'a, T: 'static> Drop for ScopedRefMut<'a, T> {
422    fn drop(&mut self) {
423        self.cell.borrow.set(0);
424    }
425}
426
427impl<'a, T: 'static> Deref for ScopedRefMut<'a, T> {
428    type Target = T;
429    fn deref(&self) -> &T {
430        // SAFETY: same as `ScopedRef::deref`.
431        unsafe { self.ptr.as_ref() }
432    }
433}
434
435impl<'a, T: 'static> DerefMut for ScopedRefMut<'a, T> {
436    fn deref_mut(&mut self) -> &mut T {
437        // SAFETY: same as `ScopedRef::deref`, plus the cell's `borrow == -1`
438        // ensures no other shared or exclusive borrow is currently outstanding.
439        unsafe { self.ptr.as_mut() }
440    }
441}
442
443/// Handle passed to the closure body of [`Lua::scope`].
444///
445/// `Scope::create_userdata` produces an [`AnyUserData`] whose backing storage
446/// is a borrow you provide; when the scope drops every cell it created is
447/// invalidated. Any later Lua call that reaches one of those userdatas fails
448/// with a clean error rather than touching freed memory.
449pub struct Scope<'scope> {
450    invalidators: RefCell<Vec<Rc<dyn ScopeInvalidate>>>,
451    _phantom: std::marker::PhantomData<&'scope mut ()>,
452}
453
454impl<'scope> Scope<'scope> {
455    fn new() -> Self {
456        Self {
457            invalidators: RefCell::new(Vec::new()),
458            _phantom: std::marker::PhantomData,
459        }
460    }
461
462    /// Wrap a `&mut T` borrow as a Lua userdata that lives for the duration of
463    /// this scope. Any call from Lua to the returned userdata after the scope
464    /// ends fails with a clean Lua runtime error instead of touching the
465    /// freed borrow.
466    ///
467    /// Naming mirrors mlua's `Scope::create_userdata_ref_mut`. The bare
468    /// `create_userdata` name on `Scope` is intentionally reserved for the
469    /// future by-value, non-`'static` constructor (mlua's
470    /// `Scope::create_userdata<T: UserData + 'env>(T)`), tracked as a
471    /// follow-up to lua-rs#27.
472    pub fn create_userdata_ref_mut<T>(&self, lua: &Lua, data: &'scope mut T) -> Result<AnyUserData>
473    where
474        T: UserData,
475    {
476        let cell = Rc::new(ScopedCell::<T>::new(data));
477        self.invalidators
478            .borrow_mut()
479            .push(cell.clone() as Rc<dyn ScopeInvalidate>);
480        lua.create_scoped_userdata::<T>(cell)
481    }
482
483    /// Build a Lua [`Function`] from a non-`'static` Rust closure. The closure
484    /// is owned by a [`ScopedFnCell`] that the scope holds; once the scope
485    /// drops, the cell drops the closure and any later Lua call that reaches
486    /// the returned function fails cleanly with "no longer valid" instead of
487    /// touching the released captures.
488    ///
489    /// This is the function counterpart to [`Self::create_userdata`] — pair
490    /// them when you want to hand Lua a `&mut World` plus a few closures that
491    /// also borrow from the same stack frame.
492    pub fn create_function<A, R, F>(&self, lua: &Lua, func: F) -> Result<Function>
493    where
494        A: FromLuaMulti + 'static,
495        R: IntoLuaMulti + 'static,
496        F: Fn(&Lua, A) -> Result<R> + 'scope,
497    {
498        let adapter: Box<dyn Fn(&Lua, Vec<Value>) -> Result<Vec<Value>> + 'scope> =
499            Box::new(move |lua, args| {
500                let args = A::from_lua_multi(args, lua)?;
501                let returns = func(lua, args)?;
502                returns.into_lua_multi(lua)
503            });
504        self.install_function(lua, adapter)
505    }
506
507    /// Like [`Self::create_function`] but accepts an `FnMut`. Mirrors
508    /// [`Lua::create_function_mut`]: re-entrant calls into the same closure
509    /// are rejected with an "already borrowed" runtime error rather than
510    /// producing aliasing `&mut` captures.
511    pub fn create_function_mut<A, R, F>(&self, lua: &Lua, func: F) -> Result<Function>
512    where
513        A: FromLuaMulti + 'static,
514        R: IntoLuaMulti + 'static,
515        F: FnMut(&Lua, A) -> Result<R> + 'scope,
516    {
517        let func = RefCell::new(func);
518        self.create_function(lua, move |lua, args| {
519            let mut func = func.try_borrow_mut().map_err(|_| {
520                LuaError::runtime(format_args!("mutable Rust callback is already borrowed"))
521            })?;
522            func(lua, args)
523        })
524    }
525
526    /// Internal: launder the closure's `'scope` lifetime bound to `'static`
527    /// so the resulting cell can be held by a `'static` Lua callback, park
528    /// the box inside a [`ScopedFnCell`], and register that cell with the
529    /// scope so its closure is dropped on scope end.
530    fn install_function(
531        &self,
532        lua: &Lua,
533        adapter: Box<dyn Fn(&Lua, Vec<Value>) -> Result<Vec<Value>> + 'scope>,
534    ) -> Result<Function> {
535        // SAFETY: extending the trait-object lifetime bound from `'scope` to
536        // `'static` is sound here because the closure is owned by the
537        // [`ScopedFnCell`] we are about to build, that cell is registered in
538        // `self.invalidators`, and `Scope::drop` invokes `invalidate()` on
539        // every registered cell. `invalidate()` calls `take()` on the box,
540        // which drops the closure (and therefore its `'scope` captures)
541        // while `'scope` is still alive (we are mid-drop of `Scope`). After
542        // `invalidate()` the cell holds `None`, so any subsequent call sees
543        // "no longer valid" before it can reach a dangling capture.
544        let adapter_static: Box<dyn Fn(&Lua, Vec<Value>) -> Result<Vec<Value>>> =
545            unsafe { std::mem::transmute(adapter) };
546        let cell = Rc::new(ScopedFnCell {
547            boxed: RefCell::new(Some(adapter_static)),
548        });
549        self.invalidators
550            .borrow_mut()
551            .push(cell.clone() as Rc<dyn ScopeInvalidate>);
552        lua.create_scoped_function(cell)
553    }
554}
555
556impl<'scope> Drop for Scope<'scope> {
557    fn drop(&mut self) {
558        for inv in self.invalidators.borrow().iter() {
559            inv.invalidate();
560        }
561    }
562}
563
564/// Owns a scoped Rust closure on behalf of [`Scope`]. The closure is stored
565/// as `Box<dyn Fn(...)>` (lifetime laundered to `'static`); on scope drop the
566/// option is taken and the closure (with its `'scope` captures) is dropped.
567/// All later calls see `None` and return a clean Lua runtime error.
568struct ScopedFnCell {
569    boxed: RefCell<Option<Box<dyn Fn(&Lua, Vec<Value>) -> Result<Vec<Value>>>>>,
570}
571
572impl ScopedFnCell {
573    /// Dispatch the wrapped closure, or surface a clean error if the scope
574    /// already ended.
575    fn try_call(&self, lua: &Lua, args: Vec<Value>) -> Result<Vec<Value>> {
576        let guard = self.boxed.borrow();
577        let func = guard.as_deref().ok_or_else(|| {
578            LuaError::runtime(format_args!(
579                "scoped function is no longer valid (its scope has ended)"
580            ))
581        })?;
582        func(lua, args)
583    }
584}
585
586impl ScopeInvalidate for ScopedFnCell {
587    fn invalidate(&self) {
588        *self.boxed.borrow_mut() = None;
589    }
590}
591
592// ---------------------------------------------------------------------------
593// Delegated cell: a sub-userdata that re-acquires a fresh `&mut S` from a
594// parent cell on every method call. Lives at the same scope as the parent.
595//
596// The cell stores no live borrow itself. Instead it holds a closure that
597// knows how to enter the parent (`try_borrow_mut`), apply the user's
598// accessor (`|p: &mut P| -> &mut S`), invoke a caller-supplied callback
599// with the derived `&mut S`, then release the parent's borrow. Methods on
600// the sub-userdata's metatable invoke `enter_mut` to do their work; if a
601// nested Lua call tries to re-enter the parent during a delegate call, the
602// inner `try_borrow_mut` surfaces the same "already borrowed" error path
603// `ScopedCell` already uses.
604//
605// Invalidation: the cell holds an `Rc<dyn ScopeInvalidate>` for the parent
606// so the scope drop chain still works. The cell's own `invalidate` also
607// nulls the `enter_mut` closure to short-circuit any caller that managed
608// to retain a `Rc<DelegatedCell<S>>` past scope end (the closure captures
609// the parent cell's `Rc`, which we want to release).
610//
611// Generic over `S` only — the parent type `P` is type-erased inside the
612// closure so that one `Rc<DelegatedCell<S>>` covers any chain of accessors
613// regardless of where it bottomed out (`App -> World`, `World -> Inner`,
614// etc.). Composition (`delegate` on a delegated userdata) builds a fresh
615// closure that wraps the parent's `enter_mut` plus the new accessor.
616struct DelegatedCell<S: 'static> {
617    enter_mut: RefCell<Option<Box<dyn Fn(&mut dyn FnMut(&mut S)) -> Result<()>>>>,
618}
619
620impl<S: 'static> DelegatedCell<S> {
621    fn enter_mut(&self, f: &mut dyn FnMut(&mut S)) -> Result<()> {
622        let guard = self.enter_mut.borrow();
623        let inner = guard.as_ref().ok_or_else(|| {
624            LuaError::runtime(format_args!(
625                "scoped userdata is no longer valid (its scope has ended)"
626            ))
627        })?;
628        inner(f)
629    }
630}
631
632impl<S: 'static> ScopeInvalidate for DelegatedCell<S> {
633    fn invalidate(&self) {
634        *self.enter_mut.borrow_mut() = None;
635    }
636}
637
638// ---------------------------------------------------------------------------
639
640struct RustCallbackCell {
641    function: LuaRustFunction,
642}
643
644struct ActiveStateGuard<'a> {
645    inner: &'a LuaInner,
646    previous: *mut LuaState,
647}
648
649impl Drop for ActiveStateGuard<'_> {
650    fn drop(&mut self) {
651        self.inner.active_state.set(self.previous);
652    }
653}
654
655impl LuaInner {
656    fn enter_active(&self, state: *mut LuaState) -> ActiveStateGuard<'_> {
657        let previous = self.active_state.replace(state);
658        ActiveStateGuard {
659            inner: self,
660            previous,
661        }
662    }
663
664    fn flush_pending_external_unroots(&self, state: &mut LuaState) {
665        let pending = self.pending_external_unroots.replace(Vec::new());
666        if pending.is_empty() {
667            return;
668        }
669
670        let mut still_pending = Vec::new();
671        for key in pending {
672            if state.try_external_unroot_value(key).is_err() {
673                still_pending.push(key);
674            }
675        }
676
677        if !still_pending.is_empty() {
678            self.pending_external_unroots
679                .borrow_mut()
680                .extend(still_pending);
681        }
682    }
683}
684
685impl fmt::Debug for Lua {
686    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
687        f.debug_struct("Lua").finish_non_exhaustive()
688    }
689}
690
691impl Lua {
692    /// Create a Lua runtime with parser and standard libraries installed.
693    pub fn new() -> Self {
694        Self::try_new().expect("Lua runtime should initialize")
695    }
696
697    /// Fallible variant of [`Lua::new`].
698    pub fn try_new() -> Result<Self> {
699        Self::with_hooks(HostHooks::default())
700    }
701
702    /// Create a Lua runtime with the supplied host capabilities.
703    pub fn with_hooks(hooks: HostHooks) -> Result<Self> {
704        let mut state = new_state().ok_or(LuaError::Memory)?;
705        install_parser_hook(&mut state);
706        hooks.install(&mut state);
707        open_libs(&mut state)?;
708        Ok(Self::from_initialized_state(state))
709    }
710
711    fn from_initialized_state(state: LuaState) -> Self {
712        Lua {
713            inner: Rc::new(LuaInner {
714                state: RefCell::new(state),
715                active_state: Cell::new(std::ptr::null_mut()),
716                pending_external_unroots: RefCell::new(Vec::new()),
717                userdata_metatables: RefCell::new(HashMap::new()),
718                userdata_scoped_metatables: RefCell::new(HashMap::new()),
719            }),
720        }
721    }
722
723    fn with_state<R>(&self, f: impl FnOnce(&mut LuaState) -> R) -> R {
724        if let Ok(mut state) = self.inner.state.try_borrow_mut() {
725            let _active = self.inner.enter_active(&mut *state);
726            self.inner.flush_pending_external_unroots(&mut state);
727            let result = f(&mut state);
728            self.inner.flush_pending_external_unroots(&mut state);
729            return result;
730        }
731
732        let state = self
733            .active_state_mut()
734            .expect("re-entrant Lua access without an active state");
735        let result = f(state);
736        self.inner.flush_pending_external_unroots(state);
737        result
738    }
739
740    fn active_state_mut(&self) -> Option<&mut LuaState> {
741        let state = self.inner.active_state.get();
742        if state.is_null() {
743            return None;
744        }
745
746        // SAFETY: `active_state` is set only while this `Lua` owns the outer
747        // `RefCell` borrow and is executing VM code. Re-entrant access can only
748        // happen when that VM frame has synchronously transferred control to a
749        // Rust callback and is suspended. The callback path does not touch the
750        // suspended `&mut LuaState` while user code re-enters through `Lua`.
751        Some(unsafe { &mut *state })
752    }
753
754    fn unroot_external_key(&self, key: ExternalRootKey) {
755        let removed = if let Ok(mut state) = self.inner.state.try_borrow_mut() {
756            let _active = self.inner.enter_active(&mut *state);
757            self.inner.flush_pending_external_unroots(&mut state);
758            let removed = state.try_external_unroot_value(key).is_ok();
759            self.inner.flush_pending_external_unroots(&mut state);
760            removed
761        } else {
762            if let Some(state) = self.active_state_mut() {
763                let removed = state.try_external_unroot_value(key).is_ok();
764                self.inner.flush_pending_external_unroots(state);
765                removed
766            } else {
767                false
768            }
769        };
770
771        if !removed {
772            self.inner.pending_external_unroots.borrow_mut().push(key);
773        }
774    }
775
776    fn root_raw(&self, value: RawLuaValue) -> RootedValue {
777        let key = self.with_state(|state| state.external_root_value(value));
778        RootedValue {
779            lua: self.clone(),
780            key,
781        }
782    }
783
784    fn root_raw_in_state(&self, state: &mut LuaState, value: RawLuaValue) -> RootedValue {
785        let key = state.external_root_value(value);
786        RootedValue {
787            lua: self.clone(),
788            key,
789        }
790    }
791
792    fn userdata_cell<'a, T: 'static>(
793        &self,
794        userdata: &'a AnyUserData,
795    ) -> Result<&'a UserDataCell<T>> {
796        if !Rc::ptr_eq(&self.inner, &userdata.root.lua.inner) {
797            return Err(LuaError::runtime(format_args!(
798                "Lua userdata belongs to a different state"
799            )));
800        }
801        userdata.host_cell()
802    }
803
804    /// Load a Lua source chunk.
805    pub fn load(&self, source: impl AsRef<[u8]>) -> Chunk {
806        Chunk {
807            lua: self.clone(),
808            source: source.as_ref().to_vec(),
809            name: b"chunk".to_vec(),
810        }
811    }
812
813    /// Return the global environment table.
814    pub fn globals(&self) -> Table {
815        let raw = self.with_state(|state| state.global().globals.clone());
816        Table {
817            root: self.root_raw(raw),
818        }
819    }
820
821    /// Create a new empty table.
822    pub fn create_table(&self) -> Result<Table> {
823        let root = self.with_state(|state| {
824            let _heap_guard = heap_guard(state);
825            let table = state.new_table();
826            let raw = RawLuaValue::Table(table);
827            let key = state.external_root_value(raw);
828            state.gc().check_step();
829            RootedValue {
830                lua: self.clone(),
831                key,
832            }
833        });
834        Ok(Table { root })
835    }
836
837    /// Create a new Lua string from bytes.
838    pub fn create_string(&self, bytes: impl AsRef<[u8]>) -> Result<LuaString> {
839        let bytes = bytes.as_ref();
840        let root = self.with_state(|state| {
841            let _heap_guard = heap_guard(state);
842            let string = state.new_string(bytes)?;
843            let raw = RawLuaValue::Str(string);
844            let key = state.external_root_value(raw);
845            state.gc().check_step();
846            Ok::<_, LuaError>(RootedValue {
847                lua: self.clone(),
848                key,
849            })
850        })?;
851        Ok(LuaString { root })
852    }
853
854    pub fn create_function<A, R, F>(&self, func: F) -> Result<Function>
855    where
856        A: FromLuaMulti + 'static,
857        R: IntoLuaMulti + 'static,
858        F: Fn(&Lua, A) -> Result<R> + 'static,
859    {
860        let lua_weak = Rc::downgrade(&self.inner);
861        let callable: LuaRustFunction = Rc::new(move |state| {
862            let lua = match lua_weak.upgrade() {
863                Some(inner) => Lua { inner },
864                None => {
865                    return Err(LuaError::runtime(format_args!(
866                        "Lua callback fired after the state was dropped"
867                    )))
868                }
869            };
870            match catch_unwind(AssertUnwindSafe(|| {
871                let args = callback_args(state, &lua)?;
872                let args = A::from_lua_multi(args, &lua)?;
873                let returns = func(&lua, args)?;
874                let returns = returns.into_lua_multi(&lua)?;
875                push_callback_returns(state, &lua, returns)
876            })) {
877                Ok(result) => result,
878                Err(_) => Err(LuaError::runtime(format_args!("Rust callback panicked"))),
879            }
880        });
881        self.create_registered_function(callable)
882    }
883
884    pub fn create_function_mut<A, R, F>(&self, func: F) -> Result<Function>
885    where
886        A: FromLuaMulti + 'static,
887        R: IntoLuaMulti + 'static,
888        F: FnMut(&Lua, A) -> Result<R> + 'static,
889    {
890        let func = RefCell::new(func);
891        self.create_function(move |lua, args| {
892            let mut func = func.try_borrow_mut().map_err(|_| {
893                LuaError::runtime(format_args!("mutable Rust callback is already borrowed"))
894            })?;
895            func(lua, args)
896        })
897    }
898
899    fn create_registered_function(&self, callable: LuaRustFunction) -> Result<Function> {
900        let root = self.with_state(|state| {
901            let trampoline = rust_callback_trampoline as lua_vm::state::LuaCFunction;
902            let idx = {
903                let mut global = state.global_mut();
904                match global.c_functions.iter().position(|existing| {
905                    existing
906                        .as_bare()
907                        .is_some_and(|existing| std::ptr::fn_addr_eq(existing, trampoline))
908                }) {
909                    Some(idx) => idx,
910                    None => {
911                        let idx = global.c_functions.len();
912                        global.c_functions.push(LuaCallable::bare(trampoline));
913                        idx
914                    }
915                }
916            };
917            let raw = with_heap_guard(state, || {
918                let callback_payload = GcRef::new(RawLuaUserData {
919                    data: Box::new([]),
920                    uv: Vec::new(),
921                    metatable: RefCell::new(None),
922                    host_value: RefCell::new(Some(
923                        Rc::new(RustCallbackCell { function: callable }) as Rc<dyn Any>,
924                    )),
925                });
926                RawLuaValue::Function(RawLuaClosure::C(GcRef::new(RawLuaCClosure {
927                    func: idx,
928                    upvalues: vec![RawLuaValue::UserData(callback_payload)],
929                })))
930            });
931            let key = state.external_root_value(raw);
932            state.gc().check_step();
933            RootedValue {
934                lua: self.clone(),
935                key,
936            }
937        });
938        Ok(Function { root })
939    }
940
941    fn create_userdata_method<T, A, R, F>(&self, method: F) -> Result<Function>
942    where
943        T: UserData,
944        A: FromLuaMulti + 'static,
945        R: IntoLuaMulti + 'static,
946        F: Fn(&Lua, &T, A) -> Result<R> + 'static,
947    {
948        let lua_weak = Rc::downgrade(&self.inner);
949        let callable: LuaRustFunction = Rc::new(move |state| {
950            let lua = match lua_weak.upgrade() {
951                Some(inner) => Lua { inner },
952                None => {
953                    return Err(LuaError::runtime(format_args!(
954                        "Lua callback fired after the state was dropped"
955                    )))
956                }
957            };
958            match catch_unwind(AssertUnwindSafe(|| {
959                let (userdata, args) = callback_userdata_args(state, &lua)?;
960                let args = A::from_lua_multi(args, &lua)?;
961                let cell = lua.userdata_cell::<T>(&userdata)?;
962                let value = cell.value.try_borrow().map_err(|_| {
963                    LuaError::runtime(format_args!("userdata is already mutably borrowed"))
964                })?;
965                let returns = method(&lua, &value, args)?;
966                let returns = returns.into_lua_multi(&lua)?;
967                push_callback_returns(state, &lua, returns)
968            })) {
969                Ok(result) => result,
970                Err(_) => Err(LuaError::runtime(format_args!(
971                    "Rust userdata method panicked"
972                ))),
973            }
974        });
975        self.create_registered_function(callable)
976    }
977
978    fn create_userdata_method_mut<T, A, R, F>(&self, method: F) -> Result<Function>
979    where
980        T: UserData,
981        A: FromLuaMulti + 'static,
982        R: IntoLuaMulti + 'static,
983        F: Fn(&Lua, &mut T, A) -> Result<R> + 'static,
984    {
985        let lua_weak = Rc::downgrade(&self.inner);
986        let callable: LuaRustFunction = Rc::new(move |state| {
987            let lua = match lua_weak.upgrade() {
988                Some(inner) => Lua { inner },
989                None => {
990                    return Err(LuaError::runtime(format_args!(
991                        "Lua callback fired after the state was dropped"
992                    )))
993                }
994            };
995            match catch_unwind(AssertUnwindSafe(|| {
996                let (userdata, args) = callback_userdata_args(state, &lua)?;
997                let args = A::from_lua_multi(args, &lua)?;
998                let cell = lua.userdata_cell::<T>(&userdata)?;
999                let mut value = cell
1000                    .value
1001                    .try_borrow_mut()
1002                    .map_err(|_| LuaError::runtime(format_args!("userdata is already borrowed")))?;
1003                let returns = method(&lua, &mut value, args)?;
1004                let returns = returns.into_lua_multi(&lua)?;
1005                push_callback_returns(state, &lua, returns)
1006            })) {
1007                Ok(result) => result,
1008                Err(_) => Err(LuaError::runtime(format_args!(
1009                    "Rust userdata method panicked"
1010                ))),
1011            }
1012        });
1013        self.create_registered_function(callable)
1014    }
1015
1016    pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData>
1017    where
1018        T: UserData,
1019    {
1020        let type_id = TypeId::of::<T>();
1021        let cached = self
1022            .inner
1023            .userdata_metatables
1024            .borrow()
1025            .get(&type_id)
1026            .cloned();
1027        let metatable = match cached {
1028            Some(metatable) => metatable,
1029            None => {
1030                let mut methods = UserDataMethodRegistry::<T>::new(self);
1031                T::add_methods(&mut methods);
1032                T::add_meta_methods(&mut methods);
1033                let metatable = methods.build_metatable()?;
1034                self.inner
1035                    .userdata_metatables
1036                    .borrow_mut()
1037                    .insert(type_id, metatable.clone());
1038                metatable
1039            }
1040        };
1041        self.attach_userdata(data, metatable)
1042    }
1043
1044    /// Wrap `data` in a fresh Lua userdata that shares `metatable` (built once per
1045    /// type by [`Lua::create_userdata`]). Only the per-value data cell is allocated
1046    /// here; the binding closures live on the shared, cached metatable.
1047    fn attach_userdata<T: UserData>(
1048        &self,
1049        data: T,
1050        metatable: GcRef<RawLuaTable>,
1051    ) -> Result<AnyUserData> {
1052        let cell: Rc<dyn Any> = Rc::new(UserDataCell {
1053            value: RefCell::new(data),
1054        });
1055        let host_value = cell.clone();
1056        let root = self.with_state(|state| {
1057            let userdata = with_heap_guard(state, || {
1058                GcRef::new(RawLuaUserData {
1059                    data: Box::new([]),
1060                    uv: Vec::new(),
1061                    metatable: RefCell::new(None),
1062                    host_value: RefCell::new(None),
1063                })
1064            });
1065            userdata.set_metatable(Some(metatable));
1066            userdata.set_host_value(Some(cell));
1067            let key = state.external_root_value(RawLuaValue::UserData(userdata));
1068            RootedValue {
1069                lua: self.clone(),
1070                key,
1071            }
1072        });
1073        Ok(AnyUserData {
1074            root,
1075            host_value: Some(host_value),
1076        })
1077    }
1078
1079    /// Run `f` with a fresh [`Scope`]; any [`AnyUserData`] created via the
1080    /// scope is invalidated when `f` returns, so leaked references fail
1081    /// cleanly instead of using-after-the-borrow-ended.
1082    ///
1083    /// ```
1084    /// use lua_rs_runtime::{Lua, UserData, UserDataMethods};
1085    ///
1086    /// struct Counter { value: i64 }
1087    ///
1088    /// impl UserData for Counter {
1089    ///     fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
1090    ///         methods.add_method_mut("inc", |_lua, this, delta: i64| {
1091    ///             this.value += delta;
1092    ///             Ok(this.value)
1093    ///         });
1094    ///     }
1095    /// }
1096    ///
1097    /// let lua = Lua::new();
1098    /// let mut counter = Counter { value: 0 };
1099    ///
1100    /// lua.scope(|scope| {
1101    ///     let ud = scope.create_userdata_ref_mut(&lua, &mut counter)?;
1102    ///     lua.globals().set("c", &ud)?;
1103    ///     lua.load("c:inc(5); c:inc(7)").exec()
1104    /// }).unwrap();
1105    ///
1106    /// assert_eq!(counter.value, 12);
1107    ///
1108    /// // The script can stash the userdata on a global and try to use it
1109    /// // later, but the call cleanly errors instead of touching the
1110    /// // dropped `&mut counter`:
1111    /// lua.scope(|scope| {
1112    ///     let ud = scope.create_userdata_ref_mut(&lua, &mut counter)?;
1113    ///     lua.globals().set("leaked", &ud)
1114    /// }).unwrap();
1115    /// assert!(lua.load("leaked:inc(1)").exec().is_err());
1116    /// ```
1117    pub fn scope<F, R>(&self, f: F) -> Result<R>
1118    where
1119        F: for<'scope> FnOnce(&Scope<'scope>) -> Result<R>,
1120    {
1121        let scope = Scope::new();
1122        let result = f(&scope);
1123        // `scope` drops here, invalidating every cell it created. After this
1124        // point any Lua call that reaches a scoped userdata sees `ptr = None`
1125        // and errors.
1126        drop(scope);
1127        result
1128    }
1129
1130    /// Build (or reuse) the per-`TypeId` *scoped* metatable for `T`. Same
1131    /// metatable serves both `Scope::create_userdata_ref_mut` userdata and
1132    /// `AnyUserData::delegate` sub-userdata of type `T`, because the
1133    /// dispatch closures are cell-variant-polymorphic via
1134    /// `dispatch_scoped_borrow*`.
1135    fn scoped_metatable_for<T>(&self) -> Result<GcRef<RawLuaTable>>
1136    where
1137        T: UserData,
1138    {
1139        let type_id = TypeId::of::<T>();
1140        let cached = self
1141            .inner
1142            .userdata_scoped_metatables
1143            .borrow()
1144            .get(&type_id)
1145            .cloned();
1146        if let Some(mt) = cached {
1147            return Ok(mt);
1148        }
1149        let mut methods = UserDataMethodRegistry::<T>::new_scoped(self);
1150        T::add_methods(&mut methods);
1151        T::add_meta_methods(&mut methods);
1152        let mt = methods.build_metatable()?;
1153        self.inner
1154            .userdata_scoped_metatables
1155            .borrow_mut()
1156            .insert(type_id, mt.clone());
1157        Ok(mt)
1158    }
1159
1160    /// Attach the scoped metatable for `T` to a fresh userdata whose
1161    /// `host_value` is the given `ScopedCell<T>`.
1162    fn create_scoped_userdata<T>(&self, cell: Rc<ScopedCell<T>>) -> Result<AnyUserData>
1163    where
1164        T: UserData,
1165    {
1166        let metatable = self.scoped_metatable_for::<T>()?;
1167        self.attach_scoped_userdata::<T>(cell, metatable)
1168    }
1169
1170    /// Same as `create_scoped_userdata` but the `host_value` is a
1171    /// `DelegatedCell<S>`. The metatable is the same per-`TypeId` cached
1172    /// metatable for `S`; dispatch handles both cell variants.
1173    fn create_delegated_userdata<S>(&self, cell: Rc<DelegatedCell<S>>) -> Result<AnyUserData>
1174    where
1175        S: UserData,
1176    {
1177        let metatable = self.scoped_metatable_for::<S>()?;
1178        let host_value: Rc<dyn Any> = cell;
1179        let root = self.with_state(|state| {
1180            let userdata = with_heap_guard(state, || {
1181                GcRef::new(RawLuaUserData {
1182                    data: Box::new([]),
1183                    uv: Vec::new(),
1184                    metatable: RefCell::new(None),
1185                    host_value: RefCell::new(None),
1186                })
1187            });
1188            userdata.set_metatable(Some(metatable));
1189            userdata.set_host_value(Some(host_value.clone()));
1190            let key = state.external_root_value(RawLuaValue::UserData(userdata));
1191            RootedValue {
1192                lua: self.clone(),
1193                key,
1194            }
1195        });
1196        Ok(AnyUserData {
1197            root,
1198            host_value: Some(host_value),
1199        })
1200    }
1201
1202    /// Same shape as [`Self::attach_userdata`] but the `host_value` is the
1203    /// `ScopedCell` rather than a fresh `UserDataCell`.
1204    fn attach_scoped_userdata<T>(
1205        &self,
1206        cell: Rc<ScopedCell<T>>,
1207        metatable: GcRef<RawLuaTable>,
1208    ) -> Result<AnyUserData>
1209    where
1210        T: UserData,
1211    {
1212        let host_value: Rc<dyn Any> = cell;
1213        let root = self.with_state(|state| {
1214            let userdata = with_heap_guard(state, || {
1215                GcRef::new(RawLuaUserData {
1216                    data: Box::new([]),
1217                    uv: Vec::new(),
1218                    metatable: RefCell::new(None),
1219                    host_value: RefCell::new(None),
1220                })
1221            });
1222            userdata.set_metatable(Some(metatable));
1223            userdata.set_host_value(Some(host_value.clone()));
1224            let key = state.external_root_value(RawLuaValue::UserData(userdata));
1225            RootedValue {
1226                lua: self.clone(),
1227                key,
1228            }
1229        });
1230        Ok(AnyUserData {
1231            root,
1232            host_value: Some(host_value),
1233        })
1234    }
1235
1236    /// Polymorphic borrow over the cell variants reachable by a scoped
1237    /// userdata: `Rc<ScopedCell<T>>` (created via
1238    /// `Scope::create_userdata_ref_mut`) and `Rc<DelegatedCell<T>>`
1239    /// (created via `AnyUserData::delegate`).
1240    ///
1241    /// Each variant has its own borrow protocol, but from the caller's
1242    /// perspective both produce a `&T` (or `&mut T`) for the duration of
1243    /// the closure. The result is threaded back out via an `Option` slot
1244    /// to satisfy `FnMut`'s constraint on the inner callback. The slot is
1245    /// always populated by the enter path before it returns.
1246    fn dispatch_scoped_borrow<T, F, R>(
1247        &self,
1248        userdata: &AnyUserData,
1249        f: F,
1250    ) -> Result<R>
1251    where
1252        T: 'static,
1253        F: FnOnce(&T) -> Result<R>,
1254    {
1255        let host = userdata
1256            .host_value
1257            .as_ref()
1258            .ok_or_else(|| LuaError::runtime(format_args!("missing Rust userdata payload")))?;
1259
1260        if let Ok(scoped) = Rc::clone(host).downcast::<ScopedCell<T>>() {
1261            let borrow = scoped.try_borrow()?;
1262            return f(&*borrow);
1263        }
1264
1265        if let Ok(delegated) = Rc::clone(host).downcast::<DelegatedCell<T>>() {
1266            let mut slot: Option<Result<R>> = None;
1267            let mut f_slot = Some(f);
1268            delegated.enter_mut(&mut |t| {
1269                if let Some(f) = f_slot.take() {
1270                    slot = Some(f(&*t));
1271                }
1272            })?;
1273            return slot.expect("delegated enter_mut must invoke its callback");
1274        }
1275
1276        Err(LuaError::runtime(format_args!(
1277            "scoped userdata type mismatch"
1278        )))
1279    }
1280
1281    fn dispatch_scoped_borrow_mut<T, F, R>(
1282        &self,
1283        userdata: &AnyUserData,
1284        f: F,
1285    ) -> Result<R>
1286    where
1287        T: 'static,
1288        F: FnOnce(&mut T) -> Result<R>,
1289    {
1290        let host = userdata
1291            .host_value
1292            .as_ref()
1293            .ok_or_else(|| LuaError::runtime(format_args!("missing Rust userdata payload")))?;
1294
1295        if let Ok(scoped) = Rc::clone(host).downcast::<ScopedCell<T>>() {
1296            let mut borrow = scoped.try_borrow_mut()?;
1297            return f(&mut *borrow);
1298        }
1299
1300        if let Ok(delegated) = Rc::clone(host).downcast::<DelegatedCell<T>>() {
1301            let mut slot: Option<Result<R>> = None;
1302            let mut f_slot = Some(f);
1303            delegated.enter_mut(&mut |t| {
1304                if let Some(f) = f_slot.take() {
1305                    slot = Some(f(t));
1306                }
1307            })?;
1308            return slot.expect("delegated enter_mut must invoke its callback");
1309        }
1310
1311        Err(LuaError::runtime(format_args!(
1312            "scoped userdata type mismatch"
1313        )))
1314    }
1315
1316    /// Scoped variants of the four `create_userdata_method*` constructors. Each
1317    /// uses `dispatch_scoped_borrow*` so the same registered metatable serves
1318    /// both `Scope::create_userdata_ref_mut` userdata and
1319    /// `AnyUserData::delegate` sub-userdata.
1320    fn create_scoped_userdata_method<T, A, R, F>(&self, method: F) -> Result<Function>
1321    where
1322        T: UserData,
1323        A: FromLuaMulti + 'static,
1324        R: IntoLuaMulti + 'static,
1325        F: Fn(&Lua, &T, A) -> Result<R> + 'static,
1326    {
1327        let lua_weak = Rc::downgrade(&self.inner);
1328        let callable: LuaRustFunction = Rc::new(move |state| {
1329            let lua = match lua_weak.upgrade() {
1330                Some(inner) => Lua { inner },
1331                None => {
1332                    return Err(LuaError::runtime(format_args!(
1333                        "Lua callback fired after the state was dropped"
1334                    )))
1335                }
1336            };
1337            match catch_unwind(AssertUnwindSafe(|| {
1338                let (userdata, args) = callback_userdata_args(state, &lua)?;
1339                let args = A::from_lua_multi(args, &lua)?;
1340                let returns = lua.dispatch_scoped_borrow::<T, _, _>(&userdata, |t| {
1341                    method(&lua, t, args)
1342                })?;
1343                let returns = returns.into_lua_multi(&lua)?;
1344                push_callback_returns(state, &lua, returns)
1345            })) {
1346                Ok(result) => result,
1347                Err(_) => Err(LuaError::runtime(format_args!(
1348                    "Rust userdata method panicked"
1349                ))),
1350            }
1351        });
1352        self.create_registered_function(callable)
1353    }
1354
1355    fn create_scoped_userdata_method_mut<T, A, R, F>(&self, method: F) -> Result<Function>
1356    where
1357        T: UserData,
1358        A: FromLuaMulti + 'static,
1359        R: IntoLuaMulti + 'static,
1360        F: Fn(&Lua, &mut T, A) -> Result<R> + 'static,
1361    {
1362        let lua_weak = Rc::downgrade(&self.inner);
1363        let callable: LuaRustFunction = Rc::new(move |state| {
1364            let lua = match lua_weak.upgrade() {
1365                Some(inner) => Lua { inner },
1366                None => {
1367                    return Err(LuaError::runtime(format_args!(
1368                        "Lua callback fired after the state was dropped"
1369                    )))
1370                }
1371            };
1372            match catch_unwind(AssertUnwindSafe(|| {
1373                let (userdata, args) = callback_userdata_args(state, &lua)?;
1374                let args = A::from_lua_multi(args, &lua)?;
1375                let returns = lua.dispatch_scoped_borrow_mut::<T, _, _>(&userdata, |t| {
1376                    method(&lua, t, args)
1377                })?;
1378                let returns = returns.into_lua_multi(&lua)?;
1379                push_callback_returns(state, &lua, returns)
1380            })) {
1381                Ok(result) => result,
1382                Err(_) => Err(LuaError::runtime(format_args!(
1383                    "Rust userdata method panicked"
1384                ))),
1385            }
1386        });
1387        self.create_registered_function(callable)
1388    }
1389
1390    /// Materialize a [`Function`] whose body dispatches through a
1391    /// [`ScopedFnCell`]. The cell is closed over by the `LuaRustFunction`
1392    /// trampoline; reads of `cell.ptr` are guarded inside `try_call`, so once
1393    /// the originating [`Scope`] drops, every subsequent invocation surfaces
1394    /// "no longer valid" instead of touching the released closure.
1395    fn create_scoped_function(&self, cell: Rc<ScopedFnCell>) -> Result<Function> {
1396        let lua_weak = Rc::downgrade(&self.inner);
1397        let callable: LuaRustFunction = Rc::new(move |state| {
1398            let lua = match lua_weak.upgrade() {
1399                Some(inner) => Lua { inner },
1400                None => {
1401                    return Err(LuaError::runtime(format_args!(
1402                        "Lua callback fired after the state was dropped"
1403                    )))
1404                }
1405            };
1406            match catch_unwind(AssertUnwindSafe(|| {
1407                let args = callback_args(state, &lua)?;
1408                let returns = cell.try_call(&lua, args)?;
1409                push_callback_returns(state, &lua, returns)
1410            })) {
1411                Ok(result) => result,
1412                Err(_) => Err(LuaError::runtime(format_args!(
1413                    "scoped Rust callback panicked"
1414                ))),
1415            }
1416        });
1417        self.create_registered_function(callable)
1418    }
1419
1420    /// Run a full garbage-collection cycle.
1421    pub fn gc_collect(&self) {
1422        self.with_state(|state| state.gc().full_collect());
1423    }
1424}
1425
1426pub struct Chunk {
1427    lua: Lua,
1428    source: Vec<u8>,
1429    name: Vec<u8>,
1430}
1431
1432impl Chunk {
1433    pub fn set_name(mut self, name: impl AsRef<[u8]>) -> Self {
1434        self.name = name.as_ref().to_vec();
1435        self
1436    }
1437
1438    pub fn exec(self) -> Result<()> {
1439        self.lua
1440            .with_state(|state| exec_state(state, &self.source, &self.name))
1441    }
1442
1443    pub fn eval<T: FromLuaMulti>(self) -> Result<T> {
1444        let raws = self.lua.with_state(|state| {
1445            let saved_top = state.top_idx();
1446            let status = load_buffer(state, &self.source, &self.name)?;
1447            if status != 0 {
1448                let err = state.pop();
1449                state.set_top_idx(saved_top);
1450                return Err(LuaError::from_value(err));
1451            }
1452            match lua_vm::api::pcall_k(state, 0, T::NRESULTS, 0, 0, None) {
1453                Ok(_) => {
1454                    let nresults = if T::NRESULTS < 0 {
1455                        state.top_idx().0.saturating_sub(saved_top.0) as i32
1456                    } else {
1457                        T::NRESULTS
1458                    };
1459                    let mut values = Vec::with_capacity(nresults as usize);
1460                    for _ in 0..nresults {
1461                        values.push(state.pop());
1462                    }
1463                    values.reverse();
1464                    state.set_top_idx(saved_top);
1465                    Ok(values)
1466                }
1467                Err(err) => {
1468                    state.set_top_idx(saved_top);
1469                    Err(err)
1470                }
1471            }
1472        })?;
1473        let values = raws
1474            .into_iter()
1475            .map(|raw| Value::from_raw(&self.lua, raw))
1476            .collect::<Result<Vec<_>>>()?;
1477        T::from_lua_multi(values, &self.lua)
1478    }
1479}
1480
1481#[derive(Debug)]
1482struct RootedValue {
1483    lua: Lua,
1484    key: ExternalRootKey,
1485}
1486
1487impl RootedValue {
1488    fn raw(&self) -> Result<RawLuaValue> {
1489        self.lua
1490            .with_state(|state| state.external_rooted_value(self.key))
1491            .ok_or_else(stale_handle_error)
1492    }
1493
1494    fn raw_for_lua(&self, lua: &Lua, state: &LuaState) -> Result<RawLuaValue> {
1495        if !Rc::ptr_eq(&self.lua.inner, &lua.inner) {
1496            return Err(LuaError::runtime(format_args!(
1497                "Lua handle belongs to a different state"
1498            )));
1499        }
1500        state
1501            .external_rooted_value(self.key)
1502            .ok_or_else(stale_handle_error)
1503    }
1504}
1505
1506impl Clone for RootedValue {
1507    fn clone(&self) -> Self {
1508        let raw = self.raw().expect("rooted Lua handle should not be stale");
1509        self.lua.root_raw(raw)
1510    }
1511}
1512
1513impl Drop for RootedValue {
1514    fn drop(&mut self) {
1515        self.lua.unroot_external_key(self.key);
1516    }
1517}
1518
1519/// Dynamically typed owned Lua value.
1520#[derive(Debug, Clone)]
1521pub enum Value {
1522    Nil,
1523    Boolean(bool),
1524    Integer(i64),
1525    Number(f64),
1526    String(LuaString),
1527    Table(Table),
1528    Function(Function),
1529    UserData(AnyUserData),
1530    LightUserData(*mut c_void),
1531    Thread(Thread),
1532}
1533
1534impl Value {
1535    fn from_raw(lua: &Lua, raw: RawLuaValue) -> Result<Self> {
1536        lua.with_state(|state| Self::from_raw_in_state(lua, state, raw))
1537    }
1538
1539    fn from_raw_in_state(lua: &Lua, state: &mut LuaState, raw: RawLuaValue) -> Result<Self> {
1540        Ok(match raw {
1541            RawLuaValue::Nil => Value::Nil,
1542            RawLuaValue::Bool(v) => Value::Boolean(v),
1543            RawLuaValue::Int(v) => Value::Integer(v),
1544            RawLuaValue::Float(v) => Value::Number(v),
1545            RawLuaValue::Str(v) => Value::String(LuaString {
1546                root: lua.root_raw_in_state(state, RawLuaValue::Str(v)),
1547            }),
1548            RawLuaValue::Table(v) => Value::Table(Table {
1549                root: lua.root_raw_in_state(state, RawLuaValue::Table(v)),
1550            }),
1551            RawLuaValue::Function(v) => Value::Function(Function {
1552                root: lua.root_raw_in_state(state, RawLuaValue::Function(v)),
1553            }),
1554            RawLuaValue::UserData(v) => {
1555                let host_value = v.host_value();
1556                Value::UserData(AnyUserData {
1557                    root: lua.root_raw_in_state(state, RawLuaValue::UserData(v)),
1558                    host_value,
1559                })
1560            }
1561            RawLuaValue::LightUserData(v) => Value::LightUserData(v),
1562            RawLuaValue::Thread(v) => Value::Thread(Thread {
1563                root: lua.root_raw_in_state(state, RawLuaValue::Thread(v)),
1564            }),
1565        })
1566    }
1567
1568    fn to_raw_for_lua(&self, lua: &Lua, state: &LuaState) -> Result<RawLuaValue> {
1569        match self {
1570            Value::Nil => Ok(RawLuaValue::Nil),
1571            Value::Boolean(v) => Ok(RawLuaValue::Bool(*v)),
1572            Value::Integer(v) => Ok(RawLuaValue::Int(*v)),
1573            Value::Number(v) => Ok(RawLuaValue::Float(*v)),
1574            Value::String(v) => v.root.raw_for_lua(lua, state),
1575            Value::Table(v) => v.root.raw_for_lua(lua, state),
1576            Value::Function(v) => v.root.raw_for_lua(lua, state),
1577            Value::UserData(v) => v.root.raw_for_lua(lua, state),
1578            Value::LightUserData(v) => Ok(RawLuaValue::LightUserData(*v)),
1579            Value::Thread(v) => v.root.raw_for_lua(lua, state),
1580        }
1581    }
1582}
1583
1584#[derive(Debug, Clone)]
1585pub struct Table {
1586    root: RootedValue,
1587}
1588
1589impl Table {
1590    fn raw_table(&self) -> Result<GcRef<RawLuaTable>> {
1591        match self.root.raw()? {
1592            RawLuaValue::Table(table) => Ok(table),
1593            other => Err(type_error_raw(&other, "table")),
1594        }
1595    }
1596
1597    pub fn get<K, V>(&self, key: K) -> Result<V>
1598    where
1599        K: IntoLua,
1600        V: FromLua,
1601    {
1602        let lua = self.root.lua.clone();
1603        let key = key.into_lua(&lua)?;
1604        let value_raw = lua.with_state(|state| {
1605            let key_raw = key.to_raw_for_lua(&lua, state)?;
1606            let table_raw = self.root.raw_for_lua(&lua, state)?;
1607            state.table_get_with_tm(&table_raw, &key_raw)
1608        })?;
1609        let value = Value::from_raw(&lua, value_raw)?;
1610        V::from_lua(value, &lua)
1611    }
1612
1613    pub fn set<K, V>(&self, key: K, value: V) -> Result<()>
1614    where
1615        K: IntoLua,
1616        V: IntoLua,
1617    {
1618        let lua = self.root.lua.clone();
1619        let key = key.into_lua(&lua)?;
1620        let value = value.into_lua(&lua)?;
1621        lua.with_state(|state| {
1622            let key_raw = key.to_raw_for_lua(&lua, state)?;
1623            let value_raw = value.to_raw_for_lua(&lua, state)?;
1624            let table_raw = self.root.raw_for_lua(&lua, state)?;
1625            state.table_set_with_tm(&table_raw, key_raw, value_raw)
1626        })
1627    }
1628
1629    pub fn len(&self) -> Result<u64> {
1630        Ok(self.raw_table()?.getn())
1631    }
1632}
1633
1634#[derive(Debug, Clone)]
1635pub struct Function {
1636    root: RootedValue,
1637}
1638
1639impl Function {
1640    pub fn call<A, R>(&self, args: A) -> Result<R>
1641    where
1642        A: IntoLuaMulti,
1643        R: FromLuaMulti,
1644    {
1645        let lua = self.root.lua.clone();
1646        let args = args.into_lua_multi(&lua)?;
1647        let result_raws = lua.with_state(|state| {
1648            let arg_raws = args
1649                .iter()
1650                .map(|value| value.to_raw_for_lua(&lua, state))
1651                .collect::<Result<Vec<_>>>()?;
1652            let function_raw = self.root.raw_for_lua(&lua, state)?;
1653            let saved_top = state.top_idx();
1654            state.push(function_raw);
1655            for arg in &arg_raws {
1656                state.push(*arg);
1657            }
1658            match lua_vm::api::pcall_k(state, arg_raws.len() as i32, R::NRESULTS, 0, 0, None) {
1659                Ok(_) => {
1660                    let nresults = if R::NRESULTS < 0 {
1661                        state.top_idx().0.saturating_sub(saved_top.0) as i32
1662                    } else {
1663                        R::NRESULTS
1664                    };
1665                    let mut results = Vec::with_capacity(nresults as usize);
1666                    for _ in 0..nresults {
1667                        results.push(state.pop());
1668                    }
1669                    results.reverse();
1670                    state.set_top_idx(saved_top);
1671                    Ok(results)
1672                }
1673                Err(err) => {
1674                    state.set_top_idx(saved_top);
1675                    Err(err)
1676                }
1677            }
1678        })?;
1679        let values = result_raws
1680            .into_iter()
1681            .map(|raw| Value::from_raw(&lua, raw))
1682            .collect::<Result<Vec<_>>>()?;
1683        R::from_lua_multi(values, &lua)
1684    }
1685}
1686
1687#[derive(Debug, Clone)]
1688pub struct LuaString {
1689    root: RootedValue,
1690}
1691
1692impl LuaString {
1693    fn raw_string(&self) -> Result<GcRef<RawLuaString>> {
1694        match self.root.raw()? {
1695            RawLuaValue::Str(string) => Ok(string),
1696            other => Err(type_error_raw(&other, "string")),
1697        }
1698    }
1699
1700    pub fn as_bytes(&self) -> Result<Vec<u8>> {
1701        Ok(self.raw_string()?.as_bytes().to_vec())
1702    }
1703
1704    pub fn to_str(&self) -> Result<String> {
1705        let bytes = self.as_bytes()?;
1706        String::from_utf8(bytes)
1707            .map_err(|_| LuaError::runtime(format_args!("string is not valid UTF-8")))
1708    }
1709}
1710
1711#[derive(Clone)]
1712pub struct AnyUserData {
1713    root: RootedValue,
1714    host_value: Option<Rc<dyn Any>>,
1715}
1716
1717impl fmt::Debug for AnyUserData {
1718    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1719        f.debug_struct("AnyUserData")
1720            .field("root", &self.root)
1721            .field("has_host_value", &self.host_value.is_some())
1722            .finish()
1723    }
1724}
1725
1726impl AnyUserData {
1727    fn host_cell<T: 'static>(&self) -> Result<&UserDataCell<T>> {
1728        let host = self
1729            .host_value
1730            .as_deref()
1731            .ok_or_else(|| LuaError::runtime(format_args!("missing Rust userdata payload")))?;
1732        host.downcast_ref::<UserDataCell<T>>()
1733            .ok_or_else(|| LuaError::runtime(format_args!("userdata type mismatch")))
1734    }
1735
1736    pub fn borrow<T>(&self) -> Result<Ref<'_, T>>
1737    where
1738        T: 'static,
1739    {
1740        self.host_cell::<T>()?
1741            .value
1742            .try_borrow()
1743            .map_err(|_| LuaError::runtime(format_args!("userdata is already mutably borrowed")))
1744    }
1745
1746    pub fn borrow_mut<T>(&self) -> Result<RefMut<'_, T>>
1747    where
1748        T: 'static,
1749    {
1750        self.host_cell::<T>()?
1751            .value
1752            .try_borrow_mut()
1753            .map_err(|_| LuaError::runtime(format_args!("userdata is already borrowed")))
1754    }
1755
1756    pub fn with_borrow<T, R>(&self, f: impl FnOnce(&T) -> R) -> Result<R>
1757    where
1758        T: 'static,
1759    {
1760        let value = self.borrow::<T>()?;
1761        Ok(f(&value))
1762    }
1763
1764    pub fn with_borrow_mut<T, R>(&self, f: impl FnOnce(&mut T) -> R) -> Result<R>
1765    where
1766        T: 'static,
1767    {
1768        let mut value = self.borrow_mut::<T>()?;
1769        Ok(f(&mut value))
1770    }
1771
1772    /// Downcast `host_value` to a [`ScopedCell<T>`] reference. Mirrors
1773    /// [`Self::host_cell`] but for userdata created via [`Scope::create_userdata`].
1774    fn host_scoped_cell<T: 'static>(&self) -> Result<&ScopedCell<T>> {
1775        let host = self
1776            .host_value
1777            .as_deref()
1778            .ok_or_else(|| LuaError::runtime(format_args!("missing Rust userdata payload")))?;
1779        host.downcast_ref::<ScopedCell<T>>()
1780            .ok_or_else(|| LuaError::runtime(format_args!("scoped userdata type mismatch")))
1781    }
1782
1783    /// Rust-side shared borrow of a [`Scope::create_userdata`] payload. Routes
1784    /// through the scoped cell, so calls after the scope has dropped fail with
1785    /// the same "no longer valid" error a Lua method call would see, instead
1786    /// of returning a stale reference.
1787    pub fn scoped_borrow<T, R>(&self, f: impl FnOnce(&T) -> R) -> Result<R>
1788    where
1789        T: 'static,
1790    {
1791        let cell = self.host_scoped_cell::<T>()?;
1792        let guard = cell.try_borrow()?;
1793        Ok(f(&*guard))
1794    }
1795
1796    /// Rust-side exclusive borrow of a [`Scope::create_userdata`] payload. Same
1797    /// invalidation guarantees as [`Self::scoped_borrow`].
1798    pub fn scoped_borrow_mut<T, R>(&self, f: impl FnOnce(&mut T) -> R) -> Result<R>
1799    where
1800        T: 'static,
1801    {
1802        let cell = self.host_scoped_cell::<T>()?;
1803        let mut guard = cell.try_borrow_mut()?;
1804        Ok(f(&mut *guard))
1805    }
1806
1807    /// Create a sub-userdata in the same scope that re-acquires `&mut S`
1808    /// from this userdata's payload via `accessor` on every method call.
1809    /// The sub-userdata holds no long-lived `&mut S`: every Lua method call
1810    /// borrows the parent (mut), applies `accessor`, runs the method,
1811    /// releases. If a script tries to call a parent method while inside a
1812    /// sub-userdata method body, the inner `try_borrow_mut` surfaces the
1813    /// same "already borrowed" error path scoped cells already use.
1814    ///
1815    /// Receiver must be a [`Scope::create_userdata_ref_mut`] userdata of
1816    /// type `P`, or another delegated userdata of type `P` (chains
1817    /// compose).
1818    ///
1819    /// Scope invalidation propagates: when the originating scope drops,
1820    /// both the parent and every delegated descendant become invalid.
1821    pub fn delegate<P, S, F>(&self, lua: &Lua, accessor: F) -> Result<AnyUserData>
1822    where
1823        P: UserData,
1824        S: UserData,
1825        F: Fn(&mut P) -> &mut S + 'static,
1826    {
1827        let host = self
1828            .host_value
1829            .as_ref()
1830            .ok_or_else(|| LuaError::runtime(format_args!("missing Rust userdata payload")))?;
1831
1832        // Two parent variants are allowed: a direct `ScopedCell<P>` from
1833        // `Scope::create_userdata_ref_mut`, or another `DelegatedCell<P>`
1834        // for multi-level chains.
1835        if let Ok(parent_cell) = Rc::clone(host).downcast::<ScopedCell<P>>() {
1836            let parent_for_closure = Rc::clone(&parent_cell);
1837            let enter: Box<dyn Fn(&mut dyn FnMut(&mut S)) -> Result<()>> =
1838                Box::new(move |f| {
1839                    let mut guard = parent_for_closure.try_borrow_mut()?;
1840                    f(accessor(&mut *guard));
1841                    Ok(())
1842                });
1843            let cell = Rc::new(DelegatedCell::<S> {
1844                enter_mut: RefCell::new(Some(enter)),
1845            });
1846            return lua.create_delegated_userdata::<S>(cell);
1847        }
1848
1849        if let Ok(parent_cell) = Rc::clone(host).downcast::<DelegatedCell<P>>() {
1850            let parent_for_closure = Rc::clone(&parent_cell);
1851            let enter: Box<dyn Fn(&mut dyn FnMut(&mut S)) -> Result<()>> =
1852                Box::new(move |f| {
1853                    parent_for_closure.enter_mut(&mut |p| {
1854                        f(accessor(p));
1855                    })
1856                });
1857            let cell = Rc::new(DelegatedCell::<S> {
1858                enter_mut: RefCell::new(Some(enter)),
1859            });
1860            return lua.create_delegated_userdata::<S>(cell);
1861        }
1862
1863        Err(LuaError::runtime(format_args!(
1864            "delegate: receiver is not a scoped userdata of the expected type"
1865        )))
1866    }
1867}
1868
1869#[derive(Debug, Clone)]
1870pub struct Thread {
1871    root: RootedValue,
1872}
1873
1874/// Variable argument or return list converted element-by-element.
1875///
1876/// This mirrors mlua's `Variadic<T>` enough for dynamic callback bridges:
1877/// `create_function(|_, args: Variadic<Value>| ...)` receives all Lua
1878/// arguments, and returning `Variadic<T>` pushes all contained values.
1879#[derive(Debug, Clone, Default, PartialEq, Eq)]
1880pub struct Variadic<T>(Vec<T>);
1881
1882impl<T> Variadic<T> {
1883    pub const fn new() -> Self {
1884        Self(Vec::new())
1885    }
1886
1887    pub fn with_capacity(capacity: usize) -> Self {
1888        Self(Vec::with_capacity(capacity))
1889    }
1890
1891    pub fn into_vec(self) -> Vec<T> {
1892        self.0
1893    }
1894}
1895
1896impl<T> Deref for Variadic<T> {
1897    type Target = Vec<T>;
1898
1899    fn deref(&self) -> &Self::Target {
1900        &self.0
1901    }
1902}
1903
1904impl<T> DerefMut for Variadic<T> {
1905    fn deref_mut(&mut self) -> &mut Self::Target {
1906        &mut self.0
1907    }
1908}
1909
1910impl<T> From<Vec<T>> for Variadic<T> {
1911    fn from(value: Vec<T>) -> Self {
1912        Self(value)
1913    }
1914}
1915
1916impl<T> From<Variadic<T>> for Vec<T> {
1917    fn from(value: Variadic<T>) -> Self {
1918        value.0
1919    }
1920}
1921
1922impl<T> FromIterator<T> for Variadic<T> {
1923    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
1924        Self(Vec::from_iter(iter))
1925    }
1926}
1927
1928impl<T> IntoIterator for Variadic<T> {
1929    type Item = T;
1930    type IntoIter = std::vec::IntoIter<T>;
1931
1932    fn into_iter(self) -> Self::IntoIter {
1933        self.0.into_iter()
1934    }
1935}
1936
1937pub trait UserData: 'static {
1938    fn add_methods<M: UserDataMethods<Self>>(_methods: &mut M)
1939    where
1940        Self: Sized,
1941    {
1942    }
1943
1944    fn add_meta_methods<M: UserDataMethods<Self>>(_methods: &mut M)
1945    where
1946        Self: Sized,
1947    {
1948    }
1949}
1950
1951pub trait UserDataMethods<T: UserData> {
1952    fn add_method<A, R, F>(&mut self, name: &str, method: F)
1953    where
1954        A: FromLuaMulti + 'static,
1955        R: IntoLuaMulti + 'static,
1956        F: Fn(&Lua, &T, A) -> Result<R> + 'static;
1957
1958    fn add_method_mut<A, R, F>(&mut self, name: &str, method: F)
1959    where
1960        A: FromLuaMulti + 'static,
1961        R: IntoLuaMulti + 'static,
1962        F: Fn(&Lua, &mut T, A) -> Result<R> + 'static;
1963
1964    fn add_meta_method<A, R, F>(&mut self, metamethod: MetaMethod, method: F)
1965    where
1966        A: FromLuaMulti + 'static,
1967        R: IntoLuaMulti + 'static,
1968        F: Fn(&Lua, &T, A) -> Result<R> + 'static;
1969
1970    fn add_meta_method_mut<A, R, F>(&mut self, metamethod: MetaMethod, method: F)
1971    where
1972        A: FromLuaMulti + 'static,
1973        R: IntoLuaMulti + 'static,
1974        F: Fn(&Lua, &mut T, A) -> Result<R> + 'static;
1975
1976    /// Register a getter for `obj.name`. The runtime composes all field getters,
1977    /// the method table, and any raw `__index` into a single `__index` so fields
1978    /// and methods coexist (lookup order: field, then method, then raw `__index`).
1979    fn add_field_method_get<R, F>(&mut self, name: &str, getter: F)
1980    where
1981        R: IntoLuaMulti + 'static,
1982        F: Fn(&Lua, &T) -> Result<R> + 'static;
1983
1984    /// Register a setter for `obj.name = value`. Assigning a field with no setter
1985    /// (or an unknown field) errors unless a raw `__newindex` handles it.
1986    fn add_field_method_set<A, F>(&mut self, name: &str, setter: F)
1987    where
1988        A: FromLuaMulti + 'static,
1989        F: Fn(&Lua, &mut T, A) -> Result<()> + 'static;
1990
1991    /// Register a "function-shape" method whose callback does not extract the
1992    /// typed `&T` automatically. The userdata handle (and any other args) is
1993    /// passed to the closure as a regular [`FromLuaMulti`] tuple, so `A` is
1994    /// usually `(AnyUserData, X, Y, ...)`.
1995    ///
1996    /// Equivalent to mlua's `UserDataMethods::add_function`. The main reason
1997    /// to reach for this over [`Self::add_method`] is when the callback body
1998    /// needs the [`AnyUserData`] handle for the receiver — most commonly to
1999    /// build a sub-userdata via [`AnyUserData::delegate`].
2000    fn add_function<A, R, F>(&mut self, name: &str, function: F)
2001    where
2002        A: FromLuaMulti + 'static,
2003        R: IntoLuaMulti + 'static,
2004        F: Fn(&Lua, A) -> Result<R> + 'static;
2005
2006    /// `FnMut` variant of [`Self::add_function`]. Re-entrant calls into the
2007    /// same closure are rejected with an "already borrowed" runtime error.
2008    fn add_function_mut<A, R, F>(&mut self, name: &str, function: F)
2009    where
2010        A: FromLuaMulti + 'static,
2011        R: IntoLuaMulti + 'static,
2012        F: FnMut(&Lua, A) -> Result<R> + 'static;
2013}
2014
2015#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2016pub enum MetaMethod {
2017    Index,
2018    NewIndex,
2019    Add,
2020    Sub,
2021    Mul,
2022    Div,
2023    Mod,
2024    Pow,
2025    Unm,
2026    Len,
2027    Eq,
2028    Lt,
2029    Le,
2030    Concat,
2031    Call,
2032    ToString,
2033    Pairs,
2034}
2035
2036impl MetaMethod {
2037    fn name(self) -> &'static str {
2038        match self {
2039            MetaMethod::Index => "__index",
2040            MetaMethod::NewIndex => "__newindex",
2041            MetaMethod::Add => "__add",
2042            MetaMethod::Sub => "__sub",
2043            MetaMethod::Mul => "__mul",
2044            MetaMethod::Div => "__div",
2045            MetaMethod::Mod => "__mod",
2046            MetaMethod::Pow => "__pow",
2047            MetaMethod::Unm => "__unm",
2048            MetaMethod::Len => "__len",
2049            MetaMethod::Eq => "__eq",
2050            MetaMethod::Lt => "__lt",
2051            MetaMethod::Le => "__le",
2052            MetaMethod::Concat => "__concat",
2053            MetaMethod::Call => "__call",
2054            MetaMethod::ToString => "__tostring",
2055            MetaMethod::Pairs => "__pairs",
2056        }
2057    }
2058}
2059
2060/// Root `value` on the state for as long as the state itself lives.
2061///
2062/// The returned [`ExternalRootKey`] is intentionally discarded: this helper is
2063/// the explicit name for the "cached per-type metadata" rooting pattern used by
2064/// [`UserDataMethodRegistry::build_metatable`] (the metatable itself, the
2065/// field-getter / method / field-setter tables, and any raw `__index`/`__newindex`
2066/// referenced by the composed dispatch closures). Those values must stay
2067/// reachable for the state's whole lifetime and only ever free together with the
2068/// state. Do not call this for any value you want the GC to be able to collect
2069/// later: it is by design an un-undoable root.
2070fn root_for_state_lifetime(state: &mut LuaState, value: RawLuaValue) {
2071    let _ = state.external_root_value(value);
2072}
2073
2074/// Whether the registry wires methods through `create_userdata_method*` (owned
2075/// `T` in a `RefCell`) or through `create_scoped_userdata_method*`
2076/// (`Rc<ScopedCell<T>>` with a validity-checked pointer). The build_metatable
2077/// step is identical for both.
2078#[derive(Clone, Copy)]
2079enum RegistryMode {
2080    Owned,
2081    Scoped,
2082}
2083
2084struct UserDataMethodRegistry<'lua, T: UserData> {
2085    lua: &'lua Lua,
2086    mode: RegistryMode,
2087    methods: Vec<(String, Function)>,
2088    meta_methods: Vec<(MetaMethod, Function)>,
2089    fields_get: Vec<(String, Function)>,
2090    fields_set: Vec<(String, Function)>,
2091    error: Option<LuaError>,
2092    _marker: std::marker::PhantomData<T>,
2093}
2094
2095impl<'lua, T: UserData> UserDataMethodRegistry<'lua, T> {
2096    fn new(lua: &'lua Lua) -> Self {
2097        Self::with_mode(lua, RegistryMode::Owned)
2098    }
2099
2100    fn new_scoped(lua: &'lua Lua) -> Self {
2101        Self::with_mode(lua, RegistryMode::Scoped)
2102    }
2103
2104    fn with_mode(lua: &'lua Lua, mode: RegistryMode) -> Self {
2105        Self {
2106            lua,
2107            mode,
2108            methods: Vec::new(),
2109            meta_methods: Vec::new(),
2110            fields_get: Vec::new(),
2111            fields_set: Vec::new(),
2112            error: None,
2113            _marker: std::marker::PhantomData,
2114        }
2115    }
2116
2117    fn record(&mut self, result: Result<Function>, insert: impl FnOnce(&mut Self, Function)) {
2118        if self.error.is_some() {
2119            return;
2120        }
2121        match result {
2122            Ok(function) => insert(self, function),
2123            Err(err) => self.error = Some(err),
2124        }
2125    }
2126
2127    /// Build this type's metatable once: a method table plus any meta-methods,
2128    /// returning the raw table handle permanently rooted in the external-root set
2129    /// so it can be cached and shared by every value of the type.
2130    fn build_metatable(mut self) -> Result<GcRef<RawLuaTable>> {
2131        if let Some(err) = self.error.take() {
2132            return Err(err);
2133        }
2134
2135        let lua = self.lua;
2136
2137        let method_table = lua.create_table()?;
2138        for (name, function) in &self.methods {
2139            method_table.set(name.as_str(), function)?;
2140        }
2141
2142        let field_getters = lua.create_table()?;
2143        for (name, function) in &self.fields_get {
2144            field_getters.set(name.as_str(), function)?;
2145        }
2146        let field_setters = lua.create_table()?;
2147        for (name, function) in &self.fields_set {
2148            field_setters.set(name.as_str(), function)?;
2149        }
2150
2151        // Raw __index/__newindex are escape hatches that compose as the final
2152        // fallback; every other meta-method is set directly.
2153        let metatable = lua.create_table()?;
2154        let mut raw_index: Option<Function> = None;
2155        let mut raw_newindex: Option<Function> = None;
2156        for (metamethod, function) in &self.meta_methods {
2157            match metamethod {
2158                MetaMethod::Index => raw_index = Some(function.clone()),
2159                MetaMethod::NewIndex => raw_newindex = Some(function.clone()),
2160                other => {
2161                    metatable.set(other.name(), function)?;
2162                }
2163            }
2164        }
2165
2166        // __index: field getter, then method, then raw __index.
2167        //
2168        // - fields → must compose (field → method → raw via a single closure)
2169        // - raw_index + methods (no fields) → must compose (method → raw)
2170        // - raw_index only (no fields, no methods) → set raw __index directly,
2171        //   skipping the composed closure entirely. This is the common shape
2172        //   for bridges that bind reflected state via a raw `add_meta_method`
2173        //   (e.g. bms-lua-rs's `LuaRef`) and the lookup is on the hot path.
2174        // - method-only → method_table as __index (existing fast path)
2175        //
2176        // The composed closure deliberately captures raw `GcRef`/`RawLuaValue`
2177        // handles, not high-level `Table`/`Function`: each high-level wrapper
2178        // holds a `RootedValue` with a strong `Rc<LuaInner>`, which would cycle
2179        // through the heap-resident closure back to the state and leak it on
2180        // drop. Raw handles are rooted permanently via
2181        // [`root_for_state_lifetime`], and `Table`/`Function` views are rebuilt
2182        // per call from the closure's `&lua`.
2183        let has_fields_get = !self.fields_get.is_empty();
2184        let has_methods = !self.methods.is_empty();
2185        let needs_index_composition = has_fields_get || (raw_index.is_some() && has_methods);
2186
2187        if needs_index_composition {
2188            let (getters_raw, methods_raw, raw_index_raw) = lua.with_state(|state| {
2189                let g = match field_getters.root.raw_for_lua(lua, state)? {
2190                    RawLuaValue::Table(g) => g,
2191                    v => return Err(type_error_raw(&v, "table")),
2192                };
2193                root_for_state_lifetime(state, RawLuaValue::Table(g.clone()));
2194                let m = match method_table.root.raw_for_lua(lua, state)? {
2195                    RawLuaValue::Table(m) => m,
2196                    v => return Err(type_error_raw(&v, "table")),
2197                };
2198                root_for_state_lifetime(state, RawLuaValue::Table(m.clone()));
2199                let r = match &raw_index {
2200                    Some(f) => {
2201                        let rv = f.root.raw_for_lua(lua, state)?;
2202                        root_for_state_lifetime(state, rv.clone());
2203                        Some(rv)
2204                    }
2205                    None => None,
2206                };
2207                Ok::<_, LuaError>((g, m, r))
2208            })?;
2209            let index_fn = lua.create_function(move |lua, (ud, key): (Value, Value)| {
2210                let getters = Table {
2211                    root: lua.root_raw(RawLuaValue::Table(getters_raw.clone())),
2212                };
2213                let methods = Table {
2214                    root: lua.root_raw(RawLuaValue::Table(methods_raw.clone())),
2215                };
2216                if let Value::Function(getter) = getters.get::<_, Value>(key.clone())? {
2217                    return getter.call::<_, Value>(ud);
2218                }
2219                let method = methods.get::<_, Value>(key.clone())?;
2220                if !matches!(method, Value::Nil) {
2221                    return Ok(method);
2222                }
2223                if let Some(raw_idx) = &raw_index_raw {
2224                    let raw_fn = Function {
2225                        root: lua.root_raw(raw_idx.clone()),
2226                    };
2227                    return raw_fn.call::<_, Value>((ud, key));
2228                }
2229                Ok(Value::Nil)
2230            })?;
2231            metatable.set(MetaMethod::Index.name(), &index_fn)?;
2232        } else if let Some(raw) = raw_index.as_ref() {
2233            metatable.set(MetaMethod::Index.name(), raw)?;
2234        } else {
2235            metatable.set(MetaMethod::Index.name(), &method_table)?;
2236        }
2237
2238        // __newindex: field setter, then raw __newindex, else error. Same
2239        // composed-vs-pass-through choice as __index above.
2240        let has_fields_set = !self.fields_set.is_empty();
2241
2242        if has_fields_set {
2243            let (setters_raw, raw_newindex_raw) = lua.with_state(|state| {
2244                let s = match field_setters.root.raw_for_lua(lua, state)? {
2245                    RawLuaValue::Table(s) => s,
2246                    v => return Err(type_error_raw(&v, "table")),
2247                };
2248                root_for_state_lifetime(state, RawLuaValue::Table(s.clone()));
2249                let r = match &raw_newindex {
2250                    Some(f) => {
2251                        let rv = f.root.raw_for_lua(lua, state)?;
2252                        root_for_state_lifetime(state, rv.clone());
2253                        Some(rv)
2254                    }
2255                    None => None,
2256                };
2257                Ok::<_, LuaError>((s, r))
2258            })?;
2259            let newindex_fn =
2260                lua.create_function(move |lua, (ud, key, value): (Value, Value, Value)| {
2261                    let setters = Table {
2262                        root: lua.root_raw(RawLuaValue::Table(setters_raw.clone())),
2263                    };
2264                    if let Value::Function(setter) = setters.get::<_, Value>(key.clone())? {
2265                        return setter.call::<_, Value>((ud, value));
2266                    }
2267                    if let Some(raw) = &raw_newindex_raw {
2268                        let raw_fn = Function {
2269                            root: lua.root_raw(raw.clone()),
2270                        };
2271                        return raw_fn.call::<_, Value>((ud, key, value));
2272                    }
2273                    Err(LuaError::runtime(format_args!(
2274                        "cannot assign to unknown or read-only userdata field"
2275                    )))
2276                })?;
2277            metatable.set(MetaMethod::NewIndex.name(), &newindex_fn)?;
2278        } else if let Some(raw) = raw_newindex.as_ref() {
2279            metatable.set(MetaMethod::NewIndex.name(), raw)?;
2280        }
2281
2282        self.lua.with_state(|state| {
2283            let metatable_raw = metatable.root.raw_for_lua(self.lua, state)?;
2284            let RawLuaValue::Table(metatable) = metatable_raw else {
2285                return Err(type_error_raw(&metatable_raw, "table"));
2286            };
2287            root_for_state_lifetime(state, RawLuaValue::Table(metatable.clone()));
2288            Ok(metatable)
2289        })
2290    }
2291}
2292
2293impl<T: UserData> UserDataMethods<T> for UserDataMethodRegistry<'_, T> {
2294    fn add_method<A, R, F>(&mut self, name: &str, method: F)
2295    where
2296        A: FromLuaMulti + 'static,
2297        R: IntoLuaMulti + 'static,
2298        F: Fn(&Lua, &T, A) -> Result<R> + 'static,
2299    {
2300        let name = name.to_string();
2301        let result = match self.mode {
2302            RegistryMode::Owned => self.lua.create_userdata_method(method),
2303            RegistryMode::Scoped => self.lua.create_scoped_userdata_method(method),
2304        };
2305        self.record(result, move |this, function| {
2306            this.methods.push((name, function));
2307        });
2308    }
2309
2310    fn add_method_mut<A, R, F>(&mut self, name: &str, method: F)
2311    where
2312        A: FromLuaMulti + 'static,
2313        R: IntoLuaMulti + 'static,
2314        F: Fn(&Lua, &mut T, A) -> Result<R> + 'static,
2315    {
2316        let name = name.to_string();
2317        let result = match self.mode {
2318            RegistryMode::Owned => self.lua.create_userdata_method_mut(method),
2319            RegistryMode::Scoped => self.lua.create_scoped_userdata_method_mut(method),
2320        };
2321        self.record(result, move |this, function| {
2322            this.methods.push((name, function));
2323        });
2324    }
2325
2326    fn add_meta_method<A, R, F>(&mut self, metamethod: MetaMethod, method: F)
2327    where
2328        A: FromLuaMulti + 'static,
2329        R: IntoLuaMulti + 'static,
2330        F: Fn(&Lua, &T, A) -> Result<R> + 'static,
2331    {
2332        let result = match self.mode {
2333            RegistryMode::Owned => self.lua.create_userdata_method(method),
2334            RegistryMode::Scoped => self.lua.create_scoped_userdata_method(method),
2335        };
2336        self.record(result, move |this, function| {
2337            this.meta_methods.push((metamethod, function));
2338        });
2339    }
2340
2341    fn add_meta_method_mut<A, R, F>(&mut self, metamethod: MetaMethod, method: F)
2342    where
2343        A: FromLuaMulti + 'static,
2344        R: IntoLuaMulti + 'static,
2345        F: Fn(&Lua, &mut T, A) -> Result<R> + 'static,
2346    {
2347        let result = match self.mode {
2348            RegistryMode::Owned => self.lua.create_userdata_method_mut(method),
2349            RegistryMode::Scoped => self.lua.create_scoped_userdata_method_mut(method),
2350        };
2351        self.record(result, move |this, function| {
2352            this.meta_methods.push((metamethod, function));
2353        });
2354    }
2355
2356    fn add_field_method_get<R, F>(&mut self, name: &str, getter: F)
2357    where
2358        R: IntoLuaMulti + 'static,
2359        F: Fn(&Lua, &T) -> Result<R> + 'static,
2360    {
2361        let name = name.to_string();
2362        let wrapped = move |lua: &Lua, this: &T, ()| getter(lua, this);
2363        let result = match self.mode {
2364            RegistryMode::Owned => self.lua.create_userdata_method(wrapped),
2365            RegistryMode::Scoped => self.lua.create_scoped_userdata_method(wrapped),
2366        };
2367        self.record(result, move |this, function| {
2368            this.fields_get.push((name, function));
2369        });
2370    }
2371
2372    fn add_field_method_set<A, F>(&mut self, name: &str, setter: F)
2373    where
2374        A: FromLuaMulti + 'static,
2375        F: Fn(&Lua, &mut T, A) -> Result<()> + 'static,
2376    {
2377        let name = name.to_string();
2378        let wrapped = move |lua: &Lua, this: &mut T, arg: A| setter(lua, this, arg);
2379        let result = match self.mode {
2380            RegistryMode::Owned => self.lua.create_userdata_method_mut(wrapped),
2381            RegistryMode::Scoped => self.lua.create_scoped_userdata_method_mut(wrapped),
2382        };
2383        self.record(result, move |this, function| {
2384            this.fields_set.push((name, function));
2385        });
2386    }
2387
2388    fn add_function<A, R, F>(&mut self, name: &str, function: F)
2389    where
2390        A: FromLuaMulti + 'static,
2391        R: IntoLuaMulti + 'static,
2392        F: Fn(&Lua, A) -> Result<R> + 'static,
2393    {
2394        let name = name.to_string();
2395        // Function-shape entries don't extract `&T` from the receiver, so
2396        // they reuse the existing top-level `Lua::create_function` directly
2397        // for both Owned and Scoped registry modes.
2398        let result = self.lua.create_function(function);
2399        self.record(result, move |this, function| {
2400            this.methods.push((name, function));
2401        });
2402    }
2403
2404    fn add_function_mut<A, R, F>(&mut self, name: &str, function: F)
2405    where
2406        A: FromLuaMulti + 'static,
2407        R: IntoLuaMulti + 'static,
2408        F: FnMut(&Lua, A) -> Result<R> + 'static,
2409    {
2410        let name = name.to_string();
2411        let result = self.lua.create_function_mut(function);
2412        self.record(result, move |this, function| {
2413            this.methods.push((name, function));
2414        });
2415    }
2416}
2417
2418pub trait IntoLua {
2419    fn into_lua(self, lua: &Lua) -> Result<Value>;
2420}
2421
2422pub trait FromLua: Sized {
2423    fn from_lua(value: Value, lua: &Lua) -> Result<Self>;
2424}
2425
2426pub trait IntoLuaMulti {
2427    fn into_lua_multi(self, lua: &Lua) -> Result<Vec<Value>>;
2428}
2429
2430pub trait FromLuaMulti: Sized {
2431    const NRESULTS: i32;
2432
2433    fn from_lua_multi(values: Vec<Value>, lua: &Lua) -> Result<Self>;
2434}
2435
2436impl IntoLua for Value {
2437    fn into_lua(self, _lua: &Lua) -> Result<Value> {
2438        Ok(self)
2439    }
2440}
2441
2442impl IntoLua for &Value {
2443    fn into_lua(self, _lua: &Lua) -> Result<Value> {
2444        Ok(self.clone())
2445    }
2446}
2447
2448impl FromLua for Value {
2449    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
2450        Ok(value)
2451    }
2452}
2453
2454impl IntoLua for bool {
2455    fn into_lua(self, _lua: &Lua) -> Result<Value> {
2456        Ok(Value::Boolean(self))
2457    }
2458}
2459
2460impl FromLua for bool {
2461    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
2462        match value {
2463            Value::Boolean(v) => Ok(v),
2464            other => Err(type_error_value(&other, "boolean")),
2465        }
2466    }
2467}
2468
2469impl IntoLua for i64 {
2470    fn into_lua(self, _lua: &Lua) -> Result<Value> {
2471        Ok(Value::Integer(self))
2472    }
2473}
2474
2475impl FromLua for i64 {
2476    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
2477        match value {
2478            Value::Integer(v) => Ok(v),
2479            Value::Number(v) if v.fract() == 0.0 && v.is_finite() => Ok(v as i64),
2480            other => Err(type_error_value(&other, "integer")),
2481        }
2482    }
2483}
2484
2485impl IntoLua for i32 {
2486    fn into_lua(self, lua: &Lua) -> Result<Value> {
2487        i64::from(self).into_lua(lua)
2488    }
2489}
2490
2491impl FromLua for i32 {
2492    fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
2493        let v = i64::from_lua(value, lua)?;
2494        i32::try_from(v).map_err(|_| LuaError::runtime(format_args!("integer out of range")))
2495    }
2496}
2497
2498impl IntoLua for usize {
2499    fn into_lua(self, lua: &Lua) -> Result<Value> {
2500        let v = i64::try_from(self)
2501            .map_err(|_| LuaError::runtime(format_args!("integer out of range")))?;
2502        v.into_lua(lua)
2503    }
2504}
2505
2506impl FromLua for usize {
2507    fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
2508        let v = i64::from_lua(value, lua)?;
2509        usize::try_from(v).map_err(|_| LuaError::runtime(format_args!("integer out of range")))
2510    }
2511}
2512
2513impl IntoLua for u64 {
2514    fn into_lua(self, lua: &Lua) -> Result<Value> {
2515        let v = i64::try_from(self)
2516            .map_err(|_| LuaError::runtime(format_args!("integer out of range")))?;
2517        v.into_lua(lua)
2518    }
2519}
2520
2521impl FromLua for u64 {
2522    fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
2523        let v = i64::from_lua(value, lua)?;
2524        u64::try_from(v).map_err(|_| LuaError::runtime(format_args!("integer out of range")))
2525    }
2526}
2527
2528impl IntoLua for u32 {
2529    fn into_lua(self, lua: &Lua) -> Result<Value> {
2530        u64::from(self).into_lua(lua)
2531    }
2532}
2533
2534impl FromLua for u32 {
2535    fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
2536        let v = u64::from_lua(value, lua)?;
2537        u32::try_from(v).map_err(|_| LuaError::runtime(format_args!("integer out of range")))
2538    }
2539}
2540
2541impl IntoLua for f64 {
2542    fn into_lua(self, _lua: &Lua) -> Result<Value> {
2543        Ok(Value::Number(self))
2544    }
2545}
2546
2547impl FromLua for f64 {
2548    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
2549        match value {
2550            Value::Integer(v) => Ok(v as f64),
2551            Value::Number(v) => Ok(v),
2552            other => Err(type_error_value(&other, "number")),
2553        }
2554    }
2555}
2556
2557impl IntoLua for &str {
2558    fn into_lua(self, lua: &Lua) -> Result<Value> {
2559        Ok(Value::String(lua.create_string(self.as_bytes())?))
2560    }
2561}
2562
2563impl IntoLua for String {
2564    fn into_lua(self, lua: &Lua) -> Result<Value> {
2565        Ok(Value::String(lua.create_string(self.into_bytes())?))
2566    }
2567}
2568
2569impl FromLua for String {
2570    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
2571        match value {
2572            Value::String(s) => s.to_str(),
2573            other => Err(type_error_value(&other, "string")),
2574        }
2575    }
2576}
2577
2578impl IntoLua for &[u8] {
2579    fn into_lua(self, lua: &Lua) -> Result<Value> {
2580        Ok(Value::String(lua.create_string(self)?))
2581    }
2582}
2583
2584impl IntoLua for LuaString {
2585    fn into_lua(self, _lua: &Lua) -> Result<Value> {
2586        Ok(Value::String(self))
2587    }
2588}
2589
2590impl IntoLua for &LuaString {
2591    fn into_lua(self, _lua: &Lua) -> Result<Value> {
2592        Ok(Value::String(self.clone()))
2593    }
2594}
2595
2596impl FromLua for LuaString {
2597    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
2598        match value {
2599            Value::String(v) => Ok(v),
2600            other => Err(type_error_value(&other, "string")),
2601        }
2602    }
2603}
2604
2605impl IntoLua for Table {
2606    fn into_lua(self, _lua: &Lua) -> Result<Value> {
2607        Ok(Value::Table(self))
2608    }
2609}
2610
2611impl IntoLua for &Table {
2612    fn into_lua(self, _lua: &Lua) -> Result<Value> {
2613        Ok(Value::Table(self.clone()))
2614    }
2615}
2616
2617impl FromLua for Table {
2618    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
2619        match value {
2620            Value::Table(v) => Ok(v),
2621            other => Err(type_error_value(&other, "table")),
2622        }
2623    }
2624}
2625
2626impl IntoLua for Function {
2627    fn into_lua(self, _lua: &Lua) -> Result<Value> {
2628        Ok(Value::Function(self))
2629    }
2630}
2631
2632impl IntoLua for &Function {
2633    fn into_lua(self, _lua: &Lua) -> Result<Value> {
2634        Ok(Value::Function(self.clone()))
2635    }
2636}
2637
2638impl FromLua for Function {
2639    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
2640        match value {
2641            Value::Function(v) => Ok(v),
2642            other => Err(type_error_value(&other, "function")),
2643        }
2644    }
2645}
2646
2647impl IntoLua for AnyUserData {
2648    fn into_lua(self, _lua: &Lua) -> Result<Value> {
2649        Ok(Value::UserData(self))
2650    }
2651}
2652
2653impl IntoLua for &AnyUserData {
2654    fn into_lua(self, _lua: &Lua) -> Result<Value> {
2655        Ok(Value::UserData(self.clone()))
2656    }
2657}
2658
2659impl FromLua for AnyUserData {
2660    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
2661        match value {
2662            Value::UserData(v) => Ok(v),
2663            other => Err(type_error_value(&other, "userdata")),
2664        }
2665    }
2666}
2667
2668impl<T> IntoLua for T
2669where
2670    T: UserData,
2671{
2672    fn into_lua(self, lua: &Lua) -> Result<Value> {
2673        Ok(Value::UserData(lua.create_userdata(self)?))
2674    }
2675}
2676
2677impl<T> IntoLua for Option<T>
2678where
2679    T: IntoLua,
2680{
2681    fn into_lua(self, lua: &Lua) -> Result<Value> {
2682        match self {
2683            Some(value) => value.into_lua(lua),
2684            None => Ok(Value::Nil),
2685        }
2686    }
2687}
2688
2689impl<T> FromLua for Option<T>
2690where
2691    T: FromLua,
2692{
2693    fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
2694        match value {
2695            Value::Nil => Ok(None),
2696            other => T::from_lua(other, lua).map(Some),
2697        }
2698    }
2699}
2700
2701impl<T> IntoLua for Vec<T>
2702where
2703    T: IntoLua,
2704{
2705    fn into_lua(self, lua: &Lua) -> Result<Value> {
2706        let table = lua.create_table()?;
2707        for (idx, value) in self.into_iter().enumerate() {
2708            table.set((idx + 1) as i64, value)?;
2709        }
2710        Ok(Value::Table(table))
2711    }
2712}
2713
2714impl<T> FromLua for Vec<T>
2715where
2716    T: FromLua,
2717{
2718    fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
2719        let table = Table::from_lua(value, lua)?;
2720        let raw = table.raw_table()?;
2721        let len = raw.getn();
2722        let mut out = Vec::with_capacity(len as usize);
2723        for idx in 1..=len {
2724            let value = Value::from_raw(lua, raw.get_int(idx as i64))?;
2725            out.push(T::from_lua(value, lua)?);
2726        }
2727        Ok(out)
2728    }
2729}
2730
2731impl<K, V> IntoLua for HashMap<K, V>
2732where
2733    K: IntoLua,
2734    V: IntoLua,
2735{
2736    fn into_lua(self, lua: &Lua) -> Result<Value> {
2737        let table = lua.create_table()?;
2738        for (key, value) in self {
2739            table.set(key, value)?;
2740        }
2741        Ok(Value::Table(table))
2742    }
2743}
2744
2745impl<K, V> FromLua for HashMap<K, V>
2746where
2747    K: FromLua + Eq + Hash,
2748    V: FromLua,
2749{
2750    fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
2751        let table = Table::from_lua(value, lua)?;
2752        let raw = table.raw_table()?;
2753        let mut out = HashMap::new();
2754        let mut result = Ok(());
2755        raw.for_each_entry(|key, value| {
2756            if result.is_err() {
2757                return;
2758            }
2759            result = (|| {
2760                let key = Value::from_raw(lua, *key)?;
2761                let value = Value::from_raw(lua, *value)?;
2762                out.insert(K::from_lua(key, lua)?, V::from_lua(value, lua)?);
2763                Ok(())
2764            })();
2765        });
2766        result?;
2767        Ok(out)
2768    }
2769}
2770
2771impl<T> IntoLuaMulti for Variadic<T>
2772where
2773    T: IntoLua,
2774{
2775    fn into_lua_multi(self, lua: &Lua) -> Result<Vec<Value>> {
2776        self.into_iter().map(|value| value.into_lua(lua)).collect()
2777    }
2778}
2779
2780impl<T> FromLuaMulti for Variadic<T>
2781where
2782    T: FromLua,
2783{
2784    const NRESULTS: i32 = -1;
2785
2786    fn from_lua_multi(values: Vec<Value>, lua: &Lua) -> Result<Self> {
2787        values
2788            .into_iter()
2789            .map(|value| T::from_lua(value, lua))
2790            .collect()
2791    }
2792}
2793
2794impl IntoLuaMulti for () {
2795    fn into_lua_multi(self, _lua: &Lua) -> Result<Vec<Value>> {
2796        Ok(Vec::new())
2797    }
2798}
2799
2800impl<T> IntoLuaMulti for T
2801where
2802    T: IntoLua,
2803{
2804    fn into_lua_multi(self, lua: &Lua) -> Result<Vec<Value>> {
2805        Ok(vec![self.into_lua(lua)?])
2806    }
2807}
2808
2809impl<A, B> IntoLuaMulti for (A, B)
2810where
2811    A: IntoLua,
2812    B: IntoLua,
2813{
2814    fn into_lua_multi(self, lua: &Lua) -> Result<Vec<Value>> {
2815        Ok(vec![self.0.into_lua(lua)?, self.1.into_lua(lua)?])
2816    }
2817}
2818
2819impl<A, T> IntoLuaMulti for (A, Variadic<T>)
2820where
2821    A: IntoLua,
2822    T: IntoLua,
2823{
2824    fn into_lua_multi(self, lua: &Lua) -> Result<Vec<Value>> {
2825        let mut values = vec![self.0.into_lua(lua)?];
2826        values.extend(self.1.into_lua_multi(lua)?);
2827        Ok(values)
2828    }
2829}
2830
2831impl<A, B, C> IntoLuaMulti for (A, B, C)
2832where
2833    A: IntoLua,
2834    B: IntoLua,
2835    C: IntoLua,
2836{
2837    fn into_lua_multi(self, lua: &Lua) -> Result<Vec<Value>> {
2838        Ok(vec![
2839            self.0.into_lua(lua)?,
2840            self.1.into_lua(lua)?,
2841            self.2.into_lua(lua)?,
2842        ])
2843    }
2844}
2845
2846impl<A, B, T> IntoLuaMulti for (A, B, Variadic<T>)
2847where
2848    A: IntoLua,
2849    B: IntoLua,
2850    T: IntoLua,
2851{
2852    fn into_lua_multi(self, lua: &Lua) -> Result<Vec<Value>> {
2853        let mut values = vec![self.0.into_lua(lua)?, self.1.into_lua(lua)?];
2854        values.extend(self.2.into_lua_multi(lua)?);
2855        Ok(values)
2856    }
2857}
2858
2859impl FromLuaMulti for () {
2860    const NRESULTS: i32 = 0;
2861
2862    fn from_lua_multi(_values: Vec<Value>, _lua: &Lua) -> Result<Self> {
2863        Ok(())
2864    }
2865}
2866
2867impl<T> FromLuaMulti for T
2868where
2869    T: FromLua,
2870{
2871    const NRESULTS: i32 = 1;
2872
2873    fn from_lua_multi(mut values: Vec<Value>, lua: &Lua) -> Result<Self> {
2874        let value = if values.is_empty() {
2875            Value::Nil
2876        } else {
2877            values.remove(0)
2878        };
2879        T::from_lua(value, lua)
2880    }
2881}
2882
2883impl<A, B> FromLuaMulti for (A, B)
2884where
2885    A: FromLua,
2886    B: FromLua,
2887{
2888    const NRESULTS: i32 = 2;
2889
2890    fn from_lua_multi(mut values: Vec<Value>, lua: &Lua) -> Result<Self> {
2891        let first = if values.is_empty() {
2892            Value::Nil
2893        } else {
2894            values.remove(0)
2895        };
2896        let second = if values.is_empty() {
2897            Value::Nil
2898        } else {
2899            values.remove(0)
2900        };
2901        Ok((A::from_lua(first, lua)?, B::from_lua(second, lua)?))
2902    }
2903}
2904
2905impl<A, T> FromLuaMulti for (A, Variadic<T>)
2906where
2907    A: FromLua,
2908    T: FromLua,
2909{
2910    const NRESULTS: i32 = -1;
2911
2912    fn from_lua_multi(mut values: Vec<Value>, lua: &Lua) -> Result<Self> {
2913        let first = if values.is_empty() {
2914            Value::Nil
2915        } else {
2916            values.remove(0)
2917        };
2918        Ok((
2919            A::from_lua(first, lua)?,
2920            Variadic::from_lua_multi(values, lua)?,
2921        ))
2922    }
2923}
2924
2925impl<A, B, C> FromLuaMulti for (A, B, C)
2926where
2927    A: FromLua,
2928    B: FromLua,
2929    C: FromLua,
2930{
2931    const NRESULTS: i32 = 3;
2932
2933    fn from_lua_multi(mut values: Vec<Value>, lua: &Lua) -> Result<Self> {
2934        let first = if values.is_empty() {
2935            Value::Nil
2936        } else {
2937            values.remove(0)
2938        };
2939        let second = if values.is_empty() {
2940            Value::Nil
2941        } else {
2942            values.remove(0)
2943        };
2944        let third = if values.is_empty() {
2945            Value::Nil
2946        } else {
2947            values.remove(0)
2948        };
2949        Ok((
2950            A::from_lua(first, lua)?,
2951            B::from_lua(second, lua)?,
2952            C::from_lua(third, lua)?,
2953        ))
2954    }
2955}
2956
2957impl<A, B, T> FromLuaMulti for (A, B, Variadic<T>)
2958where
2959    A: FromLua,
2960    B: FromLua,
2961    T: FromLua,
2962{
2963    const NRESULTS: i32 = -1;
2964
2965    fn from_lua_multi(mut values: Vec<Value>, lua: &Lua) -> Result<Self> {
2966        let first = if values.is_empty() {
2967            Value::Nil
2968        } else {
2969            values.remove(0)
2970        };
2971        let second = if values.is_empty() {
2972            Value::Nil
2973        } else {
2974            values.remove(0)
2975        };
2976        Ok((
2977            A::from_lua(first, lua)?,
2978            B::from_lua(second, lua)?,
2979            Variadic::from_lua_multi(values, lua)?,
2980        ))
2981    }
2982}
2983
2984fn rust_callback_trampoline(state: &mut LuaState) -> Result<usize> {
2985    let func_idx = state.current_call_info().func;
2986    let callback = match state.get_at(func_idx) {
2987        RawLuaValue::Function(RawLuaClosure::C(closure)) => {
2988            let Some(RawLuaValue::UserData(userdata)) = closure.upvalues.first() else {
2989                return Err(LuaError::runtime(format_args!(
2990                    "missing Rust callback payload"
2991                )));
2992            };
2993            let host = userdata
2994                .host_value()
2995                .ok_or_else(|| LuaError::runtime(format_args!("missing Rust callback payload")))?;
2996            host.downcast::<RustCallbackCell>().map_err(|_| {
2997                LuaError::runtime(format_args!("Rust callback payload type mismatch"))
2998            })?
2999        }
3000        _ => {
3001            return Err(LuaError::runtime(format_args!(
3002                "Rust callback trampoline called without C closure"
3003            )));
3004        }
3005    };
3006    (callback.function)(state)
3007}
3008
3009fn with_heap_guard<R>(state: &LuaState, f: impl FnOnce() -> R) -> R {
3010    let _heap_guard = heap_guard(state);
3011    f()
3012}
3013
3014fn heap_guard(state: &LuaState) -> lua_gc::HeapGuard {
3015    let global = state.global();
3016    lua_gc::HeapGuard::push(&global.heap)
3017}
3018
3019fn callback_args(state: &mut LuaState, lua: &Lua) -> Result<Vec<Value>> {
3020    let func_idx = state.current_call_info().func;
3021    let nargs = state.top_idx().0.saturating_sub(func_idx.0 + 1);
3022    let mut args = Vec::with_capacity(nargs as usize);
3023    for i in 0..nargs {
3024        let raw = state.get_at(func_idx + 1 + i as i32);
3025        args.push(Value::from_raw_in_state(lua, state, raw)?);
3026    }
3027    Ok(args)
3028}
3029
3030fn callback_userdata_args(state: &mut LuaState, lua: &Lua) -> Result<(AnyUserData, Vec<Value>)> {
3031    let mut args = callback_args(state, lua)?;
3032    if args.is_empty() {
3033        return Err(LuaError::runtime(format_args!(
3034            "userdata method missing self argument"
3035        )));
3036    }
3037    let userdata = AnyUserData::from_lua(args.remove(0), lua)?;
3038    Ok((userdata, args))
3039}
3040
3041fn push_callback_returns(state: &mut LuaState, lua: &Lua, returns: Vec<Value>) -> Result<usize> {
3042    let mut count = 0usize;
3043    for value in returns {
3044        let raw = value.to_raw_for_lua(lua, state)?;
3045        state.push(raw);
3046        count += 1;
3047    }
3048    Ok(count)
3049}
3050
3051fn stale_handle_error() -> LuaError {
3052    LuaError::runtime(format_args!("stale Lua handle"))
3053}
3054
3055fn type_error_raw(value: &RawLuaValue, expected: &str) -> LuaError {
3056    LuaError::runtime(format_args!(
3057        "{} expected, got {}",
3058        expected,
3059        value.type_name()
3060    ))
3061}
3062
3063fn type_error_value(value: &Value, expected: &str) -> LuaError {
3064    let got = match value {
3065        Value::Nil => "nil",
3066        Value::Boolean(_) => "boolean",
3067        Value::Integer(_) | Value::Number(_) => "number",
3068        Value::String(_) => "string",
3069        Value::Table(_) => "table",
3070        Value::Function(_) => "function",
3071        Value::UserData(_) | Value::LightUserData(_) => "userdata",
3072        Value::Thread(_) => "thread",
3073    };
3074    LuaError::runtime(format_args!("{} expected, got {}", expected, got))
3075}
3076
3077/// A Lua state with parser and standard libraries installed.
3078pub struct LuaRuntime {
3079    state: LuaState,
3080}
3081
3082impl LuaRuntime {
3083    /// Create a Lua runtime with parser and standard libraries installed.
3084    ///
3085    /// This installs no explicit host hooks. For a strict sandbox, construct
3086    /// with [`LuaRuntime::with_hooks`] and audit the native compatibility
3087    /// fallbacks in `lua-stdlib`.
3088    pub fn new() -> Result<Self> {
3089        Self::with_hooks(HostHooks::default())
3090    }
3091
3092    /// Create a Lua runtime with the supplied host capabilities.
3093    pub fn with_hooks(hooks: HostHooks) -> Result<Self> {
3094        let mut state = new_state().ok_or(LuaError::Memory)?;
3095        install_parser_hook(&mut state);
3096        hooks.install(&mut state);
3097        open_libs(&mut state)?;
3098        Ok(Self { state })
3099    }
3100
3101    pub fn state(&self) -> &LuaState {
3102        &self.state
3103    }
3104
3105    pub fn state_mut(&mut self) -> &mut LuaState {
3106        &mut self.state
3107    }
3108
3109    pub fn into_state(self) -> LuaState {
3110        self.state
3111    }
3112
3113    pub fn into_lua(self) -> Lua {
3114        Lua::from_initialized_state(self.state)
3115    }
3116
3117    /// Load and execute a Lua source chunk.
3118    pub fn exec(&mut self, source: &[u8], name: &[u8]) -> Result<()> {
3119        exec_state(&mut self.state, source, name)
3120    }
3121}
3122
3123fn exec_state(state: &mut LuaState, source: &[u8], name: &[u8]) -> Result<()> {
3124    let status = load_buffer(state, source, name)?;
3125    if status != 0 {
3126        let err = state.pop();
3127        return Err(LuaError::from_value(err));
3128    }
3129    lua_vm::api::pcall_k(state, 0, 0, 0, 0, None)?;
3130    Ok(())
3131}
3132
3133pub fn install_parser_hook(state: &mut LuaState) {
3134    state.global_mut().parser_hook = Some(parser_hook);
3135}
3136
3137fn parser_hook(
3138    state: &mut LuaState,
3139    source: &[u8],
3140    name: &[u8],
3141    firstchar: i32,
3142) -> Result<GcRef<LuaLClosure>> {
3143    let _heap_guard = heap_guard(state);
3144    let proto = lua_parse::parse(
3145        state,
3146        lua_parse::DynData::default(),
3147        source,
3148        name,
3149        firstchar,
3150    )?;
3151    let nupvals = proto.upvalues.len();
3152    let mut upvals = Vec::with_capacity(nupvals);
3153    for _ in 0..nupvals {
3154        upvals.push(std::cell::Cell::new(GcRef::new(UpVal::closed(
3155            RawLuaValue::Nil,
3156        ))));
3157    }
3158    Ok(GcRef::new(LuaLClosure {
3159        proto: GcRef::new(*proto),
3160        upvals,
3161    }))
3162}
3163
3164#[cfg(test)]
3165mod tests {
3166    use super::*;
3167    use std::cell::Cell;
3168
3169    fn external_root_count(lua: &Lua) -> usize {
3170        lua.with_state(|state| state.global().external_roots.len())
3171    }
3172
3173    struct Counter {
3174        value: i64,
3175    }
3176
3177    impl UserData for Counter {
3178        fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
3179            methods.add_method("get", |_lua, this, ()| Ok(this.value));
3180            methods.add_method_mut("inc", |_lua, this, delta: i64| {
3181                this.value += delta;
3182                Ok(this.value)
3183            });
3184        }
3185    }
3186
3187    struct PropertyBag {
3188        value: i64,
3189    }
3190
3191    impl UserData for PropertyBag {
3192        fn add_meta_methods<M: UserDataMethods<Self>>(methods: &mut M) {
3193            methods.add_meta_method(MetaMethod::Index, |_lua, this, key: String| {
3194                if key == "value" {
3195                    Ok(Value::Integer(this.value))
3196                } else {
3197                    Ok(Value::Nil)
3198                }
3199            });
3200            methods.add_meta_method_mut(
3201                MetaMethod::NewIndex,
3202                |_lua, this, (key, value): (String, i64)| {
3203                    if key != "value" {
3204                        return Err(LuaError::runtime(format_args!("unknown property")));
3205                    }
3206                    this.value = value;
3207                    Ok(())
3208                },
3209            );
3210        }
3211    }
3212
3213    #[test]
3214    fn rooted_table_clone_and_drop_manage_root_slots() {
3215        let lua = Lua::new();
3216        assert_eq!(external_root_count(&lua), 0);
3217
3218        let table = lua.create_table().expect("table should allocate");
3219        assert_eq!(external_root_count(&lua), 1);
3220
3221        let cloned = table.clone();
3222        assert_eq!(external_root_count(&lua), 2);
3223
3224        drop(table);
3225        assert_eq!(external_root_count(&lua), 1);
3226
3227        cloned.set("answer", 42_i64).expect("set should succeed");
3228        lua.gc_collect();
3229        assert_eq!(
3230            cloned.get::<_, i64>("answer").expect("get should succeed"),
3231            42
3232        );
3233
3234        drop(cloned);
3235        assert_eq!(external_root_count(&lua), 0);
3236    }
3237
3238    #[test]
3239    fn table_values_survive_forced_collection_between_operations() {
3240        let lua = Lua::new();
3241        let table = lua.create_table().expect("table should allocate");
3242
3243        lua.gc_collect();
3244        table.set("k", "v").expect("set should succeed");
3245        table.set(1_i64, "array").expect("array set should succeed");
3246        lua.gc_collect();
3247
3248        let value: String = table.get("k").expect("get should succeed");
3249        assert_eq!(value, "v");
3250        assert_eq!(table.len().expect("len should succeed"), 1);
3251    }
3252
3253    #[test]
3254    fn chunk_exec_eval_and_function_call_use_rooted_handles() {
3255        let lua = Lua::new();
3256        lua.load("function add(a, b) return a + b end")
3257            .set_name("test")
3258            .exec()
3259            .expect("chunk should execute");
3260
3261        let globals = lua.globals();
3262        let add: Function = globals.get("add").expect("function should exist");
3263        let result: i64 = add.call((20_i64, 22_i64)).expect("call should work");
3264        assert_eq!(result, 42);
3265
3266        let eval_result: i64 = lua
3267            .load("return add(1, 2)")
3268            .eval()
3269            .expect("eval should work");
3270        assert_eq!(eval_result, 3);
3271    }
3272
3273    #[test]
3274    fn rust_callback_captures_state_and_reenters_lua() {
3275        let lua = Lua::new();
3276        lua.load("function twice(v) return v * 2 end")
3277            .exec()
3278            .expect("chunk should execute");
3279
3280        let globals = lua.globals();
3281        let twice: Function = globals.get("twice").expect("function should exist");
3282        let calls = Rc::new(Cell::new(0));
3283        let calls_for_callback = calls.clone();
3284
3285        let callback = lua
3286            .create_function(move |_lua, value: i64| {
3287                calls_for_callback.set(calls_for_callback.get() + 1);
3288                let doubled: i64 = twice.call(value)?;
3289                Ok(doubled + 1)
3290            })
3291            .expect("callback should create");
3292        globals
3293            .set("from_rust", callback)
3294            .expect("callback should register");
3295
3296        let result: i64 = lua
3297            .load("return from_rust(20)")
3298            .eval()
3299            .expect("callback should run");
3300        assert_eq!(result, 41);
3301        assert_eq!(calls.get(), 1);
3302    }
3303
3304    #[test]
3305    fn rust_callback_accepts_and_returns_collectable_values() {
3306        let lua = Lua::new();
3307        let globals = lua.globals();
3308        let callback = lua
3309            .create_function(|lua, name: String| {
3310                let table = lua.create_table()?;
3311                table.set("name", name)?;
3312                Ok(table)
3313            })
3314            .expect("callback should create");
3315        globals
3316            .set("make_record", callback)
3317            .expect("callback should register");
3318
3319        let result: String = lua
3320            .load("return make_record('lua-rs').name")
3321            .eval()
3322            .expect("callback should return table");
3323        assert_eq!(result, "lua-rs");
3324    }
3325
3326    #[test]
3327    fn rust_callback_mut_tracks_state() {
3328        let lua = Lua::new();
3329        let globals = lua.globals();
3330        let mut next = 0_i64;
3331        let callback = lua
3332            .create_function_mut(move |_lua, delta: i64| {
3333                next += delta;
3334                Ok(next)
3335            })
3336            .expect("callback should create");
3337        globals
3338            .set("next", callback)
3339            .expect("callback should register");
3340
3341        let result: (i64, i64) = lua
3342            .load("return next(2), next(5)")
3343            .eval()
3344            .expect("callback should run");
3345        assert_eq!(result, (2, 7));
3346    }
3347
3348    #[test]
3349    fn dropped_rust_callback_releases_captured_handles_after_gc() {
3350        let lua = Lua::new();
3351        let table = lua.create_table().expect("table should allocate");
3352        table.set("value", 42_i64).expect("set should succeed");
3353        assert_eq!(external_root_count(&lua), 1);
3354
3355        let callback = {
3356            let captured = table.clone();
3357            lua.create_function(move |_lua, ()| captured.get::<_, i64>("value"))
3358                .expect("callback should create")
3359        };
3360        assert_eq!(external_root_count(&lua), 3);
3361
3362        drop(callback);
3363        lua.gc_collect();
3364        assert_eq!(external_root_count(&lua), 1);
3365        assert_eq!(table.get::<_, i64>("value").expect("table should live"), 42);
3366    }
3367
3368    #[test]
3369    fn metatable_is_built_once_per_type() {
3370        use std::sync::atomic::{AtomicUsize, Ordering};
3371        static BUILDS: AtomicUsize = AtomicUsize::new(0);
3372
3373        struct Widget {
3374            n: i64,
3375        }
3376        impl UserData for Widget {
3377            fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
3378                BUILDS.fetch_add(1, Ordering::SeqCst);
3379                methods.add_method("n", |_lua, this, ()| Ok(this.n));
3380            }
3381        }
3382
3383        let lua = Lua::new();
3384        let a = lua.create_userdata(Widget { n: 1 }).expect("first");
3385        let b = lua.create_userdata(Widget { n: 2 }).expect("second");
3386        let c = lua.create_userdata(Widget { n: 3 }).expect("third");
3387
3388        // Built exactly once despite three values of the same type.
3389        assert_eq!(BUILDS.load(Ordering::SeqCst), 1);
3390
3391        // Each value still carries its own data and dispatches correctly.
3392        let globals = lua.globals();
3393        globals.set("a", &a).unwrap();
3394        globals.set("b", &b).unwrap();
3395        globals.set("c", &c).unwrap();
3396        let sum: i64 = lua.load("return a:n() + b:n() + c:n()").eval().unwrap();
3397        assert_eq!(sum, 6);
3398    }
3399
3400    /// Reproducer for the callback-to-`Lua` reference cycle:
3401    /// `create_userdata_method` captures a strong `Lua` (`Rc<LuaInner>`) into each
3402    /// callback closure, the closure lives in a heap GC object owned by `LuaState`,
3403    /// and `LuaState` is owned by `LuaInner` — so dropping every external `Lua`
3404    /// handle still leaves the closures holding a strong `Rc<LuaInner>` to the
3405    /// state that owns them. Per-type metatable caching makes this permanent for
3406    /// any type a userdata is ever created for.
3407    ///
3408    /// This test holds a `Weak<LuaInner>`, drops every external `Lua`, and asserts
3409    /// the inner has actually been freed. It fails today and is what the
3410    /// `Weak`-capture fix in the callback constructors is meant to make pass.
3411    #[test]
3412    fn lua_state_frees_after_userdata_with_methods_is_dropped() {
3413        use std::rc::Rc;
3414
3415        let weak_inner = {
3416            let lua = Lua::new();
3417            let weak = Rc::downgrade(&lua.inner);
3418            // Create + drop a userdata of a type that registers methods. This
3419            // primes the per-type metatable cache and installs method closures
3420            // that capture `Lua` strongly.
3421            let _ = lua
3422                .create_userdata(Counter { value: 1 })
3423                .expect("userdata should create");
3424            weak
3425        };
3426
3427        assert!(
3428            weak_inner.upgrade().is_none(),
3429            "LuaInner is still alive after every external Lua handle dropped: \
3430             internal callback closures hold a strong Rc<LuaInner>, leaking the state"
3431        );
3432    }
3433
3434    /// Same cycle issue as above, on the `create_function` path: the Rust
3435    /// callback closure used to capture a strong `Lua`, so a function that
3436    /// outlived all external handles would keep the state pinned.
3437    #[test]
3438    fn lua_state_frees_after_create_function_handle_drops() {
3439        use std::rc::Rc;
3440
3441        let weak_inner = {
3442            let lua = Lua::new();
3443            let weak = Rc::downgrade(&lua.inner);
3444            let _f = lua
3445                .create_function(|_, ()| Ok(()))
3446                .expect("create_function should succeed");
3447            weak
3448        };
3449
3450        assert!(
3451            weak_inner.upgrade().is_none(),
3452            "LuaInner is still alive after the only Lua handle dropped: \
3453             the create_function callback held a strong Rc<LuaInner>"
3454        );
3455    }
3456
3457    /// Field-bearing types take the composed `__index` path in `build_metatable`,
3458    /// where the composing closure is itself passed to `create_function` and
3459    /// captures the field-getter table, method table, and optional raw
3460    /// `__index` function. Each of those is a `Table` or `Function` whose
3461    /// `RootedValue` holds a strong `Rc<LuaInner>`. Even with the outer
3462    /// `Weak` fix, that user closure still leaks the state.
3463    #[test]
3464    fn lua_state_frees_after_userdata_with_fields_drops() {
3465        use std::rc::Rc;
3466
3467        struct Point {
3468            x: f64,
3469        }
3470        impl UserData for Point {
3471            fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
3472                m.add_field_method_get("x", |_, this| Ok(this.x));
3473                m.add_field_method_set("x", |_, this, v: f64| {
3474                    this.x = v;
3475                    Ok(())
3476                });
3477            }
3478        }
3479
3480        let weak_inner = {
3481            let lua = Lua::new();
3482            let weak = Rc::downgrade(&lua.inner);
3483            let _ = lua
3484                .create_userdata(Point { x: 1.0 })
3485                .expect("userdata should create");
3486            weak
3487        };
3488
3489        assert!(
3490            weak_inner.upgrade().is_none(),
3491            "LuaInner leaked via the composed __index/__newindex closures: \
3492             they capture Table/Function values whose RootedValue holds a \
3493             strong Rc<LuaInner>"
3494        );
3495    }
3496
3497    /// Maximal mixed shape: field getter + field setter + regular method +
3498    /// raw `__index` + raw `__newindex` all on one type. Exercises every
3499    /// branch of the composed dispatch and every permanently rooted handle.
3500    /// If a future change reintroduces a captured wrapper anywhere in the
3501    /// composition path, this is the test most likely to catch it.
3502    #[test]
3503    fn lua_state_frees_with_fields_methods_and_raw_meta() {
3504        use std::rc::Rc;
3505
3506        struct Mixed {
3507            x: f64,
3508            log: Vec<String>,
3509        }
3510        impl UserData for Mixed {
3511            fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
3512                m.add_field_method_get("x", |_, this| Ok(this.x));
3513                m.add_field_method_set("x", |_, this, v: f64| {
3514                    this.x = v;
3515                    Ok(())
3516                });
3517                m.add_method("log_len", |_, this, ()| Ok(this.log.len() as i64));
3518                m.add_method_mut("push_log", |_, this, s: String| {
3519                    this.log.push(s);
3520                    Ok(())
3521                });
3522                m.add_meta_method(MetaMethod::Index, |_, _this, key: String| {
3523                    Ok(::std::format!("dynamic:{key}"))
3524                });
3525                m.add_meta_method_mut(
3526                    MetaMethod::NewIndex,
3527                    |_, _this, (_k, _v): (String, Value)| Ok(()),
3528                );
3529            }
3530        }
3531
3532        let weak_inner = {
3533            let lua = Lua::new();
3534            let weak = Rc::downgrade(&lua.inner);
3535            let _ = lua
3536                .create_userdata(Mixed {
3537                    x: 1.0,
3538                    log: Vec::new(),
3539                })
3540                .expect("create");
3541            weak
3542        };
3543
3544        assert!(
3545            weak_inner.upgrade().is_none(),
3546            "maximal-composition userdata leaked LuaInner: \
3547             check the composed __index / __newindex captures"
3548        );
3549    }
3550
3551    /// The composed `__index` allocates two or three temporary external roots
3552    /// per call (for the per-call `Table`/`Function` views) and relies on
3553    /// `pending_external_unroots` being flushed by the next `with_state`. If
3554    /// that plumbing ever breaks, every field read silently leaks a root. Hammer
3555    /// it in a loop and assert `external_roots.len()` returns to baseline.
3556    #[test]
3557    fn composed_dispatch_does_not_accumulate_external_roots() {
3558        struct Probe {
3559            x: i64,
3560        }
3561        impl UserData for Probe {
3562            fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
3563                m.add_field_method_get("x", |_, this| Ok(this.x));
3564            }
3565        }
3566
3567        let lua = Lua::new();
3568        lua.globals()
3569            .set("v", lua.create_userdata(Probe { x: 1 }).unwrap())
3570            .unwrap();
3571        let baseline = external_root_count(&lua);
3572
3573        for _ in 0..1000 {
3574            let _: i64 = lua.load("return v.x").eval().unwrap();
3575        }
3576        // The last iteration's temp roots queue for unroot on exit of its
3577        // outer with_state; force one more so the flush definitely runs.
3578        let after = external_root_count(&lua);
3579
3580        assert!(
3581            after <= baseline + 2,
3582            "external roots grew under composed __index churn: baseline={baseline} after={after}"
3583        );
3584    }
3585
3586    /// A Rust userdata method takes a Lua `Function` and calls it. Exercises
3587    /// the Weak<LuaInner> upgrade plus the `active_state` reentrancy pointer
3588    /// together. The bms-lua-rs reflection bridge hits this shape on every
3589    /// component access; an existing test covers `create_function` reentry but
3590    /// not the userdata-method path.
3591    #[test]
3592    fn userdata_method_can_reenter_lua_from_callback() {
3593        struct Calc;
3594        impl UserData for Calc {
3595            fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
3596                m.add_method("apply", |_lua, _this, f: Function| {
3597                    let r: i64 = f.call(7_i64)?;
3598                    Ok(r + 1)
3599                });
3600            }
3601        }
3602
3603        let lua = Lua::new();
3604        lua.globals()
3605            .set("c", lua.create_userdata(Calc).unwrap())
3606            .unwrap();
3607        let r: i64 = lua
3608            .load("return c:apply(function(n) return n * 2 end)")
3609            .eval()
3610            .unwrap();
3611        assert_eq!(r, 15);
3612    }
3613
3614    /// Two `Lua::new()` instances must each build their own metatable for the
3615    /// same Rust type. Counts calls to `add_methods` across both states and
3616    /// asserts each state builds independently while still de-duplicating
3617    /// within its own scope.
3618    #[test]
3619    fn metatable_cache_is_per_lua_state() {
3620        use std::sync::atomic::{AtomicUsize, Ordering};
3621        static BUILDS: AtomicUsize = AtomicUsize::new(0);
3622
3623        struct Marker {
3624            v: i64,
3625        }
3626        impl UserData for Marker {
3627            fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
3628                BUILDS.fetch_add(1, Ordering::SeqCst);
3629                m.add_method("v", |_, this, ()| Ok(this.v));
3630            }
3631        }
3632
3633        let start = BUILDS.load(Ordering::SeqCst);
3634
3635        let lua_a = Lua::new();
3636        let _a1 = lua_a.create_userdata(Marker { v: 1 }).unwrap();
3637        assert_eq!(BUILDS.load(Ordering::SeqCst) - start, 1, "state A first build");
3638        let _a2 = lua_a.create_userdata(Marker { v: 2 }).unwrap();
3639        assert_eq!(BUILDS.load(Ordering::SeqCst) - start, 1, "state A reuses cache");
3640
3641        let lua_b = Lua::new();
3642        let _b1 = lua_b.create_userdata(Marker { v: 3 }).unwrap();
3643        assert_eq!(BUILDS.load(Ordering::SeqCst) - start, 2, "state B is independent");
3644
3645        let _a3 = lua_a.create_userdata(Marker { v: 4 }).unwrap();
3646        assert_eq!(BUILDS.load(Ordering::SeqCst) - start, 2, "state A still cached");
3647    }
3648
3649    /// Field beats method when names collide. The composed `__index` looks up
3650    /// field getters before the method table; pin that order so a future
3651    /// refactor of the dispatch closure does not silently swap precedence.
3652    #[test]
3653    fn field_shadows_method_of_same_name() {
3654        struct Shadow {
3655            x: i64,
3656        }
3657        impl UserData for Shadow {
3658            fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
3659                m.add_field_method_get("x", |_, this| Ok(this.x));
3660                m.add_method("x", |_, _this, ()| Ok(999_i64));
3661            }
3662        }
3663
3664        let lua = Lua::new();
3665        lua.globals()
3666            .set("v", lua.create_userdata(Shadow { x: 42 }).unwrap())
3667            .unwrap();
3668
3669        let r: i64 = lua.load("return v.x").eval().unwrap();
3670        assert_eq!(r, 42, "the field getter should beat the method of the same name");
3671    }
3672
3673    /// Direct Lua-side proof the cache is real: two userdata of the same type
3674    /// share the same metatable object as observed by `getmetatable`. If the
3675    /// cache regressed to per-value metatables this returns false.
3676    #[test]
3677    fn cached_metatable_is_shared_across_values_in_lua() {
3678        struct Twin;
3679        impl UserData for Twin {
3680            fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
3681                m.add_method("ping", |_, _this, ()| Ok(1_i64));
3682            }
3683        }
3684
3685        let lua = Lua::new();
3686        lua.globals()
3687            .set("a", lua.create_userdata(Twin).unwrap())
3688            .unwrap();
3689        lua.globals()
3690            .set("b", lua.create_userdata(Twin).unwrap())
3691            .unwrap();
3692
3693        let same: bool = lua
3694            .load("return getmetatable(a) == getmetatable(b)")
3695            .eval()
3696            .unwrap();
3697        assert!(same, "cached metatable must be shared across values of the same type");
3698    }
3699
3700    #[test]
3701    fn fields_and_methods_coexist() {
3702        struct Vec2 {
3703            x: f64,
3704            y: f64,
3705        }
3706        impl UserData for Vec2 {
3707            fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
3708                m.add_field_method_get("x", |_, this| Ok(this.x));
3709                m.add_field_method_get("y", |_, this| Ok(this.y));
3710                m.add_field_method_set("x", |_, this, v: f64| {
3711                    this.x = v;
3712                    Ok(())
3713                });
3714                m.add_field_method_set("y", |_, this, v: f64| {
3715                    this.y = v;
3716                    Ok(())
3717                });
3718                m.add_method("length", |_, this, ()| {
3719                    Ok((this.x * this.x + this.y * this.y).sqrt())
3720                });
3721                m.add_method_mut("scale", |_, this, k: f64| {
3722                    this.x *= k;
3723                    this.y *= k;
3724                    Ok(())
3725                });
3726            }
3727        }
3728
3729        let lua = Lua::new();
3730        let v = lua.create_userdata(Vec2 { x: 3.0, y: 4.0 }).unwrap();
3731        lua.globals().set("v", &v).unwrap();
3732
3733        // method call and field reads on the same value
3734        assert_eq!(lua.load("return v:length()").eval::<f64>().unwrap(), 5.0);
3735        assert_eq!(lua.load("return v.x + v.y").eval::<f64>().unwrap(), 7.0);
3736
3737        // field write
3738        lua.load("v.x = 6").exec().unwrap();
3739        assert_eq!(lua.load("return v.x").eval::<f64>().unwrap(), 6.0);
3740
3741        // method mutation is visible through field reads
3742        lua.load("v:scale(2)").exec().unwrap();
3743        assert_eq!(lua.load("return v.x").eval::<f64>().unwrap(), 12.0);
3744        assert_eq!(lua.load("return v.y").eval::<f64>().unwrap(), 8.0);
3745
3746        // unknown field assignment errors
3747        assert!(lua.load("v.z = 1").exec().is_err());
3748    }
3749
3750    #[test]
3751    fn userdata_methods_dispatch_and_track_borrows() {
3752        let lua = Lua::new();
3753        let globals = lua.globals();
3754        let counter = lua
3755            .create_userdata(Counter { value: 1 })
3756            .expect("userdata should create");
3757        globals
3758            .set("counter", &counter)
3759            .expect("userdata should register");
3760
3761        let result: i64 = lua
3762            .load("counter:inc(5); return counter:get()")
3763            .eval()
3764            .expect("methods should dispatch");
3765        assert_eq!(result, 6);
3766        assert_eq!(
3767            counter
3768                .with_borrow::<Counter, _>(|counter| counter.value)
3769                .expect("borrow should work"),
3770            6
3771        );
3772
3773        {
3774            let borrowed = counter
3775                .borrow::<Counter>()
3776                .expect("borrow guard should work");
3777            assert_eq!(borrowed.value, 6);
3778        }
3779
3780        {
3781            let mut borrowed = counter
3782                .borrow_mut::<Counter>()
3783                .expect("mutable borrow guard should work");
3784            borrowed.value = 9;
3785        }
3786
3787        assert_eq!(
3788            lua.load("return counter:get()")
3789                .eval::<i64>()
3790                .expect("method should see guard mutation"),
3791            9
3792        );
3793    }
3794
3795    #[test]
3796    fn userdata_payload_survives_gc_while_lua_holds_userdata() {
3797        let lua = Lua::new();
3798        let globals = lua.globals();
3799        let counter = lua
3800            .create_userdata(Counter { value: 10 })
3801            .expect("userdata should create");
3802        globals
3803            .set("counter", counter)
3804            .expect("userdata should register");
3805
3806        lua.gc_collect();
3807        let result: i64 = lua
3808            .load("counter:inc(2); collectgarbage('collect'); return counter:get()")
3809            .eval()
3810            .expect("userdata should survive collection");
3811        assert_eq!(result, 12);
3812    }
3813
3814    #[test]
3815    fn userdata_runtime_borrow_conflict_returns_lua_error() {
3816        let lua = Lua::new();
3817        let globals = lua.globals();
3818        let counter = lua
3819            .create_userdata(Counter { value: 1 })
3820            .expect("userdata should create");
3821        globals
3822            .set("counter", &counter)
3823            .expect("userdata should register");
3824
3825        let failed = counter
3826            .with_borrow::<Counter, _>(|_| lua.load("return counter:inc(1)").eval::<i64>().is_err())
3827            .expect("outer borrow should succeed");
3828        assert!(
3829            failed,
3830            "mutable method should fail while immutable borrow is held"
3831        );
3832        assert_eq!(
3833            counter
3834                .with_borrow::<Counter, _>(|counter| counter.value)
3835                .expect("borrow should work"),
3836            1
3837        );
3838    }
3839
3840    #[test]
3841    fn userdata_index_and_newindex_metamethods_dispatch() {
3842        let lua = Lua::new();
3843        let globals = lua.globals();
3844        let bag = lua
3845            .create_userdata(PropertyBag { value: 7 })
3846            .expect("userdata should create");
3847        globals.set("bag", &bag).expect("userdata should register");
3848
3849        let result: i64 = lua
3850            .load("bag.value = 42; return bag.value")
3851            .eval()
3852            .expect("metamethods should dispatch");
3853        assert_eq!(result, 42);
3854        assert_eq!(
3855            bag.with_borrow::<PropertyBag, _>(|bag| bag.value)
3856                .expect("borrow should work"),
3857            42
3858        );
3859    }
3860
3861    #[test]
3862    fn userdata_values_convert_directly_with_into_lua() {
3863        let lua = Lua::new();
3864        let globals = lua.globals();
3865        globals
3866            .set("counter", Counter { value: 3 })
3867            .expect("userdata should convert through IntoLua");
3868
3869        let result: i64 = lua
3870            .load("counter:inc(4); return counter:get()")
3871            .eval()
3872            .expect("converted userdata should dispatch methods");
3873        assert_eq!(result, 7);
3874    }
3875
3876    #[test]
3877    fn variadic_args_and_returns_convert_all_values() {
3878        let lua = Lua::new();
3879        let globals = lua.globals();
3880
3881        let sum = lua
3882            .create_function(|_lua, values: Variadic<i64>| Ok(values.iter().sum::<i64>()))
3883            .expect("variadic callback should create");
3884        globals.set("sum", sum).expect("callback should register");
3885        let result: i64 = lua
3886            .load("return sum(3, 2, 5)")
3887            .eval()
3888            .expect("variadic callback should run");
3889        assert_eq!(result, 10);
3890
3891        let echo = lua
3892            .create_function(|_lua, values: Variadic<Value>| Ok(values))
3893            .expect("variadic return callback should create");
3894        globals.set("echo", echo).expect("callback should register");
3895        let result: (i64, i64, i64) = lua
3896            .load("return echo(1, 2, 3)")
3897            .eval()
3898            .expect("variadic returns should stay separate");
3899        assert_eq!(result, (1, 2, 3));
3900
3901        let values: Variadic<i64> = lua
3902            .load("return 4, 5, 6")
3903            .eval()
3904            .expect("variadic eval should collect all returns");
3905        assert_eq!(values.into_vec(), vec![4, 5, 6]);
3906    }
3907
3908    #[test]
3909    fn vectors_maps_and_triple_returns_convert_through_tables() {
3910        let lua = Lua::new();
3911        let globals = lua.globals();
3912
3913        globals
3914            .set("list", vec![1_i64, 2, 3])
3915            .expect("vector should convert to table");
3916        let second: i64 = lua
3917            .load("return list[2]")
3918            .eval()
3919            .expect("table should be readable from Lua");
3920        assert_eq!(second, 2);
3921
3922        let list: Vec<i64> = lua
3923            .load("return {4, 5, 6}")
3924            .eval()
3925            .expect("table should convert to vector");
3926        assert_eq!(list, vec![4, 5, 6]);
3927
3928        let mut map = HashMap::new();
3929        map.insert("left".to_string(), 10_i64);
3930        map.insert("right".to_string(), 20_i64);
3931        globals
3932            .set("map", map)
3933            .expect("map should convert to table");
3934        let sum: i64 = lua
3935            .load("return map.left + map.right")
3936            .eval()
3937            .expect("map table should be readable from Lua");
3938        assert_eq!(sum, 30);
3939
3940        let map: HashMap<String, i64> = lua
3941            .load("return {alpha = 3, beta = 9}")
3942            .eval()
3943            .expect("table should convert to map");
3944        assert_eq!(map.get("alpha"), Some(&3));
3945        assert_eq!(map.get("beta"), Some(&9));
3946
3947        let triple: (i64, i64, i64) = lua
3948            .load("return 1, 2, 3")
3949            .eval()
3950            .expect("triple returns should convert");
3951        assert_eq!(triple, (1, 2, 3));
3952    }
3953
3954    /// Pull the human-readable message out of a `LuaError::Runtime(LuaValue::Str)`.
3955    /// The default `Display` for `LuaError` just defers to `Debug`, which prints
3956    /// `Runtime(Str(GcRef(Gc(0x…))))` for runtime errors that were raised through
3957    /// Lua. The actual string lives behind the GcRef; this helper digs it out so
3958    /// assertions can check the message text directly.
3959    fn runtime_error_message(err: &LuaError) -> String {
3960        match err {
3961            LuaError::Runtime(v) | LuaError::Syntax(v) => match v {
3962                RawLuaValue::Str(s) => String::from_utf8_lossy(s.as_bytes()).into_owned(),
3963                other => format!("{other:?}"),
3964            },
3965            other => format!("{other:?}"),
3966        }
3967    }
3968
3969    /// Helper userdata for scope tests: carries a single mutable field so a
3970    /// `&mut Counter` borrow handed to Lua can be observed from Rust after the
3971    /// scope ends. Distinct from the module-level `Counter` only to keep the
3972    /// owned-vs-scoped paths from sharing fixtures.
3973    struct ScopedCounter {
3974        value: i64,
3975        calls: Cell<u32>,
3976    }
3977
3978    impl UserData for ScopedCounter {
3979        fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
3980            methods.add_method("get", |_lua, this, ()| {
3981                this.calls.set(this.calls.get() + 1);
3982                Ok(this.value)
3983            });
3984            methods.add_method_mut("inc", |_lua, this, delta: i64| {
3985                this.value += delta;
3986                Ok(this.value)
3987            });
3988            methods.add_method("calls", |_lua, this, ()| Ok(this.calls.get() as i64));
3989            methods.add_method("call_get_via_global", |lua, _this, ()| {
3990                lua.load("return c:get()").eval::<i64>()
3991            });
3992            methods.add_method_mut("inc_via_global", |lua, this, ()| {
3993                this.value += 1;
3994                lua.load("return c:get()").eval::<i64>()
3995            });
3996        }
3997    }
3998
3999    struct ScopedBag {
4000        value: i64,
4001    }
4002
4003    impl UserData for ScopedBag {
4004        fn add_meta_methods<M: UserDataMethods<Self>>(methods: &mut M) {
4005            methods.add_meta_method(MetaMethod::Index, |_lua, this, key: String| {
4006                if key == "value" {
4007                    Ok(Value::Integer(this.value))
4008                } else {
4009                    Ok(Value::Nil)
4010                }
4011            });
4012            methods.add_meta_method_mut(
4013                MetaMethod::NewIndex,
4014                |_lua, this, (key, value): (String, i64)| {
4015                    if key != "value" {
4016                        return Err(LuaError::runtime(format_args!("unknown property")));
4017                    }
4018                    this.value = value;
4019                    Ok(())
4020                },
4021            );
4022        }
4023    }
4024
4025    struct ScopedFielded {
4026        n: i64,
4027    }
4028
4029    impl UserData for ScopedFielded {
4030        fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
4031            methods.add_field_method_get("n", |_lua, this| Ok(this.n));
4032            methods.add_field_method_set("n", |_lua, this, new: i64| {
4033                this.n = new;
4034                Ok(())
4035            });
4036        }
4037    }
4038
4039    /// Smoke test for [`Lua::scope`]: a `&mut ScopedCounter` borrow lives on
4040    /// the Rust stack, gets handed to Lua as a userdata for the duration of a
4041    /// scope body, and the original is mutated through it.
4042    #[test]
4043    fn scope_userdata_dispatches_method_calls_against_borrow() {
4044        let lua = Lua::new();
4045        let mut counter = ScopedCounter {
4046            value: 10,
4047            calls: Cell::new(0),
4048        };
4049
4050        let observed: i64 = lua
4051            .scope(|scope| {
4052                let ud = scope.create_userdata_ref_mut(&lua, &mut counter)?;
4053                lua.globals().set("c", &ud)?;
4054                lua.load("return c:get()").eval::<i64>()
4055            })
4056            .expect("scope body should succeed");
4057        assert_eq!(observed, 10);
4058        assert_eq!(counter.value, 10);
4059        assert_eq!(counter.calls.get(), 1);
4060    }
4061
4062    /// Mutations through a scoped `&mut T` method must be visible to the Rust
4063    /// owner after the scope returns. This is the central reason for the API:
4064    /// `&mut World` etc. need to round-trip cleanly.
4065    #[test]
4066    fn scope_userdata_mut_method_propagates_to_external_borrow() {
4067        let lua = Lua::new();
4068        let mut counter = ScopedCounter {
4069            value: 0,
4070            calls: Cell::new(0),
4071        };
4072
4073        lua.scope(|scope| {
4074            let ud = scope.create_userdata_ref_mut(&lua, &mut counter)?;
4075            lua.globals().set("c", &ud)?;
4076            lua.load("c:inc(5); c:inc(7)").exec()
4077        })
4078        .expect("scope body should succeed");
4079        assert_eq!(counter.value, 12);
4080    }
4081
4082    /// Headline safety property: any AnyUserData that leaks past its scope
4083    /// must fail cleanly (Lua runtime error), not touch the freed `&mut` slot.
4084    /// We persist the leaked userdata on `globals` precisely to model the
4085    /// adversarial case from the issue: a script squirrels away a `&mut World`
4086    /// and tries to use it later.
4087    #[test]
4088    fn scope_userdata_invalidated_after_scope_returns_runtime_error() {
4089        let lua = Lua::new();
4090        let mut counter = ScopedCounter {
4091            value: 99,
4092            calls: Cell::new(0),
4093        };
4094
4095        lua.scope(|scope| {
4096            let ud = scope.create_userdata_ref_mut(&lua, &mut counter)?;
4097            lua.globals().set("leaked", &ud)?;
4098            Ok(())
4099        })
4100        .expect("scope body should succeed");
4101
4102        let err = lua
4103            .load("return leaked:get()")
4104            .eval::<i64>()
4105            .expect_err("scoped userdata must be unusable after scope ends");
4106        let msg = runtime_error_message(&err);
4107        assert!(
4108            msg.contains("no longer valid") || msg.contains("scope has ended"),
4109            "expected invalidation error, got: {msg}"
4110        );
4111    }
4112
4113    /// Even a `pcall`-wrapped post-scope invocation must surface a Lua-level
4114    /// error rather than crashing. Models the case where the script tries to
4115    /// recover from the failure.
4116    #[test]
4117    fn scope_userdata_invalidated_is_recoverable_via_pcall() {
4118        let lua = Lua::new();
4119        let mut counter = ScopedCounter {
4120            value: 5,
4121            calls: Cell::new(0),
4122        };
4123
4124        lua.scope(|scope| {
4125            let ud = scope.create_userdata_ref_mut(&lua, &mut counter)?;
4126            lua.globals().set("leaked", &ud)?;
4127            Ok(())
4128        })
4129        .expect("scope body should succeed");
4130
4131        let (ok, _err_msg): (bool, String) = lua
4132            .load("local ok, e = pcall(function() return leaked:get() end); return ok, tostring(e)")
4133            .eval()
4134            .expect("pcall harness should produce two values");
4135        assert!(!ok, "post-scope call must fail");
4136    }
4137
4138    /// Re-entry from inside a `&mut` method body into Lua that calls another
4139    /// method on the *same* scoped userdata must be rejected at the second
4140    /// borrow attempt, not produce aliasing `&mut`s. This is the aliasing
4141    /// concern called out in the design.
4142    #[test]
4143    fn scope_userdata_reentrant_borrow_during_mut_method_returns_error() {
4144        let lua = Lua::new();
4145        let mut counter = ScopedCounter {
4146            value: 0,
4147            calls: Cell::new(0),
4148        };
4149
4150        let err = lua
4151            .scope(|scope| {
4152                let ud = scope.create_userdata_ref_mut(&lua, &mut counter)?;
4153                lua.globals().set("c", &ud)?;
4154                lua.load("return c:inc_via_global()").eval::<i64>()
4155            })
4156            .expect_err("re-entry while mut-borrowed must fail");
4157        let msg = runtime_error_message(&err);
4158        assert!(
4159            msg.contains("already") && msg.contains("borrowed"),
4160            "expected borrow-conflict error, got: {msg}"
4161        );
4162        assert_eq!(counter.value, 1, "outer mutation persists despite inner failure");
4163    }
4164
4165    /// Two shared borrows of the same scoped cell must be compatible: a
4166    /// `:get()` re-entering Lua to call `:get()` again should succeed.
4167    #[test]
4168    fn scope_userdata_reentrant_shared_borrows_are_compatible() {
4169        let lua = Lua::new();
4170        let mut counter = ScopedCounter {
4171            value: 17,
4172            calls: Cell::new(0),
4173        };
4174
4175        let observed: i64 = lua
4176            .scope(|scope| {
4177                let ud = scope.create_userdata_ref_mut(&lua, &mut counter)?;
4178                lua.globals().set("c", &ud)?;
4179                lua.load("return c:call_get_via_global()").eval::<i64>()
4180            })
4181            .expect("nested shared borrows should succeed");
4182        assert_eq!(observed, 17);
4183        assert_eq!(counter.calls.get(), 1);
4184    }
4185
4186    /// Field methods route through `create_scoped_userdata_method`/`_mut` via
4187    /// the registry's `RegistryMode::Scoped` branch. Verifies that path is
4188    /// wired correctly for both get and set.
4189    #[test]
4190    fn scope_userdata_field_methods_get_and_set() {
4191        let lua = Lua::new();
4192        let mut bag = ScopedFielded { n: 3 };
4193
4194        let read_back: i64 = lua
4195            .scope(|scope| {
4196                let ud = scope.create_userdata_ref_mut(&lua, &mut bag)?;
4197                lua.globals().set("f", &ud)?;
4198                lua.load("f.n = f.n + 39; return f.n").eval::<i64>()
4199            })
4200            .expect("field methods should dispatch");
4201        assert_eq!(read_back, 42);
4202        assert_eq!(bag.n, 42);
4203    }
4204
4205    /// Meta-methods (`__index`/`__newindex` written by hand on a type) must
4206    /// also route through the scoped path.
4207    #[test]
4208    fn scope_userdata_meta_methods_dispatch() {
4209        let lua = Lua::new();
4210        let mut bag = ScopedBag { value: 100 };
4211
4212        let read: i64 = lua
4213            .scope(|scope| {
4214                let ud = scope.create_userdata_ref_mut(&lua, &mut bag)?;
4215                lua.globals().set("b", &ud)?;
4216                lua.load("b.value = 200; return b.value").eval::<i64>()
4217            })
4218            .expect("scoped meta-methods should dispatch");
4219        assert_eq!(read, 200);
4220        assert_eq!(bag.value, 200);
4221    }
4222
4223    /// Multiple scoped userdatas of the *same* type in one scope are
4224    /// independent: each call routes to the correct cell.
4225    #[test]
4226    fn scope_userdata_multiple_borrows_same_type_in_one_scope() {
4227        let lua = Lua::new();
4228        let mut a = ScopedCounter {
4229            value: 1,
4230            calls: Cell::new(0),
4231        };
4232        let mut b = ScopedCounter {
4233            value: 100,
4234            calls: Cell::new(0),
4235        };
4236
4237        lua.scope(|scope| {
4238            let ua = scope.create_userdata_ref_mut(&lua, &mut a)?;
4239            let ub = scope.create_userdata_ref_mut(&lua, &mut b)?;
4240            lua.globals().set("a", &ua)?;
4241            lua.globals().set("b", &ub)?;
4242            lua.load("a:inc(10); b:inc(1)").exec()
4243        })
4244        .expect("scope body should succeed");
4245        assert_eq!(a.value, 11);
4246        assert_eq!(b.value, 101);
4247    }
4248
4249    /// Different types in one scope share the scope's invalidation but live
4250    /// in independent metatables; both must work.
4251    #[test]
4252    fn scope_userdata_different_types_coexist_in_one_scope() {
4253        let lua = Lua::new();
4254        let mut counter = ScopedCounter {
4255            value: 0,
4256            calls: Cell::new(0),
4257        };
4258        let mut bag = ScopedBag { value: 0 };
4259
4260        lua.scope(|scope| {
4261            let uc = scope.create_userdata_ref_mut(&lua, &mut counter)?;
4262            let ub = scope.create_userdata_ref_mut(&lua, &mut bag)?;
4263            lua.globals().set("c", &uc)?;
4264            lua.globals().set("b", &ub)?;
4265            lua.load("c:inc(7); b.value = 13").exec()
4266        })
4267        .expect("scope body should succeed");
4268        assert_eq!(counter.value, 7);
4269        assert_eq!(bag.value, 13);
4270    }
4271
4272    /// `Lua::scope` threads its closure's return value out — used for
4273    /// extracting Lua results without leaking them through globals.
4274    #[test]
4275    fn scope_userdata_scope_returns_closure_value() {
4276        let lua = Lua::new();
4277        let mut counter = ScopedCounter {
4278            value: 4,
4279            calls: Cell::new(0),
4280        };
4281
4282        let doubled: i64 = lua
4283            .scope(|scope| {
4284                let ud = scope.create_userdata_ref_mut(&lua, &mut counter)?;
4285                lua.globals().set("c", &ud)?;
4286                lua.load("return c:inc(c:get())").eval::<i64>()
4287            })
4288            .expect("scope body should succeed");
4289        assert_eq!(doubled, 8);
4290        assert_eq!(counter.value, 8);
4291    }
4292
4293    /// A scoped userdata invalidated by its scope still keeps the
4294    /// `host_value` Rc alive on the userdata; calling it from a *different*
4295    /// `Lua` instance (which doesn't own this cell) is independently rejected
4296    /// by `scoped_userdata_cell`'s state check. We cannot fully test the
4297    /// cross-state case because `globals().set` requires the same Lua, but we
4298    /// can verify the cached scoped metatable is per-state: building a fresh
4299    /// `Lua` doesn't see the prior state's metatable cache.
4300    #[test]
4301    fn scope_userdata_metatable_cache_is_per_state() {
4302        let lua_a = Lua::new();
4303        let lua_b = Lua::new();
4304        let mut a = ScopedCounter {
4305            value: 1,
4306            calls: Cell::new(0),
4307        };
4308        let mut b = ScopedCounter {
4309            value: 2,
4310            calls: Cell::new(0),
4311        };
4312
4313        lua_a
4314            .scope(|scope| {
4315                let _ud = scope.create_userdata_ref_mut(&lua_a, &mut a)?;
4316                Ok(())
4317            })
4318            .expect("scope on A should succeed");
4319        lua_b
4320            .scope(|scope| {
4321                let _ud = scope.create_userdata_ref_mut(&lua_b, &mut b)?;
4322                Ok(())
4323            })
4324            .expect("scope on B should succeed");
4325
4326        let cache_a_len = lua_a.inner.userdata_scoped_metatables.borrow().len();
4327        let cache_b_len = lua_b.inner.userdata_scoped_metatables.borrow().len();
4328        assert_eq!(cache_a_len, 1);
4329        assert_eq!(cache_b_len, 1);
4330    }
4331
4332    /// The scoped-metatable cache must not be repopulated on every scope:
4333    /// a second scope of the same type re-uses the metatable built by the
4334    /// first. Confirms the `match cached { Some(mt) => mt, None => ... }`
4335    /// branch in `create_scoped_userdata`.
4336    #[test]
4337    fn scope_userdata_metatable_is_built_once_per_type() {
4338        let lua = Lua::new();
4339        let mut a = ScopedCounter {
4340            value: 0,
4341            calls: Cell::new(0),
4342        };
4343        let mut b = ScopedCounter {
4344            value: 0,
4345            calls: Cell::new(0),
4346        };
4347
4348        lua.scope(|scope| {
4349            let _ud = scope.create_userdata_ref_mut(&lua, &mut a)?;
4350            Ok(())
4351        })
4352        .expect("first scope should succeed");
4353        let after_first = lua.inner.userdata_scoped_metatables.borrow().len();
4354
4355        lua.scope(|scope| {
4356            let _ud = scope.create_userdata_ref_mut(&lua, &mut b)?;
4357            Ok(())
4358        })
4359        .expect("second scope should succeed");
4360        let after_second = lua.inner.userdata_scoped_metatables.borrow().len();
4361
4362        assert_eq!(after_first, 1);
4363        assert_eq!(after_second, 1);
4364    }
4365
4366    /// Rust-side shared borrow of a scoped userdata works inside the scope.
4367    #[test]
4368    fn scope_userdata_rust_side_scoped_borrow_inside_scope() {
4369        let lua = Lua::new();
4370        let mut counter = ScopedCounter {
4371            value: 21,
4372            calls: Cell::new(0),
4373        };
4374
4375        let observed = lua
4376            .scope(|scope| {
4377                let ud = scope.create_userdata_ref_mut(&lua, &mut counter)?;
4378                ud.scoped_borrow::<ScopedCounter, _>(|c| c.value)
4379            })
4380            .expect("scoped_borrow should succeed inside scope");
4381        assert_eq!(observed, 21);
4382    }
4383
4384    /// Rust-side mut borrow of a scoped userdata mutates the source.
4385    #[test]
4386    fn scope_userdata_rust_side_scoped_borrow_mut_inside_scope() {
4387        let lua = Lua::new();
4388        let mut counter = ScopedCounter {
4389            value: 0,
4390            calls: Cell::new(0),
4391        };
4392
4393        lua.scope(|scope| {
4394            let ud = scope.create_userdata_ref_mut(&lua, &mut counter)?;
4395            ud.scoped_borrow_mut::<ScopedCounter, _>(|c| c.value = 5)
4396        })
4397        .expect("scoped_borrow_mut should succeed");
4398        assert_eq!(counter.value, 5);
4399    }
4400
4401    /// The headline FFI-side guarantee: an `AnyUserData` smuggled out of its
4402    /// scope cannot hand out a `&T` from Rust either. Cell invalidation drives
4403    /// both sides; this test pins it down on the Rust side.
4404    #[test]
4405    fn scope_userdata_rust_side_borrow_after_scope_errors() {
4406        let lua = Lua::new();
4407        let mut counter = ScopedCounter {
4408            value: 7,
4409            calls: Cell::new(0),
4410        };
4411
4412        let leaked: AnyUserData = lua
4413            .scope(|scope| scope.create_userdata_ref_mut(&lua, &mut counter))
4414            .expect("scope body should succeed");
4415
4416        let err = leaked
4417            .scoped_borrow::<ScopedCounter, _>(|c| c.value)
4418            .expect_err("post-scope Rust borrow must fail");
4419        let msg = runtime_error_message(&err);
4420        assert!(
4421            msg.contains("no longer valid") || msg.contains("scope has ended"),
4422            "expected invalidation error, got: {msg}"
4423        );
4424
4425        let err = leaked
4426            .scoped_borrow_mut::<ScopedCounter, _>(|c| c.value = 99)
4427            .expect_err("post-scope Rust mut-borrow must fail");
4428        let msg = runtime_error_message(&err);
4429        assert!(
4430            msg.contains("no longer valid") || msg.contains("scope has ended"),
4431            "expected invalidation error, got: {msg}"
4432        );
4433
4434        assert_eq!(counter.value, 7, "the borrow must not have been touched");
4435    }
4436
4437    /// The owned `AnyUserData::borrow`/`with_borrow` path is for
4438    /// `Lua::create_userdata` (Rc<UserDataCell<T>> host); calling it against a
4439    /// scoped userdata downcasts cleanly to None and errors. This is a safety
4440    /// claim worth pinning explicitly: the owned path cannot accidentally
4441    /// reach into a scoped cell.
4442    #[test]
4443    fn scope_userdata_owned_borrow_path_rejects_scoped_cells() {
4444        let lua = Lua::new();
4445        let mut counter = ScopedCounter {
4446            value: 1,
4447            calls: Cell::new(0),
4448        };
4449
4450        let err = lua
4451            .scope(|scope| {
4452                let ud = scope.create_userdata_ref_mut(&lua, &mut counter)?;
4453                Ok(ud.with_borrow::<ScopedCounter, _>(|c| c.value))
4454            })
4455            .expect("scope body should succeed")
4456            .expect_err("owned borrow path must not reach a scoped cell");
4457        let msg = runtime_error_message(&err);
4458        assert!(
4459            msg.contains("type mismatch"),
4460            "expected type-mismatch error, got: {msg}"
4461        );
4462    }
4463
4464    /// And the reverse: an owned (`Lua::create_userdata`) AnyUserData rejects
4465    /// `scoped_borrow`. Confirms the two paths are isolated.
4466    #[test]
4467    fn scope_userdata_scoped_borrow_rejects_owned_cells() {
4468        let lua = Lua::new();
4469        let ud = lua
4470            .create_userdata(ScopedCounter {
4471                value: 5,
4472                calls: Cell::new(0),
4473            })
4474            .expect("owned userdata should create");
4475
4476        let err = ud
4477            .scoped_borrow::<ScopedCounter, _>(|c| c.value)
4478            .expect_err("scoped borrow must not reach an owned cell");
4479        let msg = runtime_error_message(&err);
4480        assert!(
4481            msg.contains("type mismatch"),
4482            "expected type-mismatch error, got: {msg}"
4483        );
4484    }
4485
4486    /// `scope.create_function` accepts a closure that captures by reference
4487    /// from the surrounding stack frame; calling it from Lua sees the live
4488    /// borrow. Mirrors the userdata-side basic test, but for closures.
4489    #[test]
4490    fn scope_function_captures_borrow_and_is_callable_from_lua() {
4491        let lua = Lua::new();
4492        let mut acc: i64 = 0;
4493
4494        let total: i64 = lua
4495            .scope(|scope| {
4496                let f = scope.create_function_mut(&lua, |_lua, n: i64| {
4497                    acc += n;
4498                    Ok(acc)
4499                })?;
4500                lua.globals().set("add", &f)?;
4501                lua.load("add(2); add(3); return add(5)").eval::<i64>()
4502            })
4503            .expect("scoped function should dispatch");
4504        assert_eq!(total, 10);
4505        assert_eq!(acc, 10);
4506    }
4507
4508    /// The closure body sees borrowed state across multiple invocations
4509    /// inside one scope — verifies the closure isn't being re-built per call.
4510    #[test]
4511    fn scope_function_calls_share_one_closure() {
4512        let lua = Lua::new();
4513        let counts = Cell::new(0u32);
4514
4515        lua.scope(|scope| {
4516            let f = scope.create_function(&lua, |_lua, ()| {
4517                counts.set(counts.get() + 1);
4518                Ok(())
4519            })?;
4520            lua.globals().set("tick", &f)?;
4521            lua.load("for _ = 1, 4 do tick() end").exec()
4522        })
4523        .expect("scope should succeed");
4524        assert_eq!(counts.get(), 4);
4525    }
4526
4527    /// Headline safety property for functions: a `Function` smuggled past its
4528    /// scope must error cleanly when called, not reach into the dropped
4529    /// closure.
4530    #[test]
4531    fn scope_function_invalidated_after_scope_returns_runtime_error() {
4532        let lua = Lua::new();
4533        let mut acc: i64 = 0;
4534
4535        lua.scope(|scope| {
4536            let f = scope.create_function_mut(&lua, |_lua, n: i64| {
4537                acc += n;
4538                Ok(acc)
4539            })?;
4540            lua.globals().set("add", &f)?;
4541            lua.load("add(1)").exec()
4542        })
4543        .expect("scope body should succeed");
4544        assert_eq!(acc, 1);
4545
4546        let err = lua
4547            .load("return add(100)")
4548            .eval::<i64>()
4549            .expect_err("post-scope call must fail");
4550        let msg = runtime_error_message(&err);
4551        assert!(
4552            msg.contains("no longer valid") || msg.contains("scope has ended"),
4553            "expected invalidation error, got: {msg}"
4554        );
4555        assert_eq!(acc, 1, "the closure's borrow must not have been touched");
4556    }
4557
4558    /// FnMut re-entry: if the closure calls back into Lua which calls itself,
4559    /// the inner `try_borrow_mut` on the closure's `RefCell` must reject the
4560    /// nested call rather than producing aliasing `&mut` captures.
4561    #[test]
4562    fn scope_function_reentrant_fnmut_is_rejected() {
4563        let lua = Lua::new();
4564        let mut count: i64 = 0;
4565
4566        let err = lua
4567            .scope(|scope| {
4568                let f = scope.create_function_mut(&lua, |lua, ()| {
4569                    count += 1;
4570                    if count < 2 {
4571                        lua.load("recurse()").exec()?;
4572                    }
4573                    Ok(())
4574                })?;
4575                lua.globals().set("recurse", &f)?;
4576                lua.load("recurse()").exec()
4577            })
4578            .expect_err("re-entrant FnMut must error");
4579        let msg = runtime_error_message(&err);
4580        assert!(
4581            msg.contains("already borrowed"),
4582            "expected FnMut-conflict error, got: {msg}"
4583        );
4584    }
4585
4586    /// Pairing test: a scoped userdata and a scoped function in the same
4587    /// scope can both borrow from the same stack frame (different parts of
4588    /// it). Models the Bevy use case: `&mut World` userdata plus a few
4589    /// closures that look at adjacent locals.
4590    #[test]
4591    fn scope_function_and_userdata_in_same_scope() {
4592        let lua = Lua::new();
4593        let mut bag = ScopedFielded { n: 0 };
4594        let log = Cell::new(0i64);
4595
4596        lua.scope(|scope| {
4597            let ud = scope.create_userdata_ref_mut(&lua, &mut bag)?;
4598            let logger = scope.create_function(&lua, |_lua, n: i64| {
4599                log.set(log.get() + n);
4600                Ok(())
4601            })?;
4602            lua.globals().set("b", &ud)?;
4603            lua.globals().set("log", &logger)?;
4604            lua.load("b.n = 42; log(b.n); log(b.n + 1)").exec()
4605        })
4606        .expect("mixed scope body should succeed");
4607        assert_eq!(bag.n, 42);
4608        assert_eq!(log.get(), 85);
4609    }
4610
4611    /// Even if the scope body errors before returning, the scoped function is
4612    /// still invalidated so a follow-up Lua call cannot resurrect the dead
4613    /// closure.
4614    #[test]
4615    fn scope_function_invalidated_even_when_body_errors() {
4616        let lua = Lua::new();
4617        let value = Cell::new(5i64);
4618
4619        let _err = lua
4620            .scope(|scope| -> Result<()> {
4621                let f = scope.create_function(&lua, |_lua, ()| Ok(value.get()))?;
4622                lua.globals().set("get", &f)?;
4623                Err(LuaError::runtime(format_args!("aborting")))
4624            })
4625            .expect_err("scope body should propagate error");
4626
4627        let err = lua
4628            .load("return get()")
4629            .eval::<i64>()
4630            .expect_err("function must be invalidated after error-exit scope");
4631        let msg = runtime_error_message(&err);
4632        assert!(
4633            msg.contains("no longer valid") || msg.contains("scope has ended"),
4634            "expected invalidation error, got: {msg}"
4635        );
4636    }
4637
4638    /// Many functions in one scope, all calling into shared borrowed state.
4639    /// Stresses the invalidator list ordering: every closure must remain
4640    /// callable until the scope ends, and all are invalidated together.
4641    #[test]
4642    fn scope_function_many_closures_in_one_scope() {
4643        let lua = Lua::new();
4644        let total = Cell::new(0i64);
4645        let total_ref = &total;
4646
4647        lua.scope(|scope| {
4648            for i in 1..=8 {
4649                let f = scope.create_function(&lua, move |_lua, ()| {
4650                    total_ref.set(total_ref.get() + i);
4651                    Ok(())
4652                })?;
4653                lua.globals().set(format!("f{}", i).as_str(), &f)?;
4654            }
4655            lua.load("f1(); f2(); f3(); f4(); f5(); f6(); f7(); f8()").exec()
4656        })
4657        .expect("scope with many closures should succeed");
4658        assert_eq!(total.get(), 36);
4659    }
4660
4661    /// If the closure body returns an error, the scope still drops and
4662    /// invalidates everything it created. We confirm by then using the
4663    /// leaked global from a follow-up call — it must report invalidated, not
4664    /// stale-but-alive.
4665    #[test]
4666    fn scope_userdata_invalidated_even_when_body_errors() {
4667        let lua = Lua::new();
4668        let mut counter = ScopedCounter {
4669            value: 1,
4670            calls: Cell::new(0),
4671        };
4672
4673        let err = lua
4674            .scope(|scope| -> Result<()> {
4675                let ud = scope.create_userdata_ref_mut(&lua, &mut counter)?;
4676                lua.globals().set("c", &ud)?;
4677                Err(LuaError::runtime(format_args!("aborting scope")))
4678            })
4679            .expect_err("scope body should propagate error");
4680        let _ = err;
4681
4682        let leaked_err = lua
4683            .load("return c:get()")
4684            .eval::<i64>()
4685            .expect_err("leaked userdata must still be invalidated");
4686        let msg = runtime_error_message(&leaked_err);
4687        assert!(
4688            msg.contains("no longer valid") || msg.contains("scope has ended"),
4689            "expected invalidation error after scope-with-error, got: {msg}"
4690        );
4691    }
4692
4693    /// Cloning an `AnyUserData` produces two handles to the same scope cell.
4694    /// Invalidation runs against the cell, so a clone that escapes via a
4695    /// global must fail at the same point a direct handle would. Pins the
4696    /// "every reference to the same cell sees invalidation together"
4697    /// invariant.
4698    #[test]
4699    fn scope_userdata_cloned_handles_invalidate_together() {
4700        let lua = Lua::new();
4701        let mut counter = ScopedCounter {
4702            value: 9,
4703            calls: Cell::new(0),
4704        };
4705
4706        lua.scope(|scope| {
4707            let ud = scope.create_userdata_ref_mut(&lua, &mut counter)?;
4708            let clone = ud.clone();
4709            lua.globals().set("a", &ud)?;
4710            lua.globals().set("b", &clone)?;
4711            lua.load("assert(a:get() == 9); assert(b:get() == 9)").exec()
4712        })
4713        .expect("scope body should succeed");
4714
4715        let err_a = lua
4716            .load("return a:get()")
4717            .eval::<i64>()
4718            .expect_err("original handle must error post-scope");
4719        let err_b = lua
4720            .load("return b:get()")
4721            .eval::<i64>()
4722            .expect_err("cloned handle must error post-scope");
4723        assert!(runtime_error_message(&err_a).contains("no longer valid"));
4724        assert!(runtime_error_message(&err_b).contains("no longer valid"));
4725    }
4726
4727    /// Nested `Lua::scope` calls: cells created in the inner scope invalidate
4728    /// when the inner returns; cells in the outer remain live until the outer
4729    /// returns. Pins that scope cells don't leak across siblings/parents.
4730    #[test]
4731    fn scope_userdata_nested_scopes_isolated() {
4732        let lua = Lua::new();
4733        let mut outer_counter = ScopedCounter {
4734            value: 1,
4735            calls: Cell::new(0),
4736        };
4737        let mut inner_counter = ScopedCounter {
4738            value: 100,
4739            calls: Cell::new(0),
4740        };
4741
4742        lua.scope(|outer| {
4743            let o = outer.create_userdata_ref_mut(&lua, &mut outer_counter)?;
4744            lua.globals().set("outer", &o)?;
4745
4746            lua.scope(|inner| {
4747                let i = inner.create_userdata_ref_mut(&lua, &mut inner_counter)?;
4748                lua.globals().set("inner", &i)?;
4749                lua.load("assert(outer:get() == 1); assert(inner:get() == 100)").exec()
4750            })?;
4751
4752            // Inner ended. `inner` global is dead, but `outer` is still live.
4753            let inner_err = lua
4754                .load("return inner:get()")
4755                .eval::<i64>()
4756                .expect_err("inner userdata must be dead after inner scope");
4757            assert!(runtime_error_message(&inner_err).contains("no longer valid"));
4758
4759            let outer_alive: i64 = lua
4760                .load("return outer:get()")
4761                .eval()
4762                .expect("outer userdata must still be alive in outer scope");
4763            assert_eq!(outer_alive, 1);
4764            Ok(())
4765        })
4766        .expect("scope body should succeed");
4767
4768        // Outer ended; both should now be dead.
4769        let err = lua
4770            .load("return outer:get()")
4771            .eval::<i64>()
4772            .expect_err("outer userdata must be dead after outer scope");
4773        assert!(runtime_error_message(&err).contains("no longer valid"));
4774    }
4775
4776    // -- Direct exercises of the unsafe machinery, no Lua state --
4777    //
4778    // These tests bypass the full `Lua::scope` plumbing and poke `ScopedCell`
4779    // / `ScopedFnCell` directly. They exist so `cargo miri test scope_cell_`
4780    // can validate the scope unsafe surface in isolation. The full suite
4781    // still routes through the rest of the runtime, which currently has
4782    // pre-existing aliasing violations under Miri (lua-gc raw-pointer
4783    // patterns, unrelated to scope); these direct tests are the
4784    // miri-runnable subset.
4785
4786    #[test]
4787    fn scope_cell_shared_then_shared_succeeds() {
4788        let mut data = 17_i32;
4789        let cell = ScopedCell::<i32>::new(&mut data);
4790
4791        let a = cell.try_borrow().expect("first shared borrow");
4792        let b = cell.try_borrow().expect("second shared borrow");
4793        assert_eq!(*a, 17);
4794        assert_eq!(*b, 17);
4795        drop(a);
4796        drop(b);
4797
4798        cell.invalidate();
4799        assert!(cell.try_borrow().is_err(), "post-invalidate must fail");
4800    }
4801
4802    #[test]
4803    fn scope_cell_mut_then_shared_fails() {
4804        let mut data = 5_i32;
4805        let cell = ScopedCell::<i32>::new(&mut data);
4806
4807        let mut m = cell.try_borrow_mut().expect("first mut borrow");
4808        *m = 42;
4809        let s = cell.try_borrow();
4810        assert!(s.is_err(), "shared borrow while mut-held must fail");
4811        drop(m);
4812
4813        let s = cell.try_borrow().expect("shared borrow after mut release");
4814        assert_eq!(*s, 42);
4815    }
4816
4817    #[test]
4818    fn scope_cell_shared_then_mut_fails() {
4819        let mut data = 99_i32;
4820        let cell = ScopedCell::<i32>::new(&mut data);
4821
4822        let s = cell.try_borrow().expect("first shared borrow");
4823        let m = cell.try_borrow_mut();
4824        assert!(m.is_err(), "mut borrow while shared-held must fail");
4825        drop(s);
4826
4827        let mut m = cell.try_borrow_mut().expect("mut borrow after shared release");
4828        *m = 100;
4829        drop(m);
4830        assert_eq!(data, 100);
4831    }
4832
4833    #[test]
4834    fn scope_cell_invalidate_after_drop_of_guards_is_clean() {
4835        let mut data = String::from("hi");
4836        let cell = ScopedCell::<String>::new(&mut data);
4837        {
4838            let guard = cell.try_borrow().expect("borrow");
4839            assert_eq!(&*guard, "hi");
4840        }
4841        cell.invalidate();
4842        assert!(cell.try_borrow().is_err());
4843        assert!(cell.try_borrow_mut().is_err());
4844    }
4845
4846    #[test]
4847    fn scope_cell_drop_guard_decrements_borrow_count() {
4848        let mut data = 0_i32;
4849        let cell = ScopedCell::<i32>::new(&mut data);
4850        {
4851            let _a = cell.try_borrow().expect("a");
4852            let _b = cell.try_borrow().expect("b");
4853            assert!(cell.try_borrow_mut().is_err());
4854        }
4855        cell.try_borrow_mut().expect("mut borrow once guards drop");
4856    }
4857
4858    #[test]
4859    fn scope_fn_cell_dispatches_and_invalidates() {
4860        let counter = Cell::new(0i64);
4861        let adapter: Box<dyn Fn(&Lua, Vec<Value>) -> Result<Vec<Value>>> =
4862            Box::new(|_lua, _args| Ok(Vec::new()));
4863        let cell = Rc::new(ScopedFnCell {
4864            boxed: RefCell::new(Some(adapter)),
4865        });
4866
4867        let lua = Lua::new();
4868        cell.try_call(&lua, Vec::new()).expect("pre-invalidate call");
4869        counter.set(counter.get() + 1);
4870
4871        cell.invalidate();
4872
4873        let err = cell
4874            .try_call(&lua, Vec::new())
4875            .expect_err("post-invalidate call must fail");
4876        let msg = runtime_error_message(&err);
4877        assert!(msg.contains("no longer valid"), "got: {msg}");
4878        assert_eq!(counter.get(), 1);
4879    }
4880}