Skip to main content

lua_rs_runtime/
lib.rs

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