diff --git a/RELEASES.md b/RELEASES.md index ccba5394..b04aa5af 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -11,6 +11,7 @@ Bug fixes * Fixes a bug in `Module::set_fn_4_mut`. * Module API's now properly handle `&str` and `String` parameters. * Indexers are available under `no_object`. +* Registered operator-assignment functions (e.g. `+=`) now work correctly. New features ------------ diff --git a/doc/src/language/arrays.md b/doc/src/language/arrays.md index 5c0ce9e9..2e1b8cc9 100644 --- a/doc/src/language/arrays.md +++ b/doc/src/language/arrays.md @@ -33,7 +33,9 @@ The following methods (mostly defined in the [`BasicArrayPackage`][packages] but | Function | Parameter(s) | Description | | ------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | `push` | element to insert | inserts an element at the end | -| `+=` operator, `append` | array to append | concatenates the second array to the end of the first | +| `+=` operator | array, element to insert (not another array) | inserts an element at the end | +| `append` | array to append | concatenates the second array to the end of the first | +| `+=` operator | array, array to append | concatenates the second array to the end of the first | | `+` operator | first array, second array | concatenates the first array with the second | | `insert` | element to insert, position
(beginning if <= 0, end if >= length) | insert an element at a certain index | | `pop` | _none_ | removes the last element and returns it ([`()`] if empty) | @@ -49,7 +51,9 @@ Use Custom Types With Arrays --------------------------- To use a [custom type] with arrays, a number of array functions need to be manually implemented, -in particular `push`, `pad` and the `==` operator (in order to support the `in` operator). +in particular `push`, `pad` and the `+=` operator. In addition, the `==` operator must be +implemented for the [custom type] in order to support the `in` operator which uses `==` to +compare elements. See the section on [custom types] for more details. @@ -104,7 +108,7 @@ let foo = y[0]; foo == 1; y.push(4); // 4 elements -y.push(5); // 5 elements +y += 5; // 5 elements y.len == 5; diff --git a/doc/src/rust/custom.md b/doc/src/rust/custom.md index 22bea701..824588d5 100644 --- a/doc/src/rust/custom.md +++ b/doc/src/rust/custom.md @@ -161,13 +161,15 @@ x.type_of() == "Hello"; Use the Custom Type With Arrays ------------------------------ -The `push` and `pad` functions for [arrays] are only defined for standard built-in types. -For custom types, type-specific versions must be registered: +The `push` and `pad` functions, as well as the `+=` operator, for [arrays] are only defined for +standard built-in types. For custom types, type-specific versions must be registered: ```rust engine .register_fn("push", |list: &mut Array, item: TestStruct| { list.push(Dynamic::from(item)); + }).register_fn("+=", |list: &mut Array, item: TestStruct| { + list.push(Dynamic::from(item)); }).register_fn("pad", |list: &mut Array, len: i64, item: TestStruct| { if len as usize > list.len() { list.resize(len as usize, item); @@ -176,7 +178,7 @@ engine ``` In particular, in order to use the `in` operator with a custom type for an [array], -the `==` operator must be registered for that custom type: +the `==` operator must be registered for the custom type: ```rust // Assume 'TestStruct' implements `PartialEq` diff --git a/src/engine.rs b/src/engine.rs index a35d3458..1436ff14 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1360,40 +1360,56 @@ impl Engine { let arg_types = once(lhs_ptr.type_id()).chain(once(rhs_val.type_id())); let hash_fn = calc_fn_hash(empty(), op, 2, arg_types); - if let Some(CallableFunction::Method(func)) = self + match self .global_module .get_fn(hash_fn, false) .or_else(|| self.packages.get_fn(hash_fn, false)) { - if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { - let mut lock_guard = lhs_ptr.write_lock::().unwrap(); - let lhs_ptr_inner = lock_guard.deref_mut(); + // op= function registered as method + Some(func) if func.is_method() => { + let mut lock_guard; + let lhs_ptr_inner; + + if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { + lock_guard = lhs_ptr.write_lock::().unwrap(); + lhs_ptr_inner = lock_guard.deref_mut(); + } else { + lhs_ptr_inner = lhs_ptr; + } + + let args = &mut [lhs_ptr_inner, &mut rhs_val]; // Overriding exact implementation - func(self, lib, &mut [lhs_ptr_inner, &mut rhs_val])?; - } else { - // Overriding exact implementation - func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; + if func.is_plugin_fn() { + func.get_plugin_fn().call(args)?; + } else { + func.get_native_fn()(self, lib, args)?; + } } - } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { - // Not built in, map to `var = var op rhs` - let op = &op[..op.len() - 1]; // extract operator without = + // Built-in op-assignment function + _ if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_some() => {} + // Not built-in: expand to `var = var op rhs` + _ => { + let op = &op[..op.len() - 1]; // extract operator without = - // Clone the LHS value - let args = &mut [&mut lhs_ptr.clone(), &mut rhs_val]; + // Clone the LHS value + let args = &mut [&mut lhs_ptr.clone(), &mut rhs_val]; - // Run function - let (value, _) = self - .exec_fn_call( - state, lib, op, 0, args, false, false, false, None, None, level, - ) - .map_err(|err| err.new_position(*op_pos))?; + // Run function + let (value, _) = self + .exec_fn_call( + state, lib, op, 0, args, false, false, false, None, None, + level, + ) + .map_err(|err| err.new_position(*op_pos))?; - let value = value.flatten(); - if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { - *lhs_ptr.write_lock::().unwrap() = value; - } else { - *lhs_ptr = value; + let value = value.flatten(); + + if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { + *lhs_ptr.write_lock::().unwrap() = value; + } else { + *lhs_ptr = value; + } } } Ok(Default::default()) diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 05599eac..b641db3e 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -49,6 +49,7 @@ macro_rules! gen_array_functions { macro_rules! reg_functions { ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( set_exported_fn!($mod_name, "push", $root::$arg_type::push); + set_exported_fn!($mod_name, "+=", $root::$arg_type::push); set_exported_fn!($mod_name, "insert", $root::$arg_type::insert); $mod_name.set_raw_fn("pad", @@ -58,8 +59,6 @@ macro_rules! reg_functions { } def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { - combine_with_exported_module!(lib, "array", array_functions); - reg_functions!(lib += basic; INT, bool, char, ImmutableString, FnPtr, Array, Unit); #[cfg(not(feature = "only_i32"))] @@ -77,6 +76,9 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { #[cfg(not(feature = "no_object"))] reg_functions!(lib += map; Map); + // Merge in the module at the end to override `+=` for arrays + combine_with_exported_module!(lib, "array", array_functions); + // Register array iterator lib.set_iter( TypeId::of::(), diff --git a/tests/arrays.rs b/tests/arrays.rs index 8ea61cdf..62d9de0b 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -13,6 +13,10 @@ fn test_arrays() -> Result<(), Box> { '3' ); assert!(engine.eval::("let y = [1, 2, 3]; 2 in y")?); + assert_eq!(engine.eval::("let y = [1, 2, 3]; y += 4; y[3]")?, 4); + + #[cfg(not(feature = "no_object"))] + assert_eq!(engine.eval::("let y = [1, 2, 3]; y.push(4); y[3]")?, 4); #[cfg(not(feature = "no_object"))] assert_eq!(