Tighten abstract types with only one concrete implementation.
 - Narrow fields, casts, and instanceof  when there is only one concrete implementation of the type
 - Replace polymorphic invocations of abstract methods in a base class with references to the final method defined in a single concrete class
 - Change the return types of (native) methods from abstract types to single concrete types

Patch by: bobv
Review by: scottb


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1532 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 b878e53..e6a2750 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
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.dev.jjs.impl;
 
+import com.google.gwt.dev.jjs.ast.CanBeAbstract;
 import com.google.gwt.dev.jjs.ast.Context;
 import com.google.gwt.dev.jjs.ast.JArrayRef;
 import com.google.gwt.dev.jjs.ast.JBinaryOperation;
@@ -102,8 +103,9 @@
         if (!instance.hasSideEffects()) {
           instance = program.getLiteralNull();
         }
-        JArrayRef arrayRef = new JArrayRef(program, x.getSourceInfo(),
-            instance, program.getLiteralInt(0));
+        JArrayRef arrayRef =
+            new JArrayRef(program, x.getSourceInfo(), instance, program
+                .getLiteralInt(0));
         ctx.replaceMe(arrayRef);
       }
     }
@@ -116,16 +118,18 @@
         // this doesn't really belong here, but while we're here let's remove
         // non-side-effect qualifiers to statics
         if (!instance.hasSideEffects()) {
-          JFieldRef fieldRef = new JFieldRef(program, x.getSourceInfo(), null,
-              x.getField(), x.getEnclosingType());
+          JFieldRef fieldRef =
+              new JFieldRef(program, x.getSourceInfo(), null, x.getField(), x
+                  .getEnclosingType());
           ctx.replaceMe(fieldRef);
         }
       } else if (!isStatic && instance.getType() == typeNull) {
         if (!instance.hasSideEffects()) {
           instance = program.getLiteralNull();
         }
-        JFieldRef fieldRef = new JFieldRef(program, x.getSourceInfo(),
-            instance, program.getNullField(), null);
+        JFieldRef fieldRef =
+            new JFieldRef(program, x.getSourceInfo(), instance, program
+                .getNullField(), null);
         ctx.replaceMe(fieldRef);
       }
     }
@@ -140,8 +144,8 @@
         // this doesn't really belong here, but while we're here let's remove
         // non-side-effect qualifiers to statics
         if (!instance.hasSideEffects()) {
-          JMethodCall newCall = new JMethodCall(program, x.getSourceInfo(),
-              null, x.getTarget());
+          JMethodCall newCall =
+              new JMethodCall(program, x.getSourceInfo(), null, x.getTarget());
           newCall.getArgs().addAll(x.getArgs());
           ctx.replaceMe(newCall);
         }
@@ -150,8 +154,9 @@
         if (!instance.hasSideEffects()) {
           instance = program.getLiteralNull();
         }
-        JMethodCall newCall = new JMethodCall(program, x.getSourceInfo(),
-            instance, program.getNullMethod());
+        JMethodCall newCall =
+            new JMethodCall(program, x.getSourceInfo(), instance, program
+                .getNullMethod());
         ctx.replaceMe(newCall);
       } else if (isStaticImpl && x.getArgs().size() > 0
           && x.getArgs().get(0).getType() == typeNull) {
@@ -160,8 +165,9 @@
         if (!instance.hasSideEffects()) {
           instance = program.getLiteralNull();
         }
-        JMethodCall newCall = new JMethodCall(program, x.getSourceInfo(),
-            instance, program.getNullMethod());
+        JMethodCall newCall =
+            new JMethodCall(program, x.getSourceInfo(), instance, program
+                .getNullMethod());
         ctx.replaceMe(newCall);
       }
     }
@@ -226,7 +232,8 @@
       for (JMethod method : x.overrides) {
         addOverrider(method, x);
       }
-      JMethod[] allVirtualOverrides = program.typeOracle.getAllVirtualOverrides(x);
+      JMethod[] allVirtualOverrides =
+          program.typeOracle.getAllVirtualOverrides(x);
       for (JMethod method : allVirtualOverrides) {
         addOverrider(method, x);
       }
@@ -404,9 +411,19 @@
         ctx.replaceMe(x.getExpr());
       } else if (triviallyFalse) {
         // replace with a magic NULL cast
-        JCastOperation newOp = new JCastOperation(program, x.getSourceInfo(),
-            program.getTypeNull(), x.getExpr());
+        JCastOperation newOp =
+            new JCastOperation(program, x.getSourceInfo(), program
+                .getTypeNull(), x.getExpr());
         ctx.replaceMe(newOp);
+      } else {
+        // If possible, try to use a narrower cast
+        JClassType concreteType = getSingleConcreteType(toType);
+        if (concreteType != null) {
+          JCastOperation newOp =
+              new JCastOperation(program, x.getSourceInfo(), concreteType, x
+                  .getExpr());
+          ctx.replaceMe(newOp);
+        }
       }
     }
 
@@ -444,13 +461,23 @@
       if (triviallyTrue) {
         // replace with a simple null test
         JNullLiteral nullLit = program.getLiteralNull();
-        JBinaryOperation neq = new JBinaryOperation(program, x.getSourceInfo(),
-            program.getTypePrimitiveBoolean(), JBinaryOperator.NEQ,
-            x.getExpr(), nullLit);
+        JBinaryOperation neq =
+            new JBinaryOperation(program, x.getSourceInfo(), program
+                .getTypePrimitiveBoolean(), JBinaryOperator.NEQ, x.getExpr(),
+                nullLit);
         ctx.replaceMe(neq);
       } else if (triviallyFalse) {
         // replace with a false literal
         ctx.replaceMe(program.getLiteralBoolean(false));
+      } else {
+        // If possible, try to use a narrower cast
+        JClassType concreteType = getSingleConcreteType(toType);
+        if (concreteType != null) {
+          JInstanceOf newOp =
+              new JInstanceOf(program, x.getSourceInfo(), concreteType, x
+                  .getExpr());
+          ctx.replaceMe(newOp);
+        }
       }
     }
 
