Skip to main content

lua_rs_runtime/
lib.rs

1//! Embedding helper for lua-rs.
2//!
3//! This crate sits above `lua-vm`, `lua-stdlib`, and `lua-parse` and exposes a
4//! handle-based embedding API: a [`Lua`] state, typed [`Value`] / [`Table`] /
5//! [`Function`] handles that root themselves via RAII, [`UserData`] for binding
6//! Rust types, and a typed [`LuaError`]. It also provides the common setup
7//! sequence (state, parser hook, host hooks, stdlib).
8//!
9//! # Userdata model
10//!
11//! Userdata behavior in lua-rs runs through real Lua metatables, exactly as in
12//! reference Lua 5.4. The runtime builds the metatable for a type once, on the
13//! first [`Lua::create_userdata`] for that `TypeId`, permanently roots it on
14//! the state, and shares it across every later value of the type. This keeps
15//! `getmetatable`, `setmetatable`, `rawget`, `debug.setmetatable`, and every
16//! other reflective Lua operation behaving as in C Lua, which is what lets
17//! lua-rs pass the upstream 5.4 test suite and stand in for C Lua in real
18//! embedders.
19//!
20//! Fields and methods both live on that single metatable. Register fields with
21//! [`UserDataMethods::add_field_method_get`] / `add_field_method_set` and
22//! methods with [`UserDataMethods::add_method`] / `add_method_mut`. The runtime
23//! composes a single `__index` whose lookup order is field, then method, then
24//! a raw `add_meta_method(MetaMethod::Index, ...)` if you registered one as an
25//! escape hatch, with the symmetric composition on `__newindex`.
26//!
27//! # Derive
28//!
29//! Enable the `derive` feature for `#[derive(LuaUserData)]`, `#[lua_methods]`,
30//! and `#[lua_impl(Display, PartialEq, PartialOrd)]`. The derive targets the
31//! field API above; `#[lua_methods]` exposes each `pub fn(&self / &mut self,
32//! ...)` as `obj:method(args)`; `#[lua_impl(...)]` wires `__tostring`, `__eq`,
33//! `__lt`, and `__le` from the type's Rust trait impls.
34//!
35//! ```ignore
36//! use lua_rs_runtime::{lua_methods, Lua, LuaUserData};
37//!
38//! #[derive(LuaUserData, PartialEq, PartialOrd)]
39//! #[lua(methods)]
40//! #[lua_impl(Display, PartialEq, PartialOrd)]
41//! struct Vec2 { pub x: f64, pub y: f64 }
42//!
43//! #[lua_methods]
44//! impl Vec2 {
45//!     pub fn length(&self) -> f64 { (self.x * self.x + self.y * self.y).sqrt() }
46//!     pub fn scale(&mut self, k: f64) { self.x *= k; self.y *= k; }
47//! }
48//! ```
49//!
50//! # Known limitations and planned work
51//!
52//! - `#[lua_methods]` does not yet special-case methods that return
53//!   `Result<T, E>`, associated functions and constructors (`Type::new`), or
54//!   `Option<T>` parameters and returns.
55//! - The derive does not yet handle enums (a `register_enum::<T>()` path) or
56//!   the iteration, `__close`, and arithmetic metamethods. The runtime already
57//!   supports adding these as ordinary `add_meta_method` registrations today.
58
59use std::any::{Any, TypeId};
60use std::cell::{Cell, Ref, RefCell, RefMut};
61use std::collections::HashMap;
62use std::ffi::c_void;
63use std::fmt;
64use std::hash::Hash;
65use std::ops::{Deref, DerefMut};
66use std::panic::{catch_unwind, AssertUnwindSafe};
67use std::rc::Rc;
68
69use lua_stdlib::auxlib::load_buffer;
70use lua_stdlib::init::open_libs;
71use lua_types::closure::{LuaCClosure as RawLuaCClosure, LuaClosure as RawLuaClosure, LuaLClosure};
72use lua_types::gc::GcRef;
73use lua_types::string::LuaString as RawLuaString;
74use lua_types::upval::UpVal;
75use lua_types::userdata::LuaUserData as RawLuaUserData;
76use lua_types::value::{LuaTable as RawLuaTable, LuaValue as RawLuaValue};
77use lua_vm::state::{
78    new_state, CpuClockHook, DynLibLoadHook, DynLibSymbolHook, DynLibUnloadHook, EntropyHook,
79    EnvHook, ExternalRootKey, FileLoaderHook, FileOpenHook, FileRemoveHook, FileRenameHook,
80    InputHook, LuaCallable, LuaRustFunction, LuaState, OsExecuteHook, OutputHook, PopenHook,
81    TempNameHook, UnixTimeHook,
82};
83
84pub use lua_types::{LuaError, LuaFileHandle};
85pub use lua_vm::state::{DynLibId, DynamicSymbol, OsExecuteReason, OsExecuteResult};
86
87#[cfg(feature = "derive")]
88pub use lua_rs_derive::{lua_methods, LuaUserData};
89
90pub type Error = LuaError;
91pub type Result<T> = std::result::Result<T, Error>;
92
93/// Host capabilities exposed to Lua stdlib.
94///
95/// Every field is optional. Missing file, process, and dynamic-loading hooks
96/// produce Lua errors or Lua failure tuples. On bare `wasm32-unknown-unknown`,
97/// missing stdio/time/env/temp hooks avoid unsupported Rust `std` stubs and fail
98/// at the Lua boundary. Native builds may still use compatibility fallbacks for
99/// some stdio and OS functions when hooks are absent.
100#[derive(Clone, Copy, Default)]
101pub struct HostHooks {
102    pub file_loader_hook: Option<FileLoaderHook>,
103    pub file_open_hook: Option<FileOpenHook>,
104    pub stdin_hook: Option<InputHook>,
105    pub stdout_hook: Option<OutputHook>,
106    pub stderr_hook: Option<OutputHook>,
107    pub env_hook: Option<EnvHook>,
108    pub unix_time_hook: Option<UnixTimeHook>,
109    pub cpu_clock_hook: Option<CpuClockHook>,
110    pub entropy_hook: Option<EntropyHook>,
111    pub temp_name_hook: Option<TempNameHook>,
112    pub popen_hook: Option<PopenHook>,
113    pub file_remove_hook: Option<FileRemoveHook>,
114    pub file_rename_hook: Option<FileRenameHook>,
115    pub os_execute_hook: Option<OsExecuteHook>,
116    pub dynlib_load_hook: Option<DynLibLoadHook>,
117    pub dynlib_symbol_hook: Option<DynLibSymbolHook>,
118    pub dynlib_unload_hook: Option<DynLibUnloadHook>,
119}
120
121impl HostHooks {
122    pub fn new() -> Self {
123        Self::default()
124    }
125
126    pub fn file_loader(mut self, hook: FileLoaderHook) -> Self {
127        self.file_loader_hook = Some(hook);
128        self
129    }
130
131    pub fn file_open(mut self, hook: FileOpenHook) -> Self {
132        self.file_open_hook = Some(hook);
133        self
134    }
135
136    pub fn stdin(mut self, hook: InputHook) -> Self {
137        self.stdin_hook = Some(hook);
138        self
139    }
140
141    pub fn stdout(mut self, hook: OutputHook) -> Self {
142        self.stdout_hook = Some(hook);
143        self
144    }
145
146    pub fn stderr(mut self, hook: OutputHook) -> Self {
147        self.stderr_hook = Some(hook);
148        self
149    }
150
151    pub fn env(mut self, hook: EnvHook) -> Self {
152        self.env_hook = Some(hook);
153        self
154    }
155
156    pub fn unix_time(mut self, hook: UnixTimeHook) -> Self {
157        self.unix_time_hook = Some(hook);
158        self
159    }
160
161    pub fn cpu_clock(mut self, hook: CpuClockHook) -> Self {
162        self.cpu_clock_hook = Some(hook);
163        self
164    }
165
166    pub fn entropy(mut self, hook: EntropyHook) -> Self {
167        self.entropy_hook = Some(hook);
168        self
169    }
170
171    pub fn temp_name(mut self, hook: TempNameHook) -> Self {
172        self.temp_name_hook = Some(hook);
173        self
174    }
175
176    pub fn popen(mut self, hook: PopenHook) -> Self {
177        self.popen_hook = Some(hook);
178        self
179    }
180
181    pub fn file_remove(mut self, hook: FileRemoveHook) -> Self {
182        self.file_remove_hook = Some(hook);
183        self
184    }
185
186    pub fn file_rename(mut self, hook: FileRenameHook) -> Self {
187        self.file_rename_hook = Some(hook);
188        self
189    }
190
191    pub fn os_execute(mut self, hook: OsExecuteHook) -> Self {
192        self.os_execute_hook = Some(hook);
193        self
194    }
195
196    pub fn dynlib_load(mut self, hook: DynLibLoadHook) -> Self {
197        self.dynlib_load_hook = Some(hook);
198        self
199    }
200
201    pub fn dynlib_symbol(mut self, hook: DynLibSymbolHook) -> Self {
202        self.dynlib_symbol_hook = Some(hook);
203        self
204    }
205
206    pub fn dynlib_unload(mut self, hook: DynLibUnloadHook) -> Self {
207        self.dynlib_unload_hook = Some(hook);
208        self
209    }
210
211    pub fn install(self, state: &mut LuaState) {
212        let global = &mut *state.global_mut();
213        global.file_loader_hook = self.file_loader_hook;
214        global.file_open_hook = self.file_open_hook;
215        global.stdin_hook = self.stdin_hook;
216        global.stdout_hook = self.stdout_hook;
217        global.stderr_hook = self.stderr_hook;
218        global.env_hook = self.env_hook;
219        global.unix_time_hook = self.unix_time_hook;
220        global.cpu_clock_hook = self.cpu_clock_hook;
221        global.entropy_hook = self.entropy_hook;
222        global.temp_name_hook = self.temp_name_hook;
223        global.popen_hook = self.popen_hook;
224        global.file_remove_hook = self.file_remove_hook;
225        global.file_rename_hook = self.file_rename_hook;
226        global.os_execute_hook = self.os_execute_hook;
227        global.dynlib_load_hook = self.dynlib_load_hook;
228        global.dynlib_symbol_hook = self.dynlib_symbol_hook;
229        global.dynlib_unload_hook = self.dynlib_unload_hook;
230    }
231}
232
233/// Primary owned embedding handle.
234///
235/// `Lua` is intentionally cheap to clone and single-threaded. State access is
236/// borrowed at the embedding boundary only; opcode dispatch still runs with
237/// direct `&mut LuaState` access. Captured Rust callbacks will need a call-path
238/// adapter that releases this boundary borrow before invoking user code.
239#[derive(Clone)]
240pub struct Lua {
241    inner: Rc<LuaInner>,
242}
243
244struct LuaInner {
245    state: RefCell<LuaState>,
246    active_state: Cell<*mut LuaState>,
247    pending_external_unroots: RefCell<Vec<ExternalRootKey>>,
248    /// One metatable per `UserData` type, built on first `create_userdata::<T>`
249    /// and reused for every later value of that type. Each entry is permanently
250    /// rooted in the state's external-root set, so it survives even when no
251    /// instance currently exists, and frees with the state.
252    userdata_metatables: RefCell<HashMap<TypeId, GcRef<RawLuaTable>>>,
253}
254
255struct UserDataCell<T> {
256    value: RefCell<T>,
257}
258
259struct RustCallbackCell {
260    function: LuaRustFunction,
261}
262
263struct ActiveStateGuard<'a> {
264    inner: &'a LuaInner,
265    previous: *mut LuaState,
266}
267
268impl Drop for ActiveStateGuard<'_> {
269    fn drop(&mut self) {
270        self.inner.active_state.set(self.previous);
271    }
272}
273
274impl LuaInner {
275    fn enter_active(&self, state: *mut LuaState) -> ActiveStateGuard<'_> {
276        let previous = self.active_state.replace(state);
277        ActiveStateGuard {
278            inner: self,
279            previous,
280        }
281    }
282
283    fn flush_pending_external_unroots(&self, state: &mut LuaState) {
284        let pending = self.pending_external_unroots.replace(Vec::new());
285        if pending.is_empty() {
286            return;
287        }
288
289        let mut still_pending = Vec::new();
290        for key in pending {
291            if state.try_external_unroot_value(key).is_err() {
292                still_pending.push(key);
293            }
294        }
295
296        if !still_pending.is_empty() {
297            self.pending_external_unroots
298                .borrow_mut()
299                .extend(still_pending);
300        }
301    }
302}
303
304impl fmt::Debug for Lua {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        f.debug_struct("Lua").finish_non_exhaustive()
307    }
308}
309
310impl Lua {
311    /// Create a Lua runtime with parser and standard libraries installed.
312    pub fn new() -> Self {
313        Self::try_new().expect("Lua runtime should initialize")
314    }
315
316    /// Fallible variant of [`Lua::new`].
317    pub fn try_new() -> Result<Self> {
318        Self::with_hooks(HostHooks::default())
319    }
320
321    /// Create a Lua runtime with the supplied host capabilities.
322    pub fn with_hooks(hooks: HostHooks) -> Result<Self> {
323        let mut state = new_state().ok_or(LuaError::Memory)?;
324        install_parser_hook(&mut state);
325        hooks.install(&mut state);
326        open_libs(&mut state)?;
327        Ok(Self::from_initialized_state(state))
328    }
329
330    fn from_initialized_state(state: LuaState) -> Self {
331        Lua {
332            inner: Rc::new(LuaInner {
333                state: RefCell::new(state),
334                active_state: Cell::new(std::ptr::null_mut()),
335                pending_external_unroots: RefCell::new(Vec::new()),
336                userdata_metatables: RefCell::new(HashMap::new()),
337            }),
338        }
339    }
340
341    fn with_state<R>(&self, f: impl FnOnce(&mut LuaState) -> R) -> R {
342        if let Ok(mut state) = self.inner.state.try_borrow_mut() {
343            let _active = self.inner.enter_active(&mut *state);
344            self.inner.flush_pending_external_unroots(&mut state);
345            let result = f(&mut state);
346            self.inner.flush_pending_external_unroots(&mut state);
347            return result;
348        }
349
350        let state = self
351            .active_state_mut()
352            .expect("re-entrant Lua access without an active state");
353        let result = f(state);
354        self.inner.flush_pending_external_unroots(state);
355        result
356    }
357
358    fn active_state_mut(&self) -> Option<&mut LuaState> {
359        let state = self.inner.active_state.get();
360        if state.is_null() {
361            return None;
362        }
363
364        // SAFETY: `active_state` is set only while this `Lua` owns the outer
365        // `RefCell` borrow and is executing VM code. Re-entrant access can only
366        // happen when that VM frame has synchronously transferred control to a
367        // Rust callback and is suspended. The callback path does not touch the
368        // suspended `&mut LuaState` while user code re-enters through `Lua`.
369        Some(unsafe { &mut *state })
370    }
371
372    fn unroot_external_key(&self, key: ExternalRootKey) {
373        let removed = if let Ok(mut state) = self.inner.state.try_borrow_mut() {
374            let _active = self.inner.enter_active(&mut *state);
375            self.inner.flush_pending_external_unroots(&mut state);
376            let removed = state.try_external_unroot_value(key).is_ok();
377            self.inner.flush_pending_external_unroots(&mut state);
378            removed
379        } else {
380            if let Some(state) = self.active_state_mut() {
381                let removed = state.try_external_unroot_value(key).is_ok();
382                self.inner.flush_pending_external_unroots(state);
383                removed
384            } else {
385                false
386            }
387        };
388
389        if !removed {
390            self.inner.pending_external_unroots.borrow_mut().push(key);
391        }
392    }
393
394    fn root_raw(&self, value: RawLuaValue) -> RootedValue {
395        let key = self.with_state(|state| state.external_root_value(value));
396        RootedValue {
397            lua: self.clone(),
398            key,
399        }
400    }
401
402    fn root_raw_in_state(&self, state: &mut LuaState, value: RawLuaValue) -> RootedValue {
403        let key = state.external_root_value(value);
404        RootedValue {
405            lua: self.clone(),
406            key,
407        }
408    }
409
410    fn userdata_cell<'a, T: 'static>(
411        &self,
412        userdata: &'a AnyUserData,
413    ) -> Result<&'a UserDataCell<T>> {
414        if !Rc::ptr_eq(&self.inner, &userdata.root.lua.inner) {
415            return Err(LuaError::runtime(format_args!(
416                "Lua userdata belongs to a different state"
417            )));
418        }
419        userdata.host_cell()
420    }
421
422    /// Load a Lua source chunk.
423    pub fn load(&self, source: impl AsRef<[u8]>) -> Chunk {
424        Chunk {
425            lua: self.clone(),
426            source: source.as_ref().to_vec(),
427            name: b"chunk".to_vec(),
428        }
429    }
430
431    /// Return the global environment table.
432    pub fn globals(&self) -> Table {
433        let raw = self.with_state(|state| state.global().globals.clone());
434        Table {
435            root: self.root_raw(raw),
436        }
437    }
438
439    /// Create a new empty table.
440    pub fn create_table(&self) -> Result<Table> {
441        let root = self.with_state(|state| {
442            let _heap_guard = heap_guard(state);
443            let table = state.new_table();
444            let raw = RawLuaValue::Table(table);
445            let key = state.external_root_value(raw);
446            state.gc().check_step();
447            RootedValue {
448                lua: self.clone(),
449                key,
450            }
451        });
452        Ok(Table { root })
453    }
454
455    /// Create a new Lua string from bytes.
456    pub fn create_string(&self, bytes: impl AsRef<[u8]>) -> Result<LuaString> {
457        let bytes = bytes.as_ref();
458        let root = self.with_state(|state| {
459            let _heap_guard = heap_guard(state);
460            let string = state.new_string(bytes)?;
461            let raw = RawLuaValue::Str(string);
462            let key = state.external_root_value(raw);
463            state.gc().check_step();
464            Ok::<_, LuaError>(RootedValue {
465                lua: self.clone(),
466                key,
467            })
468        })?;
469        Ok(LuaString { root })
470    }
471
472    pub fn create_function<A, R, F>(&self, func: F) -> Result<Function>
473    where
474        A: FromLuaMulti + 'static,
475        R: IntoLuaMulti + 'static,
476        F: Fn(&Lua, A) -> Result<R> + 'static,
477    {
478        let lua_weak = Rc::downgrade(&self.inner);
479        let callable: LuaRustFunction = Rc::new(move |state| {
480            let lua = match lua_weak.upgrade() {
481                Some(inner) => Lua { inner },
482                None => {
483                    return Err(LuaError::runtime(format_args!(
484                        "Lua callback fired after the state was dropped"
485                    )))
486                }
487            };
488            match catch_unwind(AssertUnwindSafe(|| {
489                let args = callback_args(state, &lua)?;
490                let args = A::from_lua_multi(args, &lua)?;
491                let returns = func(&lua, args)?;
492                let returns = returns.into_lua_multi(&lua)?;
493                push_callback_returns(state, &lua, returns)
494            })) {
495                Ok(result) => result,
496                Err(_) => Err(LuaError::runtime(format_args!("Rust callback panicked"))),
497            }
498        });
499        self.create_registered_function(callable)
500    }
501
502    pub fn create_function_mut<A, R, F>(&self, func: F) -> Result<Function>
503    where
504        A: FromLuaMulti + 'static,
505        R: IntoLuaMulti + 'static,
506        F: FnMut(&Lua, A) -> Result<R> + 'static,
507    {
508        let func = RefCell::new(func);
509        self.create_function(move |lua, args| {
510            let mut func = func.try_borrow_mut().map_err(|_| {
511                LuaError::runtime(format_args!("mutable Rust callback is already borrowed"))
512            })?;
513            func(lua, args)
514        })
515    }
516
517    fn create_registered_function(&self, callable: LuaRustFunction) -> Result<Function> {
518        let root = self.with_state(|state| {
519            let trampoline = rust_callback_trampoline as lua_vm::state::LuaCFunction;
520            let idx = {
521                let mut global = state.global_mut();
522                match global.c_functions.iter().position(|existing| {
523                    existing
524                        .as_bare()
525                        .is_some_and(|existing| std::ptr::fn_addr_eq(existing, trampoline))
526                }) {
527                    Some(idx) => idx,
528                    None => {
529                        let idx = global.c_functions.len();
530                        global.c_functions.push(LuaCallable::bare(trampoline));
531                        idx
532                    }
533                }
534            };
535            let raw = with_heap_guard(state, || {
536                let callback_payload = GcRef::new(RawLuaUserData {
537                    data: Box::new([]),
538                    uv: Vec::new(),
539                    metatable: RefCell::new(None),
540                    host_value: RefCell::new(Some(
541                        Rc::new(RustCallbackCell { function: callable }) as Rc<dyn Any>,
542                    )),
543                });
544                RawLuaValue::Function(RawLuaClosure::C(GcRef::new(RawLuaCClosure {
545                    func: idx,
546                    upvalues: vec![RawLuaValue::UserData(callback_payload)],
547                })))
548            });
549            let key = state.external_root_value(raw);
550            state.gc().check_step();
551            RootedValue {
552                lua: self.clone(),
553                key,
554            }
555        });
556        Ok(Function { root })
557    }
558
559    fn create_userdata_method<T, A, R, F>(&self, method: F) -> Result<Function>
560    where
561        T: UserData,
562        A: FromLuaMulti + 'static,
563        R: IntoLuaMulti + 'static,
564        F: Fn(&Lua, &T, A) -> Result<R> + 'static,
565    {
566        let lua_weak = Rc::downgrade(&self.inner);
567        let callable: LuaRustFunction = Rc::new(move |state| {
568            let lua = match lua_weak.upgrade() {
569                Some(inner) => Lua { inner },
570                None => {
571                    return Err(LuaError::runtime(format_args!(
572                        "Lua callback fired after the state was dropped"
573                    )))
574                }
575            };
576            match catch_unwind(AssertUnwindSafe(|| {
577                let (userdata, args) = callback_userdata_args(state, &lua)?;
578                let args = A::from_lua_multi(args, &lua)?;
579                let cell = lua.userdata_cell::<T>(&userdata)?;
580                let value = cell.value.try_borrow().map_err(|_| {
581                    LuaError::runtime(format_args!("userdata is already mutably borrowed"))
582                })?;
583                let returns = method(&lua, &value, args)?;
584                let returns = returns.into_lua_multi(&lua)?;
585                push_callback_returns(state, &lua, returns)
586            })) {
587                Ok(result) => result,
588                Err(_) => Err(LuaError::runtime(format_args!(
589                    "Rust userdata method panicked"
590                ))),
591            }
592        });
593        self.create_registered_function(callable)
594    }
595
596    fn create_userdata_method_mut<T, A, R, F>(&self, method: F) -> Result<Function>
597    where
598        T: UserData,
599        A: FromLuaMulti + 'static,
600        R: IntoLuaMulti + 'static,
601        F: Fn(&Lua, &mut T, A) -> Result<R> + 'static,
602    {
603        let lua_weak = Rc::downgrade(&self.inner);
604        let callable: LuaRustFunction = Rc::new(move |state| {
605            let lua = match lua_weak.upgrade() {
606                Some(inner) => Lua { inner },
607                None => {
608                    return Err(LuaError::runtime(format_args!(
609                        "Lua callback fired after the state was dropped"
610                    )))
611                }
612            };
613            match catch_unwind(AssertUnwindSafe(|| {
614                let (userdata, args) = callback_userdata_args(state, &lua)?;
615                let args = A::from_lua_multi(args, &lua)?;
616                let cell = lua.userdata_cell::<T>(&userdata)?;
617                let mut value = cell
618                    .value
619                    .try_borrow_mut()
620                    .map_err(|_| LuaError::runtime(format_args!("userdata is already borrowed")))?;
621                let returns = method(&lua, &mut value, args)?;
622                let returns = returns.into_lua_multi(&lua)?;
623                push_callback_returns(state, &lua, returns)
624            })) {
625                Ok(result) => result,
626                Err(_) => Err(LuaError::runtime(format_args!(
627                    "Rust userdata method panicked"
628                ))),
629            }
630        });
631        self.create_registered_function(callable)
632    }
633
634    pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData>
635    where
636        T: UserData,
637    {
638        let type_id = TypeId::of::<T>();
639        let cached = self
640            .inner
641            .userdata_metatables
642            .borrow()
643            .get(&type_id)
644            .cloned();
645        let metatable = match cached {
646            Some(metatable) => metatable,
647            None => {
648                let mut methods = UserDataMethodRegistry::<T>::new(self);
649                T::add_methods(&mut methods);
650                T::add_meta_methods(&mut methods);
651                let metatable = methods.build_metatable()?;
652                self.inner
653                    .userdata_metatables
654                    .borrow_mut()
655                    .insert(type_id, metatable.clone());
656                metatable
657            }
658        };
659        self.attach_userdata(data, metatable)
660    }
661
662    /// Wrap `data` in a fresh Lua userdata that shares `metatable` (built once per
663    /// type by [`Lua::create_userdata`]). Only the per-value data cell is allocated
664    /// here; the binding closures live on the shared, cached metatable.
665    fn attach_userdata<T: UserData>(
666        &self,
667        data: T,
668        metatable: GcRef<RawLuaTable>,
669    ) -> Result<AnyUserData> {
670        let cell: Rc<dyn Any> = Rc::new(UserDataCell {
671            value: RefCell::new(data),
672        });
673        let host_value = cell.clone();
674        let root = self.with_state(|state| {
675            let userdata = with_heap_guard(state, || {
676                GcRef::new(RawLuaUserData {
677                    data: Box::new([]),
678                    uv: Vec::new(),
679                    metatable: RefCell::new(None),
680                    host_value: RefCell::new(None),
681                })
682            });
683            userdata.set_metatable(Some(metatable));
684            userdata.set_host_value(Some(cell));
685            let key = state.external_root_value(RawLuaValue::UserData(userdata));
686            RootedValue {
687                lua: self.clone(),
688                key,
689            }
690        });
691        Ok(AnyUserData {
692            root,
693            host_value: Some(host_value),
694        })
695    }
696
697    /// Run a full garbage-collection cycle.
698    pub fn gc_collect(&self) {
699        self.with_state(|state| state.gc().full_collect());
700    }
701}
702
703pub struct Chunk {
704    lua: Lua,
705    source: Vec<u8>,
706    name: Vec<u8>,
707}
708
709impl Chunk {
710    pub fn set_name(mut self, name: impl AsRef<[u8]>) -> Self {
711        self.name = name.as_ref().to_vec();
712        self
713    }
714
715    pub fn exec(self) -> Result<()> {
716        self.lua
717            .with_state(|state| exec_state(state, &self.source, &self.name))
718    }
719
720    pub fn eval<T: FromLuaMulti>(self) -> Result<T> {
721        let raws = self.lua.with_state(|state| {
722            let saved_top = state.top_idx();
723            let status = load_buffer(state, &self.source, &self.name)?;
724            if status != 0 {
725                let err = state.pop();
726                state.set_top_idx(saved_top);
727                return Err(LuaError::from_value(err));
728            }
729            match lua_vm::api::pcall_k(state, 0, T::NRESULTS, 0, 0, None) {
730                Ok(_) => {
731                    let nresults = if T::NRESULTS < 0 {
732                        state.top_idx().0.saturating_sub(saved_top.0) as i32
733                    } else {
734                        T::NRESULTS
735                    };
736                    let mut values = Vec::with_capacity(nresults as usize);
737                    for _ in 0..nresults {
738                        values.push(state.pop());
739                    }
740                    values.reverse();
741                    state.set_top_idx(saved_top);
742                    Ok(values)
743                }
744                Err(err) => {
745                    state.set_top_idx(saved_top);
746                    Err(err)
747                }
748            }
749        })?;
750        let values = raws
751            .into_iter()
752            .map(|raw| Value::from_raw(&self.lua, raw))
753            .collect::<Result<Vec<_>>>()?;
754        T::from_lua_multi(values, &self.lua)
755    }
756}
757
758#[derive(Debug)]
759struct RootedValue {
760    lua: Lua,
761    key: ExternalRootKey,
762}
763
764impl RootedValue {
765    fn raw(&self) -> Result<RawLuaValue> {
766        self.lua
767            .with_state(|state| state.external_rooted_value(self.key))
768            .ok_or_else(stale_handle_error)
769    }
770
771    fn raw_for_lua(&self, lua: &Lua, state: &LuaState) -> Result<RawLuaValue> {
772        if !Rc::ptr_eq(&self.lua.inner, &lua.inner) {
773            return Err(LuaError::runtime(format_args!(
774                "Lua handle belongs to a different state"
775            )));
776        }
777        state
778            .external_rooted_value(self.key)
779            .ok_or_else(stale_handle_error)
780    }
781}
782
783impl Clone for RootedValue {
784    fn clone(&self) -> Self {
785        let raw = self.raw().expect("rooted Lua handle should not be stale");
786        self.lua.root_raw(raw)
787    }
788}
789
790impl Drop for RootedValue {
791    fn drop(&mut self) {
792        self.lua.unroot_external_key(self.key);
793    }
794}
795
796/// Dynamically typed owned Lua value.
797#[derive(Debug, Clone)]
798pub enum Value {
799    Nil,
800    Boolean(bool),
801    Integer(i64),
802    Number(f64),
803    String(LuaString),
804    Table(Table),
805    Function(Function),
806    UserData(AnyUserData),
807    LightUserData(*mut c_void),
808    Thread(Thread),
809}
810
811impl Value {
812    fn from_raw(lua: &Lua, raw: RawLuaValue) -> Result<Self> {
813        lua.with_state(|state| Self::from_raw_in_state(lua, state, raw))
814    }
815
816    fn from_raw_in_state(lua: &Lua, state: &mut LuaState, raw: RawLuaValue) -> Result<Self> {
817        Ok(match raw {
818            RawLuaValue::Nil => Value::Nil,
819            RawLuaValue::Bool(v) => Value::Boolean(v),
820            RawLuaValue::Int(v) => Value::Integer(v),
821            RawLuaValue::Float(v) => Value::Number(v),
822            RawLuaValue::Str(v) => Value::String(LuaString {
823                root: lua.root_raw_in_state(state, RawLuaValue::Str(v)),
824            }),
825            RawLuaValue::Table(v) => Value::Table(Table {
826                root: lua.root_raw_in_state(state, RawLuaValue::Table(v)),
827            }),
828            RawLuaValue::Function(v) => Value::Function(Function {
829                root: lua.root_raw_in_state(state, RawLuaValue::Function(v)),
830            }),
831            RawLuaValue::UserData(v) => {
832                let host_value = v.host_value();
833                Value::UserData(AnyUserData {
834                    root: lua.root_raw_in_state(state, RawLuaValue::UserData(v)),
835                    host_value,
836                })
837            }
838            RawLuaValue::LightUserData(v) => Value::LightUserData(v),
839            RawLuaValue::Thread(v) => Value::Thread(Thread {
840                root: lua.root_raw_in_state(state, RawLuaValue::Thread(v)),
841            }),
842        })
843    }
844
845    fn to_raw_for_lua(&self, lua: &Lua, state: &LuaState) -> Result<RawLuaValue> {
846        match self {
847            Value::Nil => Ok(RawLuaValue::Nil),
848            Value::Boolean(v) => Ok(RawLuaValue::Bool(*v)),
849            Value::Integer(v) => Ok(RawLuaValue::Int(*v)),
850            Value::Number(v) => Ok(RawLuaValue::Float(*v)),
851            Value::String(v) => v.root.raw_for_lua(lua, state),
852            Value::Table(v) => v.root.raw_for_lua(lua, state),
853            Value::Function(v) => v.root.raw_for_lua(lua, state),
854            Value::UserData(v) => v.root.raw_for_lua(lua, state),
855            Value::LightUserData(v) => Ok(RawLuaValue::LightUserData(*v)),
856            Value::Thread(v) => v.root.raw_for_lua(lua, state),
857        }
858    }
859}
860
861#[derive(Debug, Clone)]
862pub struct Table {
863    root: RootedValue,
864}
865
866impl Table {
867    fn raw_table(&self) -> Result<GcRef<RawLuaTable>> {
868        match self.root.raw()? {
869            RawLuaValue::Table(table) => Ok(table),
870            other => Err(type_error_raw(&other, "table")),
871        }
872    }
873
874    pub fn get<K, V>(&self, key: K) -> Result<V>
875    where
876        K: IntoLua,
877        V: FromLua,
878    {
879        let lua = self.root.lua.clone();
880        let key = key.into_lua(&lua)?;
881        let value_raw = lua.with_state(|state| {
882            let key_raw = key.to_raw_for_lua(&lua, state)?;
883            let table_raw = self.root.raw_for_lua(&lua, state)?;
884            state.table_get_with_tm(&table_raw, &key_raw)
885        })?;
886        let value = Value::from_raw(&lua, value_raw)?;
887        V::from_lua(value, &lua)
888    }
889
890    pub fn set<K, V>(&self, key: K, value: V) -> Result<()>
891    where
892        K: IntoLua,
893        V: IntoLua,
894    {
895        let lua = self.root.lua.clone();
896        let key = key.into_lua(&lua)?;
897        let value = value.into_lua(&lua)?;
898        lua.with_state(|state| {
899            let key_raw = key.to_raw_for_lua(&lua, state)?;
900            let value_raw = value.to_raw_for_lua(&lua, state)?;
901            let table_raw = self.root.raw_for_lua(&lua, state)?;
902            state.table_set_with_tm(&table_raw, key_raw, value_raw)
903        })
904    }
905
906    pub fn len(&self) -> Result<u64> {
907        Ok(self.raw_table()?.getn())
908    }
909}
910
911#[derive(Debug, Clone)]
912pub struct Function {
913    root: RootedValue,
914}
915
916impl Function {
917    pub fn call<A, R>(&self, args: A) -> Result<R>
918    where
919        A: IntoLuaMulti,
920        R: FromLuaMulti,
921    {
922        let lua = self.root.lua.clone();
923        let args = args.into_lua_multi(&lua)?;
924        let result_raws = lua.with_state(|state| {
925            let arg_raws = args
926                .iter()
927                .map(|value| value.to_raw_for_lua(&lua, state))
928                .collect::<Result<Vec<_>>>()?;
929            let function_raw = self.root.raw_for_lua(&lua, state)?;
930            let saved_top = state.top_idx();
931            state.push(function_raw);
932            for arg in &arg_raws {
933                state.push(*arg);
934            }
935            match lua_vm::api::pcall_k(state, arg_raws.len() as i32, R::NRESULTS, 0, 0, None) {
936                Ok(_) => {
937                    let nresults = if R::NRESULTS < 0 {
938                        state.top_idx().0.saturating_sub(saved_top.0) as i32
939                    } else {
940                        R::NRESULTS
941                    };
942                    let mut results = Vec::with_capacity(nresults as usize);
943                    for _ in 0..nresults {
944                        results.push(state.pop());
945                    }
946                    results.reverse();
947                    state.set_top_idx(saved_top);
948                    Ok(results)
949                }
950                Err(err) => {
951                    state.set_top_idx(saved_top);
952                    Err(err)
953                }
954            }
955        })?;
956        let values = result_raws
957            .into_iter()
958            .map(|raw| Value::from_raw(&lua, raw))
959            .collect::<Result<Vec<_>>>()?;
960        R::from_lua_multi(values, &lua)
961    }
962}
963
964#[derive(Debug, Clone)]
965pub struct LuaString {
966    root: RootedValue,
967}
968
969impl LuaString {
970    fn raw_string(&self) -> Result<GcRef<RawLuaString>> {
971        match self.root.raw()? {
972            RawLuaValue::Str(string) => Ok(string),
973            other => Err(type_error_raw(&other, "string")),
974        }
975    }
976
977    pub fn as_bytes(&self) -> Result<Vec<u8>> {
978        Ok(self.raw_string()?.as_bytes().to_vec())
979    }
980
981    pub fn to_str(&self) -> Result<String> {
982        let bytes = self.as_bytes()?;
983        String::from_utf8(bytes)
984            .map_err(|_| LuaError::runtime(format_args!("string is not valid UTF-8")))
985    }
986}
987
988#[derive(Clone)]
989pub struct AnyUserData {
990    root: RootedValue,
991    host_value: Option<Rc<dyn Any>>,
992}
993
994impl fmt::Debug for AnyUserData {
995    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
996        f.debug_struct("AnyUserData")
997            .field("root", &self.root)
998            .field("has_host_value", &self.host_value.is_some())
999            .finish()
1000    }
1001}
1002
1003impl AnyUserData {
1004    fn host_cell<T: 'static>(&self) -> Result<&UserDataCell<T>> {
1005        let host = self
1006            .host_value
1007            .as_deref()
1008            .ok_or_else(|| LuaError::runtime(format_args!("missing Rust userdata payload")))?;
1009        host.downcast_ref::<UserDataCell<T>>()
1010            .ok_or_else(|| LuaError::runtime(format_args!("userdata type mismatch")))
1011    }
1012
1013    pub fn borrow<T>(&self) -> Result<Ref<'_, T>>
1014    where
1015        T: 'static,
1016    {
1017        self.host_cell::<T>()?
1018            .value
1019            .try_borrow()
1020            .map_err(|_| LuaError::runtime(format_args!("userdata is already mutably borrowed")))
1021    }
1022
1023    pub fn borrow_mut<T>(&self) -> Result<RefMut<'_, T>>
1024    where
1025        T: 'static,
1026    {
1027        self.host_cell::<T>()?
1028            .value
1029            .try_borrow_mut()
1030            .map_err(|_| LuaError::runtime(format_args!("userdata is already borrowed")))
1031    }
1032
1033    pub fn with_borrow<T, R>(&self, f: impl FnOnce(&T) -> R) -> Result<R>
1034    where
1035        T: 'static,
1036    {
1037        let value = self.borrow::<T>()?;
1038        Ok(f(&value))
1039    }
1040
1041    pub fn with_borrow_mut<T, R>(&self, f: impl FnOnce(&mut T) -> R) -> Result<R>
1042    where
1043        T: 'static,
1044    {
1045        let mut value = self.borrow_mut::<T>()?;
1046        Ok(f(&mut value))
1047    }
1048}
1049
1050#[derive(Debug, Clone)]
1051pub struct Thread {
1052    root: RootedValue,
1053}
1054
1055/// Variable argument or return list converted element-by-element.
1056///
1057/// This mirrors mlua's `Variadic<T>` enough for dynamic callback bridges:
1058/// `create_function(|_, args: Variadic<Value>| ...)` receives all Lua
1059/// arguments, and returning `Variadic<T>` pushes all contained values.
1060#[derive(Debug, Clone, Default, PartialEq, Eq)]
1061pub struct Variadic<T>(Vec<T>);
1062
1063impl<T> Variadic<T> {
1064    pub const fn new() -> Self {
1065        Self(Vec::new())
1066    }
1067
1068    pub fn with_capacity(capacity: usize) -> Self {
1069        Self(Vec::with_capacity(capacity))
1070    }
1071
1072    pub fn into_vec(self) -> Vec<T> {
1073        self.0
1074    }
1075}
1076
1077impl<T> Deref for Variadic<T> {
1078    type Target = Vec<T>;
1079
1080    fn deref(&self) -> &Self::Target {
1081        &self.0
1082    }
1083}
1084
1085impl<T> DerefMut for Variadic<T> {
1086    fn deref_mut(&mut self) -> &mut Self::Target {
1087        &mut self.0
1088    }
1089}
1090
1091impl<T> From<Vec<T>> for Variadic<T> {
1092    fn from(value: Vec<T>) -> Self {
1093        Self(value)
1094    }
1095}
1096
1097impl<T> From<Variadic<T>> for Vec<T> {
1098    fn from(value: Variadic<T>) -> Self {
1099        value.0
1100    }
1101}
1102
1103impl<T> FromIterator<T> for Variadic<T> {
1104    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
1105        Self(Vec::from_iter(iter))
1106    }
1107}
1108
1109impl<T> IntoIterator for Variadic<T> {
1110    type Item = T;
1111    type IntoIter = std::vec::IntoIter<T>;
1112
1113    fn into_iter(self) -> Self::IntoIter {
1114        self.0.into_iter()
1115    }
1116}
1117
1118pub trait UserData: 'static {
1119    fn add_methods<M: UserDataMethods<Self>>(_methods: &mut M)
1120    where
1121        Self: Sized,
1122    {
1123    }
1124
1125    fn add_meta_methods<M: UserDataMethods<Self>>(_methods: &mut M)
1126    where
1127        Self: Sized,
1128    {
1129    }
1130}
1131
1132pub trait UserDataMethods<T: UserData> {
1133    fn add_method<A, R, F>(&mut self, name: &str, method: F)
1134    where
1135        A: FromLuaMulti + 'static,
1136        R: IntoLuaMulti + 'static,
1137        F: Fn(&Lua, &T, A) -> Result<R> + 'static;
1138
1139    fn add_method_mut<A, R, F>(&mut self, name: &str, method: F)
1140    where
1141        A: FromLuaMulti + 'static,
1142        R: IntoLuaMulti + 'static,
1143        F: Fn(&Lua, &mut T, A) -> Result<R> + 'static;
1144
1145    fn add_meta_method<A, R, F>(&mut self, metamethod: MetaMethod, method: F)
1146    where
1147        A: FromLuaMulti + 'static,
1148        R: IntoLuaMulti + 'static,
1149        F: Fn(&Lua, &T, A) -> Result<R> + 'static;
1150
1151    fn add_meta_method_mut<A, R, F>(&mut self, metamethod: MetaMethod, method: F)
1152    where
1153        A: FromLuaMulti + 'static,
1154        R: IntoLuaMulti + 'static,
1155        F: Fn(&Lua, &mut T, A) -> Result<R> + 'static;
1156
1157    /// Register a getter for `obj.name`. The runtime composes all field getters,
1158    /// the method table, and any raw `__index` into a single `__index` so fields
1159    /// and methods coexist (lookup order: field, then method, then raw `__index`).
1160    fn add_field_method_get<R, F>(&mut self, name: &str, getter: F)
1161    where
1162        R: IntoLuaMulti + 'static,
1163        F: Fn(&Lua, &T) -> Result<R> + 'static;
1164
1165    /// Register a setter for `obj.name = value`. Assigning a field with no setter
1166    /// (or an unknown field) errors unless a raw `__newindex` handles it.
1167    fn add_field_method_set<A, F>(&mut self, name: &str, setter: F)
1168    where
1169        A: FromLuaMulti + 'static,
1170        F: Fn(&Lua, &mut T, A) -> Result<()> + 'static;
1171}
1172
1173#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1174pub enum MetaMethod {
1175    Index,
1176    NewIndex,
1177    Add,
1178    Sub,
1179    Mul,
1180    Div,
1181    Mod,
1182    Pow,
1183    Unm,
1184    Len,
1185    Eq,
1186    Lt,
1187    Le,
1188    Concat,
1189    Call,
1190    ToString,
1191    Pairs,
1192}
1193
1194impl MetaMethod {
1195    fn name(self) -> &'static str {
1196        match self {
1197            MetaMethod::Index => "__index",
1198            MetaMethod::NewIndex => "__newindex",
1199            MetaMethod::Add => "__add",
1200            MetaMethod::Sub => "__sub",
1201            MetaMethod::Mul => "__mul",
1202            MetaMethod::Div => "__div",
1203            MetaMethod::Mod => "__mod",
1204            MetaMethod::Pow => "__pow",
1205            MetaMethod::Unm => "__unm",
1206            MetaMethod::Len => "__len",
1207            MetaMethod::Eq => "__eq",
1208            MetaMethod::Lt => "__lt",
1209            MetaMethod::Le => "__le",
1210            MetaMethod::Concat => "__concat",
1211            MetaMethod::Call => "__call",
1212            MetaMethod::ToString => "__tostring",
1213            MetaMethod::Pairs => "__pairs",
1214        }
1215    }
1216}
1217
1218/// Root `value` on the state for as long as the state itself lives.
1219///
1220/// The returned [`ExternalRootKey`] is intentionally discarded: this helper is
1221/// the explicit name for the "cached per-type metadata" rooting pattern used by
1222/// [`UserDataMethodRegistry::build_metatable`] (the metatable itself, the
1223/// field-getter / method / field-setter tables, and any raw `__index`/`__newindex`
1224/// referenced by the composed dispatch closures). Those values must stay
1225/// reachable for the state's whole lifetime and only ever free together with the
1226/// state. Do not call this for any value you want the GC to be able to collect
1227/// later: it is by design an un-undoable root.
1228fn root_for_state_lifetime(state: &mut LuaState, value: RawLuaValue) {
1229    let _ = state.external_root_value(value);
1230}
1231
1232struct UserDataMethodRegistry<'lua, T: UserData> {
1233    lua: &'lua Lua,
1234    methods: Vec<(String, Function)>,
1235    meta_methods: Vec<(MetaMethod, Function)>,
1236    fields_get: Vec<(String, Function)>,
1237    fields_set: Vec<(String, Function)>,
1238    error: Option<LuaError>,
1239    _marker: std::marker::PhantomData<T>,
1240}
1241
1242impl<'lua, T: UserData> UserDataMethodRegistry<'lua, T> {
1243    fn new(lua: &'lua Lua) -> Self {
1244        Self {
1245            lua,
1246            methods: Vec::new(),
1247            meta_methods: Vec::new(),
1248            fields_get: Vec::new(),
1249            fields_set: Vec::new(),
1250            error: None,
1251            _marker: std::marker::PhantomData,
1252        }
1253    }
1254
1255    fn record(&mut self, result: Result<Function>, insert: impl FnOnce(&mut Self, Function)) {
1256        if self.error.is_some() {
1257            return;
1258        }
1259        match result {
1260            Ok(function) => insert(self, function),
1261            Err(err) => self.error = Some(err),
1262        }
1263    }
1264
1265    /// Build this type's metatable once: a method table plus any meta-methods,
1266    /// returning the raw table handle permanently rooted in the external-root set
1267    /// so it can be cached and shared by every value of the type.
1268    fn build_metatable(mut self) -> Result<GcRef<RawLuaTable>> {
1269        if let Some(err) = self.error.take() {
1270            return Err(err);
1271        }
1272
1273        let lua = self.lua;
1274
1275        let method_table = lua.create_table()?;
1276        for (name, function) in &self.methods {
1277            method_table.set(name.as_str(), function)?;
1278        }
1279
1280        let field_getters = lua.create_table()?;
1281        for (name, function) in &self.fields_get {
1282            field_getters.set(name.as_str(), function)?;
1283        }
1284        let field_setters = lua.create_table()?;
1285        for (name, function) in &self.fields_set {
1286            field_setters.set(name.as_str(), function)?;
1287        }
1288
1289        // Raw __index/__newindex are escape hatches that compose as the final
1290        // fallback; every other meta-method is set directly.
1291        let metatable = lua.create_table()?;
1292        let mut raw_index: Option<Function> = None;
1293        let mut raw_newindex: Option<Function> = None;
1294        for (metamethod, function) in &self.meta_methods {
1295            match metamethod {
1296                MetaMethod::Index => raw_index = Some(function.clone()),
1297                MetaMethod::NewIndex => raw_newindex = Some(function.clone()),
1298                other => {
1299                    metatable.set(other.name(), function)?;
1300                }
1301            }
1302        }
1303
1304        // __index: field getter, then method, then raw __index.
1305        //
1306        // - fields → must compose (field → method → raw via a single closure)
1307        // - raw_index + methods (no fields) → must compose (method → raw)
1308        // - raw_index only (no fields, no methods) → set raw __index directly,
1309        //   skipping the composed closure entirely. This is the common shape
1310        //   for bridges that bind reflected state via a raw `add_meta_method`
1311        //   (e.g. bms-lua-rs's `LuaRef`) and the lookup is on the hot path.
1312        // - method-only → method_table as __index (existing fast path)
1313        //
1314        // The composed closure deliberately captures raw `GcRef`/`RawLuaValue`
1315        // handles, not high-level `Table`/`Function`: each high-level wrapper
1316        // holds a `RootedValue` with a strong `Rc<LuaInner>`, which would cycle
1317        // through the heap-resident closure back to the state and leak it on
1318        // drop. Raw handles are rooted permanently via
1319        // [`root_for_state_lifetime`], and `Table`/`Function` views are rebuilt
1320        // per call from the closure's `&lua`.
1321        let has_fields_get = !self.fields_get.is_empty();
1322        let has_methods = !self.methods.is_empty();
1323        let needs_index_composition = has_fields_get || (raw_index.is_some() && has_methods);
1324
1325        if needs_index_composition {
1326            let (getters_raw, methods_raw, raw_index_raw) = lua.with_state(|state| {
1327                let g = match field_getters.root.raw_for_lua(lua, state)? {
1328                    RawLuaValue::Table(g) => g,
1329                    v => return Err(type_error_raw(&v, "table")),
1330                };
1331                root_for_state_lifetime(state, RawLuaValue::Table(g.clone()));
1332                let m = match method_table.root.raw_for_lua(lua, state)? {
1333                    RawLuaValue::Table(m) => m,
1334                    v => return Err(type_error_raw(&v, "table")),
1335                };
1336                root_for_state_lifetime(state, RawLuaValue::Table(m.clone()));
1337                let r = match &raw_index {
1338                    Some(f) => {
1339                        let rv = f.root.raw_for_lua(lua, state)?;
1340                        root_for_state_lifetime(state, rv.clone());
1341                        Some(rv)
1342                    }
1343                    None => None,
1344                };
1345                Ok::<_, LuaError>((g, m, r))
1346            })?;
1347            let index_fn = lua.create_function(move |lua, (ud, key): (Value, Value)| {
1348                let getters = Table {
1349                    root: lua.root_raw(RawLuaValue::Table(getters_raw.clone())),
1350                };
1351                let methods = Table {
1352                    root: lua.root_raw(RawLuaValue::Table(methods_raw.clone())),
1353                };
1354                if let Value::Function(getter) = getters.get::<_, Value>(key.clone())? {
1355                    return getter.call::<_, Value>(ud);
1356                }
1357                let method = methods.get::<_, Value>(key.clone())?;
1358                if !matches!(method, Value::Nil) {
1359                    return Ok(method);
1360                }
1361                if let Some(raw_idx) = &raw_index_raw {
1362                    let raw_fn = Function {
1363                        root: lua.root_raw(raw_idx.clone()),
1364                    };
1365                    return raw_fn.call::<_, Value>((ud, key));
1366                }
1367                Ok(Value::Nil)
1368            })?;
1369            metatable.set(MetaMethod::Index.name(), &index_fn)?;
1370        } else if let Some(raw) = raw_index.as_ref() {
1371            metatable.set(MetaMethod::Index.name(), raw)?;
1372        } else {
1373            metatable.set(MetaMethod::Index.name(), &method_table)?;
1374        }
1375
1376        // __newindex: field setter, then raw __newindex, else error. Same
1377        // composed-vs-pass-through choice as __index above.
1378        let has_fields_set = !self.fields_set.is_empty();
1379
1380        if has_fields_set {
1381            let (setters_raw, raw_newindex_raw) = lua.with_state(|state| {
1382                let s = match field_setters.root.raw_for_lua(lua, state)? {
1383                    RawLuaValue::Table(s) => s,
1384                    v => return Err(type_error_raw(&v, "table")),
1385                };
1386                root_for_state_lifetime(state, RawLuaValue::Table(s.clone()));
1387                let r = match &raw_newindex {
1388                    Some(f) => {
1389                        let rv = f.root.raw_for_lua(lua, state)?;
1390                        root_for_state_lifetime(state, rv.clone());
1391                        Some(rv)
1392                    }
1393                    None => None,
1394                };
1395                Ok::<_, LuaError>((s, r))
1396            })?;
1397            let newindex_fn =
1398                lua.create_function(move |lua, (ud, key, value): (Value, Value, Value)| {
1399                    let setters = Table {
1400                        root: lua.root_raw(RawLuaValue::Table(setters_raw.clone())),
1401                    };
1402                    if let Value::Function(setter) = setters.get::<_, Value>(key.clone())? {
1403                        return setter.call::<_, Value>((ud, value));
1404                    }
1405                    if let Some(raw) = &raw_newindex_raw {
1406                        let raw_fn = Function {
1407                            root: lua.root_raw(raw.clone()),
1408                        };
1409                        return raw_fn.call::<_, Value>((ud, key, value));
1410                    }
1411                    Err(LuaError::runtime(format_args!(
1412                        "cannot assign to unknown or read-only userdata field"
1413                    )))
1414                })?;
1415            metatable.set(MetaMethod::NewIndex.name(), &newindex_fn)?;
1416        } else if let Some(raw) = raw_newindex.as_ref() {
1417            metatable.set(MetaMethod::NewIndex.name(), raw)?;
1418        }
1419
1420        self.lua.with_state(|state| {
1421            let metatable_raw = metatable.root.raw_for_lua(self.lua, state)?;
1422            let RawLuaValue::Table(metatable) = metatable_raw else {
1423                return Err(type_error_raw(&metatable_raw, "table"));
1424            };
1425            root_for_state_lifetime(state, RawLuaValue::Table(metatable.clone()));
1426            Ok(metatable)
1427        })
1428    }
1429}
1430
1431impl<T: UserData> UserDataMethods<T> for UserDataMethodRegistry<'_, T> {
1432    fn add_method<A, R, F>(&mut self, name: &str, method: F)
1433    where
1434        A: FromLuaMulti + 'static,
1435        R: IntoLuaMulti + 'static,
1436        F: Fn(&Lua, &T, A) -> Result<R> + 'static,
1437    {
1438        let name = name.to_string();
1439        let result = self.lua.create_userdata_method(method);
1440        self.record(result, move |this, function| {
1441            this.methods.push((name, function));
1442        });
1443    }
1444
1445    fn add_method_mut<A, R, F>(&mut self, name: &str, method: F)
1446    where
1447        A: FromLuaMulti + 'static,
1448        R: IntoLuaMulti + 'static,
1449        F: Fn(&Lua, &mut T, A) -> Result<R> + 'static,
1450    {
1451        let name = name.to_string();
1452        let result = self.lua.create_userdata_method_mut(method);
1453        self.record(result, move |this, function| {
1454            this.methods.push((name, function));
1455        });
1456    }
1457
1458    fn add_meta_method<A, R, F>(&mut self, metamethod: MetaMethod, method: F)
1459    where
1460        A: FromLuaMulti + 'static,
1461        R: IntoLuaMulti + 'static,
1462        F: Fn(&Lua, &T, A) -> Result<R> + 'static,
1463    {
1464        let result = self.lua.create_userdata_method(method);
1465        self.record(result, move |this, function| {
1466            this.meta_methods.push((metamethod, function));
1467        });
1468    }
1469
1470    fn add_meta_method_mut<A, R, F>(&mut self, metamethod: MetaMethod, method: F)
1471    where
1472        A: FromLuaMulti + 'static,
1473        R: IntoLuaMulti + 'static,
1474        F: Fn(&Lua, &mut T, A) -> Result<R> + 'static,
1475    {
1476        let result = self.lua.create_userdata_method_mut(method);
1477        self.record(result, move |this, function| {
1478            this.meta_methods.push((metamethod, function));
1479        });
1480    }
1481
1482    fn add_field_method_get<R, F>(&mut self, name: &str, getter: F)
1483    where
1484        R: IntoLuaMulti + 'static,
1485        F: Fn(&Lua, &T) -> Result<R> + 'static,
1486    {
1487        let name = name.to_string();
1488        let result = self
1489            .lua
1490            .create_userdata_method(move |lua, this, ()| getter(lua, this));
1491        self.record(result, move |this, function| {
1492            this.fields_get.push((name, function));
1493        });
1494    }
1495
1496    fn add_field_method_set<A, F>(&mut self, name: &str, setter: F)
1497    where
1498        A: FromLuaMulti + 'static,
1499        F: Fn(&Lua, &mut T, A) -> Result<()> + 'static,
1500    {
1501        let name = name.to_string();
1502        let result = self
1503            .lua
1504            .create_userdata_method_mut(move |lua, this, arg: A| setter(lua, this, arg));
1505        self.record(result, move |this, function| {
1506            this.fields_set.push((name, function));
1507        });
1508    }
1509}
1510
1511pub trait IntoLua {
1512    fn into_lua(self, lua: &Lua) -> Result<Value>;
1513}
1514
1515pub trait FromLua: Sized {
1516    fn from_lua(value: Value, lua: &Lua) -> Result<Self>;
1517}
1518
1519pub trait IntoLuaMulti {
1520    fn into_lua_multi(self, lua: &Lua) -> Result<Vec<Value>>;
1521}
1522
1523pub trait FromLuaMulti: Sized {
1524    const NRESULTS: i32;
1525
1526    fn from_lua_multi(values: Vec<Value>, lua: &Lua) -> Result<Self>;
1527}
1528
1529impl IntoLua for Value {
1530    fn into_lua(self, _lua: &Lua) -> Result<Value> {
1531        Ok(self)
1532    }
1533}
1534
1535impl IntoLua for &Value {
1536    fn into_lua(self, _lua: &Lua) -> Result<Value> {
1537        Ok(self.clone())
1538    }
1539}
1540
1541impl FromLua for Value {
1542    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
1543        Ok(value)
1544    }
1545}
1546
1547impl IntoLua for bool {
1548    fn into_lua(self, _lua: &Lua) -> Result<Value> {
1549        Ok(Value::Boolean(self))
1550    }
1551}
1552
1553impl FromLua for bool {
1554    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
1555        match value {
1556            Value::Boolean(v) => Ok(v),
1557            other => Err(type_error_value(&other, "boolean")),
1558        }
1559    }
1560}
1561
1562impl IntoLua for i64 {
1563    fn into_lua(self, _lua: &Lua) -> Result<Value> {
1564        Ok(Value::Integer(self))
1565    }
1566}
1567
1568impl FromLua for i64 {
1569    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
1570        match value {
1571            Value::Integer(v) => Ok(v),
1572            Value::Number(v) if v.fract() == 0.0 && v.is_finite() => Ok(v as i64),
1573            other => Err(type_error_value(&other, "integer")),
1574        }
1575    }
1576}
1577
1578impl IntoLua for i32 {
1579    fn into_lua(self, lua: &Lua) -> Result<Value> {
1580        i64::from(self).into_lua(lua)
1581    }
1582}
1583
1584impl FromLua for i32 {
1585    fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
1586        let v = i64::from_lua(value, lua)?;
1587        i32::try_from(v).map_err(|_| LuaError::runtime(format_args!("integer out of range")))
1588    }
1589}
1590
1591impl IntoLua for usize {
1592    fn into_lua(self, lua: &Lua) -> Result<Value> {
1593        let v = i64::try_from(self)
1594            .map_err(|_| LuaError::runtime(format_args!("integer out of range")))?;
1595        v.into_lua(lua)
1596    }
1597}
1598
1599impl FromLua for usize {
1600    fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
1601        let v = i64::from_lua(value, lua)?;
1602        usize::try_from(v).map_err(|_| LuaError::runtime(format_args!("integer out of range")))
1603    }
1604}
1605
1606impl IntoLua for u64 {
1607    fn into_lua(self, lua: &Lua) -> Result<Value> {
1608        let v = i64::try_from(self)
1609            .map_err(|_| LuaError::runtime(format_args!("integer out of range")))?;
1610        v.into_lua(lua)
1611    }
1612}
1613
1614impl FromLua for u64 {
1615    fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
1616        let v = i64::from_lua(value, lua)?;
1617        u64::try_from(v).map_err(|_| LuaError::runtime(format_args!("integer out of range")))
1618    }
1619}
1620
1621impl IntoLua for u32 {
1622    fn into_lua(self, lua: &Lua) -> Result<Value> {
1623        u64::from(self).into_lua(lua)
1624    }
1625}
1626
1627impl FromLua for u32 {
1628    fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
1629        let v = u64::from_lua(value, lua)?;
1630        u32::try_from(v).map_err(|_| LuaError::runtime(format_args!("integer out of range")))
1631    }
1632}
1633
1634impl IntoLua for f64 {
1635    fn into_lua(self, _lua: &Lua) -> Result<Value> {
1636        Ok(Value::Number(self))
1637    }
1638}
1639
1640impl FromLua for f64 {
1641    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
1642        match value {
1643            Value::Integer(v) => Ok(v as f64),
1644            Value::Number(v) => Ok(v),
1645            other => Err(type_error_value(&other, "number")),
1646        }
1647    }
1648}
1649
1650impl IntoLua for &str {
1651    fn into_lua(self, lua: &Lua) -> Result<Value> {
1652        Ok(Value::String(lua.create_string(self.as_bytes())?))
1653    }
1654}
1655
1656impl IntoLua for String {
1657    fn into_lua(self, lua: &Lua) -> Result<Value> {
1658        Ok(Value::String(lua.create_string(self.into_bytes())?))
1659    }
1660}
1661
1662impl FromLua for String {
1663    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
1664        match value {
1665            Value::String(s) => s.to_str(),
1666            other => Err(type_error_value(&other, "string")),
1667        }
1668    }
1669}
1670
1671impl IntoLua for &[u8] {
1672    fn into_lua(self, lua: &Lua) -> Result<Value> {
1673        Ok(Value::String(lua.create_string(self)?))
1674    }
1675}
1676
1677impl IntoLua for LuaString {
1678    fn into_lua(self, _lua: &Lua) -> Result<Value> {
1679        Ok(Value::String(self))
1680    }
1681}
1682
1683impl IntoLua for &LuaString {
1684    fn into_lua(self, _lua: &Lua) -> Result<Value> {
1685        Ok(Value::String(self.clone()))
1686    }
1687}
1688
1689impl FromLua for LuaString {
1690    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
1691        match value {
1692            Value::String(v) => Ok(v),
1693            other => Err(type_error_value(&other, "string")),
1694        }
1695    }
1696}
1697
1698impl IntoLua for Table {
1699    fn into_lua(self, _lua: &Lua) -> Result<Value> {
1700        Ok(Value::Table(self))
1701    }
1702}
1703
1704impl IntoLua for &Table {
1705    fn into_lua(self, _lua: &Lua) -> Result<Value> {
1706        Ok(Value::Table(self.clone()))
1707    }
1708}
1709
1710impl FromLua for Table {
1711    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
1712        match value {
1713            Value::Table(v) => Ok(v),
1714            other => Err(type_error_value(&other, "table")),
1715        }
1716    }
1717}
1718
1719impl IntoLua for Function {
1720    fn into_lua(self, _lua: &Lua) -> Result<Value> {
1721        Ok(Value::Function(self))
1722    }
1723}
1724
1725impl IntoLua for &Function {
1726    fn into_lua(self, _lua: &Lua) -> Result<Value> {
1727        Ok(Value::Function(self.clone()))
1728    }
1729}
1730
1731impl FromLua for Function {
1732    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
1733        match value {
1734            Value::Function(v) => Ok(v),
1735            other => Err(type_error_value(&other, "function")),
1736        }
1737    }
1738}
1739
1740impl IntoLua for AnyUserData {
1741    fn into_lua(self, _lua: &Lua) -> Result<Value> {
1742        Ok(Value::UserData(self))
1743    }
1744}
1745
1746impl IntoLua for &AnyUserData {
1747    fn into_lua(self, _lua: &Lua) -> Result<Value> {
1748        Ok(Value::UserData(self.clone()))
1749    }
1750}
1751
1752impl FromLua for AnyUserData {
1753    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
1754        match value {
1755            Value::UserData(v) => Ok(v),
1756            other => Err(type_error_value(&other, "userdata")),
1757        }
1758    }
1759}
1760
1761impl<T> IntoLua for T
1762where
1763    T: UserData,
1764{
1765    fn into_lua(self, lua: &Lua) -> Result<Value> {
1766        Ok(Value::UserData(lua.create_userdata(self)?))
1767    }
1768}
1769
1770impl<T> IntoLua for Option<T>
1771where
1772    T: IntoLua,
1773{
1774    fn into_lua(self, lua: &Lua) -> Result<Value> {
1775        match self {
1776            Some(value) => value.into_lua(lua),
1777            None => Ok(Value::Nil),
1778        }
1779    }
1780}
1781
1782impl<T> FromLua for Option<T>
1783where
1784    T: FromLua,
1785{
1786    fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
1787        match value {
1788            Value::Nil => Ok(None),
1789            other => T::from_lua(other, lua).map(Some),
1790        }
1791    }
1792}
1793
1794impl<T> IntoLua for Vec<T>
1795where
1796    T: IntoLua,
1797{
1798    fn into_lua(self, lua: &Lua) -> Result<Value> {
1799        let table = lua.create_table()?;
1800        for (idx, value) in self.into_iter().enumerate() {
1801            table.set((idx + 1) as i64, value)?;
1802        }
1803        Ok(Value::Table(table))
1804    }
1805}
1806
1807impl<T> FromLua for Vec<T>
1808where
1809    T: FromLua,
1810{
1811    fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
1812        let table = Table::from_lua(value, lua)?;
1813        let raw = table.raw_table()?;
1814        let len = raw.getn();
1815        let mut out = Vec::with_capacity(len as usize);
1816        for idx in 1..=len {
1817            let value = Value::from_raw(lua, raw.get_int(idx as i64))?;
1818            out.push(T::from_lua(value, lua)?);
1819        }
1820        Ok(out)
1821    }
1822}
1823
1824impl<K, V> IntoLua for HashMap<K, V>
1825where
1826    K: IntoLua,
1827    V: IntoLua,
1828{
1829    fn into_lua(self, lua: &Lua) -> Result<Value> {
1830        let table = lua.create_table()?;
1831        for (key, value) in self {
1832            table.set(key, value)?;
1833        }
1834        Ok(Value::Table(table))
1835    }
1836}
1837
1838impl<K, V> FromLua for HashMap<K, V>
1839where
1840    K: FromLua + Eq + Hash,
1841    V: FromLua,
1842{
1843    fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
1844        let table = Table::from_lua(value, lua)?;
1845        let raw = table.raw_table()?;
1846        let mut out = HashMap::new();
1847        let mut result = Ok(());
1848        raw.for_each_entry(|key, value| {
1849            if result.is_err() {
1850                return;
1851            }
1852            result = (|| {
1853                let key = Value::from_raw(lua, *key)?;
1854                let value = Value::from_raw(lua, *value)?;
1855                out.insert(K::from_lua(key, lua)?, V::from_lua(value, lua)?);
1856                Ok(())
1857            })();
1858        });
1859        result?;
1860        Ok(out)
1861    }
1862}
1863
1864impl<T> IntoLuaMulti for Variadic<T>
1865where
1866    T: IntoLua,
1867{
1868    fn into_lua_multi(self, lua: &Lua) -> Result<Vec<Value>> {
1869        self.into_iter().map(|value| value.into_lua(lua)).collect()
1870    }
1871}
1872
1873impl<T> FromLuaMulti for Variadic<T>
1874where
1875    T: FromLua,
1876{
1877    const NRESULTS: i32 = -1;
1878
1879    fn from_lua_multi(values: Vec<Value>, lua: &Lua) -> Result<Self> {
1880        values
1881            .into_iter()
1882            .map(|value| T::from_lua(value, lua))
1883            .collect()
1884    }
1885}
1886
1887impl IntoLuaMulti for () {
1888    fn into_lua_multi(self, _lua: &Lua) -> Result<Vec<Value>> {
1889        Ok(Vec::new())
1890    }
1891}
1892
1893impl<T> IntoLuaMulti for T
1894where
1895    T: IntoLua,
1896{
1897    fn into_lua_multi(self, lua: &Lua) -> Result<Vec<Value>> {
1898        Ok(vec![self.into_lua(lua)?])
1899    }
1900}
1901
1902impl<A, B> IntoLuaMulti for (A, B)
1903where
1904    A: IntoLua,
1905    B: IntoLua,
1906{
1907    fn into_lua_multi(self, lua: &Lua) -> Result<Vec<Value>> {
1908        Ok(vec![self.0.into_lua(lua)?, self.1.into_lua(lua)?])
1909    }
1910}
1911
1912impl<A, T> IntoLuaMulti for (A, Variadic<T>)
1913where
1914    A: IntoLua,
1915    T: IntoLua,
1916{
1917    fn into_lua_multi(self, lua: &Lua) -> Result<Vec<Value>> {
1918        let mut values = vec![self.0.into_lua(lua)?];
1919        values.extend(self.1.into_lua_multi(lua)?);
1920        Ok(values)
1921    }
1922}
1923
1924impl<A, B, C> IntoLuaMulti for (A, B, C)
1925where
1926    A: IntoLua,
1927    B: IntoLua,
1928    C: IntoLua,
1929{
1930    fn into_lua_multi(self, lua: &Lua) -> Result<Vec<Value>> {
1931        Ok(vec![
1932            self.0.into_lua(lua)?,
1933            self.1.into_lua(lua)?,
1934            self.2.into_lua(lua)?,
1935        ])
1936    }
1937}
1938
1939impl<A, B, T> IntoLuaMulti for (A, B, Variadic<T>)
1940where
1941    A: IntoLua,
1942    B: IntoLua,
1943    T: IntoLua,
1944{
1945    fn into_lua_multi(self, lua: &Lua) -> Result<Vec<Value>> {
1946        let mut values = vec![self.0.into_lua(lua)?, self.1.into_lua(lua)?];
1947        values.extend(self.2.into_lua_multi(lua)?);
1948        Ok(values)
1949    }
1950}
1951
1952impl FromLuaMulti for () {
1953    const NRESULTS: i32 = 0;
1954
1955    fn from_lua_multi(_values: Vec<Value>, _lua: &Lua) -> Result<Self> {
1956        Ok(())
1957    }
1958}
1959
1960impl<T> FromLuaMulti for T
1961where
1962    T: FromLua,
1963{
1964    const NRESULTS: i32 = 1;
1965
1966    fn from_lua_multi(mut values: Vec<Value>, lua: &Lua) -> Result<Self> {
1967        let value = if values.is_empty() {
1968            Value::Nil
1969        } else {
1970            values.remove(0)
1971        };
1972        T::from_lua(value, lua)
1973    }
1974}
1975
1976impl<A, B> FromLuaMulti for (A, B)
1977where
1978    A: FromLua,
1979    B: FromLua,
1980{
1981    const NRESULTS: i32 = 2;
1982
1983    fn from_lua_multi(mut values: Vec<Value>, lua: &Lua) -> Result<Self> {
1984        let first = if values.is_empty() {
1985            Value::Nil
1986        } else {
1987            values.remove(0)
1988        };
1989        let second = if values.is_empty() {
1990            Value::Nil
1991        } else {
1992            values.remove(0)
1993        };
1994        Ok((A::from_lua(first, lua)?, B::from_lua(second, lua)?))
1995    }
1996}
1997
1998impl<A, T> FromLuaMulti for (A, Variadic<T>)
1999where
2000    A: FromLua,
2001    T: FromLua,
2002{
2003    const NRESULTS: i32 = -1;
2004
2005    fn from_lua_multi(mut values: Vec<Value>, lua: &Lua) -> Result<Self> {
2006        let first = if values.is_empty() {
2007            Value::Nil
2008        } else {
2009            values.remove(0)
2010        };
2011        Ok((
2012            A::from_lua(first, lua)?,
2013            Variadic::from_lua_multi(values, lua)?,
2014        ))
2015    }
2016}
2017
2018impl<A, B, C> FromLuaMulti for (A, B, C)
2019where
2020    A: FromLua,
2021    B: FromLua,
2022    C: FromLua,
2023{
2024    const NRESULTS: i32 = 3;
2025
2026    fn from_lua_multi(mut values: Vec<Value>, lua: &Lua) -> Result<Self> {
2027        let first = if values.is_empty() {
2028            Value::Nil
2029        } else {
2030            values.remove(0)
2031        };
2032        let second = if values.is_empty() {
2033            Value::Nil
2034        } else {
2035            values.remove(0)
2036        };
2037        let third = if values.is_empty() {
2038            Value::Nil
2039        } else {
2040            values.remove(0)
2041        };
2042        Ok((
2043            A::from_lua(first, lua)?,
2044            B::from_lua(second, lua)?,
2045            C::from_lua(third, lua)?,
2046        ))
2047    }
2048}
2049
2050impl<A, B, T> FromLuaMulti for (A, B, Variadic<T>)
2051where
2052    A: FromLua,
2053    B: FromLua,
2054    T: FromLua,
2055{
2056    const NRESULTS: i32 = -1;
2057
2058    fn from_lua_multi(mut values: Vec<Value>, lua: &Lua) -> Result<Self> {
2059        let first = if values.is_empty() {
2060            Value::Nil
2061        } else {
2062            values.remove(0)
2063        };
2064        let second = if values.is_empty() {
2065            Value::Nil
2066        } else {
2067            values.remove(0)
2068        };
2069        Ok((
2070            A::from_lua(first, lua)?,
2071            B::from_lua(second, lua)?,
2072            Variadic::from_lua_multi(values, lua)?,
2073        ))
2074    }
2075}
2076
2077fn rust_callback_trampoline(state: &mut LuaState) -> Result<usize> {
2078    let func_idx = state.current_call_info().func;
2079    let callback = match state.get_at(func_idx) {
2080        RawLuaValue::Function(RawLuaClosure::C(closure)) => {
2081            let Some(RawLuaValue::UserData(userdata)) = closure.upvalues.first() else {
2082                return Err(LuaError::runtime(format_args!(
2083                    "missing Rust callback payload"
2084                )));
2085            };
2086            let host = userdata
2087                .host_value()
2088                .ok_or_else(|| LuaError::runtime(format_args!("missing Rust callback payload")))?;
2089            host.downcast::<RustCallbackCell>().map_err(|_| {
2090                LuaError::runtime(format_args!("Rust callback payload type mismatch"))
2091            })?
2092        }
2093        _ => {
2094            return Err(LuaError::runtime(format_args!(
2095                "Rust callback trampoline called without C closure"
2096            )));
2097        }
2098    };
2099    (callback.function)(state)
2100}
2101
2102fn with_heap_guard<R>(state: &LuaState, f: impl FnOnce() -> R) -> R {
2103    let _heap_guard = heap_guard(state);
2104    f()
2105}
2106
2107fn heap_guard(state: &LuaState) -> lua_gc::HeapGuard {
2108    let global = state.global();
2109    lua_gc::HeapGuard::push(&global.heap)
2110}
2111
2112fn callback_args(state: &mut LuaState, lua: &Lua) -> Result<Vec<Value>> {
2113    let func_idx = state.current_call_info().func;
2114    let nargs = state.top_idx().0.saturating_sub(func_idx.0 + 1);
2115    let mut args = Vec::with_capacity(nargs as usize);
2116    for i in 0..nargs {
2117        let raw = state.get_at(func_idx + 1 + i as i32);
2118        args.push(Value::from_raw_in_state(lua, state, raw)?);
2119    }
2120    Ok(args)
2121}
2122
2123fn callback_userdata_args(state: &mut LuaState, lua: &Lua) -> Result<(AnyUserData, Vec<Value>)> {
2124    let mut args = callback_args(state, lua)?;
2125    if args.is_empty() {
2126        return Err(LuaError::runtime(format_args!(
2127            "userdata method missing self argument"
2128        )));
2129    }
2130    let userdata = AnyUserData::from_lua(args.remove(0), lua)?;
2131    Ok((userdata, args))
2132}
2133
2134fn push_callback_returns(state: &mut LuaState, lua: &Lua, returns: Vec<Value>) -> Result<usize> {
2135    let mut count = 0usize;
2136    for value in returns {
2137        let raw = value.to_raw_for_lua(lua, state)?;
2138        state.push(raw);
2139        count += 1;
2140    }
2141    Ok(count)
2142}
2143
2144fn stale_handle_error() -> LuaError {
2145    LuaError::runtime(format_args!("stale Lua handle"))
2146}
2147
2148fn type_error_raw(value: &RawLuaValue, expected: &str) -> LuaError {
2149    LuaError::runtime(format_args!(
2150        "{} expected, got {}",
2151        expected,
2152        value.type_name()
2153    ))
2154}
2155
2156fn type_error_value(value: &Value, expected: &str) -> LuaError {
2157    let got = match value {
2158        Value::Nil => "nil",
2159        Value::Boolean(_) => "boolean",
2160        Value::Integer(_) | Value::Number(_) => "number",
2161        Value::String(_) => "string",
2162        Value::Table(_) => "table",
2163        Value::Function(_) => "function",
2164        Value::UserData(_) | Value::LightUserData(_) => "userdata",
2165        Value::Thread(_) => "thread",
2166    };
2167    LuaError::runtime(format_args!("{} expected, got {}", expected, got))
2168}
2169
2170/// A Lua state with parser and standard libraries installed.
2171pub struct LuaRuntime {
2172    state: LuaState,
2173}
2174
2175impl LuaRuntime {
2176    /// Create a Lua runtime with parser and standard libraries installed.
2177    ///
2178    /// This installs no explicit host hooks. For a strict sandbox, construct
2179    /// with [`LuaRuntime::with_hooks`] and audit the native compatibility
2180    /// fallbacks in `lua-stdlib`.
2181    pub fn new() -> Result<Self> {
2182        Self::with_hooks(HostHooks::default())
2183    }
2184
2185    /// Create a Lua runtime with the supplied host capabilities.
2186    pub fn with_hooks(hooks: HostHooks) -> Result<Self> {
2187        let mut state = new_state().ok_or(LuaError::Memory)?;
2188        install_parser_hook(&mut state);
2189        hooks.install(&mut state);
2190        open_libs(&mut state)?;
2191        Ok(Self { state })
2192    }
2193
2194    pub fn state(&self) -> &LuaState {
2195        &self.state
2196    }
2197
2198    pub fn state_mut(&mut self) -> &mut LuaState {
2199        &mut self.state
2200    }
2201
2202    pub fn into_state(self) -> LuaState {
2203        self.state
2204    }
2205
2206    pub fn into_lua(self) -> Lua {
2207        Lua::from_initialized_state(self.state)
2208    }
2209
2210    /// Load and execute a Lua source chunk.
2211    pub fn exec(&mut self, source: &[u8], name: &[u8]) -> Result<()> {
2212        exec_state(&mut self.state, source, name)
2213    }
2214}
2215
2216fn exec_state(state: &mut LuaState, source: &[u8], name: &[u8]) -> Result<()> {
2217    let status = load_buffer(state, source, name)?;
2218    if status != 0 {
2219        let err = state.pop();
2220        return Err(LuaError::from_value(err));
2221    }
2222    lua_vm::api::pcall_k(state, 0, 0, 0, 0, None)?;
2223    Ok(())
2224}
2225
2226pub fn install_parser_hook(state: &mut LuaState) {
2227    state.global_mut().parser_hook = Some(parser_hook);
2228}
2229
2230fn parser_hook(
2231    state: &mut LuaState,
2232    source: &[u8],
2233    name: &[u8],
2234    firstchar: i32,
2235) -> Result<GcRef<LuaLClosure>> {
2236    let _heap_guard = heap_guard(state);
2237    let proto = lua_parse::parse(
2238        state,
2239        lua_parse::DynData::default(),
2240        source,
2241        name,
2242        firstchar,
2243    )?;
2244    let nupvals = proto.upvalues.len();
2245    let mut upvals = Vec::with_capacity(nupvals);
2246    for _ in 0..nupvals {
2247        upvals.push(std::cell::Cell::new(GcRef::new(UpVal::closed(
2248            RawLuaValue::Nil,
2249        ))));
2250    }
2251    Ok(GcRef::new(LuaLClosure {
2252        proto: GcRef::new(*proto),
2253        upvals,
2254    }))
2255}
2256
2257#[cfg(test)]
2258mod tests {
2259    use super::*;
2260    use std::cell::Cell;
2261
2262    fn external_root_count(lua: &Lua) -> usize {
2263        lua.with_state(|state| state.global().external_roots.len())
2264    }
2265
2266    struct Counter {
2267        value: i64,
2268    }
2269
2270    impl UserData for Counter {
2271        fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
2272            methods.add_method("get", |_lua, this, ()| Ok(this.value));
2273            methods.add_method_mut("inc", |_lua, this, delta: i64| {
2274                this.value += delta;
2275                Ok(this.value)
2276            });
2277        }
2278    }
2279
2280    struct PropertyBag {
2281        value: i64,
2282    }
2283
2284    impl UserData for PropertyBag {
2285        fn add_meta_methods<M: UserDataMethods<Self>>(methods: &mut M) {
2286            methods.add_meta_method(MetaMethod::Index, |_lua, this, key: String| {
2287                if key == "value" {
2288                    Ok(Value::Integer(this.value))
2289                } else {
2290                    Ok(Value::Nil)
2291                }
2292            });
2293            methods.add_meta_method_mut(
2294                MetaMethod::NewIndex,
2295                |_lua, this, (key, value): (String, i64)| {
2296                    if key != "value" {
2297                        return Err(LuaError::runtime(format_args!("unknown property")));
2298                    }
2299                    this.value = value;
2300                    Ok(())
2301                },
2302            );
2303        }
2304    }
2305
2306    #[test]
2307    fn rooted_table_clone_and_drop_manage_root_slots() {
2308        let lua = Lua::new();
2309        assert_eq!(external_root_count(&lua), 0);
2310
2311        let table = lua.create_table().expect("table should allocate");
2312        assert_eq!(external_root_count(&lua), 1);
2313
2314        let cloned = table.clone();
2315        assert_eq!(external_root_count(&lua), 2);
2316
2317        drop(table);
2318        assert_eq!(external_root_count(&lua), 1);
2319
2320        cloned.set("answer", 42_i64).expect("set should succeed");
2321        lua.gc_collect();
2322        assert_eq!(
2323            cloned.get::<_, i64>("answer").expect("get should succeed"),
2324            42
2325        );
2326
2327        drop(cloned);
2328        assert_eq!(external_root_count(&lua), 0);
2329    }
2330
2331    #[test]
2332    fn table_values_survive_forced_collection_between_operations() {
2333        let lua = Lua::new();
2334        let table = lua.create_table().expect("table should allocate");
2335
2336        lua.gc_collect();
2337        table.set("k", "v").expect("set should succeed");
2338        table.set(1_i64, "array").expect("array set should succeed");
2339        lua.gc_collect();
2340
2341        let value: String = table.get("k").expect("get should succeed");
2342        assert_eq!(value, "v");
2343        assert_eq!(table.len().expect("len should succeed"), 1);
2344    }
2345
2346    #[test]
2347    fn chunk_exec_eval_and_function_call_use_rooted_handles() {
2348        let lua = Lua::new();
2349        lua.load("function add(a, b) return a + b end")
2350            .set_name("test")
2351            .exec()
2352            .expect("chunk should execute");
2353
2354        let globals = lua.globals();
2355        let add: Function = globals.get("add").expect("function should exist");
2356        let result: i64 = add.call((20_i64, 22_i64)).expect("call should work");
2357        assert_eq!(result, 42);
2358
2359        let eval_result: i64 = lua
2360            .load("return add(1, 2)")
2361            .eval()
2362            .expect("eval should work");
2363        assert_eq!(eval_result, 3);
2364    }
2365
2366    #[test]
2367    fn rust_callback_captures_state_and_reenters_lua() {
2368        let lua = Lua::new();
2369        lua.load("function twice(v) return v * 2 end")
2370            .exec()
2371            .expect("chunk should execute");
2372
2373        let globals = lua.globals();
2374        let twice: Function = globals.get("twice").expect("function should exist");
2375        let calls = Rc::new(Cell::new(0));
2376        let calls_for_callback = calls.clone();
2377
2378        let callback = lua
2379            .create_function(move |_lua, value: i64| {
2380                calls_for_callback.set(calls_for_callback.get() + 1);
2381                let doubled: i64 = twice.call(value)?;
2382                Ok(doubled + 1)
2383            })
2384            .expect("callback should create");
2385        globals
2386            .set("from_rust", callback)
2387            .expect("callback should register");
2388
2389        let result: i64 = lua
2390            .load("return from_rust(20)")
2391            .eval()
2392            .expect("callback should run");
2393        assert_eq!(result, 41);
2394        assert_eq!(calls.get(), 1);
2395    }
2396
2397    #[test]
2398    fn rust_callback_accepts_and_returns_collectable_values() {
2399        let lua = Lua::new();
2400        let globals = lua.globals();
2401        let callback = lua
2402            .create_function(|lua, name: String| {
2403                let table = lua.create_table()?;
2404                table.set("name", name)?;
2405                Ok(table)
2406            })
2407            .expect("callback should create");
2408        globals
2409            .set("make_record", callback)
2410            .expect("callback should register");
2411
2412        let result: String = lua
2413            .load("return make_record('lua-rs').name")
2414            .eval()
2415            .expect("callback should return table");
2416        assert_eq!(result, "lua-rs");
2417    }
2418
2419    #[test]
2420    fn rust_callback_mut_tracks_state() {
2421        let lua = Lua::new();
2422        let globals = lua.globals();
2423        let mut next = 0_i64;
2424        let callback = lua
2425            .create_function_mut(move |_lua, delta: i64| {
2426                next += delta;
2427                Ok(next)
2428            })
2429            .expect("callback should create");
2430        globals
2431            .set("next", callback)
2432            .expect("callback should register");
2433
2434        let result: (i64, i64) = lua
2435            .load("return next(2), next(5)")
2436            .eval()
2437            .expect("callback should run");
2438        assert_eq!(result, (2, 7));
2439    }
2440
2441    #[test]
2442    fn dropped_rust_callback_releases_captured_handles_after_gc() {
2443        let lua = Lua::new();
2444        let table = lua.create_table().expect("table should allocate");
2445        table.set("value", 42_i64).expect("set should succeed");
2446        assert_eq!(external_root_count(&lua), 1);
2447
2448        let callback = {
2449            let captured = table.clone();
2450            lua.create_function(move |_lua, ()| captured.get::<_, i64>("value"))
2451                .expect("callback should create")
2452        };
2453        assert_eq!(external_root_count(&lua), 3);
2454
2455        drop(callback);
2456        lua.gc_collect();
2457        assert_eq!(external_root_count(&lua), 1);
2458        assert_eq!(table.get::<_, i64>("value").expect("table should live"), 42);
2459    }
2460
2461    #[test]
2462    fn metatable_is_built_once_per_type() {
2463        use std::sync::atomic::{AtomicUsize, Ordering};
2464        static BUILDS: AtomicUsize = AtomicUsize::new(0);
2465
2466        struct Widget {
2467            n: i64,
2468        }
2469        impl UserData for Widget {
2470            fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
2471                BUILDS.fetch_add(1, Ordering::SeqCst);
2472                methods.add_method("n", |_lua, this, ()| Ok(this.n));
2473            }
2474        }
2475
2476        let lua = Lua::new();
2477        let a = lua.create_userdata(Widget { n: 1 }).expect("first");
2478        let b = lua.create_userdata(Widget { n: 2 }).expect("second");
2479        let c = lua.create_userdata(Widget { n: 3 }).expect("third");
2480
2481        // Built exactly once despite three values of the same type.
2482        assert_eq!(BUILDS.load(Ordering::SeqCst), 1);
2483
2484        // Each value still carries its own data and dispatches correctly.
2485        let globals = lua.globals();
2486        globals.set("a", &a).unwrap();
2487        globals.set("b", &b).unwrap();
2488        globals.set("c", &c).unwrap();
2489        let sum: i64 = lua.load("return a:n() + b:n() + c:n()").eval().unwrap();
2490        assert_eq!(sum, 6);
2491    }
2492
2493    /// Reproducer for the callback-to-`Lua` reference cycle:
2494    /// `create_userdata_method` captures a strong `Lua` (`Rc<LuaInner>`) into each
2495    /// callback closure, the closure lives in a heap GC object owned by `LuaState`,
2496    /// and `LuaState` is owned by `LuaInner` — so dropping every external `Lua`
2497    /// handle still leaves the closures holding a strong `Rc<LuaInner>` to the
2498    /// state that owns them. Per-type metatable caching makes this permanent for
2499    /// any type a userdata is ever created for.
2500    ///
2501    /// This test holds a `Weak<LuaInner>`, drops every external `Lua`, and asserts
2502    /// the inner has actually been freed. It fails today and is what the
2503    /// `Weak`-capture fix in the callback constructors is meant to make pass.
2504    #[test]
2505    fn lua_state_frees_after_userdata_with_methods_is_dropped() {
2506        use std::rc::Rc;
2507
2508        let weak_inner = {
2509            let lua = Lua::new();
2510            let weak = Rc::downgrade(&lua.inner);
2511            // Create + drop a userdata of a type that registers methods. This
2512            // primes the per-type metatable cache and installs method closures
2513            // that capture `Lua` strongly.
2514            let _ = lua
2515                .create_userdata(Counter { value: 1 })
2516                .expect("userdata should create");
2517            weak
2518        };
2519
2520        assert!(
2521            weak_inner.upgrade().is_none(),
2522            "LuaInner is still alive after every external Lua handle dropped: \
2523             internal callback closures hold a strong Rc<LuaInner>, leaking the state"
2524        );
2525    }
2526
2527    /// Same cycle issue as above, on the `create_function` path: the Rust
2528    /// callback closure used to capture a strong `Lua`, so a function that
2529    /// outlived all external handles would keep the state pinned.
2530    #[test]
2531    fn lua_state_frees_after_create_function_handle_drops() {
2532        use std::rc::Rc;
2533
2534        let weak_inner = {
2535            let lua = Lua::new();
2536            let weak = Rc::downgrade(&lua.inner);
2537            let _f = lua
2538                .create_function(|_, ()| Ok(()))
2539                .expect("create_function should succeed");
2540            weak
2541        };
2542
2543        assert!(
2544            weak_inner.upgrade().is_none(),
2545            "LuaInner is still alive after the only Lua handle dropped: \
2546             the create_function callback held a strong Rc<LuaInner>"
2547        );
2548    }
2549
2550    /// Field-bearing types take the composed `__index` path in `build_metatable`,
2551    /// where the composing closure is itself passed to `create_function` and
2552    /// captures the field-getter table, method table, and optional raw
2553    /// `__index` function. Each of those is a `Table` or `Function` whose
2554    /// `RootedValue` holds a strong `Rc<LuaInner>`. Even with the outer
2555    /// `Weak` fix, that user closure still leaks the state.
2556    #[test]
2557    fn lua_state_frees_after_userdata_with_fields_drops() {
2558        use std::rc::Rc;
2559
2560        struct Point {
2561            x: f64,
2562        }
2563        impl UserData for Point {
2564            fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
2565                m.add_field_method_get("x", |_, this| Ok(this.x));
2566                m.add_field_method_set("x", |_, this, v: f64| {
2567                    this.x = v;
2568                    Ok(())
2569                });
2570            }
2571        }
2572
2573        let weak_inner = {
2574            let lua = Lua::new();
2575            let weak = Rc::downgrade(&lua.inner);
2576            let _ = lua
2577                .create_userdata(Point { x: 1.0 })
2578                .expect("userdata should create");
2579            weak
2580        };
2581
2582        assert!(
2583            weak_inner.upgrade().is_none(),
2584            "LuaInner leaked via the composed __index/__newindex closures: \
2585             they capture Table/Function values whose RootedValue holds a \
2586             strong Rc<LuaInner>"
2587        );
2588    }
2589
2590    /// Maximal mixed shape: field getter + field setter + regular method +
2591    /// raw `__index` + raw `__newindex` all on one type. Exercises every
2592    /// branch of the composed dispatch and every permanently rooted handle.
2593    /// If a future change reintroduces a captured wrapper anywhere in the
2594    /// composition path, this is the test most likely to catch it.
2595    #[test]
2596    fn lua_state_frees_with_fields_methods_and_raw_meta() {
2597        use std::rc::Rc;
2598
2599        struct Mixed {
2600            x: f64,
2601            log: Vec<String>,
2602        }
2603        impl UserData for Mixed {
2604            fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
2605                m.add_field_method_get("x", |_, this| Ok(this.x));
2606                m.add_field_method_set("x", |_, this, v: f64| {
2607                    this.x = v;
2608                    Ok(())
2609                });
2610                m.add_method("log_len", |_, this, ()| Ok(this.log.len() as i64));
2611                m.add_method_mut("push_log", |_, this, s: String| {
2612                    this.log.push(s);
2613                    Ok(())
2614                });
2615                m.add_meta_method(MetaMethod::Index, |_, _this, key: String| {
2616                    Ok(::std::format!("dynamic:{key}"))
2617                });
2618                m.add_meta_method_mut(
2619                    MetaMethod::NewIndex,
2620                    |_, _this, (_k, _v): (String, Value)| Ok(()),
2621                );
2622            }
2623        }
2624
2625        let weak_inner = {
2626            let lua = Lua::new();
2627            let weak = Rc::downgrade(&lua.inner);
2628            let _ = lua
2629                .create_userdata(Mixed {
2630                    x: 1.0,
2631                    log: Vec::new(),
2632                })
2633                .expect("create");
2634            weak
2635        };
2636
2637        assert!(
2638            weak_inner.upgrade().is_none(),
2639            "maximal-composition userdata leaked LuaInner: \
2640             check the composed __index / __newindex captures"
2641        );
2642    }
2643
2644    /// The composed `__index` allocates two or three temporary external roots
2645    /// per call (for the per-call `Table`/`Function` views) and relies on
2646    /// `pending_external_unroots` being flushed by the next `with_state`. If
2647    /// that plumbing ever breaks, every field read silently leaks a root. Hammer
2648    /// it in a loop and assert `external_roots.len()` returns to baseline.
2649    #[test]
2650    fn composed_dispatch_does_not_accumulate_external_roots() {
2651        struct Probe {
2652            x: i64,
2653        }
2654        impl UserData for Probe {
2655            fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
2656                m.add_field_method_get("x", |_, this| Ok(this.x));
2657            }
2658        }
2659
2660        let lua = Lua::new();
2661        lua.globals()
2662            .set("v", lua.create_userdata(Probe { x: 1 }).unwrap())
2663            .unwrap();
2664        let baseline = external_root_count(&lua);
2665
2666        for _ in 0..1000 {
2667            let _: i64 = lua.load("return v.x").eval().unwrap();
2668        }
2669        // The last iteration's temp roots queue for unroot on exit of its
2670        // outer with_state; force one more so the flush definitely runs.
2671        let after = external_root_count(&lua);
2672
2673        assert!(
2674            after <= baseline + 2,
2675            "external roots grew under composed __index churn: baseline={baseline} after={after}"
2676        );
2677    }
2678
2679    /// A Rust userdata method takes a Lua `Function` and calls it. Exercises
2680    /// the Weak<LuaInner> upgrade plus the `active_state` reentrancy pointer
2681    /// together. The bms-lua-rs reflection bridge hits this shape on every
2682    /// component access; an existing test covers `create_function` reentry but
2683    /// not the userdata-method path.
2684    #[test]
2685    fn userdata_method_can_reenter_lua_from_callback() {
2686        struct Calc;
2687        impl UserData for Calc {
2688            fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
2689                m.add_method("apply", |_lua, _this, f: Function| {
2690                    let r: i64 = f.call(7_i64)?;
2691                    Ok(r + 1)
2692                });
2693            }
2694        }
2695
2696        let lua = Lua::new();
2697        lua.globals()
2698            .set("c", lua.create_userdata(Calc).unwrap())
2699            .unwrap();
2700        let r: i64 = lua
2701            .load("return c:apply(function(n) return n * 2 end)")
2702            .eval()
2703            .unwrap();
2704        assert_eq!(r, 15);
2705    }
2706
2707    /// Two `Lua::new()` instances must each build their own metatable for the
2708    /// same Rust type. Counts calls to `add_methods` across both states and
2709    /// asserts each state builds independently while still de-duplicating
2710    /// within its own scope.
2711    #[test]
2712    fn metatable_cache_is_per_lua_state() {
2713        use std::sync::atomic::{AtomicUsize, Ordering};
2714        static BUILDS: AtomicUsize = AtomicUsize::new(0);
2715
2716        struct Marker {
2717            v: i64,
2718        }
2719        impl UserData for Marker {
2720            fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
2721                BUILDS.fetch_add(1, Ordering::SeqCst);
2722                m.add_method("v", |_, this, ()| Ok(this.v));
2723            }
2724        }
2725
2726        let start = BUILDS.load(Ordering::SeqCst);
2727
2728        let lua_a = Lua::new();
2729        let _a1 = lua_a.create_userdata(Marker { v: 1 }).unwrap();
2730        assert_eq!(BUILDS.load(Ordering::SeqCst) - start, 1, "state A first build");
2731        let _a2 = lua_a.create_userdata(Marker { v: 2 }).unwrap();
2732        assert_eq!(BUILDS.load(Ordering::SeqCst) - start, 1, "state A reuses cache");
2733
2734        let lua_b = Lua::new();
2735        let _b1 = lua_b.create_userdata(Marker { v: 3 }).unwrap();
2736        assert_eq!(BUILDS.load(Ordering::SeqCst) - start, 2, "state B is independent");
2737
2738        let _a3 = lua_a.create_userdata(Marker { v: 4 }).unwrap();
2739        assert_eq!(BUILDS.load(Ordering::SeqCst) - start, 2, "state A still cached");
2740    }
2741
2742    /// Field beats method when names collide. The composed `__index` looks up
2743    /// field getters before the method table; pin that order so a future
2744    /// refactor of the dispatch closure does not silently swap precedence.
2745    #[test]
2746    fn field_shadows_method_of_same_name() {
2747        struct Shadow {
2748            x: i64,
2749        }
2750        impl UserData for Shadow {
2751            fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
2752                m.add_field_method_get("x", |_, this| Ok(this.x));
2753                m.add_method("x", |_, _this, ()| Ok(999_i64));
2754            }
2755        }
2756
2757        let lua = Lua::new();
2758        lua.globals()
2759            .set("v", lua.create_userdata(Shadow { x: 42 }).unwrap())
2760            .unwrap();
2761
2762        let r: i64 = lua.load("return v.x").eval().unwrap();
2763        assert_eq!(r, 42, "the field getter should beat the method of the same name");
2764    }
2765
2766    /// Direct Lua-side proof the cache is real: two userdata of the same type
2767    /// share the same metatable object as observed by `getmetatable`. If the
2768    /// cache regressed to per-value metatables this returns false.
2769    #[test]
2770    fn cached_metatable_is_shared_across_values_in_lua() {
2771        struct Twin;
2772        impl UserData for Twin {
2773            fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
2774                m.add_method("ping", |_, _this, ()| Ok(1_i64));
2775            }
2776        }
2777
2778        let lua = Lua::new();
2779        lua.globals()
2780            .set("a", lua.create_userdata(Twin).unwrap())
2781            .unwrap();
2782        lua.globals()
2783            .set("b", lua.create_userdata(Twin).unwrap())
2784            .unwrap();
2785
2786        let same: bool = lua
2787            .load("return getmetatable(a) == getmetatable(b)")
2788            .eval()
2789            .unwrap();
2790        assert!(same, "cached metatable must be shared across values of the same type");
2791    }
2792
2793    #[test]
2794    fn fields_and_methods_coexist() {
2795        struct Vec2 {
2796            x: f64,
2797            y: f64,
2798        }
2799        impl UserData for Vec2 {
2800            fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
2801                m.add_field_method_get("x", |_, this| Ok(this.x));
2802                m.add_field_method_get("y", |_, this| Ok(this.y));
2803                m.add_field_method_set("x", |_, this, v: f64| {
2804                    this.x = v;
2805                    Ok(())
2806                });
2807                m.add_field_method_set("y", |_, this, v: f64| {
2808                    this.y = v;
2809                    Ok(())
2810                });
2811                m.add_method("length", |_, this, ()| {
2812                    Ok((this.x * this.x + this.y * this.y).sqrt())
2813                });
2814                m.add_method_mut("scale", |_, this, k: f64| {
2815                    this.x *= k;
2816                    this.y *= k;
2817                    Ok(())
2818                });
2819            }
2820        }
2821
2822        let lua = Lua::new();
2823        let v = lua.create_userdata(Vec2 { x: 3.0, y: 4.0 }).unwrap();
2824        lua.globals().set("v", &v).unwrap();
2825
2826        // method call and field reads on the same value
2827        assert_eq!(lua.load("return v:length()").eval::<f64>().unwrap(), 5.0);
2828        assert_eq!(lua.load("return v.x + v.y").eval::<f64>().unwrap(), 7.0);
2829
2830        // field write
2831        lua.load("v.x = 6").exec().unwrap();
2832        assert_eq!(lua.load("return v.x").eval::<f64>().unwrap(), 6.0);
2833
2834        // method mutation is visible through field reads
2835        lua.load("v:scale(2)").exec().unwrap();
2836        assert_eq!(lua.load("return v.x").eval::<f64>().unwrap(), 12.0);
2837        assert_eq!(lua.load("return v.y").eval::<f64>().unwrap(), 8.0);
2838
2839        // unknown field assignment errors
2840        assert!(lua.load("v.z = 1").exec().is_err());
2841    }
2842
2843    #[test]
2844    fn userdata_methods_dispatch_and_track_borrows() {
2845        let lua = Lua::new();
2846        let globals = lua.globals();
2847        let counter = lua
2848            .create_userdata(Counter { value: 1 })
2849            .expect("userdata should create");
2850        globals
2851            .set("counter", &counter)
2852            .expect("userdata should register");
2853
2854        let result: i64 = lua
2855            .load("counter:inc(5); return counter:get()")
2856            .eval()
2857            .expect("methods should dispatch");
2858        assert_eq!(result, 6);
2859        assert_eq!(
2860            counter
2861                .with_borrow::<Counter, _>(|counter| counter.value)
2862                .expect("borrow should work"),
2863            6
2864        );
2865
2866        {
2867            let borrowed = counter
2868                .borrow::<Counter>()
2869                .expect("borrow guard should work");
2870            assert_eq!(borrowed.value, 6);
2871        }
2872
2873        {
2874            let mut borrowed = counter
2875                .borrow_mut::<Counter>()
2876                .expect("mutable borrow guard should work");
2877            borrowed.value = 9;
2878        }
2879
2880        assert_eq!(
2881            lua.load("return counter:get()")
2882                .eval::<i64>()
2883                .expect("method should see guard mutation"),
2884            9
2885        );
2886    }
2887
2888    #[test]
2889    fn userdata_payload_survives_gc_while_lua_holds_userdata() {
2890        let lua = Lua::new();
2891        let globals = lua.globals();
2892        let counter = lua
2893            .create_userdata(Counter { value: 10 })
2894            .expect("userdata should create");
2895        globals
2896            .set("counter", counter)
2897            .expect("userdata should register");
2898
2899        lua.gc_collect();
2900        let result: i64 = lua
2901            .load("counter:inc(2); collectgarbage('collect'); return counter:get()")
2902            .eval()
2903            .expect("userdata should survive collection");
2904        assert_eq!(result, 12);
2905    }
2906
2907    #[test]
2908    fn userdata_runtime_borrow_conflict_returns_lua_error() {
2909        let lua = Lua::new();
2910        let globals = lua.globals();
2911        let counter = lua
2912            .create_userdata(Counter { value: 1 })
2913            .expect("userdata should create");
2914        globals
2915            .set("counter", &counter)
2916            .expect("userdata should register");
2917
2918        let failed = counter
2919            .with_borrow::<Counter, _>(|_| lua.load("return counter:inc(1)").eval::<i64>().is_err())
2920            .expect("outer borrow should succeed");
2921        assert!(
2922            failed,
2923            "mutable method should fail while immutable borrow is held"
2924        );
2925        assert_eq!(
2926            counter
2927                .with_borrow::<Counter, _>(|counter| counter.value)
2928                .expect("borrow should work"),
2929            1
2930        );
2931    }
2932
2933    #[test]
2934    fn userdata_index_and_newindex_metamethods_dispatch() {
2935        let lua = Lua::new();
2936        let globals = lua.globals();
2937        let bag = lua
2938            .create_userdata(PropertyBag { value: 7 })
2939            .expect("userdata should create");
2940        globals.set("bag", &bag).expect("userdata should register");
2941
2942        let result: i64 = lua
2943            .load("bag.value = 42; return bag.value")
2944            .eval()
2945            .expect("metamethods should dispatch");
2946        assert_eq!(result, 42);
2947        assert_eq!(
2948            bag.with_borrow::<PropertyBag, _>(|bag| bag.value)
2949                .expect("borrow should work"),
2950            42
2951        );
2952    }
2953
2954    #[test]
2955    fn userdata_values_convert_directly_with_into_lua() {
2956        let lua = Lua::new();
2957        let globals = lua.globals();
2958        globals
2959            .set("counter", Counter { value: 3 })
2960            .expect("userdata should convert through IntoLua");
2961
2962        let result: i64 = lua
2963            .load("counter:inc(4); return counter:get()")
2964            .eval()
2965            .expect("converted userdata should dispatch methods");
2966        assert_eq!(result, 7);
2967    }
2968
2969    #[test]
2970    fn variadic_args_and_returns_convert_all_values() {
2971        let lua = Lua::new();
2972        let globals = lua.globals();
2973
2974        let sum = lua
2975            .create_function(|_lua, values: Variadic<i64>| Ok(values.iter().sum::<i64>()))
2976            .expect("variadic callback should create");
2977        globals.set("sum", sum).expect("callback should register");
2978        let result: i64 = lua
2979            .load("return sum(3, 2, 5)")
2980            .eval()
2981            .expect("variadic callback should run");
2982        assert_eq!(result, 10);
2983
2984        let echo = lua
2985            .create_function(|_lua, values: Variadic<Value>| Ok(values))
2986            .expect("variadic return callback should create");
2987        globals.set("echo", echo).expect("callback should register");
2988        let result: (i64, i64, i64) = lua
2989            .load("return echo(1, 2, 3)")
2990            .eval()
2991            .expect("variadic returns should stay separate");
2992        assert_eq!(result, (1, 2, 3));
2993
2994        let values: Variadic<i64> = lua
2995            .load("return 4, 5, 6")
2996            .eval()
2997            .expect("variadic eval should collect all returns");
2998        assert_eq!(values.into_vec(), vec![4, 5, 6]);
2999    }
3000
3001    #[test]
3002    fn vectors_maps_and_triple_returns_convert_through_tables() {
3003        let lua = Lua::new();
3004        let globals = lua.globals();
3005
3006        globals
3007            .set("list", vec![1_i64, 2, 3])
3008            .expect("vector should convert to table");
3009        let second: i64 = lua
3010            .load("return list[2]")
3011            .eval()
3012            .expect("table should be readable from Lua");
3013        assert_eq!(second, 2);
3014
3015        let list: Vec<i64> = lua
3016            .load("return {4, 5, 6}")
3017            .eval()
3018            .expect("table should convert to vector");
3019        assert_eq!(list, vec![4, 5, 6]);
3020
3021        let mut map = HashMap::new();
3022        map.insert("left".to_string(), 10_i64);
3023        map.insert("right".to_string(), 20_i64);
3024        globals
3025            .set("map", map)
3026            .expect("map should convert to table");
3027        let sum: i64 = lua
3028            .load("return map.left + map.right")
3029            .eval()
3030            .expect("map table should be readable from Lua");
3031        assert_eq!(sum, 30);
3032
3033        let map: HashMap<String, i64> = lua
3034            .load("return {alpha = 3, beta = 9}")
3035            .eval()
3036            .expect("table should convert to map");
3037        assert_eq!(map.get("alpha"), Some(&3));
3038        assert_eq!(map.get("beta"), Some(&9));
3039
3040        let triple: (i64, i64, i64) = lua
3041            .load("return 1, 2, 3")
3042            .eval()
3043            .expect("triple returns should convert");
3044        assert_eq!(triple, (1, 2, 3));
3045    }
3046}