use core::fmt;
use std::error::Error as StdError;
use std::rc::Rc;
use std::result::Result as StdResult;
use luau_compiler::CompilerError;
use luau_vm::Thread as VmThread;
use luau_vm::internal::userdata::{TypedUserdataAccess, TypedUserdataError};
use luau_vm::{VmControl, VmError, VmErrorResult, VmExit};
use crate::thread::Thread;
use crate::userdata::{MetaMethod, Userdata, UserdataFields, UserdataMethods};
use crate::value::{FromLua, IntoLua, Value};
type DynStdError = dyn StdError + 'static;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Error {
SyntaxError {
message: String,
incomplete_input: bool,
},
RuntimeError(String),
MemoryError(String),
ErrorHandlerError(String),
Interrupted,
CoroutineUnresumable,
RecursiveMutCallback,
CallbackDestructed,
StackError,
BindError,
BadArgument {
to: Option<String>,
pos: usize,
name: Option<String>,
cause: Rc<Error>,
},
FromLuaConversionError {
from: String,
to: String,
message: Option<String>,
},
IntoLuaConversionError {
from: String,
to: String,
message: Option<String>,
},
UserdataTypeMismatch,
UserdataDestructed,
UserdataBorrowError,
UserdataBorrowMutError,
MetaMethodRestricted(String),
MismatchedRegistryKey,
ForeignLuaHandle,
CallbackError {
traceback: String,
cause: Rc<Error>,
},
ExternalError(Rc<DynStdError>),
}
pub type Result<T> = StdResult<T, Error>;
impl Error {
pub fn runtime(message: impl fmt::Display) -> Self {
Self::RuntimeError(message.to_string())
}
pub fn external(error: impl Into<Box<DynStdError>>) -> Self {
let error = error.into();
match error.downcast::<Self>() {
Ok(error) => *error,
Err(error) => Self::ExternalError(Rc::from(error)),
}
}
pub fn downcast_ref<T>(&self) -> Option<&T>
where
T: StdError + 'static,
{
match self {
Self::ExternalError(error) => error.downcast_ref(),
Self::BadArgument { cause, .. } | Self::CallbackError { cause, .. } => {
cause.downcast_ref()
}
_ => None,
}
}
pub fn chain(&self) -> impl Iterator<Item = &(dyn StdError + 'static)> {
Chain {
root: self,
current: None,
}
}
pub(crate) fn from_lua_conversion(from: &str, to: &str, message: Option<&str>) -> Self {
Self::FromLuaConversionError {
from: from.to_string(),
to: to.to_string(),
message: message.map(str::to_string),
}
}
pub(crate) fn into_lua_conversion(from: &str, to: &str, message: Option<&str>) -> Self {
Self::IntoLuaConversionError {
from: from.to_string(),
to: to.to_string(),
message: message.map(str::to_string),
}
}
pub(crate) fn bad_argument(pos: usize, cause: Self) -> Self {
Self::BadArgument {
to: None,
pos,
name: None,
cause: Rc::new(cause),
}
}
pub(crate) const fn foreign_lua_handle() -> Self {
Self::ForeignLuaHandle
}
pub(crate) const fn mismatched_registry_key() -> Self {
Self::MismatchedRegistryKey
}
pub(crate) fn index_out_of_bounds() -> Self {
Self::runtime("index out of bounds")
}
fn from_thread_error(thread: &VmThread, error: VmError) -> Self {
if matches!(error, VmError::Memory) {
return Self::MemoryError("not enough memory".to_string());
}
if matches!(error, VmError::Runtime)
&& let Some(error) = unsafe { Self::from_wrapped_stack(thread, -1) }
{
return error;
}
let message = unsafe { thread.to_string(-1) }
.ok()
.flatten()
.map(ToString::to_string)
.unwrap_or_else(|| "unknown error".to_string());
Self::from_vm_error_message(error, message)
}
pub(crate) unsafe fn from_wrapped_stack(thread: &VmThread, index: i32) -> Option<Self> {
let userdata = unsafe { thread.typed_userdata_at(index) }?;
let cell = unsafe { userdata.cell_ptr::<WrappedError>() }?;
let error = unsafe { &*cell }.try_borrow().ok()?;
error.as_ref().map(|error| error.0.clone())
}
pub(crate) fn from_thread_exit(thread: impl AsRef<VmThread>, exit: impl Into<VmExit>) -> Self {
let thread = thread.as_ref();
match exit.into() {
VmExit::Error(error) => Self::from_thread_error(thread, error),
VmExit::Control(VmControl::Break) => Self::Interrupted,
VmExit::Control(VmControl::Yield) => {
Self::RuntimeError("operation yielded unexpectedly".to_string())
}
}
}
pub(crate) fn raise_error<T>(self, thread: &VmThread) -> VmErrorResult<T> {
unsafe {
let message = self.to_string();
luau_vm::error!(thread, &message)
}
}
fn from_vm_error_message(error: VmError, message: String) -> Self {
match error {
VmError::Runtime => Self::RuntimeError(message),
VmError::Syntax => Self::SyntaxError {
incomplete_input: message.ends_with("<eof>"),
message,
},
VmError::Memory => Self::MemoryError(message),
VmError::ErrorHandler => Self::ErrorHandlerError(message),
}
}
pub(crate) fn from_typed_userdata(error: TypedUserdataError) -> Self {
match error {
TypedUserdataError::TypeMismatch => Self::UserdataTypeMismatch,
TypedUserdataError::Destructed => Self::UserdataDestructed,
TypedUserdataError::Borrowed => Self::UserdataBorrowMutError,
}
}
}
impl From<CompilerError> for Error {
fn from(error: CompilerError) -> Self {
let message = error.to_string();
Self::SyntaxError {
incomplete_input: message.ends_with("<eof>"),
message,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SyntaxError { message, .. } => write!(formatter, "syntax error: {message}"),
Self::RuntimeError(message) => write!(formatter, "runtime error: {message}"),
Self::MemoryError(message) => write!(formatter, "memory error: {message}"),
Self::ErrorHandlerError(message) => {
write!(formatter, "error handler error: {message}")
}
Self::Interrupted => formatter.write_str("execution interrupted"),
Self::CoroutineUnresumable => formatter.write_str("coroutine is not resumable"),
Self::RecursiveMutCallback => {
formatter.write_str("mutable callback called recursively")
}
Self::CallbackDestructed => formatter.write_str("callback has been destructed"),
Self::StackError => formatter.write_str(
"out of Lua stack, too many arguments to a Lua function or too many return values from a callback",
),
Self::BindError => formatter.write_str("too many arguments to Function::bind"),
Self::BadArgument {
to,
pos,
name,
cause,
} => {
match name {
Some(name) => write!(formatter, "bad argument `{name}`")?,
None => write!(formatter, "bad argument #{pos}")?,
}
if let Some(to) = to {
write!(formatter, " to `{to}`")?;
}
write!(formatter, ": {cause}")
}
Self::FromLuaConversionError { from, to, message } => {
write!(formatter, "error converting Lua {from} to {to}")?;
if let Some(message) = message {
write!(formatter, " ({message})")?;
}
Ok(())
}
Self::IntoLuaConversionError { from, to, message } => {
write!(formatter, "error converting {from} to Lua {to}")?;
if let Some(message) = message {
write!(formatter, " ({message})")?;
}
Ok(())
}
Self::UserdataTypeMismatch => formatter.write_str("userdata is not expected type"),
Self::UserdataDestructed => formatter.write_str("userdata has been destructed"),
Self::UserdataBorrowError => formatter.write_str("error borrowing userdata"),
Self::UserdataBorrowMutError => formatter.write_str("error mutably borrowing userdata"),
Self::MetaMethodRestricted(name) => {
write!(formatter, "metamethod {name} is restricted")
}
Self::MismatchedRegistryKey => {
formatter.write_str("registry key belongs to a different Lua state")
}
Self::ForeignLuaHandle => formatter.write_str("value belongs to a different Lua state"),
Self::CallbackError { traceback, cause } => {
let (mut cause, mut full_traceback) = (cause, None);
while let Self::CallbackError {
cause: nested_cause,
traceback: nested_traceback,
} = &**cause
{
cause = nested_cause;
full_traceback = Some(nested_traceback);
}
write!(formatter, "{cause}")?;
let traceback = traceback.trim();
if let Some(full_traceback) = full_traceback {
let full_traceback = full_traceback.trim();
if !full_traceback.is_empty() {
write!(formatter, "\nstack traceback:\n")?;
if !traceback.is_empty()
&& let Some(position) = full_traceback.find(traceback)
{
write!(
formatter,
"{}>{}",
&full_traceback[..position],
&full_traceback[position..]
)?;
} else {
formatter.write_str(full_traceback)?;
}
}
} else if !traceback.is_empty() {
write!(formatter, "\nstack traceback:\n{traceback}")?;
}
Ok(())
}
Self::ExternalError(error) => fmt::Display::fmt(error, formatter),
}
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Self::ExternalError(error) => error.source(),
_ => None,
}
}
}
struct WrappedError(Error);
impl Userdata for WrappedError {
fn add_fields<F: UserdataFields<Self>>(fields: &mut F) {
fields.add_meta_field(MetaMethod::Type, "error");
}
fn add_methods<M: UserdataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::ToString, |_, error, arguments| {
arguments.finish(error.0.to_string())
});
}
}
impl<'lua> IntoLua<'lua> for Error {
fn into_lua(self, _: crate::LuaRef<'lua>) -> Result<Value<'lua>> {
Ok(Value::Error(Box::new(self)))
}
unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<()> {
thread.push_userdata(WrappedError(self))
}
}
impl<'lua> FromLua<'lua> for Error {
fn from_lua(value: Value<'lua>, _: crate::LuaRef<'lua>) -> Result<Self> {
match value {
Value::Error(error) => Ok(*error),
value => Ok(Self::runtime(value.to_string()?)),
}
}
}
pub trait ExternalError {
fn into_lua_err(self) -> Error;
}
impl<E> ExternalError for E
where
E: Into<Box<DynStdError>>,
{
fn into_lua_err(self) -> Error {
Error::external(self)
}
}
pub trait ExternalResult<T> {
fn into_lua_err(self) -> Result<T>;
}
impl<T, E> ExternalResult<T> for StdResult<T, E>
where
E: ExternalError,
{
fn into_lua_err(self) -> Result<T> {
self.map_err(ExternalError::into_lua_err)
}
}
struct Chain<'a> {
root: &'a Error,
current: Option<&'a (dyn StdError + 'static)>,
}
impl<'a> Iterator for Chain<'a> {
type Item = &'a (dyn StdError + 'static);
fn next(&mut self) -> Option<Self::Item> {
loop {
let error: Option<&dyn StdError> = match self.current {
None => {
self.current = Some(self.root);
self.current
}
Some(current) => match current.downcast_ref::<Error>()? {
Error::BadArgument { cause, .. } | Error::CallbackError { cause, .. } => {
self.current = Some(&**cause);
self.current
}
Error::ExternalError(error) => {
self.current = Some(&**error);
self.current
}
_ => None,
},
};
if let Some(Error::ExternalError(_)) = error?.downcast_ref::<Error>() {
continue;
}
return self.current;
}
}
}