Operations like i += d where i is an int and d is a double are not properly
truncated (narrowed) to the LHS type. This patch forces i += d to be written as
i = i + d, and applies a narrowing cast as needed, e.g. i = (int)(i + d);

Updated to fix String concat issue:

Review at http://gwt-code-reviews.appspot.com/1385803

Review by: scottb@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9903 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/javatests b/dev/core/javatests
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/dev/core/javatests
@@ -0,0 +1 @@
+test
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index bda6c03..d10fb83 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -292,10 +292,10 @@
       // (5) "Normalize" the high-level Java tree into a lower-level tree more
       // suited for JavaScript code generation. Don't go reordering these
       // willy-nilly because there are some subtle interdependencies.
-      LongCastNormalizer.exec(jprogram);
       JsoDevirtualizer.exec(jprogram);
       CatchBlockNormalizer.exec(jprogram);
       PostOptimizationCompoundAssignmentNormalizer.exec(jprogram);
+      LongCastNormalizer.exec(jprogram);
       LongEmulationNormalizer.exec(jprogram);
       CastNormalizer.exec(jprogram, options.isCastCheckingDisabled());
       ArrayNormalizer.exec(jprogram);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
index d6ea147..2f61d58 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
@@ -161,8 +161,10 @@
           new JMultiExpression(x.getSourceInfo()));
       JExpression newLhs = replacer.accept(x.getLhs());
 
