luaur_rt/error.rs
1//! Error type and `Result` alias, mirroring mlua's [`Error`] / [`Result`].
2//!
3//! We expose the common, developer-facing subset of mlua's `Error` variants
4//! (`RuntimeError`, `SyntaxError`, the two conversion errors, etc.) plus the
5//! `Error::external` / `Error::runtime` constructors. Variants specific to
6//! features we have not implemented (async, serde, scopes, registry keys) are
7//! intentionally omitted.
8
9use std::error::Error as StdError;
10use std::fmt;
11use std::sync::Arc;
12
13// `Result` is defined below; `Arc<Error>` is used by `Error::CallbackError`.
14
15/// A boxed standard error, used by [`Error::ExternalError`].
16type DynStdError = dyn StdError + Send + Sync;
17
18/// The result type used throughout `luaur-rt`, mirroring [`mlua::Result`].
19pub type Result<T> = std::result::Result<T, Error>;
20
21/// Errors that can occur when interacting with the Lua engine.
22///
23/// The variant set mirrors the commonly used part of mlua's `Error`. It is
24/// marked `#[non_exhaustive]` (like mlua's) so new variants can be added
25/// without a breaking change.
26#[derive(Debug, Clone)]
27#[non_exhaustive]
28pub enum Error {
29 /// A Lua syntax (compile/parse) error.
30 SyntaxError {
31 /// The human-readable message produced by the compiler.
32 message: String,
33 /// Whether the input looked like it was merely incomplete (e.g. an
34 /// unterminated block). Always `false` for now; reserved for REPL use.
35 incomplete_input: bool,
36 },
37 /// A Lua runtime error (`error(..)`, a failed `assert`, a type error, or a
38 /// Rust callback returning `Err`).
39 RuntimeError(String),
40 /// A memory allocation error reported by the VM.
41 MemoryError(String),
42 /// A value could not be converted **from** a Lua value into the requested
43 /// Rust type.
44 FromLuaConversionError {
45 /// The Lua type name of the source value.
46 from: &'static str,
47 /// The name of the target Rust type.
48 to: String,
49 /// Optional extra detail.
50 message: Option<String>,
51 },
52 /// A Rust value could not be converted **into** a Lua value.
53 ToLuaConversionError {
54 /// The name of the source Rust type.
55 from: &'static str,
56 /// The Lua type name being targeted.
57 to: &'static str,
58 /// Optional extra detail.
59 message: Option<String>,
60 },
61 /// A `UserData` value was accessed as the wrong concrete type.
62 UserDataTypeMismatch,
63 /// A `UserData` value was used after it had been destructed (dropped).
64 UserDataDestructed,
65 /// Either a callback or a userdata method was called, but the callback or
66 /// userdata had been destructed.
67 ///
68 /// This happens when a function/userdata created via [`Lua::scope`] is used
69 /// after the scope has ended (so the scope has already dropped the boxed
70 /// closure / invalidated the Lua object). Mirrors
71 /// `mlua::Error::CallbackDestructed`.
72 ///
73 /// [`Lua::scope`]: crate::Lua::scope
74 CallbackDestructed,
75 /// A Rust callback returned `Err`, which was raised as a Lua error and then
76 /// caught at a protected-call boundary (e.g. [`Function::call`]). The
77 /// original error is preserved in `cause`. Mirrors
78 /// `mlua::Error::CallbackError`.
79 ///
80 /// luaur-rt only produces this variant for callback errors that carry
81 /// structured meaning across the Lua boundary (currently
82 /// [`Error::CallbackDestructed`] and [`Error::UserDataDestructed`]); plain
83 /// string callback errors continue to surface as [`Error::RuntimeError`] for
84 /// backward compatibility.
85 ///
86 /// [`Function::call`]: crate::Function::call
87 CallbackError {
88 /// A Lua call-stack traceback (empty when luaur-rt does not capture one).
89 traceback: String,
90 /// The original error returned by the Rust callback.
91 cause: Arc<Error>,
92 },
93 /// A `UserData` could not be immutably borrowed because it is already
94 /// mutably borrowed.
95 UserDataBorrowError,
96 /// A `UserData` could not be mutably borrowed because it is already
97 /// borrowed.
98 UserDataBorrowMutError,
99 /// A coroutine ([`crate::Thread`]) could not be resumed because it has
100 /// finished, errored, or is currently running. Mirrors
101 /// `mlua::Error::CoroutineUnresumable`.
102 CoroutineUnresumable,
103 /// A [`crate::RegistryKey`] was used with a [`crate::Lua`] that does not
104 /// own it. Mirrors `mlua::Error::MismatchedRegistryKey`.
105 MismatchedRegistryKey,
106 /// A `create_function_mut` / `create_userdata` mutable callback was invoked
107 /// re-entrantly while a previous invocation still held the `&mut`. The inner
108 /// `RefCell` borrow failed, which we surface as this variant rather than
109 /// allowing mutable aliasing. Mirrors `mlua::Error::RecursiveMutCallback`.
110 RecursiveMutCallback,
111 /// A Rust panic was raised across a `pcall` boundary, caught and resumed
112 /// once; a later attempt to re-raise/observe it failed because the panic was
113 /// already resumed. Mirrors `mlua::Error::PreviouslyResumedPanic`.
114 PreviouslyResumedPanic,
115 /// An error originating outside Lua, wrapped via [`Error::external`].
116 ExternalError(Arc<DynStdError>),
117 /// A serialization (Rust -> Lua) error produced by the `serde` feature.
118 /// Mirrors `mlua::Error::SerializeError`.
119 #[cfg(feature = "serde")]
120 SerializeError(String),
121 /// A deserialization (Lua -> Rust) error produced by the `serde` feature.
122 /// Mirrors `mlua::Error::DeserializeError`.
123 #[cfg(feature = "serde")]
124 DeserializeError(String),
125 /// One or more static type-checker diagnostics produced by the `typecheck`
126 /// feature (e.g. [`Lua::check`](crate::Lua::check) /
127 /// [`Chunk::check`](crate::Chunk::check)). Each
128 /// [`TypeDiagnostic`](crate::TypeDiagnostic) carries its 1-based source
129 /// location.
130 ///
131 /// There is no mlua equivalent: Lua has no static types, so mlua cannot
132 /// type-check a script before running it.
133 #[cfg(feature = "typecheck")]
134 TypeError(Vec<crate::TypeDiagnostic>),
135}
136
137#[cfg(feature = "serde")]
138impl serde::ser::Error for Error {
139 fn custom<T: fmt::Display>(msg: T) -> Self {
140 Error::SerializeError(msg.to_string())
141 }
142}
143
144#[cfg(feature = "serde")]
145impl serde::de::Error for Error {
146 fn custom<T: fmt::Display>(msg: T) -> Self {
147 Error::DeserializeError(msg.to_string())
148 }
149}
150
151impl Error {
152 /// Create a [`Error::RuntimeError`] from any displayable message.
153 ///
154 /// Mirrors `mlua::Error::runtime`.
155 pub fn runtime<S: fmt::Display>(message: S) -> Self {
156 Error::RuntimeError(message.to_string())
157 }
158
159 /// Try to view the wrapped external error as a concrete type `T`.
160 ///
161 /// Mirrors the common `mlua::Error::downcast_ref` use: only
162 /// [`Error::ExternalError`] carries a wrapped error to downcast.
163 pub fn downcast_ref<T: StdError + 'static>(&self) -> Option<&T> {
164 match self {
165 Error::ExternalError(e) => e.downcast_ref::<T>(),
166 _ => None,
167 }
168 }
169
170 /// Wrap an arbitrary `std::error::Error` as an [`Error::ExternalError`].
171 ///
172 /// Mirrors `mlua::Error::external`: if the input is already a luaur
173 /// [`Error`], it is preserved as-is rather than re-wrapped.
174 pub fn external<T: Into<Box<DynStdError>>>(err: T) -> Self {
175 let boxed: Box<DynStdError> = err.into();
176 // Preserve an already-`Error` value instead of nesting it.
177 match boxed.downcast::<Error>() {
178 Ok(e) => *e,
179 Err(other) => Error::ExternalError(other.into()),
180 }
181 }
182}
183
184impl fmt::Display for Error {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 match self {
187 Error::SyntaxError { message, .. } => write!(f, "syntax error: {message}"),
188 Error::RuntimeError(msg) => write!(f, "runtime error: {msg}"),
189 Error::MemoryError(msg) => write!(f, "memory error: {msg}"),
190 Error::FromLuaConversionError { from, to, message } => {
191 write!(f, "error converting Lua {from} to {to}")?;
192 if let Some(m) = message {
193 write!(f, " ({m})")?;
194 }
195 Ok(())
196 }
197 Error::ToLuaConversionError { from, to, message } => {
198 write!(f, "error converting {from} to Lua {to}")?;
199 if let Some(m) = message {
200 write!(f, " ({m})")?;
201 }
202 Ok(())
203 }
204 Error::UserDataTypeMismatch => write!(f, "userdata type mismatch"),
205 Error::UserDataDestructed => write!(f, "userdata used after being destructed"),
206 Error::CallbackDestructed => write!(
207 f,
208 "a destructed callback or destructed userdata method was called"
209 ),
210 Error::CallbackError { cause, .. } => write!(f, "{cause}"),
211 Error::UserDataBorrowError => write!(f, "userdata already mutably borrowed"),
212 Error::UserDataBorrowMutError => write!(f, "userdata already borrowed"),
213 Error::CoroutineUnresumable => write!(f, "cannot resume this coroutine"),
214 Error::MismatchedRegistryKey => {
215 write!(f, "registry key used with the wrong Lua instance")
216 }
217 Error::RecursiveMutCallback => {
218 write!(f, "mutable callback called recursively")
219 }
220 Error::PreviouslyResumedPanic => {
221 write!(f, "previously resumed panic returned again")
222 }
223 Error::ExternalError(err) => write!(f, "{err}"),
224 #[cfg(feature = "serde")]
225 Error::SerializeError(msg) => write!(f, "serialize error: {msg}"),
226 #[cfg(feature = "serde")]
227 Error::DeserializeError(msg) => write!(f, "deserialize error: {msg}"),
228 #[cfg(feature = "typecheck")]
229 Error::TypeError(diagnostics) => {
230 write!(f, "type error(s):")?;
231 for d in diagnostics {
232 write!(f, "\n {d}")?;
233 }
234 Ok(())
235 }
236 }
237 }
238}
239
240impl StdError for Error {
241 fn source(&self) -> Option<&(dyn StdError + 'static)> {
242 match self {
243 Error::ExternalError(err) => Some(&**err),
244 Error::CallbackError { cause, .. } => Some(&**cause),
245 _ => None,
246 }
247 }
248}
249
250impl From<std::io::Error> for Error {
251 fn from(err: std::io::Error) -> Self {
252 Error::external(err)
253 }
254}
255
256impl From<&str> for Error {
257 fn from(msg: &str) -> Self {
258 Error::RuntimeError(msg.to_string())
259 }
260}
261
262impl From<String> for Error {
263 fn from(msg: String) -> Self {
264 Error::RuntimeError(msg)
265 }
266}
267
268/// Convenience for turning an arbitrary error/displayable into an [`Error`].
269///
270/// Mirrors `mlua::ExternalError`. `&str`/`String` become a [`Error::RuntimeError`]
271/// (matching mlua's runtime-error semantics for string errors); other
272/// `std::error::Error` types become an [`Error::ExternalError`].
273pub trait ExternalError {
274 /// Convert `self` into an [`Error`].
275 fn into_lua_err(self) -> Error;
276}
277
278impl<E: Into<Box<DynStdError>>> ExternalError for E {
279 fn into_lua_err(self) -> Error {
280 // `&str`/`String`/`io::Error`/... all implement `Into<Box<dyn Error>>`.
281 // Plain string errors become runtime errors (matching mlua); a wrapped
282 // `mlua::Error` is preserved by `Error::external`.
283 Error::external(self)
284 }
285}
286
287/// `Result` extension mirroring `mlua::ExternalResult`: lift any
288/// `Result<T, E>` into a `luaur` [`Result`] by converting the error.
289pub trait ExternalResult<T> {
290 /// Convert the error side via [`ExternalError::into_lua_err`].
291 fn into_lua_err(self) -> Result<T>;
292}
293
294impl<T, E: ExternalError> ExternalResult<T> for std::result::Result<T, E> {
295 fn into_lua_err(self) -> Result<T> {
296 self.map_err(ExternalError::into_lua_err)
297 }
298}