Do not parse closures when allow_anonymous_fn is false.

This commit is contained in:
Stephen Chung 2020-08-22 11:08:27 +08:00
parent 24610688d3
commit 0b04d05afe
4 changed files with 84 additions and 3 deletions

View File

@ -7,6 +7,15 @@ Version 0.19.0
Bug fixes Bug fixes
--------- ---------
* `Engine::compile_expression`, `Engine::eval_expression` etc. no longer parse anonymous functions and closures.
Version 0.18.2
==============
Bug fixes
---------
* Fixes bug that prevents calling functions in closures. * Fixes bug that prevents calling functions in closures.
* Fixes bug that erroneously consumes the first argument to a module-qualified function call. * Fixes bug that erroneously consumes the first argument to a module-qualified function call.

View File

@ -11,9 +11,11 @@ In these cases, use the `Engine::compile_expression` and `Engine::eval_expressio
let result = engine.eval_expression::<i64>("2 + (10 + 10) * 2")?; let result = engine.eval_expression::<i64>("2 + (10 + 10) * 2")?;
``` ```
When evaluating _expressions_, no full-blown statement (e.g. `if`, `while`, `for`) - not even variable assignment - When evaluating _expressions_, no full-blown statement (e.g. `if`, `while`, `for`, `fn`) - not even variable assignment -
is supported and will be considered parse errors when encountered. is supported and will be considered parse errors when encountered.
[Closures] and [anonymous functions] are also not supported because in the background they compile to functions.
```rust ```rust
// The following are all syntax errors because the script is not an expression. // The following are all syntax errors because the script is not an expression.

View File

@ -1862,7 +1862,7 @@ fn parse_unary(
} }
// | ... // | ...
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Token::Pipe | Token::Or => { Token::Pipe | Token::Or if settings.allow_anonymous_fn => {
let mut new_state = ParseState::new( let mut new_state = ParseState::new(
state.engine, state.engine,
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -3353,6 +3353,8 @@ impl Engine {
}; };
let expr = parse_expr(input, &mut state, &mut functions, settings)?; let expr = parse_expr(input, &mut state, &mut functions, settings)?;
assert!(functions.is_empty());
match input.peek().unwrap() { match input.peek().unwrap() {
(Token::EOF, _) => (), (Token::EOF, _) => (),
// Return error if the expression doesn't end // Return error if the expression doesn't end

View File

@ -1,7 +1,11 @@
#![cfg(not(feature = "no_function"))] #![cfg(not(feature = "no_function"))]
use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, RegisterFn, INT}; use rhai::{
Dynamic, Engine, EvalAltResult, FnPtr, Map, Module, ParseErrorType, RegisterFn, Scope, INT,
};
use std::any::TypeId; use std::any::TypeId;
use std::cell::RefCell;
use std::mem::take; use std::mem::take;
use std::rc::Rc;
#[test] #[test]
fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> { fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
@ -39,6 +43,14 @@ fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
fn test_closures() -> Result<(), Box<EvalAltResult>> { fn test_closures() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert!(matches!(
*engine
.compile_expression("let f = |x| {};")
.expect_err("should error")
.0,
ParseErrorType::BadInput(_)
));
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
r#" r#"
@ -181,3 +193,59 @@ fn test_closures_data_race() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
type MyType = Rc<RefCell<INT>>;
#[test]
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "sync"))]
fn test_closure_shared_obj() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
// Register API on MyType
engine
.register_type_with_name::<MyType>("MyType")
.register_get_set(
"data",
|p: &mut MyType| *p.borrow(),
|p: &mut MyType, value: INT| *p.borrow_mut() = value,
)
.register_fn("+=", |p1: &mut MyType, p2: MyType| {
*p1.borrow_mut() += *p2.borrow()
})
.register_fn("-=", |p1: &mut MyType, p2: MyType| {
*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::<Map>(&ast)?;
// Make closure
let f = move |p1: MyType, p2: MyType| -> Result<(), Box<EvalAltResult>> {
let action_ptr = res["action"].clone().cast::<FnPtr>();
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(())
}