-      JBinaryOperation operation = new JBinaryOperation(x.getSourceInfo(),
+      JExpression operation = new JBinaryOperation(x.getSourceInfo(),
           newLhs.getType(), op.getNonAssignmentOf(), newLhs, x.getRhs());
+      operation = modifyResultOperation((JBinaryOperation) operation);
+      
       // newLhs is cloned below because it was used in operation
       JBinaryOperation asg = new JBinaryOperation(x.getSourceInfo(), newLhs.getType(),
           JBinaryOperator.ASG, cloner.cloneExpression(newLhs),
@@ -285,6 +287,16 @@
     return lhs;
   }
 
+  /**
+   * Decide what expression to return when breaking up a compound assignment of
+   * the form <code>lhs op= rhs</code>. The breakup creates an expression of
+   * the form <code>lhs = lhs op rhs</code>, and the right hand side of the
+   * newly created expression is passed to this method.
+   */
+  protected JExpression modifyResultOperation(JBinaryOperation op) {
+    return op;
+  }
+  
   protected abstract boolean shouldBreakUp(JBinaryOperation x);
 
   protected abstract boolean shouldBreakUp(JPostfixOperation x);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/PostOptimizationCompoundAssignmentNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/PostOptimizationCompoundAssignmentNormalizer.java
index f925f12..a25fd54 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/PostOptimizationCompoundAssignmentNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/PostOptimizationCompoundAssignmentNormalizer.java
@@ -17,10 +17,13 @@
 
 import com.google.gwt.dev.jjs.ast.JBinaryOperation;
 import com.google.gwt.dev.jjs.ast.JBinaryOperator;
+import com.google.gwt.dev.jjs.ast.JCastOperation;
+import com.google.gwt.dev.jjs.ast.JExpression;
 import com.google.gwt.dev.jjs.ast.JPostfixOperation;
 import com.google.gwt.dev.jjs.ast.JPrefixOperation;
 import com.google.gwt.dev.jjs.ast.JPrimitiveType;
 import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JType;
 
 /**
  * Normalize compound assignments as needed after optimization. Integer division
@@ -36,6 +39,21 @@
   }
 
   @Override
+  protected JExpression modifyResultOperation(JBinaryOperation op) {
+    JType lhsType = op.getLhs().getType();
+    JType rhsType = op.getRhs().getType();
+    if (lhsType != rhsType) {
+      // first widen binary op to encompass both sides, then add narrow cast
+      return new JCastOperation(op.getSourceInfo(), lhsType,
+          new JBinaryOperation(op.getSourceInfo(),
+              widenType(lhsType, rhsType),
+              op.getOp(),
+              op.getLhs(), op.getRhs()));
+    }
+    return op;
+  }
+
+  @Override
   protected boolean shouldBreakUp(JBinaryOperation x) {
     if (x.getType() == JPrimitiveType.LONG) {
       return true;
@@ -45,6 +63,19 @@
         && x.getType() != JPrimitiveType.DOUBLE) {
       return true;
     }
+
+    JType lhsType = x.getLhs().getType();
+    JType rhsType = x.getRhs().getType();
+
+    // don't bother with float op= double since we don't float == double in JS
+    if (lhsType == JPrimitiveType.FLOAT && rhsType == JPrimitiveType.DOUBLE) {
+      return false;
+    }
+    // break up so that result may be coerced to LHS type
+    if (lhsType instanceof JPrimitiveType && rhsType instanceof JPrimitiveType
+        && widenType(lhsType, rhsType) != lhsType) {
+      return true;
+    }
     return false;
   }
 
@@ -63,4 +94,22 @@
     }
     return false;
   }
+
+  /**
+   * Implements http://java.sun.com/docs/books/jls/third_edition/html/conversions.html#26917
+   */
+  private JType widenType(JType lhsType, JType rhsType) {
+    if (lhsType == JPrimitiveType.DOUBLE ||
+        rhsType == JPrimitiveType.DOUBLE) {
+      return JPrimitiveType.DOUBLE;
+    } else if (lhsType == JPrimitiveType.FLOAT ||
+        rhsType == JPrimitiveType.FLOAT) {
+      return JPrimitiveType.FLOAT;
+    } else if (lhsType == JPrimitiveType.LONG ||
+        rhsType == JPrimitiveType.LONG) {
+      return JPrimitiveType.LONG;
+    } else {
+      return JPrimitiveType.INT;
+    }
+  }
 }
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/PostOptimizationCompoundAssignmentNormalizerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/PostOptimizationCompoundAssignmentNormalizerTest.java
new file mode 100644
index 0000000..32f77b9
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/PostOptimizationCompoundAssignmentNormalizerTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jjs.impl;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JProgram;
+
+/**
+ * Tests {@link PostOptimizationCompoundAssignmentNormalizer}.
+ */
+public class PostOptimizationCompoundAssignmentNormalizerTest
+    extends OptimizerTestBase {
+
+  public void testIntegralFloatCoercion() throws Exception {
+    // long op= float
+    optimize("void", "long x=2L; float d=3; x += d;").into(
+        "long x=2L; float d=3; x = (long)((float)x + d);");
+    // long op= long
+     optimize("void", "long x=2L; long d=3L; x += d;").into(
+        "long x=2L; long d=3L; x = x + d;");
+    // don't touch int op= int
+    optimize("void", "int x=2; int d=3; x += d;").into(
+        "int x=2; int d=3; x += d;");
+    // don't touch, integral types with lhs wider than rhs
+    optimize("void", "int x=2; short d=3; x += d;").into(
+        "int x=2; short d=3; x += d;");
+    // different integral types, but should narrow result
+    optimize("void", "int x=2; short d=3; d += x;").into(
+        "int x=2; short d=3; d = (short)(d + x);");
+    // integral with long, should break up
+    optimize("void", "int x=2; long d=3L; x += d;").into(
+        "int x=2; long d=3L; x = (int)((long)x + d);");
+    // integral with float
+    optimize("void", "int x=2; float d=3.0f; x += d;").into(
+        "int x=2; float d=3.0f; x = (int)(x + d);");
+    // integral with double
+    optimize("void", "int x=2; double d=3.0; x += d;").into(
+        "int x=2; double d=3.0; x = (int)(x + d);");
+    // float and double, don't touch
+    optimize("void", "float x=2; double d=3.0; x += d;").into(
+        "float x=2; double d=3.0; x += d;");
+  }
+  
+  protected boolean optimizeMethod(JProgram program, JMethod method) {
+    PostOptimizationCompoundAssignmentNormalizer.exec(program);
+    LongCastNormalizer.exec(program);
+    return true;
+  }
+}