Browse Source

Allow overloading assignment operators (#944)

* added assignment operator overload tests
* implement assignment operator overloading
Gökhan Kurt 4 years ago
parent
commit
27d36467fe

+ 58 - 0
Jint.Tests/Runtime/OperatorOverloadingTests.cs

@@ -47,6 +47,7 @@ namespace Jint.Tests.Runtime
             public static Vector2 operator +(Vector2 left, double right) => new Vector2(left.X + right, left.Y + right);
             public static Vector2 operator +(Vector2 left, double right) => new Vector2(left.X + right, left.Y + right);
             public static Vector2 operator +(string left, Vector2 right) => new Vector2(right.X, right.Y);
             public static Vector2 operator +(string left, Vector2 right) => new Vector2(right.X, right.Y);
             public static Vector2 operator +(double left, Vector2 right) => new Vector2(right.X + left, right.Y + left);
             public static Vector2 operator +(double left, Vector2 right) => new Vector2(right.X + left, right.Y + left);
+            public static Vector2 operator -(Vector2 left, Vector2 right) => new Vector2(left.X - right.X, left.Y - right.Y);
             public static Vector2 operator *(Vector2 left, double right) => new Vector2(left.X * right, left.Y * right);
             public static Vector2 operator *(Vector2 left, double right) => new Vector2(left.X * right, left.Y * right);
             public static Vector2 operator /(Vector2 left, double right) => new Vector2(left.X / right, left.Y / right);
             public static Vector2 operator /(Vector2 left, double right) => new Vector2(left.X / right, left.Y / right);
 
 
@@ -137,6 +138,9 @@ namespace Jint.Tests.Runtime
                 equal(3, r8.X);
                 equal(3, r8.X);
                 equal(0, r8.Y);
                 equal(0, r8.Y);
 
 
+                var r9 = v2 - v1;
+                equal(2, r9.X);
+                equal(2, r9.Y);
                 
                 
                 var vSmall = new Vector2(3, 4);
                 var vSmall = new Vector2(3, 4);
                 var vBig = new Vector2(4, 4);
                 var vBig = new Vector2(4, 4);
@@ -159,6 +163,60 @@ namespace Jint.Tests.Runtime
             ");
             ");
         }
         }
 
 
