Improve module mode:

- Don't hide module function inside `luaopen_%` function.
- Raise Lua exception instead of panic if module function returns error.
This commit is contained in:
Alex Orlenko 2021-06-13 22:38:51 +01:00
parent 3b9d8a7b5f
commit 08ffeb0ca9
4 changed files with 42 additions and 17 deletions

View file

@ -32,3 +32,8 @@ fn rust_module_second(lua: &Lua) -> LuaResult<LuaTable> {
exports.set("userdata", lua.create_userdata(MyUserData(123))?)?;
Ok(exports)
}
#[mlua::lua_module]
fn rust_module_error(_: &Lua) -> LuaResult<LuaTable> {
Err("custom module error".to_lua_err())
}

View file

@ -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")
}
};

View file

@ -536,7 +536,15 @@ impl Lua {
F: 'static + MaybeSend + Fn(&'callback Lua) -> Result<R>,
{
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.

View file

@ -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",