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