From a3a167424b0e6e386da3d691a0177d021de1fef4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 27 Jul 2020 12:52:32 +0800 Subject: [PATCH] Allow Rust functions in FnPtr::call_dynamic. --- doc/src/rust/register-raw.md | 3 +- examples/repl.rs | 8 +- src/engine.rs | 66 ++++++++----- src/fn_call.rs | 174 ++++++++++++++++++++++------------- src/fn_native.rs | 48 ++++++++-- src/module.rs | 12 +-- src/optimize.rs | 1 + src/parser.rs | 14 ++- tests/call_fn.rs | 57 +++++++++--- 9 files changed, 248 insertions(+), 135 deletions(-) diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index 76866249..cf997286 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -125,8 +125,7 @@ engine.register_raw_fn( let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer // Use 'FnPtr::call_dynamic' to call the function pointer. - // Beware, only script-defined functions are supported by 'FnPtr::call_dynamic'. - // If it is a native Rust function, directly call it here in Rust instead! + // Beware, private script-defined functions will not be found. fp.call_dynamic(engine, lib, Some(this_ptr), [value]) }, ); diff --git a/examples/repl.rs b/examples/repl.rs index cf255986..b3877dd0 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -72,15 +72,17 @@ fn main() { println!("=============="); print_help(); - loop { + 'main_loop: loop { print!("rhai> "); stdout().flush().expect("couldn't flush stdout"); input.clear(); loop { - if let Err(err) = stdin().read_line(&mut input) { - panic!("input error: {}", err); + match stdin().read_line(&mut input) { + Ok(0) => break 'main_loop, + Ok(_) => (), + Err(err) => panic!("input error: {}", err), } let line = input.as_str().trim_end(); diff --git a/src/engine.rs b/src/engine.rs index f4435698..aea01500 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -695,8 +695,8 @@ impl Engine { let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val]; self.exec_fn_call( - state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None, - level, + state, lib, FN_IDX_SET, true, 0, args, is_ref, true, false, + None, level, ) .or_else(|err| match *err { // If there is no index setter, no need to set it back because the indexer is read-only @@ -719,8 +719,8 @@ impl Engine { let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val]; self.exec_fn_call( - state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None, - level, + state, lib, FN_IDX_SET, true, 0, args, is_ref, true, false, + None, level, )?; } // Error @@ -741,7 +741,12 @@ impl Engine { match rhs { // xxx.fn_name(arg_expr_list) Expr::FnCall(x) if x.1.is_none() => { - self.make_method_call(state, lib, target, rhs, idx_val, level) + let ((name, native, pos), _, hash, _, def_val) = x.as_ref(); + self.make_method_call( + state, lib, name, *hash, target, idx_val, *def_val, *native, false, + level, + ) + .map_err(|err| err.new_position(*pos)) } // xxx.module::fn_name(...) - syntax error Expr::FnCall(_) => unreachable!(), @@ -770,7 +775,8 @@ impl Engine { let ((_, _, setter), pos) = x.as_ref(); let mut args = [target.as_mut(), _new_val.as_mut().unwrap()]; self.exec_fn_call( - state, lib, setter, true, 0, &mut args, is_ref, true, None, level, + state, lib, setter, true, 0, &mut args, is_ref, true, false, None, + level, ) .map(|(v, _)| (v, true)) .map_err(|err| err.new_position(*pos)) @@ -780,7 +786,8 @@ impl Engine { let ((_, getter, _), pos) = x.as_ref(); let mut args = [target.as_mut()]; self.exec_fn_call( - state, lib, getter, true, 0, &mut args, is_ref, true, None, level, + state, lib, getter, true, 0, &mut args, is_ref, true, false, None, + level, ) .map(|(v, _)| (v, false)) .map_err(|err| err.new_position(*pos)) @@ -797,9 +804,13 @@ impl Engine { } // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr Expr::FnCall(x) if x.1.is_none() => { - let (val, _) = self.make_method_call( - state, lib, target, sub_lhs, idx_val, level, - )?; + let ((name, native, pos), _, hash, _, def_val) = x.as_ref(); + let (val, _) = self + .make_method_call( + state, lib, name, *hash, target, idx_val, *def_val, + *native, false, level, + ) + .map_err(|err| err.new_position(*pos))?; val.into() } // {xxx:map}.module::fn_name(...) - syntax error @@ -826,8 +837,8 @@ impl Engine { let args = &mut arg_values[..1]; let (mut val, updated) = self .exec_fn_call( - state, lib, getter, true, 0, args, is_ref, true, None, - level, + state, lib, getter, true, 0, args, is_ref, true, false, + None, level, ) .map_err(|err| err.new_position(*pos))?; @@ -847,7 +858,7 @@ impl Engine { arg_values[1] = val; self.exec_fn_call( state, lib, setter, true, 0, arg_values, is_ref, true, - None, level, + false, None, level, ) .or_else( |err| match *err { @@ -864,9 +875,13 @@ impl Engine { } // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr Expr::FnCall(x) if x.1.is_none() => { - let (mut val, _) = self.make_method_call( - state, lib, target, sub_lhs, idx_val, level, - )?; + let ((name, native, pos), _, hash, _, def_val) = x.as_ref(); + let (mut val, _) = self + .make_method_call( + state, lib, name, *hash, target, idx_val, *def_val, + *native, false, level, + ) + .map_err(|err| err.new_position(*pos))?; let val = &mut val; let target = &mut val.into(); @@ -1132,7 +1147,7 @@ impl Engine { let type_name = val.type_name(); let args = &mut [val, &mut _idx]; self.exec_fn_call( - state, _lib, FN_IDX_GET, true, 0, args, is_ref, true, None, _level, + state, _lib, FN_IDX_GET, true, 0, args, is_ref, true, false, None, _level, ) .map(|(v, _)| v.into()) .map_err(|err| match *err { @@ -1188,7 +1203,7 @@ impl Engine { let (r, _) = self .call_fn_raw( - &mut scope, mods, state, lib, op, hashes, args, false, false, + &mut scope, mods, state, lib, op, hashes, args, false, false, false, def_value, level, ) .map_err(|err| err.new_position(rhs.position()))?; @@ -1303,7 +1318,8 @@ impl Engine { // Run function let (value, _) = self .exec_fn_call( - state, lib, op, true, hash, args, false, false, None, level, + state, lib, op, true, hash, args, false, false, false, None, + level, ) .map_err(|err| err.new_position(*op_pos))?; // Set value to LHS @@ -1331,9 +1347,11 @@ impl Engine { &mut self.eval_expr(scope, mods, state, lib, this_ptr, lhs_expr, level)?, &mut rhs_val, ]; - self.exec_fn_call(state, lib, op, true, hash, args, false, false, None, level) - .map(|(v, _)| v) - .map_err(|err| err.new_position(*op_pos))? + self.exec_fn_call( + state, lib, op, true, hash, args, false, false, false, None, level, + ) + .map(|(v, _)| v) + .map_err(|err| err.new_position(*op_pos))? }); match lhs_expr { @@ -1403,7 +1421,7 @@ impl Engine { let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref(); self.make_function_call( scope, mods, state, lib, this_ptr, name, args_expr, *def_val, *hash, *native, - level, + false, level, ) .map_err(|err| err.new_position(*pos)) } @@ -1413,7 +1431,7 @@ impl Engine { let ((name, _, pos), modules, hash, args_expr, def_val) = x.as_ref(); self.make_qualified_function_call( scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash, - level, + true, level, ) .map_err(|err| err.new_position(*pos)) } diff --git a/src/fn_call.rs b/src/fn_call.rs index c55124e2..d581268a 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -8,10 +8,10 @@ use crate::engine::{ KEYWORD_TYPE_OF, }; use crate::error::ParseErrorType; -use crate::fn_native::{FnCallArgs, FnPtr}; +use crate::fn_native::{CallableFunction, FnCallArgs, FnPtr}; use crate::module::{Module, ModuleRef}; use crate::optimize::OptimizationLevel; -use crate::parser::{Expr, ImmutableString, AST, INT}; +use crate::parser::{Expr, FnAccess, ImmutableString, AST, INT}; use crate::result::EvalAltResult; use crate::scope::Scope; use crate::token::Position; @@ -105,6 +105,19 @@ fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCal } } +#[inline] +fn check_public_access(func: &CallableFunction) -> Option<&CallableFunction> { + if func.access() == FnAccess::Private { + None + } else { + Some(func) + } +} +#[inline(always)] +fn no_check_access(func: &CallableFunction) -> Option<&CallableFunction> { + Some(func) +} + impl Engine { /// Universal method for calling functions either registered with the `Engine` or written in Rhai. /// Position in `EvalAltResult` is `None` and must be set afterwards. @@ -125,6 +138,7 @@ impl Engine { args: &mut FnCallArgs, is_ref: bool, _is_method: bool, + pub_only: bool, def_val: Option, _level: usize, ) -> Result<(Dynamic, bool), Box> { @@ -132,6 +146,12 @@ impl Engine { let native_only = hash_script == 0; + let check = if pub_only { + check_public_access + } else { + no_check_access + }; + // Check for stack overflow #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "unchecked"))] @@ -150,14 +170,16 @@ impl Engine { // Then search packages // NOTE: We skip script functions for global_module and packages, and native functions for lib let func = if !native_only { - lib.get_fn(hash_script) //.or_else(|| lib.get_fn(hash_fn)) + lib.get_fn(hash_script).and_then(check) //.or_else(|| lib.get_fn(hash_fn)).and_then(check) } else { None } - //.or_else(|| self.global_module.get_fn(hash_script)) + //.or_else(|| self.global_module.get_fn(hash_script)).and_then(check) .or_else(|| self.global_module.get_fn(hash_fn)) - //.or_else(|| self.packages.get_fn(hash_script)) - .or_else(|| self.packages.get_fn(hash_fn)); + .and_then(check) + //.or_else(|| self.packages.get_fn(hash_script)).and_then(check) + .or_else(|| self.packages.get_fn(hash_fn)) + .and_then(check); if let Some(func) = func { #[cfg(not(feature = "no_function"))] @@ -378,18 +400,28 @@ impl Engine { } // Has a system function an override? - fn has_override(&self, lib: &Module, hash_fn: u64, hash_script: u64) -> bool { + fn has_override(&self, lib: &Module, hash_fn: u64, hash_script: u64, pub_only: bool) -> bool { + let check = if pub_only { + check_public_access + } else { + no_check_access + }; + // NOTE: We skip script functions for global_module and packages, and native functions for lib // First check script-defined functions - lib.contains_fn(hash_script) - //|| lib.contains_fn(hash_fn) - // Then check registered functions - //|| self.global_module.contains_fn(hash_script) - || self.global_module.contains_fn(hash_fn) - // Then check packages - //|| self.packages.contains_fn(hash_script) - || self.packages.contains_fn(hash_fn) + lib.get_fn(hash_script) + .and_then(check) + //.or_else(|| lib.get_fn(hash_fn)).and_then(check) + // Then check registered functions + //.or_else(|| self.global_module.get_fn(hash_script)).and_then(check) + .or_else(|| self.global_module.get_fn(hash_fn)) + .and_then(check) + // Then check packages + //.or_else(|| self.packages.get_fn(hash_script)).and_then(check) + .or_else(|| self.packages.get_fn(hash_fn)) + .and_then(check) + .is_some() } /// Perform an actual function call, taking care of special functions @@ -410,6 +442,7 @@ impl Engine { args: &mut FnCallArgs, is_ref: bool, is_method: bool, + pub_only: bool, def_val: Option, level: usize, ) -> Result<(Dynamic, bool), Box> { @@ -420,7 +453,9 @@ impl Engine { match fn_name { // type_of - KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { + KEYWORD_TYPE_OF + if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) => + { Ok(( self.map_type_name(args[0].type_name()).to_string().into(), false, @@ -428,7 +463,9 @@ impl Engine { } // Fn - KEYWORD_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { + KEYWORD_FN_PTR + if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) => + { Err(Box::new(EvalAltResult::ErrorRuntime( "'Fn' should not be called in method style. Try Fn(...);".into(), Position::none(), @@ -436,7 +473,9 @@ impl Engine { } // eval - reaching this point it must be a method-style call - KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { + KEYWORD_EVAL + if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) => + { Err(Box::new(EvalAltResult::ErrorRuntime( "'eval' should not be called in method style. Try eval(...);".into(), Position::none(), @@ -449,7 +488,7 @@ impl Engine { let mut mods = Imports::new(); self.call_fn_raw( &mut scope, &mut mods, state, lib, fn_name, hashes, args, is_ref, is_method, - def_val, level, + pub_only, def_val, level, ) } } @@ -499,28 +538,28 @@ impl Engine { } /// Call a dot method. + /// Position in `EvalAltResult` is `None` and must be set afterwards. #[cfg(not(feature = "no_object"))] pub(crate) fn make_method_call( &self, state: &mut State, lib: &Module, + name: &str, + hash: u64, target: &mut Target, - expr: &Expr, idx_val: Dynamic, + def_val: Option, + native: bool, + pub_only: bool, level: usize, ) -> Result<(Dynamic, bool), Box> { - let ((name, native, pos), _, hash, _, def_val) = match expr { - Expr::FnCall(x) => x.as_ref(), - _ => unreachable!(), - }; - let is_ref = target.is_ref(); let is_value = target.is_value(); // Get a reference to the mutation target Dynamic let obj = target.as_mut(); let mut idx = idx_val.cast::>(); - let mut _fn_name = name.as_ref(); + let mut _fn_name = name; let (result, updated) = if _fn_name == KEYWORD_FN_PTR_CALL && obj.is::() { // FnPtr call @@ -539,7 +578,7 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - state, lib, fn_name, *native, hash, args, false, false, *def_val, level, + state, lib, fn_name, native, hash, args, false, false, pub_only, def_val, level, ) } else if _fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::() { // FnPtr call on object @@ -558,7 +597,7 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - state, lib, &fn_name, *native, hash, args, is_ref, true, *def_val, level, + state, lib, &fn_name, native, hash, args, is_ref, true, pub_only, def_val, level, ) } else if _fn_name == KEYWORD_FN_PTR_CURRY && obj.is::() { // Curry call @@ -579,7 +618,7 @@ impl Engine { } else { #[cfg(not(feature = "no_object"))] let redirected; - let mut _hash = *hash; + let mut _hash = hash; // Check if it is a map method call in OOP style #[cfg(not(feature = "no_object"))] @@ -600,10 +639,9 @@ impl Engine { let args = arg_values.as_mut(); self.exec_fn_call( - state, lib, _fn_name, *native, _hash, args, is_ref, true, *def_val, level, + state, lib, _fn_name, native, _hash, args, is_ref, true, pub_only, def_val, level, ) - } - .map_err(|err| err.new_position(*pos))?; + }?; // Feed the changed temp value back if updated && !is_ref && !is_value { @@ -628,13 +666,14 @@ impl Engine { def_val: Option, mut hash: u64, native: bool, + pub_only: bool, level: usize, ) -> Result> { // Handle Fn() if name == KEYWORD_FN_PTR && args_expr.len() == 1 { let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); - if !self.has_override(lib, hash_fn, hash) { + if !self.has_override(lib, hash_fn, hash, pub_only) { // Fn - only in function call style let expr = args_expr.get(0).unwrap(); let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; @@ -685,7 +724,7 @@ impl Engine { if name == KEYWORD_EVAL && args_expr.len() == 1 { let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); - if !self.has_override(lib, hash_fn, hash) { + if !self.has_override(lib, hash_fn, hash, pub_only) { // eval - only in function call style let prev_len = scope.len(); let expr = args_expr.get(0).unwrap(); @@ -710,7 +749,10 @@ impl Engine { let mut curry: StaticVec<_> = Default::default(); let mut name = name; - if name == KEYWORD_FN_PTR_CALL && args_expr.len() >= 1 && !self.has_override(lib, 0, hash) { + if name == KEYWORD_FN_PTR_CALL + && args_expr.len() >= 1 + && !self.has_override(lib, 0, hash, pub_only) + { let expr = args_expr.get(0).unwrap(); let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; @@ -779,7 +821,7 @@ impl Engine { let args = args.as_mut(); self.exec_fn_call( - state, lib, name, native, hash, args, is_ref, false, def_val, level, + state, lib, name, native, hash, args, is_ref, false, pub_only, def_val, level, ) .map(|(v, _)| v) } @@ -798,6 +840,7 @@ impl Engine { args_expr: &[Expr], def_val: Option, hash_script: u64, + pub_only: bool, level: usize, ) -> Result> { let modules = modules.as_ref().unwrap(); @@ -844,9 +887,15 @@ impl Engine { let module = search_imports(mods, state, modules)?; // First search in script-defined functions (can override built-in) - let func = match module.get_qualified_fn(hash_script) { - Err(err) if matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) => { - // Then search in Rust functions + let check = if pub_only { + check_public_access + } else { + no_check_access + }; + + let func = match module.get_qualified_fn(hash_script).and_then(check) { + // Then search in Rust functions + None => { self.inc_operations(state)?; // Qualified Rust functions are indexed in two steps: @@ -858,14 +907,14 @@ impl Engine { // 3) The final hash is the XOR of the two hashes. let hash_qualified_fn = hash_script ^ hash_fn_args; - module.get_qualified_fn(hash_qualified_fn) + module.get_qualified_fn(hash_qualified_fn).and_then(check) } r => r, }; match func { #[cfg(not(feature = "no_function"))] - Ok(f) if f.is_script() => { + Some(f) if f.is_script() => { let args = args.as_mut(); let fn_def = f.get_fn_def(); let mut scope = Scope::new(); @@ -874,31 +923,24 @@ impl Engine { &mut scope, &mut mods, state, lib, &mut None, name, fn_def, args, level, ) } - Ok(f) => f.get_native_fn()(self, lib, args.as_mut()), - Err(err) => match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) if def_val.is_some() => { - Ok(def_val.unwrap().into()) - } - EvalAltResult::ErrorFunctionNotFound(_, pos) => { - Err(Box::new(EvalAltResult::ErrorFunctionNotFound( - format!( - "{}{} ({})", - modules, - name, - args.iter() - .map(|a| if a.is::() { - "&str | ImmutableString | String" - } else { - self.map_type_name((*a).type_name()) - }) - .collect::>() - .join(", ") - ), - pos, - ))) - } - _ => Err(err), - }, + Some(f) => f.get_native_fn()(self, lib, args.as_mut()), + None if def_val.is_some() => Ok(def_val.unwrap().into()), + None => Err(Box::new(EvalAltResult::ErrorFunctionNotFound( + format!( + "{}{} ({})", + modules, + name, + args.iter() + .map(|a| if a.is::() { + "&str | ImmutableString | String" + } else { + self.map_type_name((*a).type_name()) + }) + .collect::>() + .join(", ") + ), + Position::none(), + ))), } } } diff --git a/src/fn_native.rs b/src/fn_native.rs index bfb8b956..22919274 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,16 +1,18 @@ //! Module defining interfaces to native-Rust functions. use crate::any::Dynamic; +use crate::calc_fn_hash; use crate::engine::Engine; use crate::module::Module; +use crate::parser::FnAccess; use crate::result::EvalAltResult; use crate::token::{is_valid_identifier, Position}; use crate::utils::ImmutableString; #[cfg(not(feature = "no_function"))] -use crate::{module::FuncReturn, parser::ScriptFnDef, scope::Scope, utils::StaticVec}; +use crate::{module::FuncReturn, parser::ScriptFnDef, utils::StaticVec}; -use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, string::String, vec::Vec}; +use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, string::String, vec::Vec}; #[cfg(not(feature = "no_function"))] use crate::stdlib::mem; @@ -89,9 +91,7 @@ impl FnPtr { /// Call the function pointer with curried arguments (if any). /// - /// The function must be a script-defined function. It cannot be a Rust function. - /// - /// To call a Rust function, just call it directly in Rust! + /// If this function is a script-defined function, it must not be marked private. /// /// ## WARNING /// @@ -107,14 +107,39 @@ impl FnPtr { this_ptr: Option<&mut Dynamic>, mut arg_values: impl AsMut<[Dynamic]>, ) -> FuncReturn { - let args = self + let mut args_data = self .1 .iter() .cloned() .chain(arg_values.as_mut().iter_mut().map(|v| mem::take(v))) .collect::>(); - engine.call_fn_dynamic(&mut Scope::new(), lib, self.0.as_str(), this_ptr, args) + let has_this = this_ptr.is_some(); + let args_len = args_data.len(); + let mut args = args_data.iter_mut().collect::>(); + + if let Some(obj) = this_ptr { + args.insert(0, obj); + } + + let fn_name = self.0.as_str(); + let hash_script = calc_fn_hash(empty(), fn_name, args_len, empty()); + + engine + .exec_fn_call( + &mut Default::default(), + lib.as_ref(), + fn_name, + false, + hash_script, + args.as_mut(), + has_this, + has_this, + true, + None, + 0, + ) + .map(|(v, _)| v) } } @@ -255,6 +280,15 @@ impl CallableFunction { Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false, } } + /// Get the access mode. + pub fn access(&self) -> FnAccess { + match self { + CallableFunction::Pure(_) + | CallableFunction::Method(_) + | CallableFunction::Iterator(_) => FnAccess::Public, + CallableFunction::Script(f) => f.access, + } + } /// Get a reference to a native Rust function. /// /// # Panics diff --git a/src/module.rs b/src/module.rs index 07430f6e..9fad5571 100644 --- a/src/module.rs +++ b/src/module.rs @@ -912,16 +912,8 @@ impl Module { /// /// The `u64` hash is calculated by the function `crate::calc_fn_hash` and must match /// the hash calculated by `index_all_sub_modules`. - pub(crate) fn get_qualified_fn( - &self, - hash_qualified_fn: u64, - ) -> Result<&Func, Box> { - self.all_functions.get(&hash_qualified_fn).ok_or_else(|| { - Box::new(EvalAltResult::ErrorFunctionNotFound( - String::new(), - Position::none(), - )) - }) + pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&Func> { + self.all_functions.get(&hash_qualified_fn) } /// Merge another module into this module. diff --git a/src/optimize.rs b/src/optimize.rs index 0948fbd5..bf5f6cd3 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -141,6 +141,7 @@ fn call_fn_with_constant_arguments( arg_values.iter_mut().collect::>().as_mut(), false, false, + true, None, 0, ) diff --git a/src/parser.rs b/src/parser.rs index 51685a6c..1177c3c2 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2674,10 +2674,9 @@ fn parse_block( // Parse statements inside the block settings.is_global = false; - let stmt = if let Some(s) = parse_stmt(input, state, lib, settings.level_up())? { - s - } else { - continue; + let stmt = match parse_stmt(input, state, lib, settings.level_up())? { + Some(s) => s, + None => continue, }; // See if it needs a terminating semicolon @@ -3137,10 +3136,9 @@ impl Engine { pos: Position::none(), }; - let stmt = if let Some(s) = parse_stmt(input, &mut state, &mut functions, settings)? { - s - } else { - continue; + let stmt = match parse_stmt(input, &mut state, &mut functions, settings)? { + Some(s) => s, + None => continue, }; let need_semicolon = !stmt.is_self_terminated(); diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 3b93d520..e75d93ec 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -1,6 +1,7 @@ #![cfg(not(feature = "no_function"))] use rhai::{ - Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, Scope, INT, + Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, RegisterFn, + Scope, INT, }; use std::any::TypeId; @@ -119,21 +120,23 @@ fn test_anonymous_fn() -> Result<(), Box> { fn test_fn_ptr_raw() -> Result<(), Box> { let mut engine = Engine::new(); - engine.register_raw_fn( - "bar", - &[ - TypeId::of::(), - TypeId::of::(), - TypeId::of::(), - ], - move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { - let fp = std::mem::take(args[1]).cast::(); - let value = args[2].clone(); - let this_ptr = args.get_mut(0).unwrap(); + engine + .register_fn("mul", |x: &mut INT, y: INT| *x *= y) + .register_raw_fn( + "bar", + &[ + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + ], + move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { + let fp = std::mem::take(args[1]).cast::(); + let value = args[2].clone(); + let this_ptr = args.get_mut(0).unwrap(); - fp.call_dynamic(engine, lib, Some(this_ptr), [value]) - }, - ); + fp.call_dynamic(engine, lib, Some(this_ptr), [value]) + }, + ); assert_eq!( engine.eval::( @@ -148,6 +151,30 @@ fn test_fn_ptr_raw() -> Result<(), Box> { 42 ); + assert!(matches!( + *engine.eval::( + r#" + private fn foo(x) { this += x; } + + let x = 41; + x.bar(Fn("foo"), 1); + x + "# + ).expect_err("should error"), + EvalAltResult::ErrorFunctionNotFound(x, _) if x.starts_with("foo (") + )); + + assert_eq!( + engine.eval::( + r#" + let x = 21; + x.bar(Fn("mul"), 2); + x + "# + )?, + 42 + ); + Ok(()) }