Change LuaError ToLua approach

Lua should be consistent independent of the way they are produced.  pcall in lua
should produce the same sort of error that would be returned by a LuaError
conversion.

The situation is not currently great, because LuaError is not Clone, so passing
a LuaError back into rust will result in the error being "consumed".
This commit is contained in:
kyren 2017-06-23 19:41:08 -04:00
parent 8e3a9f0e84
commit 802d5467c1
4 changed files with 110 additions and 36 deletions

View file

@ -1,6 +1,5 @@
use std::collections::{HashMap, BTreeMap};
use std::hash::Hash;
use error_chain::ChainedError;
use error::*;
use lua::*;
@ -264,15 +263,3 @@ impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Option<T> {
}
}
}
impl LuaUserDataType for LuaError {
fn add_methods(methods: &mut LuaUserDataMethods<Self>) {
methods.add_method("backtrace", |lua, err, _| {
lua.pack(format!("{}", err.display()))
});
methods.add_meta_method(LuaMetaMethod::ToString, |lua, err, _| {
lua.pack(err.to_string())
});
}
}

View file

@ -118,6 +118,15 @@ pub trait FromLuaMulti<'a>: Sized {
fn from_lua_multi(values: LuaMultiValue<'a>, lua: &'a Lua) -> LuaResult<Self>;
}
impl<'lua> ToLua<'lua> for LuaError {
fn to_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
unsafe {
push_error(lua.state, self);
Ok(lua.pop_value(lua.state))
}
}
}
type LuaCallback = Box<
for<'lua> FnMut(&'lua Lua, LuaMultiValue<'lua>)
-> LuaResult<LuaMultiValue<'lua>>,

View file

