Skip to main content

luaur_rt/
state.rs

1//! The [`Lua`] handle and the shared inner state.
2//!
3//! ## Lifetime model (mirrors mlua's `Rc<inner> + registry-key` design)
4//!
5//! [`Lua`] owns the `*mut lua_State`. The state is wrapped in an [`Rc`]
6//! ([`LuaInner`]) so that long-lived handles ([`Table`], [`Function`],
7//! [`LuaString`], the corresponding [`Value`] variants, userdata) can hold a
8//! clone of that `Rc` and keep the state alive for as long as they exist.
9//!
10//! Each such handle additionally holds a **registry reference** obtained via
11//! [`lua_ref`] (luaur's `lua_ref`/`lua_unref`). That keeps the underlying Lua
12//! value reachable by the GC, and lets the handle re-push the value onto the
13//! stack on demand. On `Drop` the handle releases its registry slot with
14//! [`lua_unref`] — but only if the state is still alive (the `Rc` keeps it so).
15//!
16//! `Lua` is single-threaded (`Rc`, so `!Send`/`!Sync`), matching mlua's
17//! non-`Send` default.
18//!
19//! ## The `send` feature
20//!
21//! Under the `send` feature (mirroring mlua) the shared interior uses
22//! [`XRc`] = `Arc` instead of `Rc`, and [`LuaInner`] / [`LuaRef`] carry a
23//! documented `unsafe impl Send`. That makes [`Lua`] and every handle `Send` so
24//! the whole VM can be **moved** to another thread. It is *not* made `Sync`: the
25//! VM is still single-threaded, the user must serialize all access, and only the
26//! ownership *transfer* crosses threads (exactly mlua's `send` contract).
27
28use std::cell::Cell;
29
30use crate::error::{Error, Result};
31use crate::sync::{MaybeSend, MaybeSync, NotSync, XRc, XWeak, NOT_SYNC};
32use crate::sys::*;
33use crate::value::Value;
34
35// Re-export the GC-control types here so they live at `luaur_rt::state::{..}`,
36// matching mlua's `mlua::state::{GcMode, GcIncParams, GcGenParams}` path.
37pub use crate::gc::{GcGenParams, GcIncParams, GcMode};
38
39/// The reference-counted, shared interior of a [`Lua`] instance.
40///
41/// Held by [`Lua`] and cloned into every long-lived handle. When the last
42/// `XRc<LuaInner>` is dropped, [`Drop`] closes the `lua_State`.
43pub(crate) struct LuaInner {
44    /// The owned VM state pointer. Never null while this `LuaInner` exists.
45    pub(crate) state: *mut lua_State,
46    /// Whether this `LuaInner` is responsible for closing the state. The
47    /// trampoline builds a *borrowed* [`Lua`] around the calling thread's
48    /// state and must not close it.
49    owned: bool,
50    /// Host type definitions accumulated via [`Lua::add_definitions`] (the
51    /// `typecheck` feature), in Luau definition-file syntax. Each registration
52    /// is appended separated by a newline; the whole buffer is fed to the
53    /// type-checker by [`Lua::check`] / [`Chunk::check`]. Uses the crate's
54    /// `RefCell` interior-mutability idiom (the VM is single-threaded).
55    #[cfg(feature = "typecheck")]
56    typecheck_defs: std::cell::RefCell<String>,
57}
58
59impl LuaInner {
60    /// Build a fresh `LuaInner`, initializing every field (including the
61    /// feature-gated `typecheck_defs` store). Used by all `Lua` constructors so
62    /// the field set stays in one place.
63    fn new(state: *mut lua_State, owned: bool) -> LuaInner {
64        LuaInner {
65            state,
66            owned,
67            #[cfg(feature = "typecheck")]
68            typecheck_defs: std::cell::RefCell::new(String::new()),
69        }
70    }
71}
72
73impl Drop for LuaInner {
74    fn drop(&mut self) {
75        if self.owned && !self.state.is_null() {
76            // Drop this VM's application-data store before closing the state
77            // (it is keyed by the global-state pointer, still valid here).
78            crate::app_data::clear_app_data(self.state);
79            unsafe {
80                // Reset the active memory category to 0 ("main") before closing.
81                // `Lua::set_memory_category` may have left a non-main category
82                // active; allocations made during teardown would otherwise be
83                // accounted to it, tripping `close_state`'s debug invariant that
84                // only category 0 is non-empty at shutdown.
85                crate::sys::lua_setmemcat(self.state, 0);
86                lua_close(self.state)
87            }
88        }
89    }
90}
91
92// Under the `send` feature, allow a `Lua` (and every handle, transitively) to be
93// **moved** across threads. The raw `*mut lua_State` is `!Send`/`!Sync` by
94// default; these impls encode luaur-rt's documented contract — single-threaded
95// *use*, only *ownership transfer* across threads, never concurrent access.
96//
97// `Send` is the property we actually expose. `Sync` is needed only as an
98// internal obligation: `XRc<LuaInner>` is `Arc<LuaInner>` under the feature, and
99// `Arc<T>: Send` requires `T: Send + Sync`. We therefore mark `LuaInner` (the
100// non-public interior) `Sync`, and then keep the *public* `Lua`/handle types
101// `!Sync` with a `NotSync` phantom marker (see [`NotSync`]). Net effect: the VM
102// can be moved across threads but never shared/accessed concurrently — exactly
103// mlua's `send` contract, minus mlua's extra `Sync` (luaur-rt stays `!Sync`).
104#[cfg(feature = "send")]
105unsafe impl Send for LuaInner {}
106#[cfg(feature = "send")]
107unsafe impl Sync for LuaInner {}
108
109/// A handle to a Lua interpreter.
110///
111/// Mirrors `mlua::Lua`. Cloning produces another handle to the **same** VM
112/// (the inner state is shared via `Rc`), exactly like mlua.
113#[derive(Clone)]
114pub struct Lua {
115    pub(crate) inner: XRc<LuaInner>,
116    /// Keeps `Lua` `!Sync` under the `send` feature (the VM is move-only, never
117    /// shareable). A zero-sized `()` under the default build. See [`NotSync`].
118    pub(crate) _not_sync: NotSync,
119}
120
121impl Lua {
122    /// Create a new Lua state with the standard library opened.
123    ///
124    /// Mirrors `mlua::Lua::new`.
125    pub fn new() -> Lua {
126        // luaur's v11+ bytecode needs the default Luau flags on (see the
127        // umbrella crate's `eval`).
128        luaur_common::set_all_flags(true);
129        unsafe {
130            let state = lua_l_newstate();
131            assert!(!state.is_null(), "lua_l_newstate returned null");
132            lua_l_openlibs(state);
133            Lua {
134                inner: XRc::new(LuaInner::new(state, true)),
135                _not_sync: NOT_SYNC,
136            }
137        }
138    }
139
140    /// Create a new Lua state **without** opening the standard library.
141    ///
142    /// A deliberate deviation from mlua (which exposes `StdLib` flags); a
143    /// minimal convenience for embedders who want a clean global table.
144    pub fn new_empty() -> Lua {
145        luaur_common::set_all_flags(true);
146        let state = lua_l_newstate();
147        assert!(!state.is_null(), "lua_l_newstate returned null");
148        Lua {
149            inner: XRc::new(LuaInner::new(state, true)),
150            _not_sync: NOT_SYNC,
151        }
152    }
153
154    /// Create a new Lua state with the standard library opened, **without** the
155    /// extra safety restrictions a safe `Lua::new` would impose.
156    ///
157    /// Mirrors `mlua::Lua::unsafe_new`. In Luau there is no separate set of
158    /// "unsafe" base libraries (the `debug`/`ffi`/`package` distinction is a
159    /// Lua-5.x concept), so this is equivalent to [`Lua::new`]; it exists for
160    /// mlua signature parity.
161    ///
162    /// # Safety
163    /// Provided for parity with mlua's `unsafe_new`, which can open libraries
164    /// that allow loading native code. luaur's Luau base library does not expose
165    /// such facilities, so this is in practice as safe as [`Lua::new`]; the
166    /// `unsafe` marker is retained to match mlua's signature.
167    pub unsafe fn unsafe_new() -> Lua {
168        Lua::new()
169    }
170
171    /// Create a new Lua state opening the libraries selected by `libs`, with the
172    /// behavioral `options`. Mirrors `mlua::Lua::new_with`.
173    ///
174    /// **DEVIATION:** luaur opens the Luau base libraries as a unit, so any
175    /// non-empty `libs` opens the full standard library and [`StdLib::NONE`]
176    /// opens nothing (see [`StdLib`]). `options` is recorded on the VM (currently
177    /// only `catch_rust_panics` is observable).
178    pub fn new_with(
179        libs: crate::options::StdLib,
180        options: crate::options::LuaOptions,
181    ) -> Result<Lua> {
182        luaur_common::set_all_flags(true);
183        unsafe {
184            let state = lua_l_newstate();
185            assert!(!state.is_null(), "lua_l_newstate returned null");
186            if !libs.is_none() {
187                lua_l_openlibs(state);
188            }
189            let lua = Lua {
190                inner: XRc::new(LuaInner::new(state, true)),
191                _not_sync: NOT_SYNC,
192            };
193            lua.set_catch_rust_panics(options.catch_rust_panics);
194            Ok(lua)
195        }
196    }
197
198    /// The raw state pointer. Internal use only.
199    #[inline]
200    pub(crate) fn state(&self) -> *mut lua_State {
201        self.inner.state
202    }
203
204    /// Wrap an *already-existing* state (e.g. the thread passed into a C
205    /// trampoline) in a borrowed [`Lua`] that will **not** close it on drop.
206    ///
207    /// # Safety
208    /// `state` must be a valid `lua_State` that outlives the returned handle
209    /// and all handles cloned from it.
210    pub(crate) unsafe fn from_borrowed(state: *mut lua_State) -> Lua {
211        Lua {
212            inner: XRc::new(LuaInner::new(state, false)),
213            _not_sync: NOT_SYNC,
214        }
215    }
216
217    /// Register a value sitting at stack index `idx` in the registry and return
218    /// a [`LuaRef`] that owns the slot. Does not pop the value.
219    pub(crate) fn register_ref(&self, idx: c_int) -> LuaRef {
220        let id = unsafe { lua_ref(self.state(), idx) };
221        LuaRef {
222            inner: self.inner.clone(),
223            id: Cell::new(id),
224        }
225    }
226
227    /// Pop the top stack value and register it, returning a [`LuaRef`].
228    pub(crate) fn pop_ref(&self) -> LuaRef {
229        let r = self.register_ref(-1);
230        unsafe { lua_pop(self.state(), 1) };
231        r
232    }
233}
234
235impl Default for Lua {
236    fn default() -> Self {
237        Lua::new()
238    }
239}
240
241impl Lua {
242    /// A non-owning, weak handle to this VM. Mirrors `mlua::Lua::weak`.
243    ///
244    /// The [`WeakLua`] does not keep the VM alive; it can be upgraded back to a
245    /// strong [`Lua`] only while at least one strong handle still exists.
246    pub fn weak(&self) -> WeakLua {
247        WeakLua(XRc::downgrade(&self.inner))
248    }
249}
250
251/// A weak handle to a [`Lua`] instance. Mirrors `mlua::WeakLua`.
252///
253/// Holds a non-owning reference to the shared VM interior; upgrade it to a
254/// strong [`Lua`] with [`WeakLua::try_upgrade`] / [`WeakLua::upgrade`].
255#[derive(Clone)]
256pub struct WeakLua(pub(crate) XWeak<LuaInner>);
257
258impl WeakLua {
259    /// Try to obtain a strong [`Lua`] handle. Returns `None` if the VM has
260    /// already been destroyed. Mirrors `mlua::WeakLua::try_upgrade`.
261    pub fn try_upgrade(&self) -> Option<Lua> {
262        self.0.upgrade().map(|inner| Lua {
263            inner,
264            _not_sync: NOT_SYNC,
265        })
266    }
267
268    /// Obtain a strong [`Lua`] handle, panicking if the VM has been destroyed.
269    /// Mirrors `mlua::WeakLua::upgrade`.
270    pub fn upgrade(&self) -> Lua {
271        self.try_upgrade().expect("Lua instance is destroyed")
272    }
273}
274
275// ---------------------------------------------------------------------------
276// Public, mlua-style construction API.
277// ---------------------------------------------------------------------------
278
279use crate::callback::{create_callback_function, BoxedCallback};
280use crate::chunk::Chunk;
281use crate::function::Function;
282use crate::multi::MultiValue;
283use crate::string::LuaString;
284use crate::table::Table;
285use crate::traits::{FromLuaMulti, IntoLuaMulti};
286use crate::userdata::{AnyUserData, UserData};
287
288impl Lua {
289    /// The globals table.
290    ///
291    /// Mirrors `mlua::Lua::globals`. Returns a [`Table`] handle to the global
292    /// environment (the table reachable at `LUA_GLOBALSINDEX`).
293    pub fn globals(&self) -> Table {
294        let state = self.state();
295        unsafe {
296            // Push the globals table (a copy of the LUA_GLOBALSINDEX pseudo
297            // value) and take a ref to it.
298            lua_pushvalue(state, LUA_GLOBALSINDEX);
299            Table::from_ref(self.pop_ref())
300        }
301    }
302
303    /// Create a new, empty table.
304    ///
305    /// Mirrors `mlua::Lua::create_table` (infallible here, so no `Result`
306    /// wrapper is strictly needed — but we also provide the `_result` variant
307    /// for signature parity below).
308    pub fn create_table(&self) -> Table {
309        crate::table::create_table(self)
310    }
311
312    /// `Result`-returning alias of [`Lua::create_table`] for mlua signature
313    /// parity.
314    pub fn create_table_result(&self) -> Result<Table> {
315        Ok(self.create_table())
316    }
317
318    /// Create a Lua string from bytes/str.
319    ///
320    /// Mirrors `mlua::Lua::create_string`.
321    pub fn create_string(&self, s: impl AsRef<[u8]>) -> LuaString {
322        crate::string::create_string(self, s.as_ref())
323    }
324
325    /// Create a table and populate it from an iterator of key/value pairs.
326    ///
327    /// Mirrors `mlua::Lua::create_table_from`.
328    pub fn create_table_from<K, V, I>(&self, iter: I) -> Result<Table>
329    where
330        K: crate::traits::IntoLua,
331        V: crate::traits::IntoLua,
332        I: IntoIterator<Item = (K, V)>,
333    {
334        let t = self.create_table();
335        for (k, v) in iter {
336            t.raw_set(k, v)?;
337        }
338        Ok(t)
339    }
340
341    /// Create a sequence (1-based array) table from an iterator of values.
342    ///
343    /// Mirrors `mlua::Lua::create_sequence_from`.
344    pub fn create_sequence_from<V, I>(&self, iter: I) -> Result<Table>
345    where
346        V: crate::traits::IntoLua,
347        I: IntoIterator<Item = V>,
348    {
349        let t = self.create_table();
350        for (i, v) in iter.into_iter().enumerate() {
351            t.raw_set((i + 1) as i64, v)?;
352        }
353        Ok(t)
354    }
355
356    /// Run a full garbage-collection cycle.
357    ///
358    /// Mirrors `mlua::Lua::gc_collect` (infallible here — luaur's `lua_gc`
359    /// cannot fail for `collect`).
360    pub fn gc_collect(&self) -> Result<()> {
361        lua_gc(self.state(), lua_GCOp::LUA_GCCOLLECT as c_int, 0);
362        Ok(())
363    }
364
365    /// Create a Lua function from a Rust closure.
366    ///
367    /// Mirrors `mlua::Lua::create_function`. The closure receives `&Lua` and
368    /// the arguments converted via [`FromLuaMulti`]; its `Ok` return is
369    /// converted via [`IntoLuaMulti`]. Returning `Err` (or panicking) surfaces
370    /// as a catchable Lua error.
371    pub fn create_function<F, A, R>(&self, func: F) -> Result<Function>
372    where
373        F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
374        A: FromLuaMulti,
375        R: IntoLuaMulti,
376    {
377        let boxed: BoxedCallback = Box::new(move |lua, args| {
378            let a = A::from_lua_multi(args, lua)?;
379            let r = func(lua, a)?;
380            r.into_lua_multi(lua)
381        });
382        create_callback_function(self, boxed)
383    }
384
385    /// Create a Lua function from a Rust **mutable** closure.
386    ///
387    /// Mirrors `mlua::Lua::create_function_mut`. The closure is guarded by a
388    /// [`RefCell`](std::cell::RefCell); a re-entrant call (the callback running
389    /// Lua that calls the same callback again) surfaces as
390    /// [`Error::RecursiveMutCallback`](crate::Error::RecursiveMutCallback)
391    /// rather than allowing mutable aliasing.
392    pub fn create_function_mut<F, A, R>(&self, func: F) -> Result<Function>
393    where
394        F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
395        A: FromLuaMulti,
396        R: IntoLuaMulti,
397    {
398        let func = std::cell::RefCell::new(func);
399        self.create_function(move |lua, args| {
400            let mut borrow = func
401                .try_borrow_mut()
402                .map_err(|_| Error::RecursiveMutCallback)?;
403            (borrow)(lua, args)
404        })
405    }
406
407    /// Create userdata wrapping a `T: UserData` value.
408    ///
409    /// Mirrors `mlua::Lua::create_userdata`.
410    pub fn create_userdata<T: UserData + MaybeSend + MaybeSync + 'static>(
411        &self,
412        data: T,
413    ) -> Result<AnyUserData> {
414        crate::userdata::create_userdata(self, data)
415    }
416
417    /// Create a Lua function from a Rust **async** closure (the `async`
418    /// feature).
419    ///
420    /// Mirrors `mlua::Lua::create_async_function`. The closure receives an owned
421    /// [`Lua`] and the converted arguments, and returns a `Future`. When the
422    /// resulting Lua function is called, it runs on a coroutine that **yields**
423    /// while the future is pending; a driver such as
424    /// [`Function::call_async`](crate::Function::call_async) /
425    /// [`Chunk::eval_async`](crate::Chunk::eval_async) resumes the coroutine,
426    /// polls the future, and resumes it with the result when ready.
427    ///
428    /// The executor is provided by the caller (luaur-rt is executor-agnostic,
429    /// exactly like mlua): the returned futures must be `.await`ed / polled on
430    /// the caller's runtime (e.g. tokio).
431    #[cfg(feature = "async")]
432    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
433    pub fn create_async_function<F, A, FR, R>(&self, func: F) -> Result<Function>
434    where
435        F: Fn(Lua, A) -> FR + 'static,
436        A: FromLuaMulti,
437        FR: std::future::Future<Output = Result<R>> + 'static,
438        R: IntoLuaMulti,
439    {
440        let callback: crate::async_support::AsyncCallback = Box::new(move |lua, args| {
441            // Convert the arguments eagerly; defer the conversion error into the
442            // future so it surfaces uniformly on the first poll.
443            let a = A::from_lua_multi(args, &lua);
444            let fut = a.map(|a| func(lua.clone(), a));
445            Box::pin(async move {
446                let r = fut?.await?;
447                r.into_lua_multi(&lua)
448            })
449        });
450        crate::async_support::create_async_callback(self, callback)
451    }
452
453    /// Creates and returns a Luau [buffer] object from a byte slice of data.
454    ///
455    /// Mirrors `mlua::Lua::create_buffer`.
456    ///
457    /// [buffer]: https://luau.org/library#buffer-library
458    pub fn create_buffer(&self, data: impl AsRef<[u8]>) -> Result<crate::buffer::Buffer> {
459        let data = data.as_ref();
460        let buffer = self.create_buffer_with_capacity(data.len())?;
461        if !data.is_empty() {
462            buffer.write_bytes(0, data);
463        }
464        Ok(buffer)
465    }
466
467    /// Creates and returns a Luau [buffer] object with the specified size.
468    ///
469    /// Size limit is 1GB. All bytes are initialized to zero. Exceeding the
470    /// limit returns a `RuntimeError` carrying a `"memory allocation error"`
471    /// message (matching mlua).
472    ///
473    /// Mirrors `mlua::Lua::create_buffer_with_capacity`.
474    ///
475    /// [buffer]: https://luau.org/library#buffer-library
476    pub fn create_buffer_with_capacity(&self, size: usize) -> Result<crate::buffer::Buffer> {
477        crate::buffer::create_buffer_with_capacity(self, size)
478    }
479
480    /// Creates and returns a Luau [`Vector`](crate::Vector) value.
481    ///
482    /// Mirrors `mlua::Lua::create_vector`. luaur is a 3-wide vector build.
483    pub fn create_vector(&self, x: f32, y: f32, z: f32) -> crate::vector::Vector {
484        crate::vector::Vector::new(x, y, z)
485    }
486
487    /// Load a chunk of Lua source for execution.
488    ///
489    /// Mirrors `mlua::Lua::load`. Returns a [`Chunk`]; finalize with
490    /// [`Chunk::exec`] / [`Chunk::eval`] / [`Chunk::into_function`].
491    pub fn load(&self, source: impl AsRef<str>) -> Chunk {
492        Chunk {
493            lua: self.clone(),
494            source: source.as_ref().to_string(),
495            name: "chunk".to_string(),
496            environment: None,
497            compiler: None,
498        }
499    }
500
501    /// Convert a Rust value into a single Lua [`Value`].
502    ///
503    /// Mirrors `mlua::Lua::pack`-ish convenience. Provided so callers can build
504    /// `Value`s without importing the trait.
505    pub fn pack(&self, value: impl crate::traits::IntoLua) -> Result<crate::value::Value> {
506        value.into_lua(self)
507    }
508
509    /// Build a [`MultiValue`] from anything `IntoLuaMulti`.
510    pub fn pack_multi(&self, values: impl IntoLuaMulti) -> Result<MultiValue> {
511        values.into_lua_multi(self)
512    }
513
514    /// Convert any `FromLuaMulti` from a packed [`MultiValue`]. Mirrors
515    /// `mlua::Lua::unpack_multi` (and `unpack` for the single-value case).
516    pub fn unpack_multi<T: FromLuaMulti>(&self, values: MultiValue) -> Result<T> {
517        T::from_lua_multi(values, self)
518    }
519
520    /// Convert a single Lua [`Value`] to a Rust value. Mirrors `mlua::Lua::unpack`.
521    pub fn unpack<T: crate::traits::FromLua>(&self, value: Value) -> Result<T> {
522        T::from_lua(value, self)
523    }
524
525    /// Coerce a [`Value`] to an integer the way Lua's `tonumber`+integer check
526    /// would (`"1"` -> `Some(1)`, `"1.5"` -> `None`, a non-numeric value ->
527    /// `None`). Mirrors `mlua::Lua::coerce_integer`.
528    pub fn coerce_integer(&self, value: Value) -> Result<Option<crate::value::Integer>> {
529        let state = self.state();
530        unsafe {
531            self.push_value(&value)?;
532            let mut isnum: c_int = 0;
533            let n = lua_tonumberx(state, -1, &mut isnum);
534            lua_pop(state, 1);
535            if isnum == 0 {
536                return Ok(None);
537            }
538            // An integral, in-range float coerces to an integer; otherwise None.
539            if n.fract() == 0.0 && n.is_finite() && n >= i64::MIN as f64 && n <= i64::MAX as f64 {
540                Ok(Some(n as i64))
541            } else {
542                Ok(None)
543            }
544        }
545    }
546
547    /// Coerce a [`Value`] to a float the way Lua's `tonumber` would. Mirrors
548    /// `mlua::Lua::coerce_number`.
549    pub fn coerce_number(&self, value: Value) -> Result<Option<crate::value::Number>> {
550        let state = self.state();
551        unsafe {
552            self.push_value(&value)?;
553            let mut isnum: c_int = 0;
554            let n = lua_tonumberx(state, -1, &mut isnum);
555            lua_pop(state, 1);
556            if isnum == 0 {
557                Ok(None)
558            } else {
559                Ok(Some(n))
560            }
561        }
562    }
563
564    /// Replace the global environment with `globals`. Mirrors
565    /// `mlua::Lua::set_globals`.
566    ///
567    /// In a sandboxed Lua state the globals table is read-only and cannot be
568    /// replaced; this returns a [`Error::RuntimeError`] in that case (matching
569    /// mlua / Luau).
570    pub fn set_globals(&self, globals: Table) -> Result<()> {
571        if self.is_sandboxed() {
572            return Err(Error::runtime(
573                "cannot change globals in a sandboxed Lua state",
574            ));
575        }
576        let state = self.state();
577        unsafe {
578            globals.push_to_stack();
579            lua_replace(state, LUA_GLOBALSINDEX);
580        }
581        Ok(())
582    }
583
584    /// Build a stack traceback string for this VM. Mirrors `mlua::Lua::traceback`.
585    ///
586    /// `msg`, if present, is prepended to the traceback; `level` selects the
587    /// starting stack level. The returned [`LuaString`] holds the traceback as
588    /// produced by `luaL_traceback`.
589    pub fn traceback(&self, msg: Option<&str>, level: usize) -> Result<LuaString> {
590        let state = self.state();
591        unsafe {
592            lua_l_traceback(state, state, msg, level as c_int);
593            // luaL_traceback pushes the resulting string onto the stack.
594            Ok(LuaString::from_ref(self.pop_ref()))
595        }
596    }
597}
598
599// ---------------------------------------------------------------------------
600// Static type-checking (the `typecheck` feature).
601//
602// luaur ships Luau's static type checker, so — unlike mlua — a script can be
603// type-checked against the host surface *before* it runs. The host surface is
604// described in Luau definition-file syntax and accumulated on the `Lua` via
605// `add_definitions`; `check` / `Chunk::check` then validate source against it.
606// ---------------------------------------------------------------------------
607#[cfg(feature = "typecheck")]
608#[cfg_attr(docsrs, doc(cfg(feature = "typecheck")))]
609impl Lua {
610    /// Register host type `definitions` (Luau definition-file syntax) so later
611    /// [`Lua::check`] / [`Chunk::check`] calls type-check against them.
612    ///
613    /// `definitions` describes the host-provided globals — the Rust functions,
614    /// values, and userdata you expose to the runtime (e.g. via
615    /// [`Lua::create_function`] / [`UserData`](crate::UserData)):
616    ///
617    /// ```text
618    /// declare function add(a: number, b: number): number
619    /// declare config: { name: string, retries: number }
620    /// ```
621    ///
622    /// The definitions are validated before being recorded: if they are
623    /// malformed, this returns [`Error::TypeError`](crate::Error::TypeError)
624    /// carrying the (`in_definitions`) diagnostics and records nothing. On
625    /// success they are appended to this VM's accumulated definitions.
626    pub fn add_definitions(&self, defs: &str) -> Result<()> {
627        // Validate the new definitions in isolation by checking a trivial body.
628        if let Err(diagnostics) = crate::typecheck::check_with_definitions("return nil", defs) {
629            // Only the definition-side diagnostics are this call's fault; a
630            // type error in the trivial body would be ours, not the caller's.
631            let def_errors: Vec<crate::TypeDiagnostic> = diagnostics
632                .into_iter()
633                .filter(|d| d.in_definitions)
634                .collect();
635            if !def_errors.is_empty() {
636                return Err(Error::TypeError(def_errors));
637            }
638        }
639        // Append, newline-separated, to the accumulated definitions.
640        let mut store = self.inner.typecheck_defs.borrow_mut();
641        if !store.is_empty() {
642            store.push('\n');
643        }
644        store.push_str(defs);
645        Ok(())
646    }
647
648    /// Type-check `source` against this VM's accumulated host definitions.
649    ///
650    /// Returns `Ok(())` if the source type-checks clean, or
651    /// [`Error::TypeError`](crate::Error::TypeError) carrying the structured
652    /// diagnostics otherwise.
653    ///
654    /// The Luau VM is dynamically typed, so this is **advisory**: a script that
655    /// fails the check can still be run (`exec`/`eval`). The value is catching
656    /// host-API misuse statically, before running untrusted or generated code.
657    pub fn check(&self, source: &str) -> Result<()> {
658        let defs = self.inner.typecheck_defs.borrow();
659        let result = if defs.is_empty() {
660            crate::typecheck::check(source)
661        } else {
662            crate::typecheck::check_with_definitions(source, &defs)
663        };
664        result.map_err(Error::TypeError)
665    }
666
667    /// Type-check `source` against this VM's accumulated host definitions **plus**
668    /// the extra `defs` (for a one-off check that does not persist `defs`).
669    ///
670    /// Same mapping as [`Lua::check`]: `Ok(())` when clean, otherwise
671    /// [`Error::TypeError`](crate::Error::TypeError).
672    pub fn check_with_definitions(&self, source: &str, defs: &str) -> Result<()> {
673        let accumulated = self.inner.typecheck_defs.borrow();
674        let combined = if accumulated.is_empty() {
675            defs.to_string()
676        } else {
677            format!("{accumulated}\n{defs}")
678        };
679        crate::typecheck::check_with_definitions(source, &combined).map_err(Error::TypeError)
680    }
681}
682
683/// An owned registry reference to a Lua value.
684///
685/// Keeps both the value reachable (registry slot) and the VM alive (the cloned
686/// `XRc<LuaInner>`). On drop it releases the slot via [`lua_unref`].
687pub(crate) struct LuaRef {
688    inner: XRc<LuaInner>,
689    id: Cell<c_int>,
690}
691
692// `LuaRef` is shared behind `XRc<LuaRef>` (`Arc<LuaRef>` under the feature) by
693// every handle, so it must be `Send + Sync` for the handles to be `Send`. The
694// `Cell<c_int>` slot is only ever mutated on the owning thread (the move-only
695// contract); marking `LuaRef` `Sync` is sound under that contract. Handles stay
696// `!Sync` via their own `NotSync` markers.
697#[cfg(feature = "send")]
698unsafe impl Send for LuaRef {}
699#[cfg(feature = "send")]
700unsafe impl Sync for LuaRef {}
701
702impl LuaRef {
703    /// The owning [`Lua`] handle (a fresh borrow sharing the same inner state).
704    pub(crate) fn lua(&self) -> Lua {
705        Lua {
706            inner: self.inner.clone(),
707            _not_sync: NOT_SYNC,
708        }
709    }
710
711    /// The raw state pointer this ref belongs to.
712    #[inline]
713    pub(crate) fn state(&self) -> *mut lua_State {
714        self.inner.state
715    }
716
717    /// The registry id. (Retained for internal diagnostics; handle identity is
718    /// established via `lua_topointer`, not the registry slot id.)
719    #[inline]
720    #[allow(dead_code)]
721    pub(crate) fn id(&self) -> c_int {
722        self.id.get()
723    }
724
725    /// Push the referenced value back onto the stack.
726    pub(crate) fn push(&self) {
727        // The registry table lives at LUA_REGISTRYINDEX; `lua_ref` stores
728        // values keyed by their integer id, so a `rawgeti` on the registry
729        // recovers them. luaur exposes this through getfield on the registry
730        // via the same mechanism `lua_getref` uses in upstream Luau:
731        // `lua_rawgeti(L, LUA_REGISTRYINDEX, id)`.
732        unsafe {
733            luaur_vm::functions::lua_rawgeti::lua_rawgeti(
734                self.state(),
735                luaur_vm::macros::lua_registryindex::LUA_REGISTRYINDEX,
736                self.id.get(),
737            );
738        }
739    }
740}
741
742impl Clone for LuaRef {
743    fn clone(&self) -> Self {
744        // Re-push the value and take a fresh registry slot, so each clone owns
745        // an independent slot (simplest correct behavior).
746        self.push();
747        let new = self.lua().pop_ref();
748        new
749    }
750}
751
752impl Drop for LuaRef {
753    fn drop(&mut self) {
754        let id = self.id.get();
755        // Only unref live, real slots.
756        if id > 0 && !self.inner.state.is_null() {
757            unsafe { lua_unref(self.inner.state, id) };
758        }
759    }
760}
761
762impl Lua {
763    /// Convenience: convert a top-of-stack value (at `idx`) into a [`Value`],
764    /// taking a registry ref for reference types. Does not pop.
765    pub(crate) fn value_from_stack(&self, idx: c_int) -> Result<Value> {
766        crate::value::value_from_stack(self, idx)
767    }
768
769    /// Push a [`Value`] onto the stack.
770    pub(crate) fn push_value(&self, value: &Value) -> Result<()> {
771        crate::value::push_value(self, value)
772    }
773
774    /// Metatable-aware `tostring` of a [`Value`] (honors `__tostring`),
775    /// mirroring Lua's `tostring`/`luaL_tolstring`.
776    pub(crate) fn value_to_string(&self, value: &Value) -> Result<String> {
777        let state = self.state();
778        unsafe {
779            self.push_value(value)?;
780            let mut len = 0usize;
781            let p = lua_l_tolstring(state, -1, &mut len);
782            let out = if p.is_null() {
783                String::new()
784            } else {
785                let bytes = core::slice::from_raw_parts(p as *const u8, len);
786                String::from_utf8_lossy(bytes).into_owned()
787            };
788            // luaL_tolstring pushes the result string; pop it plus the value.
789            lua_pop(state, 2);
790            Ok(out)
791        }
792    }
793
794    /// Map a `lua_pcall`/`luau_load` status code plus the error object on the
795    /// stack into an [`Error`]. Assumes a non-zero status and that the error
796    /// object is on top of the stack; pops it.
797    pub(crate) fn pop_error(&self, status: c_int) -> Error {
798        let state = self.state();
799        unsafe {
800            // First, see if the error object is one of our *structured* error
801            // userdata (raised for scope-destruction errors). If so, recover the
802            // original `Error` and wrap it in `CallbackError`, mirroring mlua.
803            if let Some(cause) = crate::callback::recover_wrapped_error(state, -1) {
804                lua_pop(state, 1);
805                return Error::CallbackError {
806                    traceback: String::new(),
807                    cause: std::sync::Arc::new(cause),
808                };
809            }
810            // Otherwise, fall back to the flat string error path.
811            let mut len = 0usize;
812            let s = lua_tolstring(state, -1, &mut len);
813            let msg = if s.is_null() {
814                "<non-string error>".to_string()
815            } else {
816                let bytes = core::slice::from_raw_parts(s as *const u8, len);
817                String::from_utf8_lossy(bytes).into_owned()
818            };
819            lua_pop(state, 1);
820            // `LUA_ERRMEM` (status 4) is an out-of-memory error (the VM set the
821            // error object to "not enough memory"); surface it as `MemoryError`
822            // so `set_memory_limit` callers can match it, mirroring mlua.
823            // `luau_load` reports OOM with a generic non-zero rc but the same
824            // "not enough memory" message, so we also detect it by message.
825            if status == 4 || msg == "not enough memory" {
826                return Error::MemoryError(msg);
827            }
828            Error::RuntimeError(msg)
829        }
830    }
831}