use mlua::{Error, Function, Lua, LuaString, Result, Table, Variadic}; #[test] fn test_function_call() -> Result<()> { let lua = Lua::new(); let concat = lua .load(r#"function(arg1, arg2) return arg1 .. arg2 end"#) .eval::()?; assert_eq!(concat.call::(("foo", "bar"))?, "foobar"); Ok(()) } #[test] fn test_function_call_error() -> Result<()> { let lua = Lua::new(); let concat_err = lua .load(r#"function(arg1, arg2) error("concat error") end"#) .eval::()?; match concat_err.call::(("foo", "bar")) { Err(Error::RuntimeError(msg)) if msg.contains("concat error") => {} other => panic!("unexpected result: {other:?}"), } Ok(()) } #[test] fn test_function_bind() -> Result<()> { let lua = Lua::new(); let globals = lua.globals(); lua.load( r#" function concat(...) local res = "" for _, s in pairs({...}) do res = res..s end return res end "#, ) .exec()?; let mut concat = globals.get::("concat")?; concat = concat.bind("foo")?; concat = concat.bind("bar")?; concat = concat.bind(("baz", "baf"))?; assert_eq!(concat.call::(())?, "foobarbazbaf"); assert_eq!(concat.call::(("hi", "wut"))?, "foobarbazbafhiwut"); let mut concat2 = globals.get::("concat")?; concat2 = concat2.bind(())?; assert_eq!(concat2.call::(())?, ""); assert_eq!(concat2.call::(("ab", "cd"))?, "abcd"); Ok(()) } #[test] #[cfg(not(target_arch = "wasm32"))] fn test_function_bind_error() -> Result<()> { let lua = Lua::new(); let func = lua.load(r#"function(...) end"#).eval::()?; assert!(func.bind(Variadic::from_iter(1..1000000)).is_err()); assert!(func.call::<()>(Variadic::from_iter(1..1000000)).is_err()); Ok(()) } #[test] fn test_function_environment() -> Result<()> { let lua = Lua::new(); let globals = lua.globals(); // We must not get or set environment for C functions let rust_func = lua.create_function(|_, ()| Ok("hello"))?; assert_eq!(rust_func.environment(), None); assert_eq!(rust_func.set_environment(globals.clone()).ok(), Some(false)); // Test getting Lua function environment globals.set("hello", "global")?; let lua_func = lua .load( r#" local t = "" return function() -- two upvalues return t .. hello end "#, ) .eval::()?; let lua_func2 = lua.load("return hello").into_function()?; assert_eq!(lua_func.call::(())?, "global"); assert_eq!(lua_func.environment().as_ref(), Some(&globals)); // Test changing the environment let env = lua.create_table_from([("hello", "local")])?; assert!(lua_func.set_environment(env.clone())?); assert_eq!(lua_func.call::(())?, "local"); assert_eq!(lua_func2.call::(())?, "global"); // More complex case lua.load( r#" local number = 15 function lucky() return tostring("number is "..number) end new_env = { tostring = function() return tostring(number) end, } "#, ) .exec()?; let lucky = globals.get::("lucky")?; assert_eq!(lucky.call::(())?, "number is 15"); let new_env = globals.get::("new_env")?; lucky.set_environment(new_env)?; assert_eq!(lucky.call::(())?, "15"); // Test inheritance let lua_func2 = lua .load(r#"return function() return (function() return hello end)() end"#) .eval::()?; assert!(lua_func2.set_environment(env.clone())?); lua.gc_collect()?; assert_eq!(lua_func2.call::(())?, "local"); // Test getting environment set by chunk loader let chunk = lua .load("return hello") .set_environment(lua.create_table_from([("hello", "chunk")])?) .into_function()?; assert_eq!(chunk.environment().unwrap().get::("hello")?, "chunk"); Ok(()) } #[test] fn test_function_info() -> Result<()> { let lua = Lua::new(); let globals = lua.globals(); lua.load( r#" function function1() return function() end end "#, ) .set_name("source1") .exec()?; let function1 = globals.get::("function1")?; let function2 = function1.call::(())?; let function3 = lua.create_function(|_, ()| Ok(()))?; let function1_info = function1.info(); #[cfg(feature = "luau")] assert_eq!(function1_info.name.as_deref(), Some("function1")); assert_eq!(function1_info.source.as_deref(), Some("source1")); assert_eq!(function1_info.line_defined, Some(2)); #[cfg(not(feature = "luau"))] assert_eq!(function1_info.last_line_defined, Some(4)); #[cfg(feature = "luau")] assert_eq!(function1_info.last_line_defined, None); assert_eq!(function1_info.what, "Lua"); let function2_info = function2.info(); assert_eq!(function2_info.name, None); assert_eq!(function2_info.source.as_deref(), Some("source1")); assert_eq!(function2_info.line_defined, Some(3)); #[cfg(not(feature = "luau"))] assert_eq!(function2_info.last_line_defined, Some(3)); #[cfg(feature = "luau")] assert_eq!(function2_info.last_line_defined, None); assert_eq!(function2_info.what, "Lua"); let function3_info = function3.info(); assert_eq!(function3_info.name, None); assert_eq!(function3_info.source.as_deref(), Some("=[C]")); assert_eq!(function3_info.line_defined, None); assert_eq!(function3_info.last_line_defined, None); assert_eq!(function3_info.what, "C"); let print_info = globals.get::("print")?.info(); #[cfg(feature = "luau")] assert_eq!(print_info.name.as_deref(), Some("print")); assert_eq!(print_info.source.as_deref(), Some("=[C]")); assert_eq!(print_info.what, "C"); assert_eq!(print_info.line_defined, None); // Function with upvalues and params #[cfg(not(any(feature = "lua51", feature = "luajit")))] { let func_with_upvalues = lua .load( r#" local x, y = ... return function(a, ...) return a*x + y end "#, ) .call::((10, 20))?; let func_with_upvalues_info = func_with_upvalues.info(); assert_eq!(func_with_upvalues_info.num_upvalues, 2); assert_eq!(func_with_upvalues_info.num_params, 1); assert_eq!(func_with_upvalues_info.is_vararg, true); } Ok(()) } #[cfg(not(feature = "luau"))] #[test] fn test_function_dump() -> Result<()> { let lua = unsafe { Lua::unsafe_new() }; let concat_lua = lua .load(r#"function(arg1, arg2) return arg1 .. arg2 end"#) .eval::()?; let concat = lua.load(&concat_lua.dump(false)).into_function()?; assert_eq!(concat.call::(("foo", "bar"))?, "foobar"); Ok(()) } #[cfg(feature = "luau")] #[test] fn test_function_coverage() -> Result<()> { let lua = Lua::new(); lua.set_compiler(mlua::Compiler::default().set_coverage_level(1)); let f = lua .load( r#"local s = "abc" assert(#s == 3) function abc(i) if i < 5 then return 0 else return 1 end end (function() (function() abc(10) end)() end)() "#, ) .into_function()?; f.call::<()>(())?; let mut report = Vec::new(); f.coverage(|cov| { report.push(cov); }); assert_eq!( report[0], mlua::function::CoverageInfo { function: None, line_defined: 1, depth: 0, hits: vec![-1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1], } ); assert_eq!( report[1], mlua::function::CoverageInfo { function: Some("abc".into()), line_defined: 4, depth: 1, hits: vec![-1, -1, -1, -1, -1, 1, 0, -1, 1, -1, -1, -1, -1, -1, -1, -1], } ); assert_eq!( report[2], mlua::function::CoverageInfo { function: None, line_defined: 12, depth: 1, hits: vec![-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1], } ); assert_eq!( report[3], mlua::function::CoverageInfo { function: None, line_defined: 13, depth: 2, hits: vec![-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1], } ); Ok(()) } #[test] fn test_function_pointer() -> Result<()> { let lua = Lua::new(); let func1 = lua.load("return function() end").into_function()?; let func2 = func1.call::(())?; assert_eq!(func1.to_pointer(), func1.clone().to_pointer()); assert_ne!(func1.to_pointer(), func2.to_pointer()); Ok(()) } #[cfg(feature = "luau")] #[test] fn test_function_deep_clone() -> Result<()> { let lua = Lua::new(); lua.globals().set("a", 1)?; let func1 = lua.load("a += 1; return a").into_function()?; let func2 = func1.deep_clone()?; assert_ne!(func1.to_pointer(), func2.to_pointer()); assert_eq!(func1.call::(())?, 2); assert_eq!(func2.call::(())?, 3); // Check that for Rust functions deep_clone is just a clone let rust_func = lua.create_function(|_, ()| Ok(42))?; let rust_func2 = rust_func.deep_clone()?; assert_eq!(rust_func.to_pointer(), rust_func2.to_pointer()); Ok(()) } #[test] fn test_function_wrap() -> Result<()> { let lua = Lua::new(); let f = Function::wrap(|s: LuaString, n| Ok(s.to_str().unwrap().repeat(n))); lua.globals().set("f", f)?; lua.load(r#"assert(f("hello", 2) == "hellohello")"#) .exec() .unwrap(); // Return error let ferr = Function::wrap(|| Err::<(), _>(Error::runtime("some error"))); lua.globals().set("ferr", ferr)?; lua.load( r#" local ok, err = pcall(ferr) assert(not ok and tostring(err):find("some error")) "#, ) .exec() .unwrap(); // Mutable callback let mut i = 0; let fmut = Function::wrap_mut(move || { i += 1; Ok(i) }); lua.globals().set("fmut", fmut)?; lua.load(r#"fmut(); fmut(); assert(fmut() == 3)"#).exec().unwrap(); // Check mutable callback with error let fmut_err = Function::wrap_mut(|| Err::<(), _>(Error::runtime("some error"))); lua.globals().set("fmut_err", fmut_err)?; lua.load( r#" local ok, err = pcall(fmut_err) assert(not ok and tostring(err):find("some error")) "#, ) .exec() .unwrap(); // Check recursive mut callback error let fmut = Function::wrap_mut(|f: Function| match f.call::<()>(&f) { Err(Error::CallbackError { cause, .. }) => match cause.as_ref() { Error::RecursiveMutCallback { .. } => Ok(()), other => panic!("incorrect result: {other:?}"), }, other => panic!("incorrect result: {other:?}"), }); let fmut = lua.convert::(fmut)?; assert!(fmut.call::<()>(&fmut).is_ok()); Ok(()) } #[test] fn test_function_wrap_raw() -> Result<()> { let lua = Lua::new(); let f = Function::wrap_raw(|| "hello"); lua.globals().set("f", f)?; lua.load(r#"assert(f() == "hello")"#).exec().unwrap(); // Return error let ferr = Function::wrap_raw(|| Err::<(), _>("some error")); lua.globals().set("ferr", ferr)?; lua.load( r#" local _, err = ferr() assert(err == "some error") "#, ) .exec() .unwrap(); // Mutable callback let mut i = 0; let fmut = Function::wrap_raw_mut(move || { i += 1; i }); lua.globals().set("fmut", fmut)?; lua.load(r#"fmut(); fmut(); assert(fmut() == 3)"#).exec().unwrap(); // Check mutable callback with error let fmut_err = Function::wrap_raw_mut(|| Err::<(), _>("some error")); lua.globals().set("fmut_err", fmut_err)?; lua.load( r#" local _, err = fmut_err() assert(err == "some error") "#, ) .exec() .unwrap(); Ok(()) }