From 09e6b2172907c2bed68ee8d170ab973e240c0725 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 8 Nov 2021 09:27:08 +0800 Subject: [PATCH] Move mutable runtime global state to Imports. --- src/engine.rs | 255 ++++++++++++++++++++++++++------------------- src/engine_api.rs | 12 ++- src/fn_call.rs | 85 +++++++-------- src/optimize.rs | 2 +- tests/functions.rs | 42 +++++--- 5 files changed, 222 insertions(+), 174 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index e8b8b50f..b3e27d94 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -13,8 +13,8 @@ use crate::packages::{Package, StandardPackage}; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::token::Token; use crate::{ - Dynamic, EvalAltResult, Identifier, ImmutableString, Module, Position, RhaiResult, Scope, - Shared, StaticVec, INT, + Dynamic, EvalAltResult, Identifier, ImmutableString, Locked, Module, Position, RhaiResult, + Scope, Shared, StaticVec, INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -40,7 +40,7 @@ use crate::ast::FnCallHashes; pub type Precedence = NonZeroU8; -/// _(internals)_ A stack of imported [modules][Module]. +/// _(internals)_ A stack of imported [modules][Module] plus mutable runtime global states. /// Exported under the `internals` feature only. /// /// # Volatile Data Structure @@ -49,17 +49,34 @@ pub type Precedence = NonZeroU8; // // # Implementation Notes // -// We cannot use Cow here because `eval` may load a [module][Module] and -// the module name will live beyond the AST of the eval script text. -// The best we can do is a shared reference. -// // This implementation splits the module names from the shared modules to improve data locality. // Most usage will be looking up a particular key from the list and then getting the module that // corresponds to that key. #[derive(Clone)] pub struct Imports { + /// Stack of module names. + // + // # Implementation Notes + // + // We cannot use Cow here because `eval` may load a [module][Module] and + // the module name will live beyond the AST of the eval script text. keys: StaticVec, + /// Stack of imported modules. modules: StaticVec>, + /// Source of the current context. + pub source: Option, + /// Number of operations performed. + pub num_operations: u64, + /// Number of modules loaded. + pub num_modules: usize, + /// Function call hashes to FN_IDX_GET and FN_IDX_SET. + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + fn_hash_indexing: (u64, u64), + /// Embedded module resolver. + #[cfg(not(feature = "no_module"))] + pub embedded_module_resolver: Option>, + /// Cache of globally-defined constants. + global_constants: Option>>>, } impl Imports { @@ -70,6 +87,14 @@ impl Imports { Self { keys: StaticVec::new(), modules: StaticVec::new(), + source: None, + num_operations: 0, + num_modules: 0, + #[cfg(not(feature = "no_module"))] + embedded_module_resolver: None, + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + fn_hash_indexing: (0, 0), + global_constants: None, } } /// Get the length of this stack of imported [modules][Module]. @@ -175,6 +200,68 @@ impl Imports { .rev() .find_map(|m| m.get_qualified_iter(id)) } + /// Get a mutable reference to the cache of globally-defined constants. + #[must_use] + pub(crate) fn global_constants_mut<'a>( + &'a mut self, + ) -> Option> + 'a> { + if let Some(ref mut global_constants) = self.global_constants { + #[cfg(not(feature = "sync"))] + return Some(global_constants.borrow_mut()); + + #[cfg(feature = "sync")] + return Some(global_constants.lock().unwrap()); + } else { + None + } + } + /// Set a constant into the cache of globally-defined constants. + pub(crate) fn set_global_constant(&mut self, name: &str, value: Dynamic) { + if self.global_constants.is_none() { + let dict: Locked<_> = BTreeMap::new().into(); + self.global_constants = Some(dict.into()); + } + + #[cfg(not(feature = "sync"))] + self.global_constants + .as_mut() + .unwrap() + .borrow_mut() + .insert(name.into(), value); + + #[cfg(feature = "sync")] + self.global_constants + .as_mut() + .lock() + .unwrap() + .insert(name.into(), value); + } + /// Get the pre-calculated index getter hash. + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + #[must_use] + pub(crate) fn fn_hash_idx_get(&mut self) -> u64 { + if self.fn_hash_indexing != (0, 0) { + self.fn_hash_indexing.0 + } else { + let n1 = crate::calc_fn_hash(FN_IDX_GET, 2); + let n2 = crate::calc_fn_hash(FN_IDX_SET, 3); + self.fn_hash_indexing = (n1, n2); + n1 + } + } + /// Get the pre-calculated index setter hash. + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + #[must_use] + pub(crate) fn fn_hash_idx_set(&mut self) -> u64 { + if self.fn_hash_indexing != (0, 0) { + self.fn_hash_indexing.1 + } else { + let n1 = crate::calc_fn_hash(FN_IDX_GET, 2); + let n2 = crate::calc_fn_hash(FN_IDX_SET, 3); + self.fn_hash_indexing = (n1, n2); + n2 + } + } } impl IntoIterator for Imports { @@ -663,8 +750,6 @@ pub type FnResolutionCache = BTreeMap>>; /// This type is volatile and may change. #[derive(Debug, Clone)] pub struct EvalState { - /// Source of the current context. - pub source: Option, /// Normally, access to variables are parsed with a relative offset into the [`Scope`] to avoid a lookup. /// In some situation, e.g. after running an `eval` statement, or after a custom syntax statement, /// subsequent offsets may become mis-aligned. @@ -673,20 +758,8 @@ pub struct EvalState { /// Level of the current scope. The global (root) level is zero, a new block (or function call) /// is one level higher, and so on. pub scope_level: usize, - /// Number of operations performed. - pub num_operations: u64, - /// Number of modules loaded. - pub num_modules: usize, /// Stack of function resolution caches. fn_resolution_caches: StaticVec, - /// Embedded module resolver. - #[cfg(not(feature = "no_module"))] - pub embedded_module_resolver: Option>, - /// Function call hashes to FN_IDX_GET and FN_IDX_SET. - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - fn_hash_indexing: (u64, u64), - /// Cache of globally-defined constants. - pub global_constants: BTreeMap, } impl EvalState { @@ -695,17 +768,9 @@ impl EvalState { #[must_use] pub fn new() -> Self { Self { - source: None, always_search_scope: false, scope_level: 0, - num_operations: 0, - num_modules: 0, - #[cfg(not(feature = "no_module"))] - embedded_module_resolver: None, fn_resolution_caches: StaticVec::new(), - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - fn_hash_indexing: (0, 0), - global_constants: BTreeMap::new(), } } /// Is the state currently at global (root) level? @@ -743,32 +808,6 @@ impl EvalState { .pop() .expect("at least one function resolution cache"); } - /// Get the pre-calculated index getter hash. - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - #[must_use] - pub fn fn_hash_idx_get(&mut self) -> u64 { - if self.fn_hash_indexing != (0, 0) { - self.fn_hash_indexing.0 - } else { - let n1 = crate::calc_fn_hash(FN_IDX_GET, 2); - let n2 = crate::calc_fn_hash(FN_IDX_SET, 3); - self.fn_hash_indexing = (n1, n2); - n1 - } - } - /// Get the pre-calculated index setter hash. - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - #[must_use] - pub fn fn_hash_idx_set(&mut self) -> u64 { - if self.fn_hash_indexing != (0, 0) { - self.fn_hash_indexing.1 - } else { - let n1 = crate::calc_fn_hash(FN_IDX_GET, 2); - let n2 = crate::calc_fn_hash(FN_IDX_SET, 3); - self.fn_hash_indexing = (n1, n2); - n2 - } - } } /// _(internals)_ A type containing all the limits imposed by the [`Engine`]. @@ -861,7 +900,7 @@ impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, 'pt> { #[inline(always)] #[must_use] pub fn source(&self) -> Option<&str> { - self.state.source.as_ref().map(|s| s.as_str()) + self.mods.source.as_ref().map(|s| s.as_str()) } /// The current [`Scope`]. #[inline(always)] @@ -1188,43 +1227,48 @@ impl Engine { target.set_access_mode(AccessMode::ReadOnly); Ok((target.into(), *_var_pos)) } - Err(mut err) => { - match *err { - EvalAltResult::ErrorVariableNotFound(ref mut err_name, _) => { - *err_name = format!( + Err(err) => Err(match *err { + EvalAltResult::ErrorVariableNotFound(_, _) => { + EvalAltResult::ErrorVariableNotFound( + format!( "{}{}{}", namespace, Token::DoubleColon.literal_syntax(), var_name - ); - } - _ => (), + ), + namespace[0].pos, + ) + .into() } - Err(err.fill_position(*_var_pos)) - } + _ => err.fill_position(*_var_pos), + }), }; } #[cfg(not(feature = "no_function"))] if namespace.len() == 1 && namespace[0].name == KEYWORD_GLOBAL { // global::VARIABLE - return if let Some(value) = state.global_constants.get_mut(var_name) { - let mut target: Target = value.clone().into(); - // Module variables are constant - target.set_access_mode(AccessMode::ReadOnly); - Ok((target.into(), *_var_pos)) - } else { - Err(EvalAltResult::ErrorVariableNotFound( - format!( - "{}{}{}", - namespace, - Token::DoubleColon.literal_syntax(), - var_name - ), - namespace[0].pos, - ) - .into()) - }; + let global_constants = mods.global_constants_mut(); + + if let Some(mut guard) = global_constants { + if let Some(value) = guard.get_mut(var_name) { + let mut target: Target = value.clone().into(); + // Module variables are constant + target.set_access_mode(AccessMode::ReadOnly); + return Ok((target.into(), *_var_pos)); + } + } + + return Err(EvalAltResult::ErrorVariableNotFound( + format!( + "{}{}{}", + namespace, + Token::DoubleColon.literal_syntax(), + var_name + ), + namespace[0].pos, + ) + .into()); } Err( @@ -1374,7 +1418,7 @@ impl Engine { if let Some(mut new_val) = try_setter { // Try to call index setter if value is changed - let hash_set = FnCallHashes::from_native(state.fn_hash_idx_set()); + let hash_set = FnCallHashes::from_native(mods.fn_hash_idx_set()); let args = &mut [target, &mut idx_val_for_setter, &mut new_val]; if let Err(err) = self.exec_fn_call( @@ -1421,7 +1465,7 @@ impl Engine { if let Some(mut new_val) = try_setter { // Try to call index setter - let hash_set = FnCallHashes::from_native(state.fn_hash_idx_set()); + let hash_set = FnCallHashes::from_native(mods.fn_hash_idx_set()); let args = &mut [target, &mut idx_val_for_setter, &mut new_val]; self.exec_fn_call( @@ -1552,7 +1596,7 @@ impl Engine { // Try an indexer if property does not exist EvalAltResult::ErrorDotExpr(_, _) => { let args = &mut [target, &mut name.into(), &mut new_val]; - let hash_set = FnCallHashes::from_native(state.fn_hash_idx_set()); + let hash_set = FnCallHashes::from_native(mods.fn_hash_idx_set()); let pos = Position::NONE; self.exec_fn_call( @@ -1711,7 +1755,7 @@ impl Engine { let args = &mut [target.as_mut(), &mut name.into(), val]; let hash_set = FnCallHashes::from_native( - state.fn_hash_idx_set(), + mods.fn_hash_idx_set(), ); self.exec_fn_call( mods, state, lib, FN_IDX_SET, hash_set, args, @@ -1800,7 +1844,7 @@ impl Engine { // id.??? or id[???] Expr::Variable(_, var_pos, x) => { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, *var_pos)?; + self.inc_operations(&mut mods.num_operations, *var_pos)?; let (mut target, _) = self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?; @@ -1851,7 +1895,7 @@ impl Engine { level: usize, ) -> Result<(), Box> { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, expr.position())?; + self.inc_operations(&mut mods.num_operations, expr.position())?; let _parent_chain_type = parent_chain_type; @@ -1977,7 +2021,7 @@ impl Engine { level: usize, ) -> Result, Box> { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, Position::NONE)?; + self.inc_operations(&mut mods.num_operations, Position::NONE)?; let mut idx = idx; let _add_if_not_found = add_if_not_found; @@ -2117,7 +2161,7 @@ impl Engine { _ if use_indexers => { let args = &mut [target, &mut idx]; - let hash_get = FnCallHashes::from_native(state.fn_hash_idx_get()); + let hash_get = FnCallHashes::from_native(mods.fn_hash_idx_get()); let idx_pos = Position::NONE; self.exec_fn_call( @@ -2150,7 +2194,7 @@ impl Engine { level: usize, ) -> RhaiResult { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, expr.position())?; + self.inc_operations(&mut mods.num_operations, expr.position())?; let result = match expr { Expr::DynamicConstant(x, _) => Ok(x.as_ref().clone()), @@ -2502,7 +2546,7 @@ impl Engine { level: usize, ) -> RhaiResult { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, stmt.position())?; + self.inc_operations(&mut mods.num_operations, stmt.position())?; let result = match stmt { // No-op @@ -2535,7 +2579,7 @@ impl Engine { } #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, pos)?; + self.inc_operations(&mut mods.num_operations, pos)?; self.eval_op_assignment( mods, @@ -2688,7 +2732,7 @@ impl Engine { } } else { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, body.position())?; + self.inc_operations(&mut mods.num_operations, body.position())?; } }, @@ -2812,7 +2856,7 @@ impl Engine { } #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, statements.position())?; + self.inc_operations(&mut mods.num_operations, statements.position())?; if statements.is_empty() { continue; @@ -2906,7 +2950,7 @@ impl Engine { err_map.insert("message".into(), err.to_string().into()); - if let Some(ref source) = state.source { + if let Some(ref source) = mods.source { err_map.insert("source".into(), source.as_str().into()); } @@ -3002,8 +3046,9 @@ impl Engine { let (var_name, _alias): (Cow<'_, str>, _) = if state.is_global() { #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_module"))] if entry_type == AccessMode::ReadOnly && lib.iter().any(|&m| !m.is_empty()) { - state.global_constants.insert(name.clone(), value.clone()); + mods.set_global_constant(name, value.clone()); } ( @@ -3029,7 +3074,7 @@ impl Engine { Stmt::Import(expr, export, _pos) => { // Guard against too many modules #[cfg(not(feature = "unchecked"))] - if state.num_modules >= self.max_modules() { + if mods.num_modules >= self.max_modules() { return Err(EvalAltResult::ErrorTooManyModules(*_pos).into()); } @@ -3039,10 +3084,10 @@ impl Engine { { use crate::ModuleResolver; - let source = state.source.as_ref().map(|s| s.as_str()); + let source = mods.source.as_ref().map(|s| s.as_str()); let path_pos = expr.position(); - let module = state + let module = mods .embedded_module_resolver .as_ref() .and_then(|r| match r.resolve(self, source, &path, path_pos) { @@ -3076,7 +3121,7 @@ impl Engine { } } - state.num_modules += 1; + mods.num_modules += 1; Ok(Dynamic::UNIT) } else { @@ -3256,19 +3301,19 @@ impl Engine { #[cfg(not(feature = "unchecked"))] pub(crate) fn inc_operations( &self, - state: &mut EvalState, + num_operations: &mut u64, pos: Position, ) -> Result<(), Box> { - state.num_operations += 1; + *num_operations += 1; // Guard against too many operations - if self.max_operations() > 0 && state.num_operations > self.max_operations() { + if self.max_operations() > 0 && *num_operations > self.max_operations() { return Err(EvalAltResult::ErrorTooManyOperations(pos).into()); } // Report progress - only in steps if let Some(ref progress) = self.progress { - if let Some(token) = progress(state.num_operations) { + if let Some(token) = progress(*num_operations) { // Terminate script if progress returns a termination token return Err(EvalAltResult::ErrorTerminated(token, pos).into()); } diff --git a/src/engine_api.rs b/src/engine_api.rs index 664d0387..8bf946df 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -1739,10 +1739,12 @@ impl Engine { level: usize, ) -> RhaiResult { let mut state = EvalState::new(); - state.source = ast.source_raw().cloned(); + if ast.source_raw().is_some() { + mods.source = ast.source_raw().cloned(); + } #[cfg(not(feature = "no_module"))] { - state.embedded_module_resolver = ast.resolver(); + mods.embedded_module_resolver = ast.resolver(); } let statements = ast.statements(); @@ -1817,10 +1819,12 @@ impl Engine { ) -> Result<(), Box> { let mods = &mut Imports::new(); let mut state = EvalState::new(); - state.source = ast.source_raw().cloned(); + if ast.source_raw().is_some() { + mods.source = ast.source_raw().cloned(); + } #[cfg(not(feature = "no_module"))] { - state.embedded_module_resolver = ast.resolver(); + mods.embedded_module_resolver = ast.resolver(); } let statements = ast.statements(); diff --git a/src/fn_call.rs b/src/fn_call.rs index 84b1016a..50279236 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -179,7 +179,7 @@ impl Engine { args: Option<&mut FnCallArgs>, allow_dynamic: bool, is_op_assignment: bool, - ) -> &'s Option> { + ) -> Option<&'s FnResolutionCacheEntry> { let mut hash = args.as_ref().map_or(hash_script, |args| { combine_hashes( hash_script, @@ -187,7 +187,7 @@ impl Engine { ) }); - &*state + let result = state .fn_resolution_cache_mut() .entry(hash) .or_insert_with(|| { @@ -295,7 +295,9 @@ impl Engine { } } } - }) + }); + + result.as_ref().map(Box::as_ref) } /// Call a native Rust function registered with the [`Engine`]. @@ -307,7 +309,7 @@ impl Engine { /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! pub(crate) fn call_native_fn( &self, - mods: &Imports, + mods: &mut Imports, state: &mut EvalState, lib: &[&Module], name: &str, @@ -318,15 +320,14 @@ impl Engine { pos: Position, ) -> Result<(Dynamic, bool), Box> { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, pos)?; + self.inc_operations(&mut mods.num_operations, pos)?; - let state_source = state.source.clone(); + let parent_source = mods.source.clone(); // Check if function access already in the cache let func = self.resolve_fn(mods, state, lib, name, hash, Some(args), true, is_op_assign); - if let Some(f) = func { - let FnResolutionCacheEntry { func, source } = f.as_ref(); + if let Some(FnResolutionCacheEntry { func, source }) = func { assert!(func.is_native()); // Calling pure function but the first argument is a reference? @@ -342,18 +343,17 @@ impl Engine { // Run external function let source = source .as_ref() - .or_else(|| state_source.as_ref()) + .or_else(|| parent_source.as_ref()) .map(|s| s.as_str()); + let context = (self, name, source, &*mods, lib, pos).into(); + let result = if func.is_plugin_fn() { - let context = (self, name, source, mods, lib, pos).into(); func.get_plugin_fn() .expect("plugin function") .call(context, args) } else { - let func = func.get_native_fn().expect("native function"); - let context = (self, name, source, mods, lib, pos).into(); - func(context, args) + func.get_native_fn().expect("native function")(context, args) }; // Restore the original reference @@ -388,7 +388,7 @@ impl Engine { pos, ) })?; - let source = state.source.as_ref().map(|s| s.as_str()); + let source = mods.source.as_ref().map(|s| s.as_str()); (debug(&text, source, pos).into(), false) } else { (Dynamic::UNIT, false) @@ -498,7 +498,7 @@ impl Engine { fn make_error( name: String, fn_def: &crate::ast::ScriptFnDef, - state: &EvalState, + mods: &Imports, err: Box, pos: Position, ) -> RhaiResult { @@ -508,7 +508,7 @@ impl Engine { .lib .as_ref() .and_then(|m| m.id().map(|id| id.to_string())) - .or_else(|| state.source.as_ref().map(|s| s.to_string())) + .or_else(|| mods.source.as_ref().map(|s| s.to_string())) .unwrap_or_default(), err, pos, @@ -517,7 +517,7 @@ impl Engine { } #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, pos)?; + self.inc_operations(&mut mods.num_operations, pos)?; if fn_def.body.is_empty() { return Ok(Dynamic::UNIT); @@ -569,7 +569,6 @@ impl Engine { // Evaluate the function let body = &fn_def.body; - let result = self .eval_stmt_block(scope, mods, state, unified_lib, this_ptr, body, true, level) .or_else(|err| match *err { @@ -583,7 +582,7 @@ impl Engine { format!("{} @ '{}' < {}", name, src, fn_def.name) }; - make_error(fn_name, fn_def, state, err, pos) + make_error(fn_name, fn_def, mods, err, pos) } // System errors are passed straight-through mut err if err.is_system_exception() => { @@ -591,7 +590,7 @@ impl Engine { Err(err.into()) } // Other errors are wrapped in `ErrorInFunctionCall` - _ => make_error(fn_def.name.to_string(), fn_def, state, err, pos), + _ => make_error(fn_def.name.to_string(), fn_def, mods, err, pos), }); // Remove all local variables @@ -723,11 +722,10 @@ impl Engine { let hash_script = hashes.script; #[cfg(not(feature = "no_function"))] - if let Some(f) = hash_script.and_then(|hash| { + if let Some(FnResolutionCacheEntry { func, source }) = hash_script.and_then(|hash| { self.resolve_fn(mods, state, lib, fn_name, hash, None, false, false) - .clone() + .cloned() }) { - let FnResolutionCacheEntry { func, source } = *f; // Script function call assert!(func.is_script()); @@ -759,8 +757,8 @@ impl Engine { .split_first_mut() .expect("method call has first parameter"); - let orig_source = state.source.take(); - state.source = source; + let orig_source = mods.source.take(); + mods.source = source; let level = _level + 1; @@ -777,7 +775,7 @@ impl Engine { ); // Restore the original source - state.source = orig_source; + mods.source = orig_source; result? } else { @@ -792,8 +790,8 @@ impl Engine { .change_first_arg_to_copy(args); } - let orig_source = state.source.take(); - state.source = source; + let orig_source = mods.source.take(); + mods.source = source; let level = _level + 1; @@ -801,7 +799,7 @@ impl Engine { self.call_script_fn(scope, mods, state, lib, &mut None, func, args, pos, level); // Restore the original source - state.source = orig_source; + mods.source = orig_source; // Restore the original reference if let Some(bk) = backup { @@ -848,14 +846,13 @@ impl Engine { &self, scope: &mut Scope, mods: &mut Imports, - state: &mut EvalState, lib: &[&Module], script: &str, _pos: Position, level: usize, ) -> RhaiResult { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, _pos)?; + self.inc_operations(&mut mods.num_operations, _pos)?; let script = script.trim(); if script.is_empty() { @@ -882,16 +879,7 @@ impl Engine { } // Evaluate the AST - let mut new_state = EvalState::new(); - new_state.source = state.source.clone(); - new_state.num_operations = state.num_operations; - - let result = - self.eval_global_statements(scope, mods, &mut new_state, statements, lib, level); - - state.num_operations = new_state.num_operations; - - result + self.eval_global_statements(scope, mods, &mut EvalState::new(), statements, lib, level) } /// Call a dot method. @@ -1230,7 +1218,7 @@ impl Engine { .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; let result = - self.eval_script_expr_in_place(scope, mods, state, lib, script, pos, level + 1); + self.eval_script_expr_in_place(scope, mods, lib, script, pos, level + 1); // IMPORTANT! If the eval defines new variables in the current scope, // all variable offsets from this point on will be mis-aligned. @@ -1241,8 +1229,7 @@ impl Engine { return result.map_err(|err| { EvalAltResult::ErrorInFunctionCall( KEYWORD_EVAL.to_string(), - state - .source + mods.source .as_ref() .map(Identifier::to_string) .unwrap_or_default(), @@ -1289,7 +1276,7 @@ impl Engine { } #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, _pos)?; + self.inc_operations(&mut mods.num_operations, _pos)?; #[cfg(not(feature = "no_closure"))] let target_is_shared = target.is_shared(); @@ -1369,7 +1356,7 @@ impl Engine { self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?; #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, _pos)?; + self.inc_operations(&mut mods.num_operations, _pos)?; #[cfg(not(feature = "no_closure"))] let target_is_shared = target.is_shared(); @@ -1410,7 +1397,7 @@ impl Engine { // Then search in Rust functions None => { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, pos)?; + self.inc_operations(&mut mods.num_operations, pos)?; let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id())); let hash_qualified_fn = combine_hashes(hash, hash_params); @@ -1439,7 +1426,7 @@ impl Engine { let new_scope = &mut Scope::new(); let mut source = module.id_raw().cloned(); - mem::swap(&mut state.source, &mut source); + mem::swap(&mut mods.source, &mut source); let level = level + 1; @@ -1447,7 +1434,7 @@ impl Engine { new_scope, mods, state, lib, &mut None, fn_def, &mut args, pos, level, ); - state.source = source; + mods.source = source; result } diff --git a/src/optimize.rs b/src/optimize.rs index 157d3365..899ce282 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -133,7 +133,7 @@ impl<'a> OptimizerState<'a> { ) -> Option { self.engine .call_native_fn( - &Imports::new(), + &mut Imports::new(), &mut EvalState::new(), self.lib, fn_name, diff --git a/tests/functions.rs b/tests/functions.rs index aa7ecc57..e0b4af5a 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -96,21 +96,6 @@ fn test_functions_global_module() -> Result<(), Box> { if matches!(&*err, EvalAltResult::ErrorVariableNotFound(v, _) if v == "global::ANSWER") )); - let mut module = Module::new(); - module.set_var("ANSWER", 123 as INT); - engine.register_static_module("global", module.into()); - - assert_eq!( - engine.eval::( - " - const ANSWER = 42; - fn foo() { global::ANSWER } - foo() - " - )?, - 123 - ); - engine.register_result_fn( "do_stuff", |context: NativeCallContext, callback: rhai::FnPtr| { @@ -129,5 +114,32 @@ fn test_functions_global_module() -> Result<(), Box> { if matches!(&*err, EvalAltResult::ErrorVariableNotFound(v, _) if v == "global::LOCAL_VALUE") )); + #[cfg(not(feature = "no_closure"))] + assert_eq!( + engine.eval::( + " + const GLOBAL_VALUE = 42; + do_stuff(|| global::GLOBAL_VALUE); + " + )?, + 42 + ); + + // Override global + let mut module = Module::new(); + module.set_var("ANSWER", 123 as INT); + engine.register_static_module("global", module.into()); + + assert_eq!( + engine.eval::( + " + const ANSWER = 42; + fn foo() { global::ANSWER } + foo() + " + )?, + 123 + ); + Ok(()) }