Flatten constant operations to implement JS semantics by default.

Change-Id: Id93e9a1ce77d8035928ba0b9c338a1030d0e13bd
Reviewed-on: https://dart-review.googlesource.com/c/93960
Commit-Queue: Mayank Patke <fishythefish@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/compiler/lib/src/constants/constant_system.dart b/pkg/compiler/lib/src/constants/constant_system.dart
index 5e2f563..61c1eab 100644
--- a/pkg/compiler/lib/src/constants/constant_system.dart
+++ b/pkg/compiler/lib/src/constants/constant_system.dart
@@ -14,38 +14,32 @@
 
 final _BITS32 = new BigInt.from(0xFFFFFFFF);
 
-const add = const JavaScriptAddOperation();
-const bitAnd = const JavaScriptBinaryBitOperation(const BitAndOperation());
+const add = const AddOperation();
+const bitAnd = const BitAndOperation();
 const bitNot = const BitNotOperation();
-const bitOr = const JavaScriptBinaryBitOperation(const BitOrOperation());
-const bitXor = const JavaScriptBinaryBitOperation(const BitXorOperation());
+const bitOr = const BitOrOperation();
+const bitXor = const BitXorOperation();
 const booleanAnd = const BooleanAndOperation();
 const booleanOr = const BooleanOrOperation();
-const divide =
-    const JavaScriptBinaryArithmeticOperation(const DivideOperation());
+const divide = const DivideOperation();
 const equal = const EqualsOperation();
 const greaterEqual = const GreaterEqualOperation();
 const greater = const GreaterOperation();
-const identity = const JavaScriptIdentityOperation();
+const identity = const IdentityOperation();
 const ifNull = const IfNullOperation();
 const lessEqual = const LessEqualOperation();
 const less = const LessOperation();
-const modulo =
-    const JavaScriptBinaryArithmeticOperation(const ModuloOperation());
-const multiply =
-    const JavaScriptBinaryArithmeticOperation(const MultiplyOperation());
-const negate = const JavaScriptNegateOperation();
+const modulo = const ModuloOperation();
+const multiply = const MultiplyOperation();
+const negate = const NegateOperation();
 const not = const NotOperation();
-const remainder = const JavaScriptRemainderOperation();
-const shiftLeft =
-    const JavaScriptBinaryBitOperation(const ShiftLeftOperation());
-const shiftRight = const JavaScriptShiftRightOperation();
-const subtract =
-    const JavaScriptBinaryArithmeticOperation(const SubtractOperation());
-const truncatingDivide = const JavaScriptBinaryArithmeticOperation(
-    const TruncatingDivideOperation());
-const codeUnitAt = const CodeUnitAtRuntimeOperation();
-const round = const JavaScriptRoundOperation();
+const remainder = const RemainderOperation();
+const shiftLeft = const ShiftLeftOperation();
+const shiftRight = const ShiftRightOperation();
+const subtract = const SubtractOperation();
+const truncatingDivide = const TruncatingDivideOperation();
+const codeUnitAt = const CodeUnitAtOperation();
+const round = const RoundOperation();
 const abs = const UnfoldedUnaryOperation('abs');
 
 /// Returns true if [value] will turn into NaN or infinity