@@ -464,11 +491,15 @@
      */
     @Override
     public void endVisit(JMethod x, Context ctx) {
+      JClassType concreteType = getSingleConcreteType(x.getType());
+      if (concreteType != null) {
+        x.setType(concreteType);
+        myDidChange = true;
+      }
+
       /*
-       * Explicitly NOT visiting native methods since we can't infer type
-       * information.
-       * 
-       * TODO(later): can we figure out simple pass-through info?
+       * The only information that we can infer about native methods is if they
+       * are declared to return a leaf type.
        */
       if (x.isNative()) {
         return;
@@ -521,6 +552,23 @@
       }
     }
 
+    /**
+     * Tighten the target method from the abstract base method to the final
+     * implementation.
+     */
+    @Override
+    public void endVisit(JMethodCall x, Context ctx) {
+      JMethod concreteMethod = getSingleConcreteMethod(x.getTarget());
+      if (concreteMethod != null) {
+        JMethodCall newCall =
+            new JMethodCall(program, x.getSourceInfo(), x.getInstance(),
+                concreteMethod);
+        newCall.getArgs().addAll(x.getArgs());
+
+        ctx.replaceMe(newCall);
+      }
+    }
+
     @Override
     public void endVisit(JParameter x, Context ctx) {
       tighten(x);
@@ -537,15 +585,41 @@
 
     public boolean visit(JMethod x, Context ctx) {
       /*
-       * Explicitly NOT visiting native methods since we can't infer type
-       * information.
-       * 
-       * TODO(later): can we figure out simple pass-through info?
+       * Explicitly NOT visiting native methods since we can't infer further
+       * type information.
        */
       return !x.isNative();
     }
 
     /**
+     * Find a replacement method. If the original method is abstract, this will
+     * return the leaf, final implementation of the method. If the method is
+     * already concrete, but enclosed by an abstract type, the overriding method
+     * from the leaf concrete type will be returned.
+     */
+    private JMethod getSingleConcreteMethod(JMethod method) {
+      if (getSingleConcreteType(method.getEnclosingType()) != null) {
+        return getSingleConcrete(method, overriders);
+      } else {
+        return null;
+      }
+    }
+
+    /**
+     * Given an abstract type, return the single concrete implementation of that
+     * type.
+     */
+    private JClassType getSingleConcreteType(JType type) {
+       if (type instanceof JReferenceType) {
+        JReferenceType refType = (JReferenceType) type;
+        if (refType.isAbstract()) {
+          return getSingleConcrete((JReferenceType) type, implementors);
+        }
+      }
+      return null;
+    }
+
+    /**
      * Tighten based on assignment, and for parameters, callArgs as well.
      */
     private void tighten(JVariable x) {
@@ -558,6 +632,14 @@
         return;
       }
 
+      // tighten based on leaf types
+      JClassType leafType = getSingleConcreteType(refType);
+      if (leafType != null) {
+        x.setType(leafType);
+        myDidChange = true;
+        return;
+      }
+
       // tighten based on non-instantiability
       if (!program.typeOracle.isInstantiatedType(refType)) {
         x.setType(typeNull);
@@ -627,12 +709,48 @@
     set.add(value);
   }
 
-  private final Map<JVariable, Set<JExpression>> assignments = new IdentityHashMap<JVariable, Set<JExpression>>();
-  private final Map<JReferenceType, Set<JClassType>> implementors = new IdentityHashMap<JReferenceType, Set<JClassType>>();
-  private final Map<JMethod, Set<JMethod>> overriders = new IdentityHashMap<JMethod, Set<JMethod>>();
-  private final Map<JParameter, Set<JParameter>> paramUpRefs = new IdentityHashMap<JParameter, Set<JParameter>>();
+  /**
+   * Find exactly one concrete element for a key in a Map of Sets. If there are
+   * none or more than one concrete element, return <code>null</code>.
+   */
+  private static <B, T extends CanBeAbstract> T getSingleConcrete(B x,
+      Map<? super B, Set<T>> map) {
+
+    Set<T> set = map.get(x);
+    // No set, then no concrete version
+    if (set == null) {
+      return null;
+    }
+
+    T toReturn = null;
+    for (T elt : set) {
+      if (elt.isAbstract()) {
+        continue;
+      }
+
+      // If we already have previously seen a concrete element, fail
+      if (toReturn != null) {
+        return null;
+      } else {
+        toReturn = elt;
+      }
+    }
+
+    return toReturn;
+  }
+
+  private final Map<JVariable, Set<JExpression>> assignments =
+      new IdentityHashMap<JVariable, Set<JExpression>>();
+  private final Map<JReferenceType, Set<JClassType>> implementors =
+      new IdentityHashMap<JReferenceType, Set<JClassType>>();
+  private final Map<JMethod, Set<JMethod>> overriders =
+      new IdentityHashMap<JMethod, Set<JMethod>>();
+  private final Map<JParameter, Set<JParameter>> paramUpRefs =
+      new IdentityHashMap<JParameter, Set<JParameter>>();
   private final JProgram program;
-  private final Map<JMethod, Set<JExpression>> returns = new IdentityHashMap<JMethod, Set<JExpression>>();
+  private final Map<JMethod, Set<JExpression>> returns =
+      new IdentityHashMap<JMethod, Set<JExpression>>();
+
   private final JNullType typeNull;
 
   private TypeTightener(JProgram program) {
@@ -663,5 +781,4 @@
     }
     return madeChanges;
   }
-
 }