Fixes a problem where inlining an JS method with no return statement fails to evaluate to "undefined".

Review by: bobv (postmortem)


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1558 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 801a964..0f94ec2 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsInliner.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsInliner.java
@@ -577,8 +577,8 @@
    * configured to collect idents from qualified xor unqualified JsNameRefs.
    */
   private static class IdentCollector extends JsVisitor {
-    private final Set<String> idents = new HashSet<String>();
     private final boolean collectQualified;
+    private final Set<String> idents = new HashSet<String>();
 
     public IdentCollector(boolean collectQualified) {
       this.collectQualified = collectQualified;
@@ -610,8 +610,8 @@
    * statements if the context of the invocation would allow this.
    */
   private static class InliningVisitor extends JsModVisitor {
-    private final Stack<JsFunction> functionStack = new Stack<JsFunction>();
     private final Set<JsFunction> blacklist = new HashSet<JsFunction>();
+    private final Stack<JsFunction> functionStack = new Stack<JsFunction>();
     private final JsProgram program;
 
     public InliningVisitor(JsProgram program) {
@@ -737,6 +737,7 @@
       List<JsExpression> hoisted = new ArrayList<JsExpression>(
           statements.size());
 
+      boolean sawReturnStatement = false;
       for (JsStatement statement : statements) {
         /*
          * Create replacement expressions to use in place of the original
@@ -753,41 +754,37 @@
 
         hoisted.add(h);
 
-        if (hoistedExpressionTerminal(statement)) {
+        if (isReturnStatement(statement)) {
+          sawReturnStatement = true;
           break;
         }
       }
 
       /*
-       * Determine the expressions that will be used to construct the
-       * replacement expression.
+       * If the inlined method has no return statement, synthesize an undefined
+       * reference. It will be reclaimed if the method call is from a
+       * JsExprStmt.
        */
-      JsExpression op;
+      if (!sawReturnStatement) {
+        hoisted.add(program.getUndefinedLiteral());
+      }
 
-      if (hoisted.size() == 0) {
-        /*
-         * There are no expressions evaluated in the target function, so we'll
-         * try to replace the invocation with nothing. The null literal may be
-         * reclaimed during the JsExprStmt cleanup pass.
-         */
-        op = program.getNullLiteral();
+      assert (hoisted.size() > 0);
 
-      } else {
-        /*
-         * Build up the new comma expression from right-to-left; building the
-         * rightmost comma expressions first. Bootstrapping with i.previous()
-         * ensures that this logic will function correctly in the case of a
-         * single expression.
-         */
-        ListIterator<JsExpression> i = hoisted.listIterator(hoisted.size());
-        op = i.previous();
-        while (i.hasPrevious()) {
-          JsBinaryOperation outerOp = new JsBinaryOperation(
-              JsBinaryOperator.COMMA);
-          outerOp.setArg1(i.previous());
-          outerOp.setArg2(op);
-          op = outerOp;
-        }
+      /*
+       * Build up the new comma expression from right-to-left; building the
+       * rightmost comma expressions first. Bootstrapping with i.previous()
+       * ensures that this logic will function correctly in the case of a single
+       * expression.
+       */
+      ListIterator<JsExpression> i = hoisted.listIterator(hoisted.size());
+      JsExpression op = i.previous();
+      while (i.hasPrevious()) {
+        JsBinaryOperation outerOp = new JsBinaryOperation(
+            JsBinaryOperator.COMMA);
+        outerOp.setArg1(i.previous());
+        outerOp.setArg2(op);
+        op = outerOp;
       }
 
       // Confirm that the expression conforms to the desired heuristics
@@ -890,8 +887,8 @@
    * Generally speaking, we disallow the use of parameters as lvalues.
    */
   private static class ParameterUsageVisitor extends JsVisitor {
-    private final Set<JsName> parameterNames;
     private boolean lvalue = false;
+    private final Set<JsName> parameterNames;
 
     public ParameterUsageVisitor(Set<JsName> parameterNames) {
       this.parameterNames = parameterNames;
@@ -1008,8 +1005,8 @@
    * the lifetime of the program.
    */
   private static class RedefinedFunctionCollector extends JsVisitor {
-    private final Set<JsFunction> redefined = new HashSet<JsFunction>();
     private final Map<JsName, JsFunction> nameMap = new IdentityHashMap<JsName, JsFunction>();
+    private final Set<JsFunction> redefined = new HashSet<JsFunction>();
 
     /**
      * Look for assignments to JsNames whose static references are JsFunctions.
@@ -1157,8 +1154,8 @@
    * identifiers to resolve to different values.
    */
   private static class StableNameChecker extends JsVisitor {
-    private final JsScope callerScope;
     private final JsScope calleeScope;
+    private final JsScope callerScope;
     private final Collection<JsName> parameterNames;
     private boolean stable = true;
 
@@ -1205,15 +1202,6 @@
   }
 
   /**
-   * When attempting to inline an invocation, this constant determines the
-   * maximum allowable ratio of potential inlined complexity to initial
-   * complexity. This acts as a brake on very large expansions from bloating the
-   * the generated output. Increasing this number will allow larger sections of
-   * code to be inlined, but at a cost of larger JS output.
-   */
-  private static final int MAX_COMPLEXITY_INCREASE = 10;
-
-  /**
    * A List of expression types that are known to never be affected by
    * side-effects. Used by {@link #alwaysFlexible(JsExpression)}.
    */
@@ -1223,6 +1211,15 @@
       JsThisRef.class});
 
   /**
+   * When attempting to inline an invocation, this constant determines the
+   * maximum allowable ratio of potential inlined complexity to initial
+   * complexity. This acts as a brake on very large expansions from bloating the
+   * the generated output. Increasing this number will allow larger sections of
+   * code to be inlined, but at a cost of larger JS output.
+   */
+  private static final int MAX_COMPLEXITY_INCREASE = 5;
+
+  /**
    * Static entry point used by JavaToJavaScriptCompiler.
    */
   public static boolean exec(JsProgram program) {
@@ -1328,13 +1325,10 @@
     if (statement instanceof JsExprStmt) {
       JsExprStmt exprStmt = (JsExprStmt) statement;
       expression = exprStmt.getExpression();
-
     } else if (statement instanceof JsReturn) {
       JsReturn ret = (JsReturn) statement;
       expression = ret.getExpr();
-
     } else {
-      // TODO If additional statements are supported, update hETerminal()
       return null;
     }
 
@@ -1342,16 +1336,6 @@
   }
 
   /**
-   * This is used in combination with {@link #hoistedExpression(JsStatement)} to
-   * indicate if a given statement would terminate the list of hoisted
-   * expressions.
-   */
-  private static boolean hoistedExpressionTerminal(JsStatement statement) {
-    // TODO keep this in synch with hoistedExpression
-    return statement instanceof JsReturn;
-  }
-
-  /**
    * Given a JsInvocation, determine if it is invoking a JsFunction that is
    * specified to be executed only once during the program's lifetime.
    */
@@ -1475,6 +1459,15 @@
   }
 
   /**
+   * This is used in combination with {@link #hoistedExpression(JsStatement)} to
+   * indicate if a given statement would terminate the list of hoisted
+   * expressions.
+   */
+  private static boolean isReturnStatement(JsStatement statement) {
+    return statement instanceof JsReturn;
+  }
+
+  /**
    * Utility class.
    */
   private JsInliner() {
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java b/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
index a8ca1bf..9c222b1 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
@@ -139,6 +139,10 @@
     return trueLiteral;
   }
 
+  public JsNameRef getUndefinedLiteral() {
+    return rootScope.findExistingName("undefined").makeRef();
+  }
+
   public void traverse(JsVisitor v, JsContext<JsProgram> ctx) {
     if (v.visit(this, ctx)) {
       v.accept(globalBlock);