@ -700,12 +700,12 @@ fn test_result_conversions() {
let globals = lua.globals().unwrap();
let err = lua.create_function(|lua, _| {
lua.pack(Result::Err::<String, String>(
"only through failure can we succeed".to_string(),
lua.pack(Result::Err::<String, LuaError>(
"only through failure can we succeed".into(),
))
}).unwrap();
let ok = lua.create_function(|lua, _| {
lua.pack(Result::Ok::<String, String>("!".to_string()))
lua.pack(Result::Ok::<String, LuaError>("!".to_string()))
}).unwrap();
globals.set("err", err).unwrap();
@ -713,13 +713,14 @@ fn test_result_conversions() {
lua.exec::<()>(
r#"
local err, msg = err()
assert(err == nil)
assert(msg == "only through failure can we succeed")
local r, e = err()
assert(r == nil)
assert(tostring(e) == "only through failure can we succeed")
assert(type(e:backtrace()) == "string")
local ok, extra = ok()
assert(ok == "!")
assert(extra == nil)
local r, e = ok()
assert(r == "!")
assert(e == nil)
"#,
None,
).unwrap();

View file

@ -4,6 +4,7 @@ use std::ffi::CStr;
use std::any::Any;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::{catch_unwind, resume_unwind, UnwindSafe};
use error_chain::ChainedError;
use ffi;
use error::{LuaResult, LuaError, LuaErrorKind};
@ -137,7 +138,7 @@ pub unsafe extern "C" fn destructor<T>(state: *mut ffi::lua_State) -> c_int {
}) {
Ok(r) => r,
Err(p) => {
push_error(state, WrappedError::Panic(p));
push_panic(state, p);
ffi::lua_error(state)
}
}
@ -154,16 +155,26 @@ where
match catch_unwind(f) {
Ok(Ok(r)) => r,
Ok(Err(err)) => {
push_error(state, WrappedError::Error(err));
push_error(state, err);
ffi::lua_error(state)
}
Err(p) => {
push_error(state, WrappedError::Panic(p));
push_panic(state, p);
ffi::lua_error(state)
}
}
}
// Pushes a WrappedError::Error to the top of the stack
pub unsafe fn push_error(state: *mut ffi::lua_State, err: LuaError) {
push_wrapped_error(state, WrappedError::Error(err));
}
// Pushes a WrappedError::Panic to the top of the stack
pub unsafe fn push_panic(state: *mut ffi::lua_State, panic: Box<Any + Send>) {
push_wrapped_error(state, WrappedError::Panic(panic));
}
// Pops a WrappedError off of the top of the stack, if it is a WrappedError::Error, returns it, if
// it is a WrappedError::Panic, clears the current stack and panics.
pub unsafe fn pop_error(state: *mut ffi::lua_State) -> LuaError {
@ -179,7 +190,7 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State) -> LuaError {
let userdata = ffi::lua_touserdata(state, -1);
let err = (*(userdata as *mut Option<WrappedError>))
.take()
.unwrap_or_else(|| WrappedError::Error("expired error".into()));
.unwrap_or_else(|| WrappedError::Error("consumed error".into()));
ffi::lua_pop(state, 1);
match err {
@ -226,10 +237,7 @@ pub unsafe fn pcall_with_traceback(
.to_owned();
push_error(
state,
WrappedError::Error(LuaError::with_chain(
error,
LuaErrorKind::CallbackError(traceback),
)),
LuaError::with_chain(error, LuaErrorKind::CallbackError(traceback)),
);
}
} else {
@ -268,10 +276,7 @@ pub unsafe fn resume_with_traceback(
.to_owned();
push_error(
from,
WrappedError::Error(LuaError::with_chain(
error,
LuaErrorKind::CallbackError(traceback),
)),
LuaError::with_chain(error, LuaErrorKind::CallbackError(traceback)),
);
}
} else {
@ -339,8 +344,67 @@ enum WrappedError {
}
// Pushes the given error or panic as a wrapped error onto the stack
unsafe fn push_error(state: *mut ffi::lua_State, err: WrappedError) {
ffi::luaL_checkstack(state, 6, ptr::null());
unsafe fn push_wrapped_error(state: *mut ffi::lua_State, err: WrappedError) {
// Wrapped errors have a __tostring metamethod and a 'backtrace' normal
// method.
unsafe extern "C" fn error_tostring(state: *mut ffi::lua_State) -> c_int {
callback_error(state, || {
if !is_wrapped_error(state, -1) {
return Err("not wrapped error in error method".into());
}
let userdata = ffi::lua_touserdata(state, -1);
match (*(userdata as *mut Option<WrappedError>)).as_ref() {
Some(&WrappedError::Error(ref error)) => {
push_string(state, &error.to_string());
ffi::lua_remove(state, -2);
}
Some(&WrappedError::Panic(_)) => {
// This should be impossible, there should be no way for lua
// to catch a panic error.
push_string(state, "panic error");
ffi::lua_remove(state, -2);
}
None => {
push_string(state, "consumed error");
ffi::lua_remove(state, -2);
}
}
Ok(1)
})
}
unsafe extern "C" fn error_backtrace(state: *mut ffi::lua_State) -> c_int {
callback_error(state, || {
if !is_wrapped_error(state, -1) {
return Err("not wrapped error in error method".into());
}
let userdata = ffi::lua_touserdata(state, -1);
match (*(userdata as *mut Option<WrappedError>)).as_ref() {
Some(&WrappedError::Error(ref error)) => {
push_string(state, &error.display().to_string());
ffi::lua_remove(state, -2);
}
Some(&WrappedError::Panic(_)) => {
// This should be impossible, there should be no way for lua
// to catch a panic error.
push_string(state, "panic error");
ffi::lua_remove(state, -2);
}
None => {
push_string(state, "consumed error");
ffi::lua_remove(state, -2);
}
}
Ok(1)
})
}
ffi::luaL_checkstack(state, 2, ptr::null());
let err_userdata = ffi::lua_newuserdata(state, mem::size_of::<Option<WrappedError>>()) as
*mut Option<WrappedError>;
@ -351,6 +415,8 @@ unsafe fn push_error(state: *mut ffi::lua_State, err: WrappedError) {
if ffi::lua_isnil(state, -1) != 0 {
ffi::lua_pop(state, 1);
ffi::luaL_checkstack(state, 7, ptr::null());
ffi::lua_newtable(state);
ffi::lua_pushlightuserdata(
state,
@ -362,6 +428,17 @@ unsafe fn push_error(state: *mut ffi::lua_State, err: WrappedError) {
ffi::lua_pushcfunction(state, destructor::<Option<WrappedError>>);
ffi::lua_settable(state, -3);
push_string(state, "__tostring");
ffi::lua_pushcfunction(state, error_tostring);
ffi::lua_settable(state, -3);
push_string(state, "__index");
ffi::lua_newtable(state);
push_string(state, "backtrace");
ffi::lua_pushcfunction(state, error_backtrace);
ffi::lua_settable(state, -3);
ffi::lua_settable(state, -3);
push_string(state, "__metatable");
ffi::lua_pushboolean(state, 0);
ffi::lua_settable(state, -3);