TypeTightener was incorrectly treating +=.

TypeTightener was treating += as if it were just an assignment
resulting in suboptimal and potentially incorrect results. E.g.
in "String s = null; s += null;", "s" was determined to be always
null where it clearly is "nullnull" after the second statemetn.

Change-Id: Ia8ec7eaf1a94804789752b8da445bec0bdcb702f
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
index bcef5f6..b29c1cf 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
@@ -21,6 +21,7 @@
 import com.google.gwt.dev.jjs.ast.Context;
 import com.google.gwt.dev.jjs.ast.JArrayRef;
 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.JClassType;
 import com.google.gwt.dev.jjs.ast.JConditional;
@@ -223,7 +224,8 @@
       if (x.isAssignment() && (x.getType() instanceof JReferenceType)) {
         JExpression lhs = x.getLhs();
         if (lhs instanceof JVariableRef) {
-          addAssignment(((JVariableRef) lhs).getTarget(), x.getRhs());
+          addAssignment(((JVariableRef) lhs).getTarget(),
+              x.getOp() == JBinaryOperator.ASG ? x.getRhs() : x);
         } else {
           assert lhs instanceof JArrayRef;
         }
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/TypeTightenerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/TypeTightenerTest.java
index fc5c4e8..d66b0a3 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/TypeTightenerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/TypeTightenerTest.java
@@ -45,6 +45,16 @@
     result.intoString("null a = null;", "null.nullField += null;");
   }
 
+  public void testBinaryOperation_notNullable() throws Exception {
+    optimize("void", "String other; String s = \"\"; s += null; if (s == null) { other = \"\"; } ")
+        .intoString("null other;", "String s = \"\";", "s += null;");
+  }
+
+  public void testBinaryOperation_nullableButNotNull() throws Exception {
+    optimize("void", "String s = null; s += null;")
+        .intoString("String s = null;", "s += null;");
+  }
+
   public void testCastOperation() throws Exception {
     addSnippetClassDecl("static class A { " + "String name; public void set() { name = \"A\";} }");
     addSnippetClassDecl("static class B extends A {" + "public void set() { name = \"B\";} }");
diff --git a/user/test/com/google/gwt/emultest/java/lang/StringTest.java b/user/test/com/google/gwt/emultest/java/lang/StringTest.java
index 449995c..958cc9b 100644
--- a/user/test/com/google/gwt/emultest/java/lang/StringTest.java
+++ b/user/test/com/google/gwt/emultest/java/lang/StringTest.java
@@ -519,13 +519,26 @@
     assertTrue("12t", hideFromCompiler("none").matches("^|none$"));
   }
 
-  /*
-   * TODO: needs rewriting to avoid compiler optimizations.
-   */
   public void testNull() {
-    assertNull(returnNull());
-    String a = returnNull() + returnNull();
-    assertEquals("nullnull", a);
+    {
+      assertNull(returnNull());
+      String a = returnNull() + returnNull();
+      assertEquals("nullnull", a);
+
+      String s = returnNull();
+      s += returnNull();
+      assertEquals("nullnull", s);
+    }
+
+    // Same tests allowing the compiler to propagate constants.
+    {
+      String a = null + (String) null;
+      assertEquals("nullnull", a);
+
+      String s = null;
+      s += null;
+      assertEquals("nullnull", s);
+    }
   }
 
   public void testRegionMatches() {
@@ -804,6 +817,11 @@
   }-*/;
 
   private String returnNull() {
+    if (Math.random() < -1) {
+      // Can never happen, but fools the compiler enough not to optimize this call.
+      fail();
+      return "";
+    }
     return null;
   }