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