diff --git a/CHANGELOG.md b/CHANGELOG.md index e3bc1101..3ea21d8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,10 @@ Enhancements * `StaticVec` is changed to keep three items inline instead of four. +Version 1.0.7 +============= + + Version 1.0.6 ============= @@ -66,6 +70,7 @@ Bug fixes --------- * Eliminate unnecessary property write-back when accessed via a getter since property getters are assumed to be _pure_. +* Writing to a property of an indexed valued obtained via an indexer now works properly by writing back the changed value via an index setter. Enhancements ------------ diff --git a/src/engine.rs b/src/engine.rs index ac22b5be..b0aa1400 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -336,6 +336,20 @@ impl ChainArgument { _ => None, } } + /// Return the [position][Position]. + #[inline(always)] + #[must_use] + #[allow(dead_code)] + pub const fn position(&self) -> Position { + match self { + #[cfg(not(feature = "no_object"))] + ChainArgument::Property(pos) => *pos, + #[cfg(not(feature = "no_object"))] + ChainArgument::MethodCallArgs(_, pos) => *pos, + #[cfg(not(feature = "no_index"))] + ChainArgument::IndexValue(_, pos) => *pos, + } + } } #[cfg(not(feature = "no_object"))] @@ -1279,6 +1293,7 @@ impl Engine { #[cfg(not(feature = "no_index"))] ChainType::Indexing => { let pos = rhs.position(); + let root_pos = idx_val.position(); let idx_val = idx_val .into_index_value() .expect("`chain_type` is `ChainType::Index`"); @@ -1288,17 +1303,50 @@ impl Engine { Expr::Dot(x, term, x_pos) | Expr::Index(x, term, x_pos) if !_terminate_chaining => { + let mut idx_val_for_setter = idx_val.clone(); let idx_pos = x.lhs.position(); - let obj_ptr = &mut self.get_indexed_mut( - mods, state, lib, target, idx_val, idx_pos, false, true, level, - )?; - let rhs_chain = match_chaining_type(rhs); - self.eval_dot_index_chain_helper( - mods, state, lib, this_ptr, obj_ptr, root, &x.rhs, *term, idx_values, - rhs_chain, level, new_val, - ) - .map_err(|err| err.fill_position(*x_pos)) + + let (try_setter, result) = { + let mut obj = self.get_indexed_mut( + mods, state, lib, target, idx_val, idx_pos, false, true, level, + )?; + let is_obj_temp_val = obj.is_temp_value(); + let obj_ptr = &mut obj; + + match self.eval_dot_index_chain_helper( + mods, state, lib, this_ptr, obj_ptr, root, &x.rhs, *term, + idx_values, rhs_chain, level, new_val, + ) { + Ok((result, true)) if is_obj_temp_val => { + (Some(obj.take_or_clone()), (result, true)) + } + Ok(result) => (None, result), + Err(err) => return Err(err.fill_position(*x_pos)), + } + }; + + if let Some(mut new_val) = try_setter { + // Try to call index setter if value is changed + let hash_set = + FnCallHashes::from_native(self.constants.fn_hash_idx_set); + let args = &mut [target, &mut idx_val_for_setter, &mut new_val]; + + if let Err(err) = self.exec_fn_call( + mods, state, lib, FN_IDX_SET, hash_set, args, is_ref_mut, true, + root_pos, None, level, + ) { + // Just ignore if there is no index setter + if !matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) { + return Err(err); + } + } + } + + self.check_data_size(target.as_ref()) + .map_err(|err| err.fill_position(root.1))?; + + Ok(result) } // xxx[rhs] op= new_val _ if new_val.is_some() => { @@ -1331,11 +1379,10 @@ impl Engine { let hash_set = FnCallHashes::from_native(self.constants.fn_hash_idx_set); let args = &mut [target, &mut idx_val_for_setter, &mut new_val]; - let pos = Position::NONE; self.exec_fn_call( mods, state, lib, FN_IDX_SET, hash_set, args, is_ref_mut, true, - pos, None, level, + root_pos, None, level, )?; } @@ -2368,7 +2415,7 @@ impl Engine { let args = &mut [lhs_ptr_inner, &mut new_val]; match self.call_native_fn(mods, state, lib, op, hash, args, true, true, op_pos) { - Err(err) if matches!(err.as_ref(), EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with(op)) => + Err(err) if matches!(*err, EvalAltResult::ErrorFunctionNotFound(ref f, _) if f.starts_with(op)) => { // Expand to `var = var op rhs` let op = &op[..op.len() - 1]; // extract operator without = diff --git a/tests/get_set.rs b/tests/get_set.rs index e7668df9..1b8468fa 100644 --- a/tests/get_set.rs +++ b/tests/get_set.rs @@ -119,7 +119,17 @@ fn test_get_set_chain_with_write_back() -> Result<(), Box> { engine.register_get_set("x", TestChild::get_x, TestChild::set_x); engine.register_get_set("child", TestParent::get_child, TestParent::set_child); + #[cfg(not(feature = "no_index"))] + engine.register_indexer_get_set( + |parent: &mut TestParent, _: INT| parent.child.clone(), + |parent: &mut TestParent, n: INT, mut new_child: TestChild| { + new_child.x *= n; + parent.child = new_child; + }, + ); + engine.register_fn("new_tp", TestParent::new); + engine.register_fn("new_tc", TestChild::new); assert_eq!(engine.eval::("let a = new_tp(); a.child.x")?, 1); assert_eq!( @@ -132,6 +142,18 @@ fn test_get_set_chain_with_write_back() -> Result<(), Box> { "TestParent" ); + #[cfg(not(feature = "no_index"))] + assert_eq!( + engine.eval::("let a = new_tp(); let c = new_tc(); c.x = 123; a[2] = c; a.child.x")?, + 246 + ); + + #[cfg(not(feature = "no_index"))] + assert_eq!( + engine.eval::("let a = new_tp(); a[2].x = 42; a.child.x")?, + 84 + ); + Ok(()) } @@ -204,11 +226,31 @@ fn test_get_set_chain_without_write_back() -> Result<(), Box> { |_: &mut Outer, new: Inner| panic!("Outer::inner setter called with {:?}", new), ); + #[cfg(not(feature = "no_index"))] + engine.register_indexer_get_set( + |t: &mut Outer, n: INT| Inner { + value: t.inner.value * n, + }, + |_: &mut Outer, n: INT, new: Inner| { + panic!("Outer::inner index setter called with {} and {:?}", n, new) + }, + ); + assert_eq!( engine.eval_with_scope::(&mut scope, "outer.inner.value")?, 42 ); + + #[cfg(not(feature = "no_index"))] + assert_eq!( + engine.eval_with_scope::(&mut scope, "outer[2].value")?, + 84 + ); + engine.run_with_scope(&mut scope, "print(outer.inner.value)")?; + #[cfg(not(feature = "no_index"))] + engine.run_with_scope(&mut scope, "print(outer[0].value)")?; + Ok(()) }