diff --git a/CHANGELOG.md b/CHANGELOG.md index 447568f1..6adfdff0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ Enhancements * Originally, the debugger's custom state uses the same state as `EvalState::tag()` (which is the same as `NativeCallContext::tag()`). It is now split into its own variable accessible under `Debugger::state()`. * Non-borrowed string keys can now be deserialized for object maps via `serde`. * `Scope::get` is added to get a reference to a variable's value. +* Variable resolvers can now return a _shared_ value which can be mutated. Version 1.7.0 diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 0ae9c2fb..b05a01a3 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -250,7 +250,15 @@ impl Engine { let var_name = lhs.get_variable_name(false).expect("`Expr::Variable`"); - if !lhs_ptr.is_ref() { + #[cfg(not(feature = "no_closure"))] + // Temp results from expressions are flattened so should never be shared. + // A shared value may be provided by a variable resolver, however. + let is_temp_result = !lhs_ptr.is_ref() && !lhs_ptr.is_shared(); + #[cfg(feature = "no_closure")] + let is_temp_result = !lhs_ptr.is_ref(); + + // Cannot assign to temp result from expression + if is_temp_result { return Err( ERR::ErrorAssignmentToConstant(var_name.to_string(), pos).into() ); @@ -950,7 +958,7 @@ impl Engine { if !export.is_empty() { if !module.is_indexed() { // Index the module (making a clone copy if necessary) if it is not indexed - let mut m = crate::func::native::shared_take_or_clone(module); + let mut m = crate::func::shared_take_or_clone(module); m.build_index(); global.push_import(export.name.clone(), m); } else { diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 7f21bd85..9740d7ba 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -1003,18 +1003,25 @@ impl Dynamic { /// /// Constant [`Dynamic`] values are read-only. /// - /// If a [`&mut Dynamic`][Dynamic] to such a constant is passed to a Rust function, the function - /// can use this information to return an error of - /// [`ErrorAssignmentToConstant`][crate::EvalAltResult::ErrorAssignmentToConstant] if its value - /// is going to be modified. + /// # Usage /// - /// This safe-guards constant values from being modified from within Rust functions. + /// If a [`&mut Dynamic`][Dynamic] to such a constant is passed to a Rust function, the function + /// can use this information to return the error + /// [`ErrorAssignmentToConstant`][crate::EvalAltResult::ErrorAssignmentToConstant] if its value + /// will be modified. + /// + /// This safe-guards constant values from being modified within Rust functions. + /// + /// # Shared Values + /// + /// If a [`Dynamic`] holds a _shared_ value, then it is read-only only if the shared value + /// itself is read-only. #[must_use] pub fn is_read_only(&self) -> bool { #[cfg(not(feature = "no_closure"))] match self.0 { - Union::Shared(.., ReadOnly) => return true, - + // Shared values do not consider the current access mode + //Union::Shared(.., ReadOnly) => return true, Union::Shared(ref cell, ..) => { return match locked_read(cell).access_mode() { ReadWrite => false, diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 014ec42b..5c6cc29c 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, ParseErrorType, Position, Scope, INT}; +use rhai::{Dynamic, Engine, EvalAltResult, ParseErrorType, Position, Scope, INT}; #[test] fn test_var_scope() -> Result<(), Box> { @@ -162,9 +162,16 @@ fn test_var_resolver() -> Result<(), Box> { scope.push("chameleon", 123 as INT); scope.push("DO_NOT_USE", 999 as INT); - engine.on_var(|name, _, context| { + #[cfg(not(feature = "no_closure"))] + let mut base = Dynamic::ONE.into_shared(); + #[cfg(not(feature = "no_closure"))] + let shared = base.clone(); + + engine.on_var(move |name, _, context| { match name { "MYSTIC_NUMBER" => Ok(Some((42 as INT).into())), + #[cfg(not(feature = "no_closure"))] + "HELLO" => Ok(Some(shared.clone())), // Override a variable - make it not found even if it exists! "DO_NOT_USE" => { Err(EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::NONE).into()) @@ -186,6 +193,18 @@ fn test_var_resolver() -> Result<(), Box> { engine.eval_with_scope::(&mut scope, "MYSTIC_NUMBER")?, 42 ); + + #[cfg(not(feature = "no_closure"))] + { + assert_eq!(engine.eval_with_scope::(&mut scope, "HELLO")?, 1); + *base.write_lock::().unwrap() = 42; + assert_eq!(engine.eval_with_scope::(&mut scope, "HELLO")?, 42); + assert_eq!( + engine.eval_with_scope::(&mut scope, "HELLO = HELLO * 2; HELLO")?, + 84 + ); + } + assert_eq!(engine.eval_with_scope::(&mut scope, "chameleon")?, 1); assert!( matches!(*engine.eval_with_scope::(&mut scope, "DO_NOT_USE").expect_err("should error"),