@@ -265,9 +259,12 @@
 }
 
 class BitNotOperation implements UnaryOperation {
+  @override
   final String name = '~';
+
   const BitNotOperation();
 
+  @override
   ConstantValue fold(ConstantValue constant) {
     if (isInt(constant)) {
       // In JavaScript we don't check for -0 and treat it as if it was zero.
@@ -283,42 +280,42 @@
 }
 
 class NegateOperation implements UnaryOperation {
+  @override
   final String name = 'negate';
+
   const NegateOperation();
+
+  @override
   ConstantValue fold(ConstantValue constant) {
-    if (constant.isInt) {
-      IntConstantValue intConstant = constant;
-      return createInt(-intConstant.intValue);
+    ConstantValue _fold(ConstantValue constant) {
+      if (constant.isInt) {
+        IntConstantValue intConstant = constant;
+        return createInt(-intConstant.intValue);
+      }
+      if (constant.isDouble) {
+        DoubleConstantValue doubleConstant = constant;
+        return createDouble(-doubleConstant.doubleValue);
+      }
+      return null;
     }
-    if (constant.isDouble) {
-      DoubleConstantValue doubleConstant = constant;
-      return createDouble(-doubleConstant.doubleValue);
-    }
-    return null;
-  }
-}
 
-class JavaScriptNegateOperation implements UnaryOperation {
-  final NegateOperation dartNegateOperation = const NegateOperation();
-
-  const JavaScriptNegateOperation();
-
-  String get name => dartNegateOperation.name;
-
-  ConstantValue fold(ConstantValue constant) {
     if (constant.isInt) {
       IntConstantValue intConstant = constant;
       if (intConstant.intValue == BigInt.zero) {
         return createDouble(-0.0);
       }
     }
-    return dartNegateOperation.fold(constant);
+    return _fold(constant);
   }
 }
 
 class NotOperation implements UnaryOperation {
+  @override
   final String name = '!';
+
   const NotOperation();
+
+  @override
   ConstantValue fold(ConstantValue constant) {
     if (constant.isBool) {
       BoolConstantValue boolConstant = constant;
@@ -331,30 +328,20 @@
 /// Operations that only work if both arguments are integers.
 abstract class BinaryBitOperation implements BinaryOperation {
   const BinaryBitOperation();
+
+  @override
   ConstantValue fold(ConstantValue left, ConstantValue right) {
-    if (left.isInt && right.isInt) {
-      IntConstantValue leftInt = left;
-      IntConstantValue rightInt = right;
-      BigInt resultValue = foldInts(leftInt.intValue, rightInt.intValue);
-      if (resultValue == null) return null;
-      return createInt(resultValue);
+    ConstantValue _fold(ConstantValue left, ConstantValue right) {
+      if (left.isInt && right.isInt) {
+        IntConstantValue leftInt = left;
+        IntConstantValue rightInt = right;
+        BigInt resultValue = foldInts(leftInt.intValue, rightInt.intValue);
+        if (resultValue == null) return null;
+        return createInt(resultValue);
+      }
+      return null;
     }
-    return null;
-  }
 
-  BigInt foldInts(BigInt left, BigInt right);
-}
-
-/// In JavaScript we truncate the result to an unsigned 32 bit integer. Also, -0
-/// is treated as if it was the integer 0.
-class JavaScriptBinaryBitOperation implements BinaryOperation {
-  final BinaryBitOperation dartBitOperation;
-
-  const JavaScriptBinaryBitOperation(this.dartBitOperation);
-
-  String get name => dartBitOperation.name;
-
-  ConstantValue fold(ConstantValue left, ConstantValue right) {
     // In JavaScript we don't check for -0 and treat it as if it was zero.
     if (left.isMinusZero) {
       left = createInt(BigInt.zero);
@@ -362,7 +349,7 @@
     if (right.isMinusZero) {
       right = createInt(BigInt.zero);
     }
-    IntConstantValue result = dartBitOperation.fold(left, right);
+    IntConstantValue result = _fold(left, right);
     if (result != null) {
       // We convert the result of bit-operations to 32 bit unsigned integers.
       return _createInt32(result.intValue);
@@ -370,33 +357,55 @@
     return result;
   }
 
-  apply(left, right) => dartBitOperation.apply(left, right);
+  BigInt foldInts(BigInt left, BigInt right);
 }
 
 class BitAndOperation extends BinaryBitOperation {
+  @override
   final String name = '&';
+
   const BitAndOperation();
+
+  @override
   BigInt foldInts(BigInt left, BigInt right) => left & right;
+
+  @override
   apply(left, right) => left & right;
 }
 
 class BitOrOperation extends BinaryBitOperation {
+  @override
   final String name = '|';
+
   const BitOrOperation();
+
+  @override
   BigInt foldInts(BigInt left, BigInt right) => left | right;
+
+  @override
   apply(left, right) => left | right;
 }
 
 class BitXorOperation extends BinaryBitOperation {
+  @override
   final String name = '^';
+
   const BitXorOperation();
+
+  @override
   BigInt foldInts(BigInt left, BigInt right) => left ^ right;
+
+  @override
   apply(left, right) => left ^ right;
 }
 
 class ShiftLeftOperation extends BinaryBitOperation {
+  @override
   final String name = '<<';
+
   const ShiftLeftOperation();
+
+  @override
   BigInt foldInts(BigInt left, BigInt right) {
     // TODO(floitsch): find a better way to guard against excessive shifts to
     // the left.
@@ -404,23 +413,17 @@
     return left << right.toInt();
   }
 
+  @override
   apply(left, right) => left << right;
 }
 
 class ShiftRightOperation extends BinaryBitOperation {
+  @override
   final String name = '>>';
+
   const ShiftRightOperation();
-  BigInt foldInts(BigInt left, BigInt right) {
-    if (right < BigInt.zero) return null;
-    return left >> right.toInt();
-  }
 
-  apply(left, right) => left >> right;
-}
-
-class JavaScriptShiftRightOperation extends JavaScriptBinaryBitOperation {
-  const JavaScriptShiftRightOperation() : super(const ShiftRightOperation());
-
+  @override
   ConstantValue fold(ConstantValue left, ConstantValue right) {
     // Truncate the input value to 32 bits if necessary.
     if (left.isInt) {
@@ -447,10 +450,21 @@
     }
     return super.fold(left, right);
   }
+
+  @override
+  BigInt foldInts(BigInt left, BigInt right) {
+    if (right < BigInt.zero) return null;
+    return left >> right.toInt();
+  }
+
+  @override
+  apply(left, right) => left >> right;
 }
 
 abstract class BinaryBoolOperation implements BinaryOperation {
   const BinaryBoolOperation();
+
+  @override
   ConstantValue fold(ConstantValue left, ConstantValue right) {
     if (left.isBool && right.isBool) {
       BoolConstantValue leftBool = left;
@@ -465,43 +479,63 @@
 }
 
 class BooleanAndOperation extends BinaryBoolOperation {
+  @override
   final String name = '&&';
+
   const BooleanAndOperation();
+
+  @override
   bool foldBools(bool left, bool right) => left && right;
+
+  @override
   apply(left, right) => left && right;
 }
 
 class BooleanOrOperation extends BinaryBoolOperation {
+  @override
   final String name = '||';
+
   const BooleanOrOperation();
+
+  @override
   bool foldBools(bool left, bool right) => left || right;
+
+  @override
   apply(left, right) => left || right;
 }
 
 abstract class ArithmeticNumOperation implements BinaryOperation {
   const ArithmeticNumOperation();
+
+  @override
   ConstantValue fold(ConstantValue left, ConstantValue right) {
-    if (left.isNum && right.isNum) {
-      NumConstantValue leftNum = left;
-      NumConstantValue rightNum = right;
-      var foldedValue;
-      if (left.isInt && right.isInt) {
-        IntConstantValue leftInt = leftNum;
-        IntConstantValue rightInt = rightNum;
-        foldedValue = foldInts(leftInt.intValue, rightInt.intValue);
-      } else {
-        foldedValue = foldNums(leftNum.doubleValue, rightNum.doubleValue);
+    ConstantValue _fold(ConstantValue left, ConstantValue right) {
+      if (left.isNum && right.isNum) {
+        NumConstantValue leftNum = left;
+        NumConstantValue rightNum = right;
+        var foldedValue;
+        if (left.isInt && right.isInt) {
+          IntConstantValue leftInt = leftNum;
+          IntConstantValue rightInt = rightNum;
+          foldedValue = foldInts(leftInt.intValue, rightInt.intValue);
+        } else {
+          foldedValue = foldNums(leftNum.doubleValue, rightNum.doubleValue);
+        }
+        // A division by 0 means that we might not have a folded value.
+        if (foldedValue == null) return null;
+        if (left.isInt && right.isInt && !isDivide() || isTruncatingDivide()) {
+          assert(foldedValue is BigInt);
+          return createInt(foldedValue);
+        } else {
+          return createDouble(foldedValue);
+        }
       }
-      // A division by 0 means that we might not have a folded value.
-      if (foldedValue == null) return null;
-      if (left.isInt && right.isInt && !isDivide() || isTruncatingDivide()) {
-        assert(foldedValue is BigInt);
-        return createInt(foldedValue);
-      } else {
-        return createDouble(foldedValue);
-      }
+      return null;
     }
-    return null;
+
+    ConstantValue result = _fold(left, right);
+    if (result == null) return result;
+    return _convertToJavaScriptConstant(result);
   }
 
   bool isDivide() => false;
@@ -510,137 +544,165 @@
   foldNums(num left, num right);
 }
 
-class JavaScriptBinaryArithmeticOperation implements BinaryOperation {
-  final BinaryOperation dartArithmeticOperation;
-
-  const JavaScriptBinaryArithmeticOperation(this.dartArithmeticOperation);
-
-  String get name => dartArithmeticOperation.name;
-
-  ConstantValue fold(ConstantValue left, ConstantValue right) {
-    ConstantValue result = dartArithmeticOperation.fold(left, right);
-    if (result == null) return result;
-    return _convertToJavaScriptConstant(result);
-  }
-
-  apply(left, right) => dartArithmeticOperation.apply(left, right);
-}
-
 class SubtractOperation extends ArithmeticNumOperation {
+  @override
   final String name = '-';
+
   const SubtractOperation();
+
+  @override
   BigInt foldInts(BigInt left, BigInt right) => left - right;
+
+  @override
   num foldNums(num left, num right) => left - right;
+
+  @override
   apply(left, right) => left - right;
 }
 
 class MultiplyOperation extends ArithmeticNumOperation {
+  @override
   final String name = '*';
+
   const MultiplyOperation();
+
+  @override
   BigInt foldInts(BigInt left, BigInt right) => left * right;
+
+  @override
   num foldNums(num left, num right) => left * right;
+
+  @override
   apply(left, right) => left * right;
 }
 
 class ModuloOperation extends ArithmeticNumOperation {
+  @override
   final String name = '%';
+
   const ModuloOperation();
+
+  @override
   BigInt foldInts(BigInt left, BigInt right) {
     if (right == BigInt.zero) return null;
     return left % right;
   }
 
+  @override
   num foldNums(num left, num right) => left % right;
+
+  @override
   apply(left, right) => left % right;
 }
 
-class JavaScriptRemainderOperation extends ArithmeticNumOperation {
-  String get name => 'remainder';
+class RemainderOperation extends ArithmeticNumOperation {
+  @override
+  final String name = 'remainder';
 
-  const JavaScriptRemainderOperation();
+  const RemainderOperation();
 
+  @override
   BigInt foldInts(BigInt left, BigInt right) {
     if (right == BigInt.zero) return null;
     return left.remainder(right);
   }
 
+  @override
   num foldNums(num left, num right) => left.remainder(right);
+
+  @override
   apply(left, right) => left.remainder(right);
 }
 
 class TruncatingDivideOperation extends ArithmeticNumOperation {
+  @override
   final String name = '~/';
+
   const TruncatingDivideOperation();
+
+  @override
   BigInt foldInts(BigInt left, BigInt right) {
     if (right == BigInt.zero) return null;
     return left ~/ right;
   }
 
+  @override
   BigInt foldNums(num left, num right) {
     num ratio = left / right;
     if (ratio.isNaN || ratio.isInfinite) return null;
     return new BigInt.from(ratio.truncate().toInt());
   }
 
+  @override
   apply(left, right) => left ~/ right;
+
+  @override
   bool isTruncatingDivide() => true;
 }
 
 class DivideOperation extends ArithmeticNumOperation {
+  @override
   final String name = '/';
+
   const DivideOperation();
+
+  @override
   double foldInts(BigInt left, BigInt right) => left / right;
+
+  @override
   num foldNums(num left, num right) => left / right;
+
+  @override
   bool isDivide() => true;
+
+  @override
   apply(left, right) => left / right;
 }
 
 class AddOperation implements BinaryOperation {
+  @override
   final String name = '+';
+
   const AddOperation();
+
+  @override
   ConstantValue fold(ConstantValue left, ConstantValue right) {
-    if (left.isInt && right.isInt) {
-      IntConstantValue leftInt = left;
-      IntConstantValue rightInt = right;
-      BigInt result = leftInt.intValue + rightInt.intValue;
-      return createInt(result);
-    } else if (left.isNum && right.isNum) {
-      NumConstantValue leftNum = left;
-      NumConstantValue rightNum = right;
-      double result = leftNum.doubleValue + rightNum.doubleValue;
-      return createDouble(result);
-    } else if (left.isString && right.isString) {
-      StringConstantValue leftString = left;
-      StringConstantValue rightString = right;
-      String result = leftString.stringValue + rightString.stringValue;
-      return createString(result);
-    } else {
-      return null;
+    ConstantValue _fold(ConstantValue left, ConstantValue right) {
+      if (left.isInt && right.isInt) {
+        IntConstantValue leftInt = left;
+        IntConstantValue rightInt = right;
+        BigInt result = leftInt.intValue + rightInt.intValue;
+        return createInt(result);
+      } else if (left.isNum && right.isNum) {
+        NumConstantValue leftNum = left;
+        NumConstantValue rightNum = right;
+        double result = leftNum.doubleValue + rightNum.doubleValue;
+        return createDouble(result);
+      } else if (left.isString && right.isString) {
+        StringConstantValue leftString = left;
+        StringConstantValue rightString = right;
+        String result = leftString.stringValue + rightString.stringValue;
+        return createString(result);
+      } else {
+        return null;
+      }
     }
-  }
 
-  apply(left, right) => left + right;
-}
-
-class JavaScriptAddOperation implements BinaryOperation {
-  final _addOperation = const AddOperation();
-  String get name => _addOperation.name;
-
-  const JavaScriptAddOperation();
-
-  ConstantValue fold(ConstantValue left, ConstantValue right) {
-    ConstantValue result = _addOperation.fold(left, right);
+    ConstantValue result = _fold(left, right);
     if (result != null && result.isNum) {
       return _convertToJavaScriptConstant(result);
     }
     return result;
   }
 
-  apply(left, right) => _addOperation.apply(left, right);
+  @override
+  apply(left, right) => left + right;
 }
 
 abstract class RelationalNumOperation implements BinaryOperation {
   const RelationalNumOperation();
+
+  @override
   ConstantValue fold(ConstantValue left, ConstantValue right) {
     if (!left.isNum || !right.isNum) return null;
     bool foldedValue;
@@ -662,40 +724,76 @@
 }
 
 class LessOperation extends RelationalNumOperation {
+  @override
   final String name = '<';
+
   const LessOperation();
+
+  @override
   bool foldInts(BigInt left, BigInt right) => left < right;
+
+  @override
   bool foldNums(num left, num right) => left < right;
+
+  @override
   apply(left, right) => left < right;
 }
 
 class LessEqualOperation extends RelationalNumOperation {
+  @override
   final String name = '<=';
+
   const LessEqualOperation();
+
+  @override
   bool foldInts(BigInt left, BigInt right) => left <= right;
+
+  @override
   bool foldNums(num left, num right) => left <= right;
+
+  @override
   apply(left, right) => left <= right;
 }
 
 class GreaterOperation extends RelationalNumOperation {
+  @override
   final String name = '>';
+
   const GreaterOperation();
+
+  @override
   bool foldInts(BigInt left, BigInt right) => left > right;
+
+  @override
   bool foldNums(num left, num right) => left > right;
+
+  @override
   apply(left, right) => left > right;
 }
 
 class GreaterEqualOperation extends RelationalNumOperation {
+  @override
   final String name = '>=';
+
   const GreaterEqualOperation();
+
+  @override
   bool foldInts(BigInt left, BigInt right) => left >= right;
+
+  @override
   bool foldNums(num left, num right) => left >= right;
+
+  @override
   apply(left, right) => left >= right;
 }
 
 class EqualsOperation implements BinaryOperation {
+  @override
   final String name = '==';
+
   const EqualsOperation();
+
+  @override
   ConstantValue fold(ConstantValue left, ConstantValue right) {
     // Numbers need to be treated specially because: NaN != NaN, -0.0 == 0.0,
     // and 1 == 1.0.
@@ -725,32 +823,27 @@
     return createBool(left == right);
   }
 
+  @override
   apply(left, right) => left == right;
 }
 
 class IdentityOperation implements BinaryOperation {
+  @override
   final String name = '===';
+
   const IdentityOperation();
+
+  @override
   BoolConstantValue fold(ConstantValue left, ConstantValue right) {
-    // In order to preserve runtime semantics which says that NaN !== NaN don't
-    // constant fold NaN === NaN. Otherwise the output depends on inlined
-    // variables and other optimizations.
-    if (left.isNaN && right.isNaN) return null;
-    return createBool(left == right);
-  }
+    BoolConstantValue _fold(ConstantValue left, ConstantValue right) {
+      // In order to preserve runtime semantics which says that NaN !== NaN
+      // don't constant fold NaN === NaN. Otherwise the output depends on
+      // inlined variables and other optimizations.
+      if (left.isNaN && right.isNaN) return null;
+      return createBool(left == right);
+    }
 
-  apply(left, right) => identical(left, right);
-}
-
-class JavaScriptIdentityOperation implements BinaryOperation {
-  final IdentityOperation dartIdentityOperation = const IdentityOperation();
-
-  const JavaScriptIdentityOperation();
-
-  String get name => dartIdentityOperation.name;
-
-  BoolConstantValue fold(ConstantValue left, ConstantValue right) {
-    BoolConstantValue result = dartIdentityOperation.fold(left, right);
+    BoolConstantValue result = _fold(left, right);
     if (result == null || result.boolValue) return result;
     // In JavaScript -0.0 === 0 and all doubles are equal to their integer
     // values. Furthermore NaN !== NaN.
@@ -769,29 +862,33 @@
     return result;
   }
 
+  @override
   apply(left, right) => identical(left, right);
 }
 
 class IfNullOperation implements BinaryOperation {
+  @override
   final String name = '??';
+
   const IfNullOperation();
+
+  @override
   ConstantValue fold(ConstantValue left, ConstantValue right) {
     if (left.isNull) return right;
     return left;
   }
 
+  @override
   apply(left, right) => left ?? right;
 }
 
 class CodeUnitAtOperation implements BinaryOperation {
-  String get name => 'charCodeAt';
-  const CodeUnitAtOperation();
-  ConstantValue fold(ConstantValue left, ConstantValue right) => null;
-  apply(left, right) => left.codeUnitAt(right);
-}
+  @override
+  final String name = 'charCodeAt';
 
-class CodeUnitAtRuntimeOperation extends CodeUnitAtOperation {
-  const CodeUnitAtRuntimeOperation();
+  const CodeUnitAtOperation();
+
+  @override
   IntConstantValue fold(ConstantValue left, ConstantValue right) {
     if (left.isString && right.isInt) {
       StringConstantValue stringConstant = left;
@@ -804,11 +901,18 @@
     }
     return null;
   }
+
+  @override
+  apply(left, right) => left.codeUnitAt(right);
 }
 
-class JavaScriptRoundOperation implements UnaryOperation {
-  const JavaScriptRoundOperation();
-  String get name => round.name;
+class RoundOperation implements UnaryOperation {
+  @override
+  final String name = 'round';
+
+  const RoundOperation();
+
+  @override
   ConstantValue fold(ConstantValue constant) {
     // Be careful to round() only values that do not throw on either the host or
     // target platform.
@@ -846,8 +950,12 @@
 }
 
 class UnfoldedUnaryOperation implements UnaryOperation {
+  @override
   final String name;
+
   const UnfoldedUnaryOperation(this.name);
+
+  @override
   ConstantValue fold(ConstantValue constant) {
     return null;
   }