Finishes out basic enum support in the compiler.  Some of the Class object based stuff needed for EnumSet/EnumMap to work correctly is not yet implemented.

Review by: mmendez


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1483 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsonObject.java b/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsonObject.java
index a776064..8602bc0 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsonObject.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsonObject.java
@@ -16,6 +16,7 @@
 package com.google.gwt.dev.jjs.ast.js;
 
 import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JClassType;
 import com.google.gwt.dev.jjs.ast.JExpression;
 import com.google.gwt.dev.jjs.ast.JNode;
 import com.google.gwt.dev.jjs.ast.JProgram;
@@ -54,19 +55,20 @@
     }
   }
 
-  public final List/* <JsonPropInit> */propInits = new ArrayList/* <JsonPropInit> */();
+  public final List<JsonPropInit> propInits = new ArrayList<JsonPropInit>();
 
   public JsonObject(JProgram program) {
     super(program, null);
   }
 
   public JType getType() {
-    return program.getTypeVoid();
+    // If JavaScriptObject type is not available, just return the Object type
+    JClassType jsoType = program.getJavaScriptObject();
+    return (jsoType != null) ? jsoType : program.getTypeJavaLangObject();
   }
 
   public boolean hasSideEffects() {
-    for (int i = 0, c = propInits.size(); i < c; ++i) {
-      JsonPropInit propInit = ((JsonPropInit) propInits.get(i));
+    for (JsonPropInit propInit : propInits) {
       if (propInit.labelExpr.hasSideEffects()
           || propInit.valueExpr.hasSideEffects()) {
         return true;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java b/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
index 51258e9..93873ad 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
@@ -27,7 +27,6 @@
 import com.google.gwt.dev.jjs.ast.JParameter;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JReferenceType;
-import com.google.gwt.dev.jjs.ast.JReturnStatement;
 import com.google.gwt.dev.jjs.ast.JType;
 import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
 import com.google.gwt.dev.js.JsParser;
@@ -41,9 +40,11 @@
 import org.eclipse.jdt.internal.compiler.ASTVisitor;
 import org.eclipse.jdt.internal.compiler.CompilationResult;
 import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
 import org.eclipse.jdt.internal.compiler.ast.Argument;
 import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
 import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
@@ -181,6 +182,14 @@
             false);
         mapThrownExceptions(newMethod, b);
 
+        // Enums have hidden arguments for name and value
+        if (enclosingType instanceof JEnumType) {
+          program.createParameter(info, "enum$name".toCharArray(),
+              program.getTypeJavaLangString(), true, newMethod);
+          program.createParameter(info, "enum$ordinal".toCharArray(),
+              program.getTypePrimitiveInt(), true, newMethod);
+        }
+
         // user args
         mapParameters(newMethod, ctorDecl);
         // original params are now frozen
@@ -231,8 +240,14 @@
         FieldBinding b = fieldDeclaration.binding;
         SourceInfo info = makeSourceInfo(fieldDeclaration);
         JReferenceType enclosingType = (JReferenceType) typeMap.get(scope.enclosingSourceType());
-        createField(info, b, enclosingType,
-            fieldDeclaration.initialization != null);
+        Expression initialization = fieldDeclaration.initialization;
+        if (initialization != null
+            && initialization instanceof AllocationExpression
+            && ((AllocationExpression) initialization).enumConstant != null) {
+          createEnumField(info, b, enclosingType);
+        } else {
+          createField(info, b, enclosingType, initialization != null);
+        }
         return true;
       } catch (Throwable e) {
         throw translateException(fieldDeclaration, e);
@@ -290,17 +305,20 @@
       return process(typeDeclaration);
     }
 
+    private JField createEnumField(SourceInfo info, FieldBinding binding,
+        JReferenceType enclosingType) {
+      JType type = (JType) typeMap.get(binding.type);
+      JField field = program.createEnumField(info, binding.name,
+          (JEnumType) enclosingType, (JClassType) type, binding.original().id);
+      typeMap.put(binding, field);
+      return field;
+    }
+
     private JField createField(SourceInfo info, FieldBinding binding,
         JReferenceType enclosingType, boolean hasInitializer) {
       JType type = (JType) typeMap.get(binding.type);
-      JField field;
-      if (binding.declaringClass.isEnum()) {
-        field = program.createEnumField(info, binding.name,
-            (JEnumType) enclosingType, (JClassType) type, binding.original().id);
-      } else {
-        field = program.createField(info, binding.name, enclosingType, type,
-            binding.isStatic(), binding.isFinal(), hasInitializer);
-      }
+      JField field = program.createField(info, binding.name, enclosingType,
+          type, binding.isStatic(), binding.isFinal(), hasInitializer);
       typeMap.put(binding, field);
       return field;
     }
@@ -400,7 +418,7 @@
       currentFileName = String.valueOf(compResult.fileName);
       SourceTypeBinding binding = typeDeclaration.binding;
       if (binding.isAnnotationType()) {
-        // TODO
+        // Ignore these.
         return false;
       }
       if (binding.constantPoolName() == null) {
@@ -448,10 +466,12 @@
           JInterfaceType superInterface = (JInterfaceType) typeMap.get(superInterfaceBinding);
           type.implments.add(superInterface);
         }
-        typeDecls.add(typeDeclaration);
+
         if (binding.isEnum()) {
-          processEnum(binding, type);
+          processEnumType(binding, (JEnumType) type);
         }
+
+        typeDecls.add(typeDeclaration);
         return true;
       } catch (InternalCompilerException ice) {
         ice.addNode(type);
@@ -461,7 +481,7 @@
       }
     }
 
