In r618, I made a change to stop qualifying statics with the window.  This required me to do some comma-expression juggling in assignment statements, since a comma expression could now (illegally) be the LHS of an assignment. Well, I totally missed the fact that there is another context where an LHS is needed - modifying unary operations, "++" and "--".  This patch implements the juggling in these cases as well.  Putting the patch in neatly without duplicating a bunch of code required refactoring JsPrefixOperation and JsPostfixOperation to have a common superclass; most likely it should have always been that way.

While I was at it, I went ahead and reflected the refactorings into the Java AST, even though it's not critical for this patch.

Review by: mmendez
           alex.tkachman

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@728 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JPostfixOperation.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JPostfixOperation.java
index 05c55d8..c78e2e0 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JPostfixOperation.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JPostfixOperation.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -16,41 +16,18 @@
 package com.google.gwt.dev.jjs.ast;
 
 /**
- * Java postfix expression.
+ * Java postfix operation expression.
  */
-public class JPostfixOperation extends JExpression {
-
-  private JExpression arg;
-  private final JUnaryOperator op;
+public class JPostfixOperation extends JUnaryOperation {
 
   public JPostfixOperation(JProgram program, JSourceInfo info,
       JUnaryOperator op, JExpression arg) {
-    super(program, info);
-    this.op = op;
-    this.arg = arg;
-  }
-
-  public JExpression getArg() {
-    return arg;
-  }
-
-  public JUnaryOperator getOp() {
-    return op;
-  }
-
-  public JType getType() {
-    // Unary operators don't change the type of their expression
-    return arg.getType();
-  }
-
-  public boolean hasSideEffects() {
-    return op == JUnaryOperator.DEC || op == JUnaryOperator.INC
-        || arg.hasSideEffects();
+    super(program, info, op, arg);
   }
 
   public void traverse(JVisitor visitor, Context ctx) {
     if (visitor.visit(this, ctx)) {
-      arg = visitor.accept(arg);
+      super.traverse(visitor, ctx);
     }
     visitor.endVisit(this, ctx);
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JPrefixOperation.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JPrefixOperation.java
index e550cbd..a82df68 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JPrefixOperation.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JPrefixOperation.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -18,39 +18,16 @@
 /**
  * Java prefix operation expression.
  */
-public class JPrefixOperation extends JExpression {
+public class JPrefixOperation extends JUnaryOperation {
 
-  private JExpression arg;
-  private final JUnaryOperator op;
-
-  public JPrefixOperation(JProgram program, JSourceInfo info, JUnaryOperator op,
-      JExpression arg) {
-    super(program, info);
-    this.op = op;
-    this.arg = arg;
-  }
-
-  public JExpression getArg() {
-    return arg;
-  }
-
-  public JUnaryOperator getOp() {
-    return op;
-  }
-
-  public JType getType() {
-    // Unary operators don't change the type of their expression
-    return arg.getType();
-  }
-
-  public boolean hasSideEffects() {
-    return getOp() == JUnaryOperator.DEC || getOp() == JUnaryOperator.INC
-        || arg.hasSideEffects();
+  public JPrefixOperation(JProgram program, JSourceInfo info,
+      JUnaryOperator op, JExpression arg) {
+    super(program, info, op, arg);
   }
 
   public void traverse(JVisitor visitor, Context ctx) {
     if (visitor.visit(this, ctx)) {
-      arg = visitor.accept(arg);
+      super.traverse(visitor, ctx);
     }
     visitor.endVisit(this, ctx);
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JUnaryOperation.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JUnaryOperation.java
new file mode 100644
index 0000000..068f3cb
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JUnaryOperation.java
@@ -0,0 +1,53 @@
+/*

+ * Copyright 2007 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.ast;

+

+/**

+ * Java prefix or postfix operation expression.

+ */

+public abstract class JUnaryOperation extends JExpression {

+

+  private JExpression arg;

+  private final JUnaryOperator op;

+

+  public JUnaryOperation(JProgram program, JSourceInfo info,

+      JUnaryOperator op, JExpression arg) {

+    super(program, info);

+    this.op = op;

+    this.arg = arg;

+  }

+

+  public JExpression getArg() {

+    return arg;

+  }

+

+  public JUnaryOperator getOp() {

+    return op;

+  }

+

+  public JType getType() {

+    // Unary operators don't change the type of their expression

+    return arg.getType();

+  }

+

+  public boolean hasSideEffects() {

+    return getOp().isModifying() || arg.hasSideEffects();

+  }

+

+  public void traverse(JVisitor visitor, Context ctx) {

+    arg = visitor.accept(arg);

+  }

+}

diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JUnaryOperator.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JUnaryOperator.java
index 32a2db5..d66851d 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JUnaryOperator.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JUnaryOperator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -36,6 +36,10 @@
     return symbol;
   }
 
+  public boolean isModifying() {
+    return this == INC || this == DEC;
+  }
+
   public String toString() {
     return new String(getSymbol());
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index c3f8d69..cb5ddce 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -123,6 +123,7 @@
 import com.google.gwt.dev.js.ast.JsThisRef;
 import com.google.gwt.dev.js.ast.JsThrow;
 import com.google.gwt.dev.js.ast.JsTry;
+import com.google.gwt.dev.js.ast.JsUnaryOperation;
 import com.google.gwt.dev.js.ast.JsUnaryOperator;
 import com.google.gwt.dev.js.ast.JsVars;
 import com.google.gwt.dev.js.ast.JsWhile;
@@ -441,32 +442,14 @@
           && x.getLhs().getType() instanceof JReferenceType
           && x.getRhs().getType() instanceof JReferenceType) {
         myOp = JsBinaryOperator.REF_NEQ;
-      } 
-      
-      /*
-       * Due to the way clinits are constructed, you can end up with a comma
-       * operation on the lhs, which is illegal.  Juggle things to put the
-       * operator and rhs inside of the comma expression.
-       */
-      if (myOp.isAssignment() && (lhs instanceof JsBinaryOperation)) {
-        // Find the rightmost comma operation
-        JsBinaryOperation curLhs = (JsBinaryOperation) lhs;
-        assert (curLhs.getOperator() == JsBinaryOperator.COMMA);
-        while (curLhs.getArg2() instanceof JsBinaryOperation) {
-          curLhs = (JsBinaryOperation) curLhs.getArg2();
-          assert (curLhs.getOperator() == JsBinaryOperator.COMMA);
-        }
-        // curLhs is now the rightmost comma operation; create an assignment to
-        // the last arg from this binary operation's rhs
-        JsBinaryOperation asg = new JsBinaryOperation(myOp, curLhs.getArg2(), rhs);
-        // replace the rightmost comma expression's last arg with the asg
-        curLhs.setArg2(asg);
-        // repush the entire comma expression
-        push(lhs);
-        return;
       }
 
       JsBinaryOperation binOp = new JsBinaryOperation(myOp, lhs, rhs);
+
+      if (maybeShuffleModifyingBinary(binOp)) {
+        return;
+      }
+
       push(binOp);
     }
 
@@ -675,7 +658,7 @@
        * the result expression ended up on the lhs of an assignment. A hack in
        * in endVisit(JBinaryOperation) rectifies the situation.
        */
-      
+
       // See if we need a clinit
       JsInvocation jsInvocation = maybeCreateClinitCall(field);
       if (jsInvocation != null) {
@@ -983,14 +966,26 @@
 
     // @Override
     public void endVisit(JPostfixOperation x, Context ctx) {
-      push(new JsPostfixOperation(JavaToJsOperatorMap.get(x.getOp()),
-          (JsExpression) pop())); // arg
+      JsUnaryOperation op = new JsPostfixOperation(
+          JavaToJsOperatorMap.get(x.getOp()), ((JsExpression) pop())); // arg
+
+      if (maybeShuffleModifyingUnary(op)) {
+        return;
+      }
+
+      push(op);
     }
 
     // @Override
     public void endVisit(JPrefixOperation x, Context ctx) {
-      push(new JsPrefixOperation(JavaToJsOperatorMap.get(x.getOp()),
-          (JsExpression) pop())); // arg
+      JsUnaryOperation op = new JsPrefixOperation(
+          JavaToJsOperatorMap.get(x.getOp()), ((JsExpression) pop())); // arg
+
+      if (maybeShuffleModifyingUnary(op)) {
+        return;
+      }
+
+      push(op);
     }
 
     // @Override
@@ -1541,6 +1536,66 @@
       return jsInvocation;
     }
 
+    /**
+     * Due to the way clinits are constructed, you can end up with a comma
+     * operation as the argument to a modifying operation, which is illegal.
+     * Juggle things to put the operator inside of the comma expression.
+     * 
+     * TODO: move this to a post-generation normalization pass when JS tree
+     * modifying infrastructure is in place.
+     */
+    private boolean maybeShuffleModifyingBinary(JsBinaryOperation x) {
+      JsBinaryOperator myOp = x.getOperator();
+      JsExpression lhs = x.getArg1();
+      JsExpression rhs = x.getArg2();
+
+      if (myOp.isAssignment() && (lhs instanceof JsBinaryOperation)) {
+        // Find the rightmost comma operation
+        JsBinaryOperation curLhs = (JsBinaryOperation) lhs;
+        assert (curLhs.getOperator() == JsBinaryOperator.COMMA);
+        while (curLhs.getArg2() instanceof JsBinaryOperation) {
+          curLhs = (JsBinaryOperation) curLhs.getArg2();
+          assert (curLhs.getOperator() == JsBinaryOperator.COMMA);
+        }
+        // curArg is now the rightmost comma operation; slide our operation in
+        x.setArg1(curLhs.getArg2());
+        curLhs.setArg2(x);
+        // repush the comma expression
+        push(lhs);
+        return true;
+      }
+      return false;
+    }
+
+    /**
+     * Due to the way clinits are constructed, you can end up with a comma
+     * operation as the argument to a modifying operation, which is illegal.
+     * Juggle things to put the operator inside of the comma expression.
+     * 
+     * TODO: move this to a post-generation normalization pass when JS tree
+     * modifying infrastructure is in place.
+     */
+    private boolean maybeShuffleModifyingUnary(JsUnaryOperation x) {
+      JsUnaryOperator myOp = x.getOperator();
+      JsExpression arg = x.getArg();
+      if (myOp.isModifying() && (arg instanceof JsBinaryOperation)) {
+        // Find the rightmost comma operation
+        JsBinaryOperation curArg = (JsBinaryOperation) arg;
+        assert (curArg.getOperator() == JsBinaryOperator.COMMA);
+        while (curArg.getArg2() instanceof JsBinaryOperation) {
+          curArg = (JsBinaryOperation) curArg.getArg2();
+          assert (curArg.getOperator() == JsBinaryOperator.COMMA);
+        }
+        // curArg is now the rightmost comma operation; slide our operation in
+        x.setArg(curArg.getArg2());
+        curArg.setArg2(x);
+        // repush the comma expression
+        push(arg);
+        return true;
+      }
+      return false;
+    }
+
     private JsNode pop() {
       return (JsNode) nodeStack.pop();
     }
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsPostfixOperation.java b/dev/core/src/com/google/gwt/dev/js/ast/JsPostfixOperation.java
index aa35210..ac1e9a3 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsPostfixOperation.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsPostfixOperation.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -18,36 +18,19 @@
 /**
  * A JavaScript postfix operation.
  */
-public final class JsPostfixOperation extends JsExpression {
-
-  private JsExpression arg;
-
-  private final JsUnaryOperator op;
+public final class JsPostfixOperation extends JsUnaryOperation {
 
   public JsPostfixOperation(JsUnaryOperator op) {
     this(op, null);
   }
 
   public JsPostfixOperation(JsUnaryOperator op, JsExpression arg) {
-    this.op = op;
-    this.arg = arg;
-  }
-
-  public JsExpression getArg() {
-    return arg;
-  }
-
-  public JsUnaryOperator getOperator() {
-    return op;
-  }
-
-  public void setArg(JsExpression arg) {
-    this.arg = arg;
+    super(op, arg);
   }
 
   public void traverse(JsVisitor v) {
     if (v.visit(this)) {
-      arg.traverse(v);
+      super.traverse(v);
     }
     v.endVisit(this);
   }
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsPrefixOperation.java b/dev/core/src/com/google/gwt/dev/js/ast/JsPrefixOperation.java
index 0b30e37..c1b19d7 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsPrefixOperation.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsPrefixOperation.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -18,36 +18,19 @@
 /**
  * A JavaScript prefix operation.
  */
-public final class JsPrefixOperation extends JsExpression {
-
-  private JsExpression arg;
-
-  private final JsUnaryOperator op;
+public final class JsPrefixOperation extends JsUnaryOperation {
 
   public JsPrefixOperation(JsUnaryOperator op) {
     this(op, null);
   }
 
   public JsPrefixOperation(JsUnaryOperator op, JsExpression arg) {
-    this.op = op;
-    this.arg = arg;
-  }
-
-  public JsExpression getArg() {
-    return arg;
-  }
-
-  public JsUnaryOperator getOperator() {
-    return op;
-  }
-
-  public void setArg(JsExpression arg) {
-    this.arg = arg;
+    super(op, arg);
   }
 
   public void traverse(JsVisitor v) {
     if (v.visit(this)) {
-      arg.traverse(v);
+      super.traverse(v);
     }
     v.endVisit(this);
   }
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsUnaryOperation.java b/dev/core/src/com/google/gwt/dev/js/ast/JsUnaryOperation.java
new file mode 100644
index 0000000..de9da19
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsUnaryOperation.java
@@ -0,0 +1,51 @@
+/*

+ * Copyright 2007 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.js.ast;

+

+/**

+ * A JavaScript prefix or postfix operation.

+ */

+public abstract class JsUnaryOperation extends JsExpression {

+

+  private JsExpression arg;

+

+  private final JsUnaryOperator op;

+

+  public JsUnaryOperation(JsUnaryOperator op) {

+    this(op, null);

+  }

+

+  public JsUnaryOperation(JsUnaryOperator op, JsExpression arg) {

+    this.op = op;

+    this.arg = arg;

+  }

+

+  public JsExpression getArg() {

+    return arg;

+  }

+

+  public JsUnaryOperator getOperator() {

+    return op;

+  }

+

+  public void setArg(JsExpression arg) {

+    this.arg = arg;

+  }

+

+  public void traverse(JsVisitor v) {

+    arg.traverse(v);

+  }

+}

diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsUnaryOperator.java b/dev/core/src/com/google/gwt/dev/js/ast/JsUnaryOperator.java
index 7c17de9..430e385 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsUnaryOperator.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsUnaryOperator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -44,4 +44,8 @@
   public boolean isKeyword() {
     return this == DELETE || this == TYPEOF || this == VOID;
   }
+
+  public boolean isModifying() {
+    return this == DEC || this == INC || this == DELETE;
+  }
 }