+        [Fact]
+        public void OperatorOverloading_AssignmentOperators()
+        {
+            RunTest(@"
+                var v1 = new Vector2(1, 2);
+                var v2 = new Vector2(3, 4);
+                var n = 6;
+
+                var r1 = v1;
+                r1 += v2;
+                equal(4, r1.X);
+                equal(6, r1.Y);
+
+                var r2 = n;
+                r2 += v1;
+                equal(7, r2.X);
+                equal(8, r2.Y);
+
+                var r3 = v1;
+                r3 += n;
+                equal(7, r3.X);
+                equal(8, r3.Y);
+
+                var r4 = v1;
+                r4 *= n;
+                equal(6, r4.X);
+                equal(12, r4.Y);
+
+                var r5 = v1;
+                r5 /= n;
+                equal(1 / 6, r5.X);
+                equal(2 / 6, r5.Y);
+
+                var r6 = v2;
+                r6 %= new Vector2(2, 3);
+                equal(1, r6.X);
+                equal(1, r6.Y);
+
+                var r7 = v2;
+                r7 &= v1;
+                equal(11, r7);
+
+                var r8 = new Vector2(3, 4);
+                r8 |= new Vector2(2, 0);
+                equal(3, r8.X);
+                equal(0, r8.Y);
+
+                var r9 = v2;
+                r9 -= v1;
+                equal(2, r9.X);
+                equal(2, r9.Y);
+            ");
+        }
+
         [Fact]
         [Fact]
         public void OperatorOverloading_ShouldCoerceTypes()
         public void OperatorOverloading_ShouldCoerceTypes()
         {
         {

+ 129 - 73
Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs

@@ -44,101 +44,157 @@ namespace Jint.Runtime.Interpreter.Expressions
 
 
             var rval = _right.GetValue();
             var rval = _right.GetValue();
             var lval = _engine.GetValue(lref, false);
             var lval = _engine.GetValue(lref, false);
+            var handledByOverload = false;
 
 
-            switch (_operator)
+            if (_engine.Options._IsOperatorOverloadingAllowed)
             {
             {
-                case AssignmentOperator.PlusAssign:
-                    if (AreIntegerOperands(lval, rval))
-                    {
-                        lval = (long) lval.AsInteger() + rval.AsInteger();
-                    }
-                    else
-                    {
-                        var lprim = TypeConverter.ToPrimitive(lval);
-                        var rprim = TypeConverter.ToPrimitive(rval);
+                string operatorClrName = null;
+                switch (_operator)
+                {
+                    case AssignmentOperator.PlusAssign:
+                        operatorClrName = "op_Addition";
+                        break;
+                    case AssignmentOperator.MinusAssign:
+                        operatorClrName = "op_Subtraction";
+                        break;
+                    case AssignmentOperator.TimesAssign:
+                        operatorClrName = "op_Multiply";
+                        break;
+                    case AssignmentOperator.DivideAssign:
+                        operatorClrName = "op_Division";
+                        break;
+                    case AssignmentOperator.ModuloAssign:
+                        operatorClrName = "op_Modulus";
+                        break;
+                    case AssignmentOperator.BitwiseAndAssign:
+                        operatorClrName = "op_BitwiseAnd";
+                        break;
+                    case AssignmentOperator.BitwiseOrAssign:
+                        operatorClrName = "op_BitwiseOr";
+                        break;
+                    case AssignmentOperator.BitwiseXOrAssign:
+                        operatorClrName = "op_ExclusiveOr";
+                        break;
+                    case AssignmentOperator.LeftShiftAssign:
+                        operatorClrName = "op_LeftShift";
+                        break;
+                    case AssignmentOperator.RightShiftAssign:
+                        operatorClrName = "op_RightShift";
+                        break;
+                    case AssignmentOperator.UnsignedRightShiftAssign:
+                        operatorClrName = "op_UnsignedRightShift";
+                        break;
+                    case AssignmentOperator.ExponentiationAssign:
+                    case AssignmentOperator.Assign:
+                    default:
+                        break;
+                }
 
 
-                        if (lprim.IsString() || rprim.IsString())
-                        {
-                            if (!(lprim is JsString jsString))
-                            {
-                                jsString = new JsString.ConcatenatedString(TypeConverter.ToString(lprim));
-                            }
+                if (operatorClrName != null &&
+                    JintBinaryExpression.TryOperatorOverloading(_engine, lval, rval, operatorClrName, out var result))
+                {
+                    lval = JsValue.FromObject(_engine, result);
+                    handledByOverload = true;
+                }
+            }
 
 
-                            lval = jsString.Append(rprim);
+            if (!handledByOverload)
+            {
+                switch (_operator)
+                {
+                    case AssignmentOperator.PlusAssign:
+                        if (AreIntegerOperands(lval, rval))
+                        {
+                            lval = (long) lval.AsInteger() + rval.AsInteger();
                         }
                         }
                         else
                         else
                         {
                         {
-                            lval = TypeConverter.ToNumber(lprim) + TypeConverter.ToNumber(rprim);
+                            var lprim = TypeConverter.ToPrimitive(lval);
+                            var rprim = TypeConverter.ToPrimitive(rval);
+
+                            if (lprim.IsString() || rprim.IsString())
+                            {
+                                if (!(lprim is JsString jsString))
+                                {
+                                    jsString = new JsString.ConcatenatedString(TypeConverter.ToString(lprim));
+                                }
+
+                                lval = jsString.Append(rprim);
+                            }
+                            else
+                            {
+                                lval = TypeConverter.ToNumber(lprim) + TypeConverter.ToNumber(rprim);
+                            }
                         }
                         }
-                    }
 
 
-                    break;
+                        break;
 
 
-                case AssignmentOperator.MinusAssign:
-                    lval = AreIntegerOperands(lval, rval)
-                        ? JsNumber.Create(lval.AsInteger() - rval.AsInteger())
-                        : JsNumber.Create(TypeConverter.ToNumber(lval) - TypeConverter.ToNumber(rval));
-                    break;
+                    case AssignmentOperator.MinusAssign:
+                        lval = AreIntegerOperands(lval, rval)
+                            ? JsNumber.Create(lval.AsInteger() - rval.AsInteger())
+                            : JsNumber.Create(TypeConverter.ToNumber(lval) - TypeConverter.ToNumber(rval));
+                        break;
 
 
-                case AssignmentOperator.TimesAssign:
-                    if (AreIntegerOperands(lval, rval))
-                    {
-                        lval = (long) lval.AsInteger() * rval.AsInteger();
-                    }
-                    else if (lval.IsUndefined() || rval.IsUndefined())
-                    {
-                        lval = Undefined.Instance;
-                    }
-                    else
-                    {
-                        lval = TypeConverter.ToNumber(lval) * TypeConverter.ToNumber(rval);
-                    }
+                    case AssignmentOperator.TimesAssign:
+                        if (AreIntegerOperands(lval, rval))
+                        {
+                            lval = (long) lval.AsInteger() * rval.AsInteger();
+                        }
+                        else if (lval.IsUndefined() || rval.IsUndefined())
+                        {
+                            lval = Undefined.Instance;
+                        }
+                        else
+                        {
+                            lval = TypeConverter.ToNumber(lval) * TypeConverter.ToNumber(rval);
+                        }
 
 
-                    break;
+                        break;
 
 
-                case AssignmentOperator.DivideAssign:
-                    lval = Divide(lval, rval);
-                    break;
+                    case AssignmentOperator.DivideAssign:
+                        lval = Divide(lval, rval);
+                        break;
 
 
-                case AssignmentOperator.ModuloAssign:
-                    if (lval.IsUndefined() || rval.IsUndefined())
-                    {
-                        lval = Undefined.Instance;
-                    }
-                    else
-                    {
-                        lval = TypeConverter.ToNumber(lval) % TypeConverter.ToNumber(rval);
-                    }
+                    case AssignmentOperator.ModuloAssign:
+                        if (lval.IsUndefined() || rval.IsUndefined())
+                        {
+                            lval = Undefined.Instance;
+                        }
+                        else
+                        {
+                            lval = TypeConverter.ToNumber(lval) % TypeConverter.ToNumber(rval);
+                        }
 
 
-                    break;
+                        break;
 
 
-                case AssignmentOperator.BitwiseAndAssign:
-                    lval = TypeConverter.ToInt32(lval) & TypeConverter.ToInt32(rval);
-                    break;
+                    case AssignmentOperator.BitwiseAndAssign:
+                        lval = TypeConverter.ToInt32(lval) & TypeConverter.ToInt32(rval);
+                        break;
 
 
-                case AssignmentOperator.BitwiseOrAssign:
-                    lval = TypeConverter.ToInt32(lval) | TypeConverter.ToInt32(rval);
-                    break;
+                    case AssignmentOperator.BitwiseOrAssign:
+                        lval = TypeConverter.ToInt32(lval) | TypeConverter.ToInt32(rval);
+                        break;
 
 
-                case AssignmentOperator.BitwiseXOrAssign:
-                    lval = TypeConverter.ToInt32(lval) ^ TypeConverter.ToInt32(rval);
-                    break;
+                    case AssignmentOperator.BitwiseXOrAssign:
+                        lval = TypeConverter.ToInt32(lval) ^ TypeConverter.ToInt32(rval);
+                        break;
 
 
-                case AssignmentOperator.LeftShiftAssign:
-                    lval = TypeConverter.ToInt32(lval) << (int) (TypeConverter.ToUint32(rval) & 0x1F);
-                    break;
+                    case AssignmentOperator.LeftShiftAssign:
+                        lval = TypeConverter.ToInt32(lval) << (int) (TypeConverter.ToUint32(rval) & 0x1F);
+                        break;
 
 
-                case AssignmentOperator.RightShiftAssign:
-                    lval = TypeConverter.ToInt32(lval) >> (int) (TypeConverter.ToUint32(rval) & 0x1F);
-                    break;
+                    case AssignmentOperator.RightShiftAssign:
+                        lval = TypeConverter.ToInt32(lval) >> (int) (TypeConverter.ToUint32(rval) & 0x1F);
+                        break;
 
 
-                case AssignmentOperator.UnsignedRightShiftAssign:
-                    lval = (uint) TypeConverter.ToInt32(lval) >> (int) (TypeConverter.ToUint32(rval) & 0x1F);
-                    break;
+                    case AssignmentOperator.UnsignedRightShiftAssign:
+                        lval = (uint) TypeConverter.ToInt32(lval) >> (int) (TypeConverter.ToUint32(rval) & 0x1F);
+                        break;
 
 
-                default:
-                    ExceptionHelper.ThrowNotImplementedException();
-                    return null;
+                    default:
+                        ExceptionHelper.ThrowNotImplementedException();
+                        return null;
+                }
             }
             }
 
 
             _engine.PutValue(lref, lval);
             _engine.PutValue(lref, lval);

+ 6 - 4
Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs

@@ -29,9 +29,11 @@ namespace Jint.Runtime.Interpreter.Expressions
 
 
         protected bool TryOperatorOverloading(string clrName, out object result)
         protected bool TryOperatorOverloading(string clrName, out object result)
         {
         {
-            var leftValue = _left.GetValue();
-            var rightValue = _right.GetValue();
+            return TryOperatorOverloading(_engine, _left.GetValue(), _right.GetValue(), clrName, out result);
+        }
 
 
+        internal static bool TryOperatorOverloading(Engine engine, JsValue leftValue, JsValue rightValue, string clrName, out object result)
+        {
             var left = leftValue.ToObject();
             var left = leftValue.ToObject();
             var right = rightValue.ToObject();
             var right = rightValue.ToObject();
 
 
@@ -54,12 +56,12 @@ namespace Jint.Runtime.Interpreter.Expressions
                     var methods = leftMethods.Concat(rightMethods).Where(x => x.Name == clrName && x.GetParameters().Length == 2);
                     var methods = leftMethods.Concat(rightMethods).Where(x => x.Name == clrName && x.GetParameters().Length == 2);
                     var _methods = MethodDescriptor.Build(methods.ToArray());
                     var _methods = MethodDescriptor.Build(methods.ToArray());
 
 
-                    return TypeConverter.FindBestMatch(_engine, _methods, _ => arguments).FirstOrDefault()?.Item1;
+                    return TypeConverter.FindBestMatch(engine, _methods, _ => arguments).FirstOrDefault()?.Item1;
                 });
                 });
 
 
                 if (method != null)
                 if (method != null)
                 {
                 {
-                    result = method.Call(_engine, null, arguments);
+                    result = method.Call(engine, null, arguments);
                     return true;
                     return true;
                 }
                 }
             }
             }