diff --git a/examples/module/src/lib.rs b/examples/module/src/lib.rs index dcd487a..fdaf171 100644 --- a/examples/module/src/lib.rs +++ b/examples/module/src/lib.rs @@ -32,3 +32,8 @@ fn rust_module_second(lua: &Lua) -> LuaResult { exports.set("userdata", lua.create_userdata(MyUserData(123))?)?; Ok(exports) } + +#[mlua::lua_module] +fn rust_module_error(_: &Lua) -> LuaResult { + Err("custom module error".to_lua_err()) +} diff --git a/mlua_derive/src/lib.rs b/mlua_derive/src/lib.rs index a1e70d6..152e675 100644 --- a/mlua_derive/src/lib.rs +++ b/mlua_derive/src/lib.rs @@ -1,39 +1,38 @@ use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; -use quote::quote_spanned; -use syn::{parse_macro_input, spanned::Spanned, AttributeArgs, Error, ItemFn}; +use quote::quote; +use syn::{parse_macro_input, AttributeArgs, Error, ItemFn}; #[cfg(feature = "macros")] use { crate::chunk::Chunk, proc_macro::TokenTree, proc_macro2::TokenStream as TokenStream2, - proc_macro_error::proc_macro_error, quote::quote, + proc_macro_error::proc_macro_error, }; #[proc_macro_attribute] pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream { let args = parse_macro_input!(attr as AttributeArgs); - let item = parse_macro_input!(item as ItemFn); + let func = parse_macro_input!(item as ItemFn); if !args.is_empty() { - let err = Error::new(Span::call_site(), "the number of arguments must be zero") + let err = Error::new(Span::call_site(), "the macro does not support arguments") .to_compile_error(); return err.into(); } - let span = item.span(); - let item_name = item.sig.ident.clone(); - let ext_entrypoint_name = Ident::new(&format!("luaopen_{}", item.sig.ident), Span::call_site()); + let func_name = func.sig.ident.clone(); + let ext_entrypoint_name = Ident::new(&format!("luaopen_{}", func_name), Span::call_site()); - let wrapped = quote_spanned! { span => - mlua::require_module_feature!(); + let wrapped = quote! { + ::mlua::require_module_feature!(); + + #func #[no_mangle] - unsafe extern "C" fn #ext_entrypoint_name(state: *mut mlua::lua_State) -> std::os::raw::c_int { - #item - - mlua::Lua::init_from_ptr(state) - .entrypoint1(#item_name) - .unwrap() + unsafe extern "C" fn #ext_entrypoint_name(state: *mut ::mlua::lua_State) -> ::std::os::raw::c_int { + ::mlua::Lua::init_from_ptr(state) + .entrypoint1(#func_name) + .expect("cannot initialize module") } }; diff --git a/src/lua.rs b/src/lua.rs index 8eea5c7..a9f6b5b 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -536,7 +536,15 @@ impl Lua { F: 'static + MaybeSend + Fn(&'callback Lua) -> Result, { let cb = self.create_callback(Box::new(move |lua, _| func(lua)?.to_lua_multi(lua)))?; - unsafe { self.push_value(cb.call(())?).map(|_| 1) } + match cb.call(()) { + Ok(res) => unsafe { self.push_value(res)? }, + Err(err) => unsafe { + self.push_value(Value::Error(err))?; + // This longjmp is undesired, but we cannot wrap it to a C + ffi::lua_error(self.state) + }, + } + Ok(1) } /// Sets a 'hook' function that will periodically be called as Lua code executes. diff --git a/tests/module/tests/load.rs b/tests/module/tests/load.rs index 5acad79..83a9cf8 100644 --- a/tests/module/tests/load.rs +++ b/tests/module/tests/load.rs @@ -28,6 +28,19 @@ fn test_module_multi() -> Result<()> { .exec() } +#[test] +fn test_module_error() -> Result<()> { + let lua = make_lua()?; + lua.load( + r#" + local ok, err = pcall(require, "rust_module.error") + assert(not ok) + assert(string:find(tostring(err), "custom module error")) + "#, + ) + .exec() +} + #[cfg(any( feature = "lua54", feature = "lua53",