Allows DuplicateClinitRemover to remove clinits() that appear outside of binary comma expressions.  This prepares for an improved version of InliningVisitor that inlines multi-statement functions.

Changes:
 - Add branch() methods to take care of constructing new contexts
 - Reassign the results of a visit back to the component statements and expressions of composite JsNodes
 - Remove clinits() that appear as individual JsExprStmts

Patch by: bobv
Review by: scottb



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1541 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/js/JsInliner.java b/dev/core/src/com/google/gwt/dev/js/JsInliner.java
index 10d9d01..84e5efb 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsInliner.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsInliner.java
@@ -37,6 +37,7 @@
 import com.google.gwt.dev.js.ast.JsName;
 import com.google.gwt.dev.js.ast.JsNameRef;
 import com.google.gwt.dev.js.ast.JsNew;
+import com.google.gwt.dev.js.ast.JsNode;
 import com.google.gwt.dev.js.ast.JsNullLiteral;
 import com.google.gwt.dev.js.ast.JsParameter;
 import com.google.gwt.dev.js.ast.JsPostfixOperation;
@@ -88,32 +89,64 @@
      */
     private final Set<JsName> called;
 
-    public DuplicateClinitRemover() {
+    private final JsProgram program;
+
+    public DuplicateClinitRemover(JsProgram program) {
+      this.program = program;
       called = new HashSet<JsName>();
     }
 
-    public DuplicateClinitRemover(Set<JsName> alreadyCalled) {
+    public DuplicateClinitRemover(JsProgram program, Set<JsName> alreadyCalled) {
+      this.program = program;
       called = new HashSet<JsName>(alreadyCalled);
     }
 
+    /**
+     * Look for comma expressions that contain duplicate clinit calls and handle
+     * the conditional-evaluation case of logical and/or operations.
+     */
     @Override
     public boolean visit(JsBinaryOperation x, JsContext<JsExpression> ctx) {
-      if ((x.getOperator() == JsBinaryOperator.COMMA)
-          && (x.getArg1() instanceof JsInvocation)) {
+      if (x.getOperator() == JsBinaryOperator.COMMA) {
 
-        JsName clinit = getClinitFromInvocation((JsInvocation) x.getArg1());
-        if ((clinit != null) && called.contains(clinit)) {
+        boolean left = isDuplicateClinit(x.getArg1());
+        boolean right = isDuplicateClinit(x.getArg2());
 
-          // Replace the binary operation with the RHS
-          ctx.replaceMe(x.getArg2());
+        if (left && right) {
+          /*
+           * (clinit(), clinit()) --> delete or null.
+           * 
+           * This construct is very unlikely since the InliningVisitor builds
+           * the comma expressions in a right-nested manner.
+           */
+          if (ctx.canRemove()) {
+            ctx.removeMe();
+            return false;
+          } else {
+            // The return value from a clinit is never used
+            ctx.replaceMe(program.getNullLiteral());
+            return false;
+          }
 
-          // Manually call accept on the RHS to eliminate nested comma
-          // expressions.
-          accept(x.getArg2());
+        } else if (left) {
+          // (clinit(), xyz) --> xyz
+          // This is the common case
+          ctx.replaceMe(accept(x.getArg2()));
+          return false;
 
-          // Don't continue traversing the original binary operation
+        } else if (right) {
+          // (xyz, clinit()) --> xyz
+          // Possible if a clinit() were the last element
+          ctx.replaceMe(accept(x.getArg1()));
           return false;
         }
+
+      } else if (x.getOperator().equals(JsBinaryOperator.AND)
+          || x.getOperator().equals(JsBinaryOperator.OR)) {
+        x.setArg1(accept(x.getArg1()));
+        // Possibility of conditional evaluation of second parameter
+        x.setArg2(branch(x.getArg2()));
+        return false;
       }
 
       return true;
@@ -126,73 +159,90 @@
      */
     @Override
     public boolean visit(JsBlock x, JsContext<JsStatement> ctx) {
-      (new DuplicateClinitRemover(called)).acceptWithInsertRemove(x.getStatements());
+      branch(x.getStatements());
       return false;
     }
 
     @Override
     public boolean visit(JsCase x, JsContext<JsSwitchMember> ctx) {
-      accept(x.getCaseExpr());
-      (new DuplicateClinitRemover(called)).acceptWithInsertRemove(x.getStmts());
+      x.setCaseExpr(accept(x.getCaseExpr()));
+      branch(x.getStmts());
       return false;
     }
 
     @Override
     public boolean visit(JsConditional x, JsContext<JsExpression> ctx) {
-      accept(x.getTestExpression());
-      (new DuplicateClinitRemover(called)).accept(x.getThenExpression());
-      (new DuplicateClinitRemover(called)).accept(x.getElseExpression());
+      x.setTestExpression(accept(x.getTestExpression()));
+      x.setThenExpression(branch(x.getThenExpression()));
+      x.setElseExpression(branch(x.getElseExpression()));
       return false;
     }
 
     @Override
     public boolean visit(JsDefault x, JsContext<JsSwitchMember> ctx) {
-      (new DuplicateClinitRemover(called)).acceptWithInsertRemove(x.getStmts());
+      branch(x.getStmts());
       return false;
     }
 
     @Override
+    public boolean visit(JsExprStmt x, JsContext<JsStatement> ctx) {
+      if (isDuplicateClinit(x.getExpression())) {
+        if (ctx.canRemove()) {
+          ctx.removeMe();
+        } else {
+          ctx.replaceMe(program.getEmptyStmt());
+        }
+        return false;
+
+      } else {
+        return true;
+      }
+    }
+
+    @Override
     public boolean visit(JsFor x, JsContext<JsStatement> ctx) {
       // The JsFor may have an expression xor a variable declaration.
       if (x.getInitExpr() != null) {
-        accept(x.getInitExpr());
+        x.setInitExpr(accept(x.getInitExpr()));
       } else if (x.getInitVars() != null) {
-        accept(x.getInitVars());
+        x.setInitVars(accept(x.getInitVars()));
       }
 
       // The condition is optional
       if (x.getCondition() != null) {
-        accept(x.getCondition());
+        x.setCondition(accept(x.getCondition()));
       }
 
-      // We don't check the increment expression because even if it exists, it
-      // is not guaranteed to be called at all
+      // The increment expression is optional
+      if (x.getIncrExpr() != null) {
+        x.setIncrExpr(branch(x.getIncrExpr()));
+      }
 
       // The body is not guaranteed to be a JsBlock
-      (new DuplicateClinitRemover(called)).accept(x.getBody());
+      x.setBody(branch(x.getBody()));
       return false;
     }
 
     @Override
     public boolean visit(JsForIn x, JsContext<JsStatement> ctx) {
       if (x.getIterExpr() != null) {
-        accept(x.getIterExpr());
+        x.setIterExpr(accept(x.getIterExpr()));
       }
 
-      accept(x.getObjExpr());
+      x.setObjExpr(accept(x.getObjExpr()));
 
       // The body is not guaranteed to be a JsBlock
-      (new DuplicateClinitRemover(called)).accept(x.getBody());
+      x.setBody(branch(x.getBody()));
       return false;
     }
 
     @Override
     public boolean visit(JsIf x, JsContext<JsStatement> ctx) {
-      accept(x.getIfExpr());
+      x.setIfExpr(accept(x.getIfExpr()));
 
-      (new DuplicateClinitRemover(called)).accept(x.getThenStmt());
+      x.setThenStmt(branch(x.getThenStmt()));
       if (x.getElseStmt() != null) {
-        (new DuplicateClinitRemover(called)).accept(x.getElseStmt());
+        x.setElseStmt(branch(x.getElseStmt()));
       }
 
       return false;
@@ -212,12 +262,40 @@
 
     @Override
     public boolean visit(JsWhile x, JsContext<JsStatement> ctx) {
-      accept(x.getCondition());
+      x.setCondition(accept(x.getCondition()));
 
       // The body is not guaranteed to be a JsBlock
-      (new DuplicateClinitRemover(called)).accept(x.getBody());
+      x.setBody(branch(x.getBody()));
       return false;
     }
+
+    private <T extends JsNode<T>> void branch(List<T> x) {
+      DuplicateClinitRemover dup = new DuplicateClinitRemover(program, called);
+      dup.acceptWithInsertRemove(x);
+      didChange |= dup.didChange();
+    }
+
+    private <T extends JsNode<T>> T branch(T x) {
+      DuplicateClinitRemover dup = new DuplicateClinitRemover(program, called);
+      T toReturn = dup.accept(x);
+
+      if ((toReturn != x) && !dup.didChange()) {
+        throw new InternalCompilerException(
+            "node replacement should imply didChange()");
+      }
+
+      didChange |= dup.didChange();
+      return toReturn;
+    }
+
+    private boolean isDuplicateClinit(JsExpression x) {
+      if (!(x instanceof JsInvocation)) {
+        return false;
+      }
+
+      JsName clinit = getClinitFromInvocation((JsInvocation) x);
+      return (clinit != null && called.contains(clinit));
+    }
   }
 
   /**
@@ -730,7 +808,7 @@
     InliningVisitor v = new InliningVisitor();
     v.accept(program);
 
-    DuplicateClinitRemover r = new DuplicateClinitRemover();
+    DuplicateClinitRemover r = new DuplicateClinitRemover(program);
     r.accept(program);
 
     return v.didChange() || r.didChange();