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