-    private void processEnum(SourceTypeBinding binding, JReferenceType type) {
+    private void processEnumType(SourceTypeBinding binding, JEnumType type) {
       // Visit the synthetic values() and valueOf() methods.
       for (MethodBinding methodBinding : binding.methods) {
         if (methodBinding instanceof SyntheticMethodBinding) {
@@ -469,22 +489,15 @@
           TypeBinding[] parameters = methodBinding.parameters;
           if (parameters.length == 0) {
             assert newMethod.getName().equals("values");
-            // TODO: hack
-            JMethodBody body = (JMethodBody) newMethod.getBody();
-            body.getStatements().add(
-                new JReturnStatement(program, null, program.getLiteralNull()));
           } else if (parameters.length == 1) {
             assert newMethod.getName().equals("valueOf");
             assert typeMap.get(parameters[0]) == program.getTypeJavaLangString();
             program.createParameter(null, "name".toCharArray(),
                 program.getTypeJavaLangString(), true, newMethod);
-            // TODO: hack
-            JMethodBody body = (JMethodBody) newMethod.getBody();
-            body.getStatements().add(
-                new JReturnStatement(program, null, program.getLiteralNull()));
           } else {
             assert false;
           }
+          newMethod.freezeParamTypes();
         }
       }
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
index 0119aff..e80bdac 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
@@ -37,6 +37,7 @@
 import com.google.gwt.dev.jjs.ast.JDoStatement;
 import com.google.gwt.dev.jjs.ast.JDoubleLiteral;
 import com.google.gwt.dev.jjs.ast.JEnumField;
+import com.google.gwt.dev.jjs.ast.JEnumType;
 import com.google.gwt.dev.jjs.ast.JExpression;
 import com.google.gwt.dev.jjs.ast.JExpressionStatement;
 import com.google.gwt.dev.jjs.ast.JField;
@@ -80,6 +81,7 @@
 import com.google.gwt.dev.jjs.ast.js.JsniFieldRef;
 import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
 import com.google.gwt.dev.jjs.ast.js.JsniMethodRef;
+import com.google.gwt.dev.jjs.ast.js.JsonObject;
 import com.google.gwt.dev.js.ast.JsContext;
 import com.google.gwt.dev.js.ast.JsExpression;
 import com.google.gwt.dev.js.ast.JsFunction;
@@ -260,14 +262,42 @@
       program = this.typeMap.getProgram();
     }
 
+    public void processEnumType(JEnumType type) {
+      // Create a JSNI map for string-based lookup.
+      JField mapField = createEnumValueMap(type);
+
+      // Generate the synthetic values() and valueOf() methods.
+      for (JMethod method : type.methods) {
+        currentMethod = method;
+        if ("values".equals(method.getName())) {
+          if (method.params.size() != 0) {
+            continue;
+          }
+          currentMethodBody = (JMethodBody) method.getBody();
+          writeEnumValuesMethod(type);
+        } else if ("valueOf".equals(method.getName())) {
+          if (method.params.size() != 1) {
+            continue;
+          }
+          if (method.params.get(0).getType() != program.getTypeJavaLangString()) {
+            continue;
+          }
+          currentMethodBody = (JMethodBody) method.getBody();
+          writeEnumValueOfMethod(type, mapField);
+        }
+        currentMethodBody = null;
+        currentMethod = null;
+      }
+    }
+
     /**
      * We emulate static initializers and instance initializers as methods. As
      * in other cases, this gives us: simpler AST, easier to optimize, more like
      * output JavaScript.
      */
     public void processType(TypeDeclaration x) {
-      if (x.binding.isEnum() || x.binding.isAnnotationType()) {
-        // TODO
+      if (x.binding.isAnnotationType()) {
+        // Do not process.
         return;
       }
       currentClass = (JReferenceType) typeMap.get(x.binding);
@@ -331,6 +361,10 @@
           }
         }
 
+        if (x.binding.isEnum()) {
+          processEnumType((JEnumType) currentClass);
+        }
+
         currentClassScope = null;
         currentClass = null;
         currentSeparatorPositions = null;
@@ -569,6 +603,17 @@
           }
         }
 
