diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c73eda46..7b2e04b7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ Rhai Release Notes ================== +Version 1.18.1 +============== + +Bug fixes +--------- + +* Variable resolver now correctly resolves variables that are captured in a closure. + + Version 1.18.0 ============== diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 874cccb58..6ad04e0fd 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -8,6 +8,7 @@ use crate::func::{get_builtin_op_assignment_fn, get_hasher}; use crate::tokenizer::Token; use crate::types::dynamic::{AccessMode, Union}; use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, VarDefInfo, ERR, INT}; +use core::num::NonZeroUsize; use std::hash::{Hash, Hasher}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -971,6 +972,29 @@ impl Engine { #[cfg(not(feature = "no_closure"))] Stmt::Share(x) => { for (var, index) in &**x { + // Check the variable resolver, if any + if let Some(ref resolve_var) = self.resolve_var { + let orig_scope_len = scope.len(); + + let context = + EvalContext::new(self, global, caches, scope, this_ptr.as_deref_mut()); + let resolved_var = + resolve_var(&var.name, index.map_or(0, NonZeroUsize::get), context); + + if orig_scope_len != scope.len() { + // The scope is changed, always search from now on + global.always_search_scope = true; + } + + match resolved_var { + // If resolved to a variable, skip it (because we cannot make it shared) + Ok(Some(_)) => continue, + Ok(None) => (), + Err(err) => return Err(err.fill_position(var.pos)), + } + } + + // Search the scope let index = index .map(|n| scope.len() - n.get()) .or_else(|| scope.search(&var.name)) diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 622948665..2d83387e1 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -324,7 +324,7 @@ fn test_scope_eval() { } #[test] -fn test_var_resolver() { +fn test_var_resolver1() { let mut engine = Engine::new(); let mut scope = Scope::new(); @@ -392,6 +392,22 @@ fn test_var_resolver() { EvalAltResult::ErrorVariableNotFound(n, ..) if n == "DO_NOT_USE")); } +#[cfg(not(feature = "no_closure"))] +#[cfg(not(feature = "no_function"))] +#[cfg(not(feature = "no_object"))] +#[test] +fn test_var_resolver2() { + let mut engine = Engine::new(); + let shared_state: INT = 42; + + #[allow(deprecated)] + engine.on_var(move |name, _, _| if name == "state" { Ok(Some(Dynamic::from(shared_state))) } else { Ok(None) }); + + assert_eq!(engine.eval::("state").unwrap(), 42); + assert_eq!(engine.eval::("fn f() { state }; f()").unwrap(), 42); + assert_eq!(engine.eval::("let f = || state; f.call()").unwrap(), 42); +} + #[test] fn test_var_def_filter() { let mut engine = Engine::new();