Adds patch to JsStaticEval to simplify literal1 + literal2 operations. (where literal can be one of: null, boolean, number, string)

Patch by: cromwellian
Review by: bobv



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6569 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/js/JsStaticEval.java b/dev/core/src/com/google/gwt/dev/js/JsStaticEval.java
index 113c534..6cb7c4d 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsStaticEval.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsStaticEval.java
@@ -33,15 +33,18 @@
 import com.google.gwt.dev.js.ast.JsIf;
 import com.google.gwt.dev.js.ast.JsModVisitor;
 import com.google.gwt.dev.js.ast.JsNullLiteral;
+import com.google.gwt.dev.js.ast.JsNumberLiteral;
 import com.google.gwt.dev.js.ast.JsPrefixOperation;
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.js.ast.JsStatement;
+import com.google.gwt.dev.js.ast.JsStringLiteral;
 import com.google.gwt.dev.js.ast.JsUnaryOperation;
 import com.google.gwt.dev.js.ast.JsUnaryOperator;
 import com.google.gwt.dev.js.ast.JsValueLiteral;
 import com.google.gwt.dev.js.ast.JsVars;
 import com.google.gwt.dev.js.ast.JsVisitor;
 import com.google.gwt.dev.js.ast.JsWhile;
+import com.google.gwt.dev.js.ast.JsBooleanLiteral;
 import com.google.gwt.dev.js.ast.JsVars.JsVar;
 
 import java.util.ArrayList;
@@ -163,6 +166,8 @@
         trySimplifyEq(x, arg1, arg2, ctx);
       } else if (op == JsBinaryOperator.NEQ) {
         trySimplifyNe(x, arg1, arg2, ctx);
+      } else if (op == JsBinaryOperator.ADD) {
+        trySimplifyAdd(x, arg1, arg2, ctx);
       }
     }
 
@@ -449,6 +454,22 @@
       return true;
     }
 
+    private boolean appendLiteral(StringBuilder result, JsValueLiteral val) {
+      if (val instanceof JsNumberLiteral) {
+        double number = ((JsNumberLiteral) val).getValue();
+        result.append(fixTrailingZeroes(String.valueOf(number)));
+      } else if (val instanceof JsStringLiteral) {
+        result.append(((JsStringLiteral) val).getValue());
+      } else if (val instanceof JsBooleanLiteral) {
+        result.append(((JsBooleanLiteral) val).getValue());
+      } else if (val instanceof JsNullLiteral) {
+        result.append("null");
+      } else {
+        return false;
+      }
+      return true;
+    }
+        
     /**
      * This method MUST be called whenever any statements are removed from a
      * function. This is because some statements, such as JsVars or JsFunction
@@ -480,6 +501,20 @@
       }
     }
 
+    /*
+     * String.valueOf(Double) produces trailing .0 on integers which is 
+     * incorrect for Javascript which produces conversions to string without
+     * trailing zeroes. Without this, int + String will turn out wrong.
+     */
+    private String fixTrailingZeroes(String num) {
+      if (num.endsWith(".0")) {
+        String fixNum = num.substring(0, num.length() - 2);
+        assert Double.parseDouble(fixNum) == Double.parseDouble(num);
+        num = fixNum;
+      }
+      return num;
+    }
+        
     private SourceInfo makeSourceInfo(HasSourceInfo x, String m) {
       return x.getSourceInfo().makeChild(StaticEvalVisitor.class, m);
     }
@@ -550,6 +585,30 @@
       return original;
     }
 
+    /**
+     * Simplify a + b.
+     */
+    private void trySimplifyAdd(JsExpression original, JsExpression arg1,
+        JsExpression arg2, JsContext<JsExpression> ctx) {
+      if (arg1 instanceof JsValueLiteral && arg2 instanceof JsValueLiteral) {
+        // case: number + number
+        if (arg1 instanceof JsNumberLiteral && 
+            arg2 instanceof JsNumberLiteral) {
+          ctx.replaceMe(program.getNumberLiteral(
+              ((JsNumberLiteral) arg1).getValue() + 
+              ((JsNumberLiteral) arg2).getValue()));
+        } else {
+          // cases: number + string or string + number
+          StringBuilder result = new StringBuilder();
+          if (appendLiteral(result, (JsValueLiteral) arg1) && 
+              appendLiteral(result, (JsValueLiteral) arg2)) {
+            ctx.replaceMe(program.getStringLiteral(original.getSourceInfo(),
+                result.toString()));
+          }
+        }
+      }
+    }
+  
     private void trySimplifyEq(JsExpression original, JsExpression arg1,
         JsExpression arg2, JsContext<JsExpression> ctx) {
       JsExpression updated = simplifyEq(original, arg1, arg2);
diff --git a/dev/core/test/com/google/gwt/dev/js/JsStaticEvalTest.java b/dev/core/test/com/google/gwt/dev/js/JsStaticEvalTest.java
index 52e6bc9..ea54e44 100644
--- a/dev/core/test/com/google/gwt/dev/js/JsStaticEvalTest.java
+++ b/dev/core/test/com/google/gwt/dev/js/JsStaticEvalTest.java
@@ -20,6 +20,16 @@
  */
 public class JsStaticEvalTest extends OptimizerTestBase {
 
+  public void testAddLiterals() throws Exception {
+    assertEquals("alert(42);", optimize("alert(21+21);"));
+    assertEquals("alert('Hello World');", optimize("alert('Hello '+'World');"));
+    assertEquals("alert('Hello 42');", optimize("alert('Hello ' + 42);"));
+    assertEquals("alert('42 Hello');", optimize("alert(42 + ' Hello');"));
+    assertEquals("alert('42 Hello');", optimize("alert(42.0 + ' Hello');"));
+    assertEquals("alert('42.2 Hello');", optimize("alert(42.2 + ' Hello');"));
+    assertEquals("alert('Hello 42.2');", optimize("alert('Hello ' + 42.2);"));
+  }
+
   public void testIfWithEmptyThen() throws Exception {
     assertEquals("a();", optimize("if (a()) { }"));
   }