Exposed a method to iterate over the immediate subclasses of a class.  When looking at subtypes don't reexaming fields that have already been look at during the subtype scan.  Doing so can lessen the precision when raw supertypes are involved.

Patch by: mmendez, spoon (pair prog)

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2892 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
index d99d873..c0dccfe 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
@@ -792,9 +792,6 @@
       isSpeculative = false;
     }
 
-    boolean isInstantiable = checkTypeInstantiableNoSubtypes(localLogger,
-        baseType, isSpeculative, path);
-
     JClassType[] typeArgs = NO_JCLASSES;
     JParameterizedType isParameterized = originalType.isParameterized();
     JGenericType baseAsGenericType = baseType.isGenericType();
@@ -837,90 +834,16 @@
       }
     }
 
-    isInstantiable &= parametersOkay;
+    boolean isInstantiable = parametersOkay
+        && qualifiesForSerialization(localLogger, baseType, isSpeculative, path)
+        && checkFields(localLogger, baseType, isSpeculative, path);
 
     boolean anySubtypes = false;
-    if (parametersOkay) {
-      // Speculatively check all subtypes.
-      JClassType[] subtypes = baseType.getSubtypes();
-      if (subtypes.length > 0) {
-        TreeLogger subtypesLogger = localLogger.branch(TreeLogger.DEBUG,
-            "Analyzing subclasses:", null);
-
-        for (JClassType subtype : subtypes) {
-          TreeLogger subtypeLogger = subtypesLogger.branch(TreeLogger.DEBUG,
-              subtype.getParameterizedQualifiedSourceName(), null);
-          Path subtypePath = createSubtypePath(path, subtype, originalType);
-          boolean subInstantiable = checkTypeInstantiableNoSubtypes(
-              subtypeLogger, subtype, true, subtypePath);
-          if (!subInstantiable) {
-            // If the subtype is not instantiable do not check the arguments.
-            continue;
-          }
-
-          JGenericType genericSub = subtype.isGenericType();
-          if (genericSub != null) {
-            TreeLogger paramsLogger = subtypeLogger.branch(TreeLogger.DEBUG,
-                "Checking parameters of '"
-                    + genericSub.getParameterizedQualifiedSourceName() + "'");
-            Map<JTypeParameter, Set<JTypeParameter>> subParamsConstrainedBy = subParamsConstrainedBy(
-                baseType, genericSub);
-            for (int i = 0; i < genericSub.getTypeParameters().length; i++) {
-              JTypeParameter param = genericSub.getTypeParameters()[i];
-              TreeLogger paramLogger = paramsLogger.branch(TreeLogger.DEBUG,
-                  "Checking param '"
-                      + param.getParameterizedQualifiedSourceName() + "'");
-              Set<JTypeParameter> constBy = subParamsConstrainedBy.get(param);
-              if (constBy == null) {
-                subInstantiable &= checkTypeArgument(paramLogger, genericSub,
-                    i, param.getFirstBound(), true, path);
-              } else {
-                boolean paramOK = false;
-                for (JTypeParameter constrained : constBy) {
-                  paramOK |= checkTypeArgument(paramLogger, genericSub, i,
-                      typeArgs[constrained.getOrdinal()], true, path);
-                }
-                subInstantiable &= paramOK;
-              }
-            }
-          } else {
-            /*
-             * The subtype is not generic so it must be a concrete
-             * parameterization of the query type. If the query type is also a
-             * concrete parameterization then we should be able to exclude the
-             * subtype based on an assignability check. If the query type does
-             * contain type parameters then we assume that the subtype should be
-             * included. In order to be certain, we would need to perform a full
-             * unification between the query type and subtype.
-             */
-            if (isParameterized != null) {
-              HashSet<JTypeParameter> typeParamsInQueryType = new HashSet<JTypeParameter>();
-              recordTypeParametersIn(isParameterized, typeParamsInQueryType);
-
-              if (typeParamsInQueryType.isEmpty()) {
-                if (!isParameterized.isAssignableFrom(subtype)) {
-                  subtypeLogger.log(TreeLogger.DEBUG, "Excluding type '"
-                      + subtype.getParameterizedQualifiedSourceName()
-                      + "' because it is not assignable to '"
-                      + isParameterized.getParameterizedQualifiedSourceName()
-                      + "'");
-                  subInstantiable = false;
-                }
-              }
-            }
-          }
-
-          if (subInstantiable) {
-            if (instSubtypes != null) {
-              instSubtypes.add(subtype);
-            }
-
-            // TODO: This is suspect.
-            getTypeInfoComputed(subtype, path).setInstantiable(true);
-            anySubtypes = true;
-          }
-        }
-      }
+    if (parametersOkay && baseType.getSubtypes().length > 0) {
+      TreeLogger subtypesLogger = localLogger.branch(TreeLogger.DEBUG,
+          "Analyzing subclasses:", null);
+      anySubtypes = checkSubtypes(subtypesLogger, originalType, baseType,
+          instSubtypes, typeArgs, path, baseType, false, isInstantiable);
     }
 
     anySubtypes |= isInstantiable;
@@ -939,16 +862,6 @@
     return tic.hasInstantiableSubtypes();
   }
 
