Merge branch 'bug-fixes'

This commit is contained in:
Stephen Chung 2021-09-28 16:55:33 +08:00
commit 87d48948f9
3 changed files with 106 additions and 12 deletions

View File

@ -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
------------

View File

@ -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 =

View File

@ -119,7 +119,17 @@ fn test_get_set_chain_with_write_back() -> Result<(), Box<EvalAltResult>> {
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::<INT>("let a = new_tp(); a.child.x")?, 1);
assert_eq!(
@ -132,6 +142,18 @@ fn test_get_set_chain_with_write_back() -> Result<(), Box<EvalAltResult>> {
"TestParent"
);
#[cfg(not(feature = "no_index"))]
assert_eq!(
engine.eval::<INT>("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::<INT>("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<EvalAltResult>> {
|_: &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::<INT>(&mut scope, "outer.inner.value")?,
42
);
#[cfg(not(feature = "no_index"))]
assert_eq!(
engine.eval_with_scope::<INT>(&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(())
}