diff --git a/CHANGELOG.md b/CHANGELOG.md index f00032aa..670fde5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ Rhai Release Notes ================== +Version 1.7.0 +============= + +Bug fixes +--------- + +* Compound assignments now work properly with indexers. + + Version 1.6.1 ============= @@ -9,6 +18,7 @@ Bug fixes * Functions with `Dynamic` parameters now work in qualified calls from `import`ed modules. * `rhai-repl` now compiles with the new patch version of `rustyline`. +* `rhai_codegen` dependency is now explicitly `1.4` or higher. Script-breaking changes ----------------------- diff --git a/Cargo.toml b/Cargo.toml index 47de2213..c103eabf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [".", "codegen"] [package] name = "rhai" -version = "1.6.1" +version = "1.7.0" rust-version = "1.57" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] diff --git a/src/api/deprecated.rs b/src/api/deprecated.rs index 62d7b52c..6d9d5463 100644 --- a/src/api/deprecated.rs +++ b/src/api/deprecated.rs @@ -7,6 +7,10 @@ use crate::{ #[cfg(feature = "no_std")] use std::prelude::v1::*; +#[cfg(not(feature = "no_std"))] +#[cfg(not(target_family = "wasm"))] +use std::path::PathBuf; + impl Engine { /// Evaluate a file, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. @@ -17,12 +21,12 @@ impl Engine { /// /// This method is deprecated. Use [`run_file`][Engine::run_file] instead. /// - /// This method will be removed in the next major version. + /// This method will be removed in the next majocd cr version. #[deprecated(since = "1.1.0", note = "use `run_file` instead")] #[cfg(not(feature = "no_std"))] #[cfg(not(target_family = "wasm"))] #[inline(always)] - pub fn consume_file(&self, path: std::path::PathBuf) -> RhaiResultOf<()> { + pub fn consume_file(&self, path: PathBuf) -> RhaiResultOf<()> { self.run_file(path) } @@ -40,11 +44,7 @@ impl Engine { #[cfg(not(feature = "no_std"))] #[cfg(not(target_family = "wasm"))] #[inline(always)] - pub fn consume_file_with_scope( - &self, - scope: &mut Scope, - path: std::path::PathBuf, - ) -> RhaiResultOf<()> { + pub fn consume_file_with_scope(&self, scope: &mut Scope, path: PathBuf) -> RhaiResultOf<()> { self.run_file_with_scope(scope, path) } diff --git a/src/api/files.rs b/src/api/files.rs index 1cfd82a9..28272345 100644 --- a/src/api/files.rs +++ b/src/api/files.rs @@ -6,13 +6,12 @@ use crate::types::dynamic::Variant; use crate::{Engine, RhaiResultOf, Scope, AST, ERR}; #[cfg(feature = "no_std")] use std::prelude::v1::*; +use std::{fs::File, io::Read, path::PathBuf}; impl Engine { /// Read the contents of a file into a string. - fn read_file(path: std::path::PathBuf) -> RhaiResultOf { - use std::io::Read; - - let mut f = std::fs::File::open(path.clone()).map_err(|err| { + fn read_file(path: PathBuf) -> RhaiResultOf { + let mut f = File::open(path.clone()).map_err(|err| { ERR::ErrorSystem( format!("Cannot open script file '{}'", path.to_string_lossy()), err.into(), @@ -62,7 +61,7 @@ impl Engine { /// # } /// ``` #[inline(always)] - pub fn compile_file(&self, path: std::path::PathBuf) -> RhaiResultOf { + pub fn compile_file(&self, path: PathBuf) -> RhaiResultOf { self.compile_file_with_scope(&Scope::new(), path) } /// Compile a script file into an [`AST`] using own scope, which can be used later for evaluation. @@ -100,11 +99,7 @@ impl Engine { /// # } /// ``` #[inline] - pub fn compile_file_with_scope( - &self, - scope: &Scope, - path: std::path::PathBuf, - ) -> RhaiResultOf { + pub fn compile_file_with_scope(&self, scope: &Scope, path: PathBuf) -> RhaiResultOf { Self::read_file(path).and_then(|contents| Ok(self.compile_with_scope(scope, &contents)?)) } /// Evaluate a script file. @@ -125,7 +120,7 @@ impl Engine { /// # } /// ``` #[inline] - pub fn eval_file(&self, path: std::path::PathBuf) -> RhaiResultOf { + pub fn eval_file(&self, path: PathBuf) -> RhaiResultOf { Self::read_file(path).and_then(|contents| self.eval::(&contents)) } /// Evaluate a script file with own scope. @@ -160,7 +155,7 @@ impl Engine { pub fn eval_file_with_scope( &self, scope: &mut Scope, - path: std::path::PathBuf, + path: PathBuf, ) -> RhaiResultOf { Self::read_file(path).and_then(|contents| self.eval_with_scope(scope, &contents)) } @@ -168,7 +163,7 @@ impl Engine { /// /// Not available under `no_std` or `WASM`. #[inline] - pub fn run_file(&self, path: std::path::PathBuf) -> RhaiResultOf<()> { + pub fn run_file(&self, path: PathBuf) -> RhaiResultOf<()> { Self::read_file(path).and_then(|contents| self.run(&contents)) } /// Evaluate a file with own scope, returning any error (if any). @@ -182,11 +177,7 @@ impl Engine { /// /// This allows functions to be optimized based on dynamic global constants. #[inline] - pub fn run_file_with_scope( - &self, - scope: &mut Scope, - path: std::path::PathBuf, - ) -> RhaiResultOf<()> { + pub fn run_file_with_scope(&self, scope: &mut Scope, path: PathBuf) -> RhaiResultOf<()> { Self::read_file(path).and_then(|contents| self.run_with_scope(scope, &contents)) } } diff --git a/src/api/limits.rs b/src/api/limits.rs index 876a5f1b..18eb17f1 100644 --- a/src/api/limits.rs +++ b/src/api/limits.rs @@ -27,7 +27,7 @@ pub struct Limits { #[cfg(not(feature = "no_function"))] pub max_function_expr_depth: Option, /// Maximum number of operations allowed to run. - pub max_operations: Option, + pub max_operations: Option, /// Maximum number of [modules][crate::Module] allowed to load. /// /// Set to zero to effectively disable loading any [module][crate::Module]. diff --git a/src/ast/ast.rs b/src/ast/ast.rs index 7e6f56c7..cd9f235f 100644 --- a/src/ast/ast.rs +++ b/src/ast/ast.rs @@ -8,6 +8,7 @@ use std::{ fmt, hash::Hash, ops::{Add, AddAssign}, + ptr, }; /// Compiled AST (abstract syntax tree) of a Rhai script. @@ -870,8 +871,8 @@ impl PartialEq for ASTNode<'_> { #[inline(always)] fn eq(&self, other: &Self) -> bool { match (self, other) { - (Self::Stmt(x), Self::Stmt(y)) => std::ptr::eq(*x, *y), - (Self::Expr(x), Self::Expr(y)) => std::ptr::eq(*x, *y), + (Self::Stmt(x), Self::Stmt(y)) => ptr::eq(*x, *y), + (Self::Expr(x), Self::Expr(y)) => ptr::eq(*x, *y), _ => false, } } diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 6bad94a7..0f8a1562 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -17,7 +17,11 @@ use std::{ }; #[cfg(not(feature = "no_float"))] -use std::str::FromStr; +use std::{ + hash::Hasher, + ops::{Deref, DerefMut}, + str::FromStr, +}; #[cfg(not(feature = "no_float"))] use num_traits::float::FloatCore as Float; @@ -230,7 +234,7 @@ pub struct FloatWrapper(F); #[cfg(not(feature = "no_float"))] impl Hash for FloatWrapper { #[inline(always)] - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut H) { self.0.to_ne_bytes().hash(state); } } @@ -252,7 +256,7 @@ impl AsMut for FloatWrapper { } #[cfg(not(feature = "no_float"))] -impl std::ops::Deref for FloatWrapper { +impl Deref for FloatWrapper { type Target = F; #[inline(always)] @@ -262,7 +266,7 @@ impl std::ops::Deref for FloatWrapper { } #[cfg(not(feature = "no_float"))] -impl std::ops::DerefMut for FloatWrapper { +impl DerefMut for FloatWrapper { #[inline(always)] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index bcedd846..56d3aba3 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -201,12 +201,12 @@ impl Engine { self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?; let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); - let mut idx_val_for_setter = idx_val.clone(); + let mut idx_val2 = idx_val.clone(); let try_setter = match self.get_indexed_mut( global, state, lib, target, idx_val, pos, true, false, level, ) { - // Indexed value is a reference - update directly + // Indexed value is not a temp value - update directly Ok(ref mut obj_ptr) => { self.eval_op_assignment( global, state, lib, op_info, op_pos, obj_ptr, root, new_val, @@ -217,7 +217,7 @@ impl Engine { self.check_data_size(obj_ptr, new_pos)?; None } - // Can't index - try to call an index setter + // Indexed value cannot be referenced - use indexer #[cfg(not(feature = "no_index"))] Err(err) if matches!(*err, ERR::ErrorIndexingType(..)) => Some(new_val), // Any other error @@ -225,8 +225,30 @@ impl Engine { }; if let Some(mut new_val) = try_setter { + let idx = &mut idx_val2; + + // Is this an op-assignment? + if op_info.is_some() { + let idx = &mut idx.clone(); + // Call the index getter to get the current value + if let Ok(val) = + self.call_indexer_get(global, state, lib, target, idx, level) + { + let mut res = val.into(); + // Run the op-assignment + self.eval_op_assignment( + global, state, lib, op_info, op_pos, &mut res, root, + new_val, level, + ) + .map_err(|err| err.fill_position(new_pos))?; + // Replace new value + new_val = res.take_or_clone(); + #[cfg(not(feature = "unchecked"))] + self.check_data_size(&new_val, new_pos)?; + } + } + // Try to call index setter - let idx = &mut idx_val_for_setter; let new_val = &mut new_val; self.call_indexer_set( global, state, lib, target, idx, new_val, is_ref_mut, level, @@ -333,6 +355,7 @@ impl Engine { self.call_indexer_get( global, state, lib, target, &mut prop, level, ) + .map(|r| (r, false)) .map_err(|e| { match *e { ERR::ErrorIndexingType(..) => err, @@ -398,6 +421,7 @@ impl Engine { self.call_indexer_get( global, state, lib, target, &mut prop, level, ) + .map(|r| (r, false)) .map_err(|e| match *e { ERR::ErrorIndexingType(..) => err, _ => e, @@ -494,6 +518,7 @@ impl Engine { self.call_indexer_get( global, state, lib, target, &mut prop, level, ) + .map(|r| (r, false)) .map_err( |e| match *e { ERR::ErrorIndexingType(..) => err, @@ -803,7 +828,7 @@ impl Engine { target: &mut Dynamic, idx: &mut Dynamic, level: usize, - ) -> RhaiResultOf<(Dynamic, bool)> { + ) -> RhaiResultOf { let args = &mut [target, idx]; let hash_get = crate::ast::FnCallHashes::from_native(global.hash_idx_get()); let fn_name = crate::engine::FN_IDX_GET; @@ -812,6 +837,7 @@ impl Engine { self.exec_fn_call( None, global, state, lib, fn_name, hash_get, args, true, true, pos, level, ) + .map(|(r, ..)| r) } /// Call a set indexer. @@ -1039,7 +1065,7 @@ impl Engine { _ if use_indexers => self .call_indexer_get(global, state, lib, target, &mut idx, level) - .map(|(v, ..)| v.into()), + .map(Into::into), _ => Err(ERR::ErrorIndexingType( format!( diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index 5fc04e47..e0dd361e 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -6,7 +6,7 @@ use crate::ast::{ASTNode, Expr, Stmt}; use crate::{Dynamic, Engine, EvalAltResult, Identifier, Module, Position, RhaiResultOf, Scope}; #[cfg(feature = "no_std")] use std::prelude::v1::*; -use std::{fmt, mem}; +use std::{fmt, iter::repeat, mem}; /// Callback function to initialize the debugger. #[cfg(not(feature = "sync"))] @@ -160,10 +160,7 @@ impl fmt::Display for BreakPoint { f, "{} ({})", fn_name, - std::iter::repeat("_") - .take(*args) - .collect::>() - .join(", ") + repeat("_").take(*args).collect::>().join(", ") )?; if !*enabled { f.write_str(" (disabled)")?; diff --git a/src/eval/eval_state.rs b/src/eval/eval_state.rs index b826ffd9..13acd9b5 100644 --- a/src/eval/eval_state.rs +++ b/src/eval/eval_state.rs @@ -2,10 +2,9 @@ use crate::func::call::FnResolutionCache; use crate::StaticVec; -use std::collections::BTreeMap; -use std::marker::PhantomData; #[cfg(feature = "no_std")] use std::prelude::v1::*; +use std::{collections::BTreeMap, marker::PhantomData}; /// _(internals)_ A type that holds all the current states of the [`Engine`][crate::Engine]. /// Exported under the `internals` feature only. diff --git a/src/func/register.rs b/src/func/register.rs index 289dd8db..84d1d728 100644 --- a/src/func/register.rs +++ b/src/func/register.rs @@ -48,7 +48,7 @@ pub fn by_value(data: &mut Dynamic) -> T { // # Safety // // We already checked that `T` is `&str`, so it is safe to cast here. - return unsafe { std::mem::transmute_copy::<_, T>(&ref_str) }; + return unsafe { mem::transmute_copy::<_, T>(&ref_str) }; } if TypeId::of::() == TypeId::of::() { // If T is `String`, data must be `ImmutableString`, so map directly to it diff --git a/src/module/resolvers/collection.rs b/src/module/resolvers/collection.rs index 57c2bbb1..fe03612f 100644 --- a/src/module/resolvers/collection.rs +++ b/src/module/resolvers/collection.rs @@ -1,7 +1,7 @@ use crate::{Engine, Module, ModuleResolver, Position, RhaiResultOf, Shared, ERR}; -use std::ops::AddAssign; #[cfg(feature = "no_std")] use std::prelude::v1::*; +use std::{ops::AddAssign, vec::IntoIter}; /// [Module] resolution service that holds a collection of module resolvers, /// to be searched in sequential order. @@ -108,7 +108,7 @@ impl ModuleResolversCollection { impl IntoIterator for ModuleResolversCollection { type Item = Box; - type IntoIter = std::vec::IntoIter>; + type IntoIter = IntoIter>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { diff --git a/src/module/resolvers/stat.rs b/src/module/resolvers/stat.rs index 5648b5ef..be9110a7 100644 --- a/src/module/resolvers/stat.rs +++ b/src/module/resolvers/stat.rs @@ -3,7 +3,7 @@ use crate::{ }; #[cfg(feature = "no_std")] use std::prelude::v1::*; -use std::{collections::BTreeMap, ops::AddAssign}; +use std::{collections::btree_map::IntoIter, collections::BTreeMap, ops::AddAssign}; /// A static [module][Module] resolution service that serves [modules][Module] added into it. /// @@ -120,7 +120,7 @@ impl StaticModuleResolver { impl IntoIterator for StaticModuleResolver { type Item = (Identifier, Shared); - type IntoIter = std::collections::btree_map::IntoIter>; + type IntoIter = IntoIter>; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() diff --git a/tests/get_set.rs b/tests/get_set.rs index 7ec97451..9e428f30 100644 --- a/tests/get_set.rs +++ b/tests/get_set.rs @@ -334,6 +334,30 @@ fn test_get_set_indexer() -> Result<(), Box> { 42 ); + assert_eq!( + engine.eval::( + r#" + let my_map = new_map(); + my_map["eggs"] = 41; + my_map["eggs"] = my_map["eggs"] + 1; + my_map["eggs"] + "#, + )?, + 42 + ); + + assert_eq!( + engine.eval::( + r#" + let my_map = new_map(); + my_map["eggs"] = 41; + my_map["eggs"] += 1; + my_map["eggs"] + "#, + )?, + 42 + ); + assert!(engine .eval::( r#"