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}