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!(