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}