+        // Enums: wire up synthetic name/ordinal params to the super method.
+        if (enclosingType instanceof JEnumType) {
+          assert (superOrThisCall != null);
+          JVariableRef enumNameRef = createVariableRef(
+              superOrThisCall.getSourceInfo(), ctor.params.get(0));
+          superOrThisCall.getArgs().add(0, enumNameRef);
+          JVariableRef enumOrdinalRef = createVariableRef(
+              superOrThisCall.getSourceInfo(), ctor.params.get(1));
+          superOrThisCall.getArgs().add(1, enumOrdinalRef);
+        }
+
         // optional this or super constructor call
         if (superOrThisCall != null) {
           statements.add(superOrThisCall.makeStatement());
@@ -658,6 +703,13 @@
         call = new JMethodCall(program, info, newInstance, ctor);
       }
 
+      // Enums: hidden arguments for the name and id.
+      if (x.enumConstant != null) {
+        call.getArgs().add(program.getLiteralString(x.enumConstant.name));
+        call.getArgs().add(
+            program.getLiteralInt(x.enumConstant.binding.original().id));
+      }
+
       // Plain old regular user arguments
       if (x.arguments != null) {
         for (int i = 0, n = x.arguments.length; i < n; ++i) {
@@ -1273,6 +1325,11 @@
           initializer = dispProcessExpression(declaration.initialization);
         }
 
+        if (field instanceof JEnumField) {
+          // An enum field must be initialized!
+          assert (initializer instanceof JMethodCall);
+        }
+
         if (initializer instanceof JLiteral) {
           field.constInitializer = (JLiteral) initializer;
         } else if (initializer != null) {
@@ -1768,7 +1825,7 @@
             String varName = String.valueOf(arg.name);
             JParameter param = null;
             for (int i = 0; i < currentMethod.params.size(); ++i) {
-              JParameter paramIt = (JParameter) currentMethod.params.get(i);
+              JParameter paramIt = currentMethod.params.get(i);
               if (varType == paramIt.getType()
                   && varName.equals(paramIt.getName())) {
                 param = paramIt;
@@ -1879,6 +1936,25 @@
       return call;
     }
 
+    private JField createEnumValueMap(JEnumType type) {
+      JsonObject map = new JsonObject(program);
+      for (JEnumField field : type.enumList) {
+        // JSON maps require leading underscores to prevent collisions.
+        JStringLiteral key = program.getLiteralString("_" + field.getName());
+        JFieldRef value = new JFieldRef(program, null, null, field, type);
+        map.propInits.add(new JsonObject.JsonPropInit(program, key, value));
+      }
+      JField mapField = program.createField(null, "enum$map".toCharArray(),
+          type, map.getType(), true, true, true);
+
+      // Initialize in clinit.
+      JMethodBody clinitBody = (JMethodBody) type.methods.get(0).getBody();
+      JExpressionStatement assignment = program.createAssignmentStmt(null,
+          createVariableRef(null, mapField), map);
+      clinitBody.getStatements().add(assignment);
+      return mapField;
+    }
+
     private JLocalDeclarationStatement createLocalDeclaration(SourceInfo info,
         JLocal arrayVar, JExpression value) {
       return new JLocalDeclarationStatement(program, info, new JLocalRef(
@@ -2309,6 +2385,32 @@
           toUnbox, valueMethod);
       return unboxCall;
     }
+
+    private void writeEnumValueOfMethod(JEnumType type, JField mapField) {
+      // return Enum.valueOf(map, name);
+      JFieldRef mapRef = new JFieldRef(program, null, null, mapField, type);
+      JVariableRef nameRef = createVariableRef(null,
+          currentMethod.params.get(0));
+      JMethod delegateTo = program.getIndexedMethod("Enum.valueOf");
+      JMethodCall call = new JMethodCall(program, null, null, delegateTo);
+      call.getArgs().add(mapRef);
+      call.getArgs().add(nameRef);
+      currentMethodBody.getStatements().add(
+          new JReturnStatement(program, null, call));
+    }
+
+    private void writeEnumValuesMethod(JEnumType type) {
+      // return new E[]{A,B,C};
+      JNewArray newExpr = new JNewArray(program, null, program.getTypeArray(
+          type, 1));
+      newExpr.initializers = new ArrayList<JExpression>();
+      for (JEnumField field : type.enumList) {
+        JFieldRef fieldRef = new JFieldRef(program, null, null, field, type);
+        newExpr.initializers.add(fieldRef);
+      }
+      currentMethodBody.getStatements().add(
+          new JReturnStatement(program, null, newExpr));
+    }
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java b/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
index aa37346..ad688a5 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
@@ -23,7 +23,6 @@
 import com.google.gwt.dev.jjs.ast.JBinaryOperator;
 import com.google.gwt.dev.jjs.ast.JClassLiteral;
 import com.google.gwt.dev.jjs.ast.JClassType;
-import com.google.gwt.dev.jjs.ast.JEnumField;
 import com.google.gwt.dev.jjs.ast.JExpression;
 import com.google.gwt.dev.jjs.ast.JField;
 import com.google.gwt.dev.jjs.ast.JFieldRef;
@@ -375,10 +374,6 @@
       if (target.isStatic()) {
         rescue(target.getEnclosingType(), true, false);
       }
-      // TODO: HACK
-      if (target instanceof JEnumField) {
-        rescue(((JEnumField) target).getEnclosingType(), true, true);
-      }
       rescue(target);
       return true;
     }
@@ -570,6 +565,9 @@
         /*
          * Any reference types (except String, which works by default) that take
          * part in a concat must rescue java.lang.Object.toString().
+         * 
+         * TODO: can we narrow the focus by walking up the type heirarchy or
+         * doing explicit toString calls?
          */
         JMethod toStringMethod = program.getIndexedMethod("Object.toString");
         rescue(toStringMethod);
diff --git a/user/super/com/google/gwt/emul/java/lang/Enum.java b/user/super/com/google/gwt/emul/java/lang/Enum.java
index f11649c..151b4b0 100644
--- a/user/super/com/google/gwt/emul/java/lang/Enum.java
+++ b/user/super/com/google/gwt/emul/java/lang/Enum.java
@@ -15,6 +15,8 @@
  */
 package java.lang;
 
+import com.google.gwt.core.client.JavaScriptObject;
+
 /**
  * The first-class representation of an enumeration.
  * 
@@ -22,6 +24,20 @@
  */
 public abstract class Enum<E extends Enum<E>> implements Comparable<E> {
 
+  protected static <T extends Enum<T>> T valueOf(JavaScriptObject map,
+      String name) {
+    T result = Enum.<T>valueOf0(map, "_" + name);
+    if (result == null) {
+      throw new IllegalArgumentException(name);
+    }
+    return result;
+  }
+
+  private static native <T extends Enum<T>> T valueOf0(JavaScriptObject map,
+      String name) /*-{
+    return map[name] || null;
+  }-*/;
+
   // public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)
   // {
   // throw new UnsupportedOperationException("not yet implemented.");
diff --git a/user/test/com/google/gwt/dev/jjs/test/EnumsTest.java b/user/test/com/google/gwt/dev/jjs/test/EnumsTest.java
index 34dfcb9..460ee10 100644
--- a/user/test/com/google/gwt/dev/jjs/test/EnumsTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/EnumsTest.java
@@ -168,28 +168,46 @@
     assertEquals(Basic.A, Basic.valueOf("A"));
     assertEquals(Basic.B, Basic.valueOf("B"));
     assertEquals(Basic.C, Basic.valueOf("C"));
+    try {
+      Basic.valueOf("D");
+      fail("Basic.valueOf(\"D\") -- expected IllegalArgumentException");
+    } catch (IllegalArgumentException e) {
+    }
 
     assertEquals(Complex.A, Complex.valueOf("A"));
     assertEquals(Complex.B, Complex.valueOf("B"));
     assertEquals(Complex.C, Complex.valueOf("C"));
+    try {
+      Complex.valueOf("D");
+      fail("Complex.valueOf(\"D\") -- expected IllegalArgumentException");
+    } catch (IllegalArgumentException e) {
+    }
 
     assertEquals(Subclassing.A, Subclassing.valueOf("A"));
     assertEquals(Subclassing.B, Subclassing.valueOf("B"));
     assertEquals(Subclassing.C, Subclassing.valueOf("C"));
+    try {
+      Subclassing.valueOf("D");
+      fail("Subclassing.valueOf(\"D\") -- expected IllegalArgumentException");
+    } catch (IllegalArgumentException e) {
+    }
   }
 
   public void testValues() {
     Basic[] simples = Basic.values();
+    assertEquals(3, simples.length);
     assertEquals(Basic.A, simples[0]);
     assertEquals(Basic.B, simples[1]);
     assertEquals(Basic.C, simples[2]);
 
     Complex[] complexes = Complex.values();
+    assertEquals(3, complexes.length);
     assertEquals(Complex.A, complexes[0]);
     assertEquals(Complex.B, complexes[1]);
     assertEquals(Complex.C, complexes[2]);
 
     Subclassing[] subs = Subclassing.values();
+    assertEquals(3, subs.length);
     assertEquals(Subclassing.A, subs[0]);
     assertEquals(Subclassing.B, subs[1]);
     assertEquals(Subclassing.C, subs[2]);