-  final boolean checkTypeInstantiableNoSubtypes(TreeLogger logger,
-      JClassType type, boolean isSpeculative, Path path) {
-    if (qualifiesForSerialization(logger, type, isSpeculative, path)) {
-      return type.isEnum() != null
-          || checkFields(logger, type, isSpeculative, path);
-    }
-
-    return false;
-  }
-
   int getTypeParameterExposure(JGenericType type, int index) {
     return getFlowInfo(type, index).getExposure();
   }
@@ -1127,31 +1040,22 @@
     return succeeded;
   }
 
-  private boolean checkFields(TreeLogger logger, JClassType classOrInterface,
-      boolean isSpeculative, Path parent) {
-    TypeInfoComputed typeInfo = getTypeInfoComputed(classOrInterface, parent);
+  /**
+   * Returns <code>true</code> if the declared fields of this type are all
+   * instantiable. As a side-effect it fills in {@link TypeInfoComputed} for all
+   * necessary types.
+   */
+  private boolean checkDeclaredFields(TreeLogger logger,
+      TypeInfoComputed typeInfo, boolean isSpeculative, Path parent) {
 
-    // Check all super type fields first (recursively).
-    JClassType superType = classOrInterface.getSuperclass();
-    if (superType != null
-        && getTypeInfoComputed(superType, parent).isDeclaredSerializable()) {
-      boolean superTypeOk = checkFields(logger, superType, isSpeculative,
-          parent);
-      /*
-       * If my super type did not check out, then I am not instantiable and we
-       * should error out... UNLESS I am *directly* serializable myself, in
-       * which case it's ok for me to be the root of a new instantiable
-       * hierarchy.
-       */
-      if (!superTypeOk && !typeInfo.isDirectlySerializable()) {
-        return false;
-      }
+    JClassType classOrInterface = typeInfo.getType();
+    if (classOrInterface.isEnum() != null) {
+      // The fields of an enum are never serialized; they are always okay.
+      return true;
     }
 
-    if (typeInfo.isManuallySerializable()) {
-      // All fields on a manual serializable are considered speculative.
-      isSpeculative = true;
-    }
+    // All fields on a manual serializable are considered speculative.
+    isSpeculative |= typeInfo.isManuallySerializable();
 
     boolean allSucceeded = true;
     JField[] fields = classOrInterface.getFields();
@@ -1187,9 +1091,158 @@
     if (succeeded) {
       getTypeInfoComputed(classOrInterface, parent).setFieldSerializable();
     }
+
     return succeeded;
   }
 
+  private boolean checkFields(TreeLogger logger, JClassType classOrInterface,
+      boolean isSpeculative, Path parent) {
+    TypeInfoComputed typeInfo = getTypeInfoComputed(classOrInterface, parent);
+
+    // Check all super type fields first (recursively).
+    JClassType superType = classOrInterface.getSuperclass();
+    if (superType != null
+        && getTypeInfoComputed(superType, parent).isDeclaredSerializable()) {
+      boolean superTypeOk = checkFields(logger, superType, isSpeculative,
+          parent);
+      /*
+       * If my super type did not check out, then I am not instantiable and we
+       * should error out... UNLESS I am *directly* serializable myself, in
+       * which case it's ok for me to be the root of a new instantiable
+       * hierarchy.
+       */
+      if (!superTypeOk && !typeInfo.isDirectlySerializable()) {
+        return false;
+      }
+    }
+
+    return checkDeclaredFields(logger, typeInfo, isSpeculative, parent);
+  }
+
+  /**
+   * Returns <code>true</code> if this type or one of its subtypes is
+   * instantiable relative to a known base type.
+   */
+  private boolean checkSubtypes(TreeLogger localLogger,
+      JClassType originalType, JRealClassType baseType,
+      Set<JClassType> instSubtypes, JClassType[] typeArgs, final Path path,
+      JClassType currentSubtype, boolean checkSuperFields,
+      boolean superIsFieldSerializable) {
+    assert (currentSubtype instanceof JRealClassType || currentSubtype.isGenericType() != null);
+
+    Path subtypePath = createSubtypePath(path, currentSubtype, originalType);
+    TypeInfoComputed typeInfo = getTypeInfoComputed(currentSubtype, subtypePath);
+
+    TreeLogger subtypeLogger = localLogger.branch(TreeLogger.DEBUG,
+        currentSubtype.getParameterizedQualifiedSourceName(), null);
+
+    /*
+     * If super does not qualify and I am not directly serializable then I can't
+     * be serializable.
+     */
+    boolean subInstantiable = qualifiesForSerialization(subtypeLogger,
+        currentSubtype, true, subtypePath);
+    if (!superIsFieldSerializable) {
+      subInstantiable &= typeInfo.isDirectlySerializable();
+    }
+
+    if (subInstantiable) {
+      if (checkSuperFields) {
+        subInstantiable &= checkFields(subtypeLogger, typeInfo.getType(), true,
+            subtypePath);
+      } else {
+        subInstantiable &= checkDeclaredFields(subtypeLogger, typeInfo, true,
+            subtypePath);
+      }
+    }
+
+    boolean anySubtypes = false;
+    List<JClassType> immediateSubtypes = TypeHierarchyUtils.getImmediateSubtypes(currentSubtype);
+    for (JClassType immediateSubtype : immediateSubtypes) {
+      if (immediateSubtype.isRawType() != null) {
+        immediateSubtype = immediateSubtype.isRawType().getBaseType();
+      }
+
+      checkSuperFields = currentSubtype.isInterface() != null
+          && immediateSubtype.isClass() != null;
+      anySubtypes |= checkSubtypes(localLogger, originalType, baseType,
+          instSubtypes, typeArgs, path, immediateSubtype, checkSuperFields,
+          subInstantiable);
+    }
+
+    if (!subInstantiable) {
+      // If the subtype is not instantiable do not check the arguments.
+      return anySubtypes;
+    }
+
+    if (currentSubtype == baseType) {
+      return anySubtypes;
+    }
+
+    JGenericType genericSub = currentSubtype.isGenericType();
+    if (genericSub != null) {
+      TreeLogger paramsLogger = subtypeLogger.branch(TreeLogger.DEBUG,
+          "Checking parameters of '"
+              + genericSub.getParameterizedQualifiedSourceName() + "'");
+      Map<JTypeParameter, Set<JTypeParameter>> subParamsConstrainedBy = subParamsConstrainedBy(
+          baseType, genericSub);
+      for (int i = 0; i < genericSub.getTypeParameters().length; i++) {
+        JTypeParameter param = genericSub.getTypeParameters()[i];
+        TreeLogger paramLogger = paramsLogger.branch(TreeLogger.DEBUG,
+            "Checking param '" + param.getParameterizedQualifiedSourceName()
+                + "'");
+        Set<JTypeParameter> constBy = subParamsConstrainedBy.get(param);
+        if (constBy == null) {
+          subInstantiable &= checkTypeArgument(paramLogger, genericSub, i,
+              param.getFirstBound(), true, path);
+        } else {
+          boolean paramOK = false;
+          for (JTypeParameter constrained : constBy) {
+            paramOK |= checkTypeArgument(paramLogger, genericSub, i,
+                typeArgs[constrained.getOrdinal()], true, path);
+          }
+          subInstantiable &= paramOK;
+        }
+      }
+    } else {
+      JParameterizedType isParameterized = originalType.isParameterized();
+
+      /*
+       * The subtype is not generic so it must be a concrete parameterization of
+       * the query type. If the query type is also a concrete parameterization
+       * then we should be able to exclude the subtype based on an assignability
+       * check. If the query type does contain type parameters then we assume
+       * that the subtype should be included. In order to be certain, we would
+       * need to perform a full unification between the query type and subtype.
+       */
+      if (isParameterized != null) {
+        HashSet<JTypeParameter> typeParamsInQueryType = new HashSet<JTypeParameter>();
+        recordTypeParametersIn(isParameterized, typeParamsInQueryType);
+
+        if (typeParamsInQueryType.isEmpty()) {
+          if (!isParameterized.isAssignableFrom(currentSubtype)) {
+            subtypeLogger.log(TreeLogger.DEBUG, "Excluding type '"
+                + currentSubtype.getParameterizedQualifiedSourceName()
+                + "' because it is not assignable to '"
+                + isParameterized.getParameterizedQualifiedSourceName() + "'");
+            subInstantiable = false;
+          }
+        }
+      }
+    }
+
+    if (subInstantiable) {
+      if (instSubtypes != null) {
+        instSubtypes.add(currentSubtype);
+      }
+
+      typeInfo.setInstantiable(true);
+      anySubtypes = true;
+    }
+
+    return anySubtypes;
+  }
+
   /**
    * Check the argument to a parameterized type to see if it will make the type
    * it is applied to be serializable. As a side effect, populates
diff --git a/user/src/com/google/gwt/user/rebind/rpc/TypeHierarchyUtils.java b/user/src/com/google/gwt/user/rebind/rpc/TypeHierarchyUtils.java
index 79feed8..a2fb1e8 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/TypeHierarchyUtils.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/TypeHierarchyUtils.java
@@ -68,6 +68,22 @@
     return Arrays.asList(types.toArray(new JClassType[0]));
   }
 
+  /**
+   * Returns the immediate subtypes of the erased class argument.
+   */
+  public static List<JClassType> getImmediateSubtypes(JClassType clazz) {
+    List<JClassType> immediateSubtypes = new ArrayList<JClassType>();
+    clazz = clazz.getErasedType();
+    for (JClassType subclass : clazz.getSubtypes()) {
+      if (subclass.getSuperclass() == clazz || clazz.isInterface() != null
+          && directlyImplementsInterface(subclass, clazz)) {
+        immediateSubtypes.add(subclass);
+      }
+    }
+
+    return immediateSubtypes;
+  }
+
   private static void addEdge(Map<JClassType, List<JClassType>> adjList,
       JClassType subclass, JClassType clazz) {
     List<JClassType> edges = adjList.get(subclass);
@@ -119,21 +135,6 @@
   }
 
   /**
-   * Returns the immediate subtypes of a given type.
-   */
-  private static List<JClassType> getImmediateSubtypes(JClassType clazz) {
-    List<JClassType> immediateSubtypes = new ArrayList<JClassType>();
-    for (JClassType subclass : clazz.getSubtypes()) {
-      if (subclass.getSuperclass() == clazz || clazz.isInterface() != null
-          && directlyImplementsInterface(subclass, clazz)) {
-        immediateSubtypes.add(subclass);
-      }
-    }
-
-    return immediateSubtypes;
-  }
-
-  /**
    * Given a root type return an adjacency list that is the inverted type
    * hierarchy.
    */