#![cfg(not(feature = "no_function"))] use rhai::{ Engine, EvalAltResult, FnPtr, NativeCallContext, ParseErrorType, RegisterFn, Scope, INT, }; use std::any::TypeId; use std::cell::RefCell; use std::mem::take; use std::rc::Rc; #[cfg(not(feature = "no_object"))] use rhai::Map; #[test] fn test_fn_ptr_curry_call() -> Result<(), Box> { let mut engine = Engine::new(); #[allow(deprecated)] engine.register_raw_fn( "call_with_arg", &[TypeId::of::(), TypeId::of::()], |context, args| { let fn_ptr = std::mem::take(args[0]).cast::(); fn_ptr.call_dynamic(context, None, [std::mem::take(args[1])]) }, ); #[cfg(not(feature = "no_object"))] assert_eq!( engine.eval::( r#" let addition = |x, y| { x + y }; let curried = addition.curry(2); call_with_arg(curried, 40) "# )?, 42 ); Ok(()) } #[test] #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_object"))] fn test_closures() -> Result<(), Box> { let mut engine = Engine::new(); assert!(matches!( *engine .compile_expression("let f = |x| {};") .expect_err("should error") .0, ParseErrorType::BadInput(_) )); assert_eq!( engine.eval::( r#" let x = 8; let res = |y, z| { let w = 12; return (|| x + y + z + w).call(); }.curry(15).call(2); res + (|| x - 3).call() "# )?, 42 ); assert_eq!( engine.eval::( r#" let a = 41; let foo = |x| { a += x }; foo.call(1); a "# )?, 42 ); assert!(engine.eval::( r#" let a = 41; let foo = |x| { a += x }; a.is_shared() "# )?); engine.register_fn("plus_one", |x: INT| x + 1); assert_eq!( engine.eval::( r#" let a = 41; let f = || plus_one(a); f.call() "# )?, 42 ); assert_eq!( engine.eval::( r#" let a = 40; let f = |x| { let f = |x| { let f = |x| plus_one(a) + x; f.call(x) }; f.call(x) }; f.call(1) "# )?, 42 ); assert_eq!( engine.eval::( r#" let a = 21; let f = |x| a += x; f.call(a); a "# )?, 42 ); #[allow(deprecated)] engine.register_raw_fn( "custom_call", &[TypeId::of::(), TypeId::of::()], |context, args| { let func = take(args[1]).cast::(); func.call_dynamic(context, None, []) }, ); assert_eq!( engine.eval::( r#" let a = 41; let b = 0; let f = || b.custom_call(|| a + 1); f.call() "# )?, 42 ); Ok(()) } #[test] #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "sync"))] fn test_closures_data_race() -> Result<(), Box> { let engine = Engine::new(); assert_eq!( engine.eval::( r#" let a = 1; let b = 40; let foo = |x| { this += a + x }; b.call(foo, 1); b "# )?, 42 ); assert!(matches!( *engine .eval::( r#" let a = 20; let foo = |x| { this += a + x }; a.call(foo, 1); a "# ) .expect_err("should error"), EvalAltResult::ErrorDataRace(_, _) )); Ok(()) } type TestStruct = Rc>; #[test] #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "sync"))] fn test_closures_shared_obj() -> Result<(), Box> { let mut engine = Engine::new(); // Register API on TestStruct engine .register_type_with_name::("TestStruct") .register_get_set( "data", |p: &mut TestStruct| *p.borrow(), |p: &mut TestStruct, value: INT| *p.borrow_mut() = value, ) .register_fn("+=", |p1: &mut TestStruct, p2: TestStruct| { *p1.borrow_mut() += *p2.borrow() }) .register_fn("-=", |p1: &mut TestStruct, p2: TestStruct| { *p1.borrow_mut() -= *p2.borrow() }); let engine = engine; // Make engine immutable let code = r#" #{ name: "A", description: "B", cost: 1, health_added: 0, action: |p1, p2| { p1 += p2 } } "#; let ast = engine.compile(code)?; let res = engine.eval_ast::(&ast)?; // Make closure let f = move |p1: TestStruct, p2: TestStruct| -> Result<(), Box> { let action_ptr = res["action"].clone().cast::(); let name = action_ptr.fn_name(); engine.call_fn(&mut Scope::new(), &ast, name, (p1, p2)) }; // Test closure let p1 = Rc::new(RefCell::new(41)); let p2 = Rc::new(RefCell::new(1)); f(p1.clone(), p2.clone())?; assert_eq!(*p1.borrow(), 42); Ok(()) } #[test] #[cfg(not(feature = "no_closure"))] fn test_closures_external() -> Result<(), Box> { let engine = Engine::new(); let mut ast = engine.compile( r#" let test = "hello"; |x| test + x "#, )?; // Save the function pointer together with captured variables let fn_ptr = engine.eval_ast::(&ast)?; // Get rid of the script, retaining only functions ast.retain_functions(|_, _, _, _| true); // Create function namespace from the 'AST' let lib = [ast.as_ref()]; // Create native call context let context = NativeCallContext::new(&engine, &lib); // Closure 'f' captures: the engine, the AST, and the curried function pointer let f = move |x: INT| fn_ptr.call_dynamic(context, None, [x.into()]); assert_eq!(f(42)?.as_str(), Ok("hello42")); Ok(()) }