From 8e51988b660437dc8396e753365902d425df5c15 Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Sun, 26 Jul 2020 09:18:18 +0700 Subject: [PATCH 1/4] A prototype of a new feature to automatically curry of external scope variables --- Cargo.toml | 1 + src/parser.rs | 135 ++++++++++++++++++++++++++++++++++++++--------- tests/call_fn.rs | 21 ++++++++ 3 files changed, 132 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f049777e..606412c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ only_i64 = [] # set INT=i64 (default) and disable support for all other in no_index = [] # no arrays and indexing no_object = [] # no custom objects no_function = [] # no script-defined functions +no_closures = [] # no automatic read/write binding of anonymous function's local variables to it's external context no_module = [] # no modules internals = [] # expose internal data structures unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. diff --git a/src/parser.rs b/src/parser.rs index 417c35fb..f8881342 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,7 +2,10 @@ use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; -use crate::engine::{Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; +use crate::engine::{ + Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT, + KEYWORD_FN_PTR_CURRY, +}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::fn_native::Shared; use crate::module::{Module, ModuleRef}; @@ -399,12 +402,14 @@ pub enum ReturnType { Exception, } -#[derive(Clone)] -struct ParseState<'e> { +struct ParseState<'e, 's> { /// Reference to the scripting `Engine`. engine: &'e Engine, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. stack: Vec<(String, ScopeEntryType)>, + /// Tracks a list of external variables(variables that are not explicitly + /// declared in the scope during AST evaluation). + externals: &'s mut Vec, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. modules: Vec, /// Maximum levels of expression nesting. @@ -415,40 +420,103 @@ struct ParseState<'e> { max_function_expr_depth: usize, } -impl<'e> ParseState<'e> { +impl<'e, 's> ParseState<'e, 's> { /// Create a new `ParseState`. - pub fn new( + fn new( engine: &'e Engine, + externals: &'s mut Vec, #[cfg(not(feature = "unchecked"))] max_expr_depth: usize, #[cfg(not(feature = "unchecked"))] max_function_expr_depth: usize, ) -> Self { Self { engine, + #[cfg(not(feature = "unchecked"))] max_expr_depth, + #[cfg(not(feature = "unchecked"))] max_function_expr_depth, + externals, stack: Default::default(), modules: Default::default(), - #[cfg(not(feature = "unchecked"))] - max_expr_depth, - #[cfg(not(feature = "unchecked"))] - max_function_expr_depth, } } - /// Find a variable by name in the `ParseState`, searching in reverse. + + /// Creates a new `ParseState` with empty `stack` and `modules` lists, but + /// deriving other settings from the passed `ParseState` instance. + fn derive(&'s mut self) -> Self { + Self { + engine: self.engine, + #[cfg(not(feature = "unchecked"))] + max_expr_depth: self.max_expr_depth, + #[cfg(not(feature = "unchecked"))] + max_function_expr_depth: self.max_function_expr_depth, + externals: self.externals, + stack: Default::default(), + modules: Default::default(), + } + } + + /// Find explicitly declared variable by name in the `ParseState`, + /// searching in reverse order. + /// + /// If the variable is not present in the scope adds it to the list of + /// external variables + /// /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. - /// Return zero when the variable name is not found in the `ParseState`. - pub fn find_var(&self, name: &str) -> Option { - self.stack + /// Return `None` when the variable name is not found in the `stack`. + fn access_var(&mut self, name: &str) -> Option { + let mut index = self.stack .iter() .rev() .enumerate() .find(|(_, (n, _))| *n == name) - .and_then(|(i, _)| NonZeroUsize::new(i + 1)) + .and_then(|(i, _)| NonZeroUsize::new(i + 1)); + + if index.is_some() { + return index + } + + #[cfg(not(feature = "no_closures"))] + if self.externals.iter().find(|n| *n == name).is_none() { + self.externals.push(name.to_string()); + } + + None } + + /// Creates a curry expression from a list of external variables + fn make_curry_from_externals(&self, fn_expr: Expr, settings: &ParseSettings) -> Expr { + if self.externals.is_empty() { + return fn_expr + } + + let mut args = StaticVec::new(); + + for var in self.externals.iter() { + args.push(Expr::Variable(Box::new(( + (var.clone(), settings.pos), + None, + 0, + None, + )))); + } + + Expr::Dot(Box::new(( + fn_expr, + Expr::FnCall(Box::new(( + (KEYWORD_FN_PTR_CURRY.into(), false, settings.pos), + None, + calc_fn_hash(empty(), KEYWORD_FN_PTR_CURRY, self.externals.len(), empty()), + args, + None, + ))), + settings.pos, + ))) + } + /// Find a module by name in the `ParseState`, searching in reverse. /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. - /// Return zero when the variable name is not found in the `ParseState`. - pub fn find_module(&self, name: &str) -> Option { + /// Return `None` when the variable name is not found in the `ParseState`. + fn find_module(&self, name: &str) -> Option { self.modules .iter() .rev() @@ -1577,7 +1645,7 @@ fn parse_primary( Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))), Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))), Token::Identifier(s) => { - let index = state.find_var(&s); + let index = state.access_var(&s); Expr::Variable(Box::new(((s, settings.pos), None, 0, index))) } // Function call is allowed to have reserved keyword @@ -1778,9 +1846,10 @@ fn parse_unary( // | ... #[cfg(not(feature = "no_function"))] Token::Pipe | Token::Or => { + let mut _externals = Default::default(); let mut state = ParseState::new( state.engine, - #[cfg(not(feature = "unchecked"))] + &mut _externals, state.max_function_expr_depth, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, @@ -2822,8 +2891,10 @@ fn parse_stmt( match input.next().unwrap() { (Token::Fn, pos) => { + let mut _externals = Default::default(); let mut state = ParseState::new( state.engine, + &mut _externals, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, #[cfg(not(feature = "unchecked"))] @@ -3091,7 +3162,16 @@ fn parse_anon_fn( let body = parse_stmt(input, state, lib, settings.level_up()) .map(|stmt| stmt.unwrap_or_else(|| Stmt::Noop(pos)))?; - let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect(); + let mut static_params = StaticVec::::new(); + + #[cfg(not(feature = "no_closures"))] + for closure in state.externals.iter() { + static_params.push(closure.clone()); + } + + for param in params.into_iter() { + static_params.push(param.0); + } // Calculate hash #[cfg(feature = "no_std")] @@ -3099,8 +3179,8 @@ fn parse_anon_fn( #[cfg(not(feature = "no_std"))] let mut s = DefaultHasher::new(); - s.write_usize(params.len()); - params.iter().for_each(|a| a.hash(&mut s)); + s.write_usize(static_params.len()); + static_params.iter().for_each(|a| a.hash(&mut s)); body.hash(&mut s); let hash = s.finish(); @@ -3110,12 +3190,15 @@ fn parse_anon_fn( let script = ScriptFnDef { name: fn_name.clone(), access: FnAccess::Public, - params, + params: static_params, body, pos: settings.pos, }; - let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); + let mut expr = state.make_curry_from_externals( + Expr::FnPointer(Box::new((fn_name, settings.pos))), + &settings, + ); Ok((expr, script)) } @@ -3128,9 +3211,10 @@ impl Engine { optimization_level: OptimizationLevel, ) -> Result { let mut functions = Default::default(); - + let mut _externals = Default::default(); let mut state = ParseState::new( self, + &mut _externals, #[cfg(not(feature = "unchecked"))] self.limits.max_expr_depth, #[cfg(not(feature = "unchecked"))] @@ -3174,9 +3258,10 @@ impl Engine { ) -> Result<(Vec, Vec), ParseError> { let mut statements: Vec = Default::default(); let mut functions = Default::default(); - + let mut _externals = Default::default(); let mut state = ParseState::new( self, + &mut _externals, #[cfg(not(feature = "unchecked"))] self.limits.max_expr_depth, #[cfg(not(feature = "unchecked"))] diff --git a/tests/call_fn.rs b/tests/call_fn.rs index e75d93ec..e2b64dfe 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -209,3 +209,24 @@ fn test_fn_ptr_curry_call() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_fn_closures() -> Result<(), Box> { + let mut engine = Engine::new(); + + let res = engine.eval::( + r#" + let x = 100; + + let f = || x; + + let x = 200; + + f.call() + "# + ).unwrap(); + + panic!("{:#?}", res); + + Ok(()) +} From 48356abc83e7936ff1dea6f9ed8ec9c6692c159c Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Wed, 29 Jul 2020 18:52:54 +0700 Subject: [PATCH 2/4] Autocurry fixes and test --- src/parser.rs | 78 ++++++++++++++++++++---------------------------- tests/call_fn.rs | 26 +++++++++------- 2 files changed, 48 insertions(+), 56 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index f8881342..0ab09875 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,10 +2,7 @@ use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; -use crate::engine::{ - Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT, - KEYWORD_FN_PTR_CURRY, -}; +use crate::engine::{Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::fn_native::Shared; use crate::module::{Module, ModuleRef}; @@ -18,6 +15,9 @@ use crate::utils::{StaticVec, StraightHasherBuilder}; #[cfg(not(feature = "no_function"))] use crate::engine::FN_ANONYMOUS; +#[cfg(not(feature = "no_closures"))] +use crate::engine::KEYWORD_FN_PTR_CURRY; + #[cfg(not(feature = "no_object"))] use crate::engine::{make_getter, make_setter}; @@ -402,29 +402,29 @@ pub enum ReturnType { Exception, } -struct ParseState<'e, 's> { - /// Reference to the scripting `Engine`. +struct ParseState<'e> { + // Reference to the scripting `Engine`. engine: &'e Engine, - /// Encapsulates a local stack with variable names to simulate an actual runtime scope. + // Encapsulates a local stack with variable names to simulate an actual runtime scope. stack: Vec<(String, ScopeEntryType)>, - /// Tracks a list of external variables(variables that are not explicitly - /// declared in the scope during AST evaluation). - externals: &'s mut Vec, - /// Encapsulates a local stack with variable names to simulate an actual runtime scope. + // Tracks a list of external variables(variables that are not explicitly + // declared in the scope during AST evaluation). + #[cfg(not(feature = "no_closures"))] + externals: Vec, + // Encapsulates a local stack with variable names to simulate an actual runtime scope. modules: Vec, - /// Maximum levels of expression nesting. + // Maximum levels of expression nesting. #[cfg(not(feature = "unchecked"))] max_expr_depth: usize, - /// Maximum levels of expression nesting in functions. + // Maximum levels of expression nesting in functions. #[cfg(not(feature = "unchecked"))] max_function_expr_depth: usize, } -impl<'e, 's> ParseState<'e, 's> { +impl<'e> ParseState<'e> { /// Create a new `ParseState`. fn new( engine: &'e Engine, - externals: &'s mut Vec, #[cfg(not(feature = "unchecked"))] max_expr_depth: usize, #[cfg(not(feature = "unchecked"))] max_function_expr_depth: usize, ) -> Self { @@ -432,22 +432,7 @@ impl<'e, 's> ParseState<'e, 's> { engine, #[cfg(not(feature = "unchecked"))] max_expr_depth, #[cfg(not(feature = "unchecked"))] max_function_expr_depth, - externals, - stack: Default::default(), - modules: Default::default(), - } - } - - /// Creates a new `ParseState` with empty `stack` and `modules` lists, but - /// deriving other settings from the passed `ParseState` instance. - fn derive(&'s mut self) -> Self { - Self { - engine: self.engine, - #[cfg(not(feature = "unchecked"))] - max_expr_depth: self.max_expr_depth, - #[cfg(not(feature = "unchecked"))] - max_function_expr_depth: self.max_function_expr_depth, - externals: self.externals, + #[cfg(not(feature = "no_closures"))] externals: Default::default(), stack: Default::default(), modules: Default::default(), } @@ -463,7 +448,7 @@ impl<'e, 's> ParseState<'e, 's> { /// i.e. the top element of the `ParseState` is offset 1. /// Return `None` when the variable name is not found in the `stack`. fn access_var(&mut self, name: &str) -> Option { - let mut index = self.stack + let index = self.stack .iter() .rev() .enumerate() @@ -483,6 +468,7 @@ impl<'e, 's> ParseState<'e, 's> { } /// Creates a curry expression from a list of external variables + #[cfg(not(feature = "no_closures"))] fn make_curry_from_externals(&self, fn_expr: Expr, settings: &ParseSettings) -> Expr { if self.externals.is_empty() { return fn_expr @@ -1846,11 +1832,10 @@ fn parse_unary( // | ... #[cfg(not(feature = "no_function"))] Token::Pipe | Token::Or => { - let mut _externals = Default::default(); - let mut state = ParseState::new( + let mut new_state = ParseState::new( state.engine, - &mut _externals, - state.max_function_expr_depth, + #[cfg(not(feature = "unchecked"))] + state.max_expr_depth, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, ); @@ -1866,7 +1851,12 @@ fn parse_unary( pos: *token_pos, }; - let (expr, func) = parse_anon_fn(input, &mut state, lib, settings)?; + let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?; + + #[cfg(not(feature = "no_closures"))] + for closure in new_state.externals { + state.access_var(&closure); + } // Qualifiers (none) + function name + number of arguments. let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty()); @@ -2891,12 +2881,10 @@ fn parse_stmt( match input.next().unwrap() { (Token::Fn, pos) => { - let mut _externals = Default::default(); let mut state = ParseState::new( state.engine, - &mut _externals, #[cfg(not(feature = "unchecked"))] - state.max_function_expr_depth, + state.max_expr_depth, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, ); @@ -3195,11 +3183,15 @@ fn parse_anon_fn( pos: settings.pos, }; - let mut expr = state.make_curry_from_externals( + #[cfg(not(feature = "no_closures"))] + let expr = state.make_curry_from_externals( Expr::FnPointer(Box::new((fn_name, settings.pos))), &settings, ); + #[cfg(feature = "no_closures")] + let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); + Ok((expr, script)) } @@ -3211,10 +3203,8 @@ impl Engine { optimization_level: OptimizationLevel, ) -> Result { let mut functions = Default::default(); - let mut _externals = Default::default(); let mut state = ParseState::new( self, - &mut _externals, #[cfg(not(feature = "unchecked"))] self.limits.max_expr_depth, #[cfg(not(feature = "unchecked"))] @@ -3258,10 +3248,8 @@ impl Engine { ) -> Result<(Vec, Vec), ParseError> { let mut statements: Vec = Default::default(); let mut functions = Default::default(); - let mut _externals = Default::default(); let mut state = ParseState::new( self, - &mut _externals, #[cfg(not(feature = "unchecked"))] self.limits.max_expr_depth, #[cfg(not(feature = "unchecked"))] diff --git a/tests/call_fn.rs b/tests/call_fn.rs index e2b64dfe..74a703ae 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -211,22 +211,26 @@ fn test_fn_ptr_curry_call() -> Result<(), Box> { } #[test] +#[cfg(not(feature = "no_closures"))] fn test_fn_closures() -> Result<(), Box> { - let mut engine = Engine::new(); + let engine = Engine::new(); - let res = engine.eval::( - r#" - let x = 100; + assert_eq!( + engine.eval::( + r#" + let x = 8; - let f = || x; + let res = |y, z| { + let w = 12; - let x = 200; + return (|| x + y + z + w).call(); + }.curry(15).call(2); - f.call() - "# - ).unwrap(); - - panic!("{:#?}", res); + res + (|| x - 3).call() + "# + )?, + 42 + ); Ok(()) } From a856a8bbcb3ddb0b06855d133484d3e55680bb33 Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Wed, 29 Jul 2020 19:04:33 +0700 Subject: [PATCH 3/4] max_expr_depth bug fix --- src/parser.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 0ab09875..f273cab3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1835,7 +1835,7 @@ fn parse_unary( let mut new_state = ParseState::new( state.engine, #[cfg(not(feature = "unchecked"))] - state.max_expr_depth, + state.max_function_expr_depth, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, ); @@ -2884,7 +2884,7 @@ fn parse_stmt( let mut state = ParseState::new( state.engine, #[cfg(not(feature = "unchecked"))] - state.max_expr_depth, + state.max_function_expr_depth, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, ); From 8fc16b40e6244a6022c2ff4ba98e7b3809093197 Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Wed, 29 Jul 2020 20:17:18 +0700 Subject: [PATCH 4/4] Code style issues fixed --- Cargo.toml | 2 +- src/parser.rs | 39 ++++++++++++++++++++------------------- tests/call_fn.rs | 2 +- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 606412c9..2c8df560 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ only_i64 = [] # set INT=i64 (default) and disable support for all other in no_index = [] # no arrays and indexing no_object = [] # no custom objects no_function = [] # no script-defined functions -no_closures = [] # no automatic read/write binding of anonymous function's local variables to it's external context +no_capture = [] # no automatic read/write binding of anonymous function's local variables to it's external context no_module = [] # no modules internals = [] # expose internal data structures unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. diff --git a/src/parser.rs b/src/parser.rs index f273cab3..0b545b8f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -15,7 +15,7 @@ use crate::utils::{StaticVec, StraightHasherBuilder}; #[cfg(not(feature = "no_function"))] use crate::engine::FN_ANONYMOUS; -#[cfg(not(feature = "no_closures"))] +#[cfg(not(feature = "no_capture"))] use crate::engine::KEYWORD_FN_PTR_CURRY; #[cfg(not(feature = "no_object"))] @@ -402,28 +402,29 @@ pub enum ReturnType { Exception, } +#[derive(Clone)] struct ParseState<'e> { - // Reference to the scripting `Engine`. + /// Reference to the scripting `Engine`. engine: &'e Engine, - // Encapsulates a local stack with variable names to simulate an actual runtime scope. + /// Encapsulates a local stack with variable names to simulate an actual runtime scope. stack: Vec<(String, ScopeEntryType)>, - // Tracks a list of external variables(variables that are not explicitly - // declared in the scope during AST evaluation). - #[cfg(not(feature = "no_closures"))] + /// Tracks a list of external variables(variables that are not explicitly + /// declared in the scope during AST evaluation). + #[cfg(not(feature = "no_capture"))] externals: Vec, - // Encapsulates a local stack with variable names to simulate an actual runtime scope. + /// Encapsulates a local stack with variable names to simulate an actual runtime scope. modules: Vec, - // Maximum levels of expression nesting. + /// Maximum levels of expression nesting. #[cfg(not(feature = "unchecked"))] max_expr_depth: usize, - // Maximum levels of expression nesting in functions. + /// Maximum levels of expression nesting in functions. #[cfg(not(feature = "unchecked"))] max_function_expr_depth: usize, } impl<'e> ParseState<'e> { /// Create a new `ParseState`. - fn new( + pub fn new( engine: &'e Engine, #[cfg(not(feature = "unchecked"))] max_expr_depth: usize, #[cfg(not(feature = "unchecked"))] max_function_expr_depth: usize, @@ -432,7 +433,7 @@ impl<'e> ParseState<'e> { engine, #[cfg(not(feature = "unchecked"))] max_expr_depth, #[cfg(not(feature = "unchecked"))] max_function_expr_depth, - #[cfg(not(feature = "no_closures"))] externals: Default::default(), + #[cfg(not(feature = "no_capture"))] externals: Default::default(), stack: Default::default(), modules: Default::default(), } @@ -459,7 +460,7 @@ impl<'e> ParseState<'e> { return index } - #[cfg(not(feature = "no_closures"))] + #[cfg(not(feature = "no_capture"))] if self.externals.iter().find(|n| *n == name).is_none() { self.externals.push(name.to_string()); } @@ -468,8 +469,8 @@ impl<'e> ParseState<'e> { } /// Creates a curry expression from a list of external variables - #[cfg(not(feature = "no_closures"))] - fn make_curry_from_externals(&self, fn_expr: Expr, settings: &ParseSettings) -> Expr { + #[cfg(not(feature = "no_capture"))] + pub fn make_curry_from_externals(&self, fn_expr: Expr, settings: &ParseSettings) -> Expr { if self.externals.is_empty() { return fn_expr } @@ -502,7 +503,7 @@ impl<'e> ParseState<'e> { /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. /// Return `None` when the variable name is not found in the `ParseState`. - fn find_module(&self, name: &str) -> Option { + pub fn find_module(&self, name: &str) -> Option { self.modules .iter() .rev() @@ -1853,7 +1854,7 @@ fn parse_unary( let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?; - #[cfg(not(feature = "no_closures"))] + #[cfg(not(feature = "no_capture"))] for closure in new_state.externals { state.access_var(&closure); } @@ -3152,7 +3153,7 @@ fn parse_anon_fn( let mut static_params = StaticVec::::new(); - #[cfg(not(feature = "no_closures"))] + #[cfg(not(feature = "no_capture"))] for closure in state.externals.iter() { static_params.push(closure.clone()); } @@ -3183,13 +3184,13 @@ fn parse_anon_fn( pos: settings.pos, }; - #[cfg(not(feature = "no_closures"))] + #[cfg(not(feature = "no_capture"))] let expr = state.make_curry_from_externals( Expr::FnPointer(Box::new((fn_name, settings.pos))), &settings, ); - #[cfg(feature = "no_closures")] + #[cfg(feature = "no_capture")] let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); Ok((expr, script)) diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 74a703ae..d603f743 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -211,7 +211,7 @@ fn test_fn_ptr_curry_call() -> Result<(), Box> { } #[test] -#[cfg(not(feature = "no_closures"))] +#[cfg(not(feature = "no_capture"))] fn test_fn_closures() -> Result<(), Box> { let engine = Engine::new();