CompileModule / GwtAstBuilder

Introduces a new CompileModule action, which triggers the production of decoupled ASTs during CompilationState build.  At present, these ASTs are unusable, but follow-on work to stitch together decoupled ASTs should allow us to eliminate the traditional WebModeCompilerFrontEnd / BuildTypeMap / GenerateJavaAST path to building a GWT AST.

http://gwt-code-reviews.appspot.com/1373805/


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9844 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/CompileModule.java b/dev/core/src/com/google/gwt/dev/CompileModule.java
new file mode 100644
index 0000000..d584dca
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/CompileModule.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright 2011 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;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.dev.CompileTaskRunner.CompileTask;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.javac.StandardGeneratorContext;
+import com.google.gwt.dev.jdt.RebindPermutationOracle;
+import com.google.gwt.dev.jdt.WebModeCompilerFrontEnd;
+import com.google.gwt.dev.jjs.CorrelationFactory;
+import com.google.gwt.dev.jjs.CorrelationFactory.DummyCorrelationFactory;
+import com.google.gwt.dev.jjs.InternalCompilerException;
+import com.google.gwt.dev.jjs.InternalCompilerException.NodeInfo;
+import com.google.gwt.dev.jjs.JJSOptionsImpl;
+import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.impl.BuildTypeMap;
+import com.google.gwt.dev.jjs.impl.GenerateJavaAST;
+import com.google.gwt.dev.jjs.impl.GwtAstBuilder;
+import com.google.gwt.dev.jjs.impl.TypeLinker;
+import com.google.gwt.dev.jjs.impl.TypeMap;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.util.Memory;
+import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
+import com.google.gwt.dev.util.arg.ArgHandlerModuleName;
+import com.google.gwt.dev.util.arg.ArgHandlerOutDir;
+import com.google.gwt.dev.util.arg.OptionOutDir;
+import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
+
+import org.eclipse.jdt.internal.compiler.CompilationResult;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Compiles a GWT module.
+ */
+public class CompileModule {
+
+  static class ArgProcessor extends ArgProcessorBase {
+    public ArgProcessor(CompileModuleOptions options) {
+      registerHandler(new ArgHandlerLogLevel(options));
+      registerHandler(new ArgHandlerOutDir(options) {
+        @Override
+        public String[] getDefaultArgs() {
+          return new String[]{getTag(), "bin"};
+        }
+      });
+      registerHandler(new ArgHandlerModuleName(options));
+    }
+
+    @Override
+    protected String getName() {
+      return CompileModule.class.getName();
+    }
+  }
+
+  interface CompileModuleOptions extends CompileTaskOptions, OptionOutDir {
+  }
+
+  static class CompileModuleOptionsImpl extends CompileTaskOptionsImpl implements
+      CompileModuleOptions {
+
+    private File outDir;
+
+    public CompileModuleOptionsImpl() {
+    }
+
+    public CompileModuleOptionsImpl(CompileModuleOptions other) {
+      copyFrom(other);
+    }
+
+    public void copyFrom(CompileModuleOptions other) {
+      super.copyFrom(other);
+      setOutDir(other.getOutDir());
+    }
+
+    public File getOutDir() {
+      return outDir;
+    }
+
+    public void setOutDir(File outDir) {
+      this.outDir = outDir;
+    }
+  }
+
+  public static JProgram buildGenerateJavaAst(final TreeLogger logger, ModuleDef module,
+      final CompilationState compilationState) throws UnableToCompleteException {
+    final StandardGeneratorContext genCtx =
+        new StandardGeneratorContext(compilationState, module, null, new ArtifactSet(), true);
+    RebindPermutationOracle rpo = new RebindPermutationOracle() {
+      public void clear() {
+      }
+
+      public String[] getAllPossibleRebindAnswers(TreeLogger logger, String sourceTypeName)
+          throws UnableToCompleteException {
+        return new String[0];
+      }
+
+      public CompilationState getCompilationState() {
+        return compilationState;
+      }
+
+      public StandardGeneratorContext getGeneratorContext() {
+        return genCtx;
+      }
+    };
+
+    List<String> allRootTypes = new ArrayList<String>();
+    for (CompilationUnit unit : compilationState.getCompilationUnits()) {
+      allRootTypes.add(unit.getTypeName());
+    }
+    CompilationUnitDeclaration[] goldenCuds =
+        WebModeCompilerFrontEnd.getCompilationUnitDeclarations(logger, allRootTypes
+            .toArray(new String[allRootTypes.size()]), rpo, TypeLinker.NULL_TYPE_LINKER).compiledUnits;
+
+    CorrelationFactory correlator = DummyCorrelationFactory.INSTANCE;
+    JsProgram jsProgram = new JsProgram(correlator);
+    JProgram jprogram = new JProgram(correlator);
+    TypeMap typeMap = new TypeMap(jprogram);
+    TypeDeclaration[] allTypeDeclarations = BuildTypeMap.exec(typeMap, goldenCuds, jsProgram);
+    // BuildTypeMap can uncover syntactic JSNI errors; report & abort
+    checkForErrors(logger, goldenCuds);
+
+    // Compute all super type/sub type info
+    jprogram.typeOracle.computeBeforeAST();
+
+    // (2) Create our own Java AST from the JDT AST.
+    GenerateJavaAST.exec(allTypeDeclarations, typeMap, jprogram, new JJSOptionsImpl());
+
+    // GenerateJavaAST can uncover semantic JSNI errors; report & abort
+    checkForErrors(logger, goldenCuds);
+    return jprogram;
+  }
+
+  public static CompilationState buildGwtAst(TreeLogger logger, ModuleDef module)
+      throws UnableToCompleteException {
+    boolean gwtAstWasEnabled = GwtAstBuilder.ENABLED;
+    try {
+      GwtAstBuilder.ENABLED = true;
+      long start = System.currentTimeMillis();
+      final CompilationState compilationState = module.getCompilationState(logger);
+      logger.log(TreeLogger.INFO, (System.currentTimeMillis() - start)
+          + " time to get compilation state");
+      return compilationState;
+    } finally {
+      GwtAstBuilder.ENABLED = gwtAstWasEnabled;
+    }
+  }
+
+  public static void main(String[] args) {
+    Memory.initialize();
+    if (System.getProperty("gwt.jjs.dumpAst") != null) {
+      System.out.println("Will dump AST to: " + System.getProperty("gwt.jjs.dumpAst"));
+    }
+
+    SpeedTracerLogger.init();
+
+    /*
+     * NOTE: main always exits with a call to System.exit to terminate any
+     * non-daemon threads that were started in Generators. Typically, this is to
+     * shutdown AWT related threads, since the contract for their termination is
+     * still implementation-dependent.
+     */
+    final CompileModuleOptions options = new CompileModuleOptionsImpl();
+    if (new ArgProcessor(options).processArgs(args)) {
+      CompileTask task = new CompileTask() {
+        public boolean run(TreeLogger logger) throws UnableToCompleteException {
+          // TODO: updates?
+          return new CompileModule(options).run(logger);
+        }
+      };
+      if (CompileTaskRunner.runWithAppropriateLogger(options, task)) {
+        // Exit w/ success code.
+        System.exit(0);
+      }
+    }
+    // Exit w/ non-success code.
+    System.exit(1);
+  }
+
+  static UnableToCompleteException logAndTranslateException(TreeLogger logger, Throwable e) {
+    if (e instanceof UnableToCompleteException) {
+      // just rethrow
+      return (UnableToCompleteException) e;
+    } else if (e instanceof InternalCompilerException) {
+      TreeLogger topBranch =
+          logger.branch(TreeLogger.ERROR, "An internal compiler exception occurred", e);
+      List<NodeInfo> nodeTrace = ((InternalCompilerException) e).getNodeTrace();
+      for (NodeInfo nodeInfo : nodeTrace) {
+        SourceInfo info = nodeInfo.getSourceInfo();
+        String msg;
+        if (info != null) {
+          String fileName = info.getFileName();
+          fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
+          fileName = fileName.substring(fileName.lastIndexOf('\\') + 1);
+          msg = "at " + fileName + "(" + info.getStartLine() + "): ";
+        } else {
+          msg = "<no source info>: ";
+        }
+
+        String description = nodeInfo.getDescription();
+        if (description != null) {
+          msg += description;
+        } else {
+          msg += "<no description available>";
+        }
+        TreeLogger nodeBranch = topBranch.branch(TreeLogger.ERROR, msg, null);
+        String className = nodeInfo.getClassName();
+        if (className != null) {
+          nodeBranch.log(TreeLogger.INFO, className, null);
+        }
+      }
+      return new UnableToCompleteException();
+    } else if (e instanceof VirtualMachineError) {
+      // Always rethrow VM errors (an attempt to wrap may fail).
+      throw (VirtualMachineError) e;
+    } else {
+      logger.log(TreeLogger.ERROR, "Unexpected internal compiler error", e);
+      return new UnableToCompleteException();
+    }
+  }
+
+  private static void checkForErrors(TreeLogger logger, CompilationUnitDeclaration[] goldenCuds)
+      throws UnableToCompleteException {
+    for (CompilationUnitDeclaration cud : goldenCuds) {
+      CompilationResult result = cud.compilationResult();
+      if (result.hasErrors()) {
+        logger.log(TreeLogger.ERROR, "Aborting on '" + String.valueOf(cud.getFileName()) + "'");
+        throw new UnableToCompleteException();
+      }
+    }
+  }
+
+  private final CompileModuleOptionsImpl options;
+
+  public CompileModule(CompileModuleOptions options) {
+    this.options = new CompileModuleOptionsImpl(options);
+  }
+
+  public boolean run(final TreeLogger logger) {
+    try {
+      ModuleDef module = ModuleDefLoader.loadFromClassPath(logger, options.getModuleNames().get(0));
+      final CompilationState compilationState = buildGwtAst(logger, module);
+
+      long start = System.currentTimeMillis();
+      Map<String, JDeclaredType> compStateTypes = new HashMap<String, JDeclaredType>();
+      for (CompilationUnit unit : compilationState.getCompilationUnits()) {
+        for (JDeclaredType type : unit.getTypes()) {
+          compStateTypes.put(type.getName(), type);
+        }
+      }
+      logger.log(TreeLogger.INFO, (System.currentTimeMillis() - start) + " time to get all types");
+
+      start = System.currentTimeMillis();
+      JProgram jprogram = buildGenerateJavaAst(logger, module, compilationState);
+      logger.log(TreeLogger.INFO, (System.currentTimeMillis() - start) + " time to build old AST");
+
+      for (JDeclaredType genJavaAstType : jprogram.getDeclaredTypes()) {
+        String typeName = genJavaAstType.getName();
+        if ("com.google.gwt.core.client.JavaScriptObject".equals(typeName)) {
+          // Known mismatch; genJavaAst version implements all JSO interfaces.
+          continue;
+        }
+        JDeclaredType compStateType = compStateTypes.get(typeName);
+        if (compStateType == null) {
+          System.out.println("No matching prebuilt type for '" + typeName + "'");
+        } else {
+          String oldSource = genJavaAstType.toSource();
+          String newSource = compStateType.toSource();
+          if (!oldSource.equals(newSource)) {
+            System.out.println("Mismatched output for '" + typeName + "'");
+            System.out.println("GenerateJavaAST:");
+            System.out.println(oldSource);
+            System.out.println("GwtAstBuilder:");
+            System.out.println(newSource);
+          }
+        }
+      }
+
+      return !compilationState.hasErrors();
+    } catch (Throwable e) {
+      logAndTranslateException(logger, e);
+      return false;
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/javac/CachedCompilationUnit.java b/dev/core/src/com/google/gwt/dev/javac/CachedCompilationUnit.java
index 788194b..66e8c52 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CachedCompilationUnit.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CachedCompilationUnit.java
@@ -15,6 +15,9 @@
  */
 package com.google.gwt.dev.javac;
 
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
+import com.google.gwt.dev.util.collect.Lists;
+
 import org.eclipse.jdt.core.compiler.CategorizedProblem;
 
 import java.io.IOException;
@@ -107,6 +110,12 @@
   }
 
   @Override
+  public List<JDeclaredType> getTypes() {
+    // TODO(scottb): implement.
+    return Lists.create();
+  }
+
+  @Override
   public boolean isError() {
     return isError;
   }
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java b/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java
index b7991d7..8d18de3 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java
@@ -21,6 +21,8 @@
 import com.google.gwt.dev.javac.JdtCompiler.AdditionalTypeProviderDelegate;
 import com.google.gwt.dev.javac.JdtCompiler.UnitProcessor;
 import com.google.gwt.dev.jjs.CorrelationFactory.DummyCorrelationFactory;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
+import com.google.gwt.dev.jjs.impl.GwtAstBuilder;
 import com.google.gwt.dev.js.ast.JsRootScope;
 import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.util.StringInterner;
@@ -111,9 +113,17 @@
           Dependencies dependencies =
               new Dependencies(packageName, unresolvedQualified, unresolvedSimple, apiRefs);
 
+          List<JDeclaredType> types = Collections.emptyList();
+          if (GwtAstBuilder.ENABLED) {
+            if (!cud.compilationResult().hasErrors()) {
+              // Make a GWT AST.
+              types = astBuilder.process(cud, jsniMethods, jsniRefs);
+            }
+          }
+
           CompilationUnit unit =
-              builder.build(compiledClasses, dependencies, jsniMethods.values(), methodArgs, cud
-                  .compilationResult().getProblems());
+              builder.build(compiledClasses, types, dependencies, jsniMethods.values(), methodArgs,
+                  cud.compilationResult().getProblems());
           addValidUnit(unit);
           // Cache the valid unit for future compiles.
           ContentId contentId = builder.getContentId();
@@ -140,6 +150,8 @@
      */
     private final Map<String, CompiledClass> allValidClasses = new HashMap<String, CompiledClass>();
 
+    private final GwtAstBuilder astBuilder = new GwtAstBuilder();
+
     /**
      * The JDT compiler.
      */
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java b/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
index 44c73e3..ee23e0d 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
@@ -19,6 +19,7 @@
 import com.google.gwt.dev.asm.ClassReader;
 import com.google.gwt.dev.asm.Opcodes;
 import com.google.gwt.dev.asm.commons.EmptyVisitor;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
 import com.google.gwt.dev.util.DiskCache;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.collect.HashMap;
@@ -291,6 +292,11 @@
    */
   public abstract String getTypeName();
 
+  /**
+   * Returns the GWT AST types in this unit.
+   */
+  public abstract List<JDeclaredType> getTypes();
+
   @Deprecated
   public final boolean hasAnonymousClasses() {
     for (CompiledClass cc : getCompiledClasses()) {
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationUnitBuilder.java b/dev/core/src/com/google/gwt/dev/javac/CompilationUnitBuilder.java
index 8b701cb..0b72e1b 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationUnitBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationUnitBuilder.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.dev.javac;
 
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
 import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.util.Util;
 
@@ -60,11 +61,11 @@
 
     @Override
     protected CompilationUnit makeUnit(List<CompiledClass> compiledClasses,
-        Dependencies dependencies,
+        List<JDeclaredType> types, Dependencies dependencies,
         Collection<? extends JsniMethod> jsniMethods,
         MethodArgNamesLookup methodArgs, CategorizedProblem[] problems) {
       return new GeneratedCompilationUnit(generatedUnit, compiledClasses,
-          dependencies, jsniMethods, methodArgs, problems);
+          types, dependencies, jsniMethods, methodArgs, problems);
     }
 
     @Override
@@ -145,11 +146,12 @@
 
     @Override
     protected CompilationUnit makeUnit(List<CompiledClass> compiledClasses,
-        Dependencies dependencies,
+        List<JDeclaredType> types, Dependencies dependencies,
         Collection<? extends JsniMethod> jsniMethods,
         MethodArgNamesLookup methodArgs, CategorizedProblem[] problems) {
       return new SourceFileCompilationUnit(getResource(), contentId,
-          compiledClasses, dependencies, jsniMethods, methodArgs, problems);
+          compiledClasses, types, dependencies, jsniMethods, methodArgs,
+          problems);
     }
   }
 
@@ -158,10 +160,12 @@
     private final GeneratedUnit generatedUnit;
 
     public GeneratedCompilationUnit(GeneratedUnit generatedUnit,
-        List<CompiledClass> compiledClasses, Dependencies dependencies,
+        List<CompiledClass> compiledClasses, List<JDeclaredType> types,
+        Dependencies dependencies,
         Collection<? extends JsniMethod> jsniMethods,
         MethodArgNamesLookup methodArgs, CategorizedProblem[] problems) {
-      super(compiledClasses, dependencies, jsniMethods, methodArgs, problems);
+      super(compiledClasses, types, dependencies, jsniMethods, methodArgs,
+          problems);
       this.generatedUnit = generatedUnit;
     }
 
@@ -241,12 +245,13 @@
   }
 
   public CompilationUnit build(List<CompiledClass> compiledClasses,
-      Dependencies dependencies, Collection<? extends JsniMethod> jsniMethods,
+      List<JDeclaredType> types, Dependencies dependencies,
+      Collection<? extends JsniMethod> jsniMethods,
       MethodArgNamesLookup methodArgs, CategorizedProblem[] problems) {
     // Free the source now.
     source = null;
-    return makeUnit(compiledClasses, dependencies, jsniMethods, methodArgs,
-        problems);
+    return makeUnit(compiledClasses, types, dependencies, jsniMethods,
+        methodArgs, problems);
   }
 
   public abstract ContentId getContentId();
@@ -270,8 +275,8 @@
   protected abstract String doGetSource();
 
   protected abstract CompilationUnit makeUnit(
-      List<CompiledClass> compiledClasses, Dependencies dependencies,
-      Collection<? extends JsniMethod> jsniMethods,
+      List<CompiledClass> compiledClasses, List<JDeclaredType> types,
+      Dependencies dependencies, Collection<? extends JsniMethod> jsniMethods,
       MethodArgNamesLookup methodArgs, CategorizedProblem[] errors);
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationUnitImpl.java b/dev/core/src/com/google/gwt/dev/javac/CompilationUnitImpl.java
index 3248fd8..a16776e 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationUnitImpl.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationUnitImpl.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.dev.javac;
 
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
 import com.google.gwt.dev.util.collect.Lists;
 
 import org.eclipse.jdt.core.compiler.CategorizedProblem;
@@ -26,15 +27,18 @@
 
   private final Dependencies dependencies;
   private final List<CompiledClass> exposedCompiledClasses;
+  private final List<JDeclaredType> exposedTypes;
   private final boolean hasErrors;
   private final List<JsniMethod> jsniMethods;
   private final MethodArgNamesLookup methodArgs;
   private final CategorizedProblem[] problems;
 
   public CompilationUnitImpl(List<CompiledClass> compiledClasses,
-      Dependencies dependencies, Collection<? extends JsniMethod> jsniMethods,
+      List<JDeclaredType> types, Dependencies dependencies,
+      Collection<? extends JsniMethod> jsniMethods,
       MethodArgNamesLookup methodArgs, CategorizedProblem[] problems) {
     this.exposedCompiledClasses = Lists.normalizeUnmodifiable(compiledClasses);
+    this.exposedTypes = Lists.normalizeUnmodifiable(types);
     this.dependencies = dependencies;
     this.jsniMethods = Lists.create(jsniMethods.toArray(new JsniMethod[jsniMethods.size()]));
     this.methodArgs = methodArgs;
@@ -64,6 +68,11 @@
   }
 
   @Override
+  public List<JDeclaredType> getTypes() {
+    return exposedTypes;
+  }
+
+  @Override
   public boolean isError() {
     return hasErrors;
   }
diff --git a/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java b/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java
index 37350b8..c55229f 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java
@@ -257,18 +257,11 @@
     checker.check();
   }
 
-  static String errAlreadyImplemented(String intfName, String impl1,
-      String impl2) {
-    return "Only one JavaScriptObject type may implement the methods of an "
-        + "interface that declared methods. The interface (" + intfName
-        + ") is implemented by both (" + impl1 + ") and (" + impl2 + ")";
-  }
-
   /**
    * Returns {@code true} if {@code typeBinding} is {@code JavaScriptObject} or
    * any subtype.
    */
-  static boolean isJso(TypeBinding typeBinding) {
+  public static boolean isJso(TypeBinding typeBinding) {
     if (!(typeBinding instanceof ReferenceBinding)) {
       return false;
     }
@@ -286,7 +279,7 @@
    * Returns {@code true} if {@code typeBinding} is a subtype of
    * {@code JavaScriptObject}, but not {@code JavaScriptObject} itself.
    */
-  static boolean isJsoSubclass(TypeBinding typeBinding) {
+  public static boolean isJsoSubclass(TypeBinding typeBinding) {
     if (!(typeBinding instanceof ReferenceBinding)) {
       return false;
     }
@@ -294,6 +287,13 @@
     return isJso(binding.superclass());
   }
 
+  static String errAlreadyImplemented(String intfName, String impl1,
+      String impl2) {
+    return "Only one JavaScriptObject type may implement the methods of an "
+        + "interface that declared methods. The interface (" + intfName
+        + ") is implemented by both (" + impl1 + ") and (" + impl2 + ")";
+  }
+
   private static void errorOn(ASTNode node, CompilationUnitDeclaration cud,
       String error) {
     GWTProblem.recordError(node, cud, error, new InstalledHelpInfo(
diff --git a/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
index 17de260..f7e6ab9 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.dev.jdt.TypeRefVisitor;
 import com.google.gwt.dev.jjs.InternalCompilerException;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
 import com.google.gwt.dev.util.Name.BinaryName;
 import com.google.gwt.dev.util.collect.Lists;
 import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
@@ -124,7 +125,8 @@
 
     public void process(CompilationUnitBuilder builder,
         CompilationUnitDeclaration cud, List<CompiledClass> compiledClasses) {
-      CompilationUnit unit = builder.build(compiledClasses, new Dependencies(),
+      CompilationUnit unit = builder.build(compiledClasses,
+          Collections.<JDeclaredType> emptyList(), new Dependencies(),
           Collections.<JsniMethod> emptyList(), new MethodArgNamesLookup(),
           cud.compilationResult().getProblems());
       results.add(unit);
diff --git a/dev/core/src/com/google/gwt/dev/javac/SourceFileCompilationUnit.java b/dev/core/src/com/google/gwt/dev/javac/SourceFileCompilationUnit.java
index 44fc619..d57e611 100644
--- a/dev/core/src/com/google/gwt/dev/javac/SourceFileCompilationUnit.java
+++ b/dev/core/src/com/google/gwt/dev/javac/SourceFileCompilationUnit.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.dev.javac;
 
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
 import com.google.gwt.dev.resource.Resource;
 
 import org.eclipse.jdt.core.compiler.CategorizedProblem;
@@ -39,10 +40,11 @@
   private final ContentId contentId;
 
   public SourceFileCompilationUnit(Resource sourceFile, ContentId contentId,
-      List<CompiledClass> compiledClasses, Dependencies dependencies,
-      Collection<? extends JsniMethod> jsniMethods,
+      List<CompiledClass> compiledClasses, List<JDeclaredType> types,
+      Dependencies dependencies, Collection<? extends JsniMethod> jsniMethods,
       MethodArgNamesLookup methodArgs, CategorizedProblem[] problems) {
-    super(compiledClasses, dependencies, jsniMethods, methodArgs, problems);
+    super(compiledClasses, types, dependencies, jsniMethods, methodArgs,
+        problems);
     this.sourceFile = sourceFile;
     this.contentId = contentId;
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java
index 9cc04c0..eeb174b 100755
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java
@@ -92,6 +92,15 @@
   }
 
   /**
+   * TODO: remove me, just for source compatibility.
+   */
+  @Deprecated
+  public void addField(int index, JField field) {
+    assert field.getEnclosingType() == this;
+    fields = Lists.add(fields, index, field);
+  }
+
+  /**
    * Adds a field to this type.
    */
   public void addField(JField field) {
@@ -298,7 +307,7 @@
     annotations = (List<JAnnotation>) stream.readObject();
   }
 
-/**
+  /**
    * See {@link #writeMethodBodies(ObjectOutputStream).
    * 
    * @see #writeMethodBodies(ObjectOutputStream)
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JNullType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JNullType.java
index eccad57..dea6b2e 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JNullType.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JNullType.java
@@ -27,7 +27,7 @@
   public static final JNullType INSTANCE = new JNullType(SourceOrigin.UNKNOWN);
 
   private JNullType(SourceInfo sourceInfo) {
-    super(sourceInfo, "<null>");
+    super(sourceInfo, "null");
   }
 
   @Override
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
new file mode 100644
index 0000000..387b37e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
@@ -0,0 +1,3236 @@
+/*
+ * Copyright 2010 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.impl;
+
+import com.google.gwt.dev.javac.JSORestrictionsChecker;
+import com.google.gwt.dev.javac.JsniMethod;
+import com.google.gwt.dev.jdt.SafeASTVisitor;
+import com.google.gwt.dev.jjs.InternalCompilerException;
+import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.SourceOrigin;
+import com.google.gwt.dev.jjs.ast.JAbsentArrayDimension;
+import com.google.gwt.dev.jjs.ast.JArrayLength;
+import com.google.gwt.dev.jjs.ast.JArrayRef;
+import com.google.gwt.dev.jjs.ast.JArrayType;
+import com.google.gwt.dev.jjs.ast.JAssertStatement;
+import com.google.gwt.dev.jjs.ast.JBinaryOperation;
+import com.google.gwt.dev.jjs.ast.JBinaryOperator;
+import com.google.gwt.dev.jjs.ast.JBlock;
+import com.google.gwt.dev.jjs.ast.JBooleanLiteral;
+import com.google.gwt.dev.jjs.ast.JBreakStatement;
+import com.google.gwt.dev.jjs.ast.JCaseStatement;
+import com.google.gwt.dev.jjs.ast.JCastOperation;
+import com.google.gwt.dev.jjs.ast.JCharLiteral;
+import com.google.gwt.dev.jjs.ast.JClassLiteral;
+import com.google.gwt.dev.jjs.ast.JClassType;
+import com.google.gwt.dev.jjs.ast.JConditional;
+import com.google.gwt.dev.jjs.ast.JConstructor;
+import com.google.gwt.dev.jjs.ast.JContinueStatement;
+import com.google.gwt.dev.jjs.ast.JDeclarationStatement;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
+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;
+import com.google.gwt.dev.jjs.ast.JField.Disposition;
+import com.google.gwt.dev.jjs.ast.JFieldRef;
+import com.google.gwt.dev.jjs.ast.JFloatLiteral;
+import com.google.gwt.dev.jjs.ast.JForStatement;
+import com.google.gwt.dev.jjs.ast.JIfStatement;
+import com.google.gwt.dev.jjs.ast.JInstanceOf;
+import com.google.gwt.dev.jjs.ast.JIntLiteral;
+import com.google.gwt.dev.jjs.ast.JInterfaceType;
+import com.google.gwt.dev.jjs.ast.JLabel;
+import com.google.gwt.dev.jjs.ast.JLabeledStatement;
+import com.google.gwt.dev.jjs.ast.JLiteral;
+import com.google.gwt.dev.jjs.ast.JLocal;
+import com.google.gwt.dev.jjs.ast.JLocalRef;
+import com.google.gwt.dev.jjs.ast.JLongLiteral;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JMethodBody;
+import com.google.gwt.dev.jjs.ast.JMethodCall;
+import com.google.gwt.dev.jjs.ast.JNewArray;
+import com.google.gwt.dev.jjs.ast.JNewInstance;
+import com.google.gwt.dev.jjs.ast.JNode;
+import com.google.gwt.dev.jjs.ast.JNullLiteral;
+import com.google.gwt.dev.jjs.ast.JNullType;
+import com.google.gwt.dev.jjs.ast.JParameter;
+import com.google.gwt.dev.jjs.ast.JParameterRef;
+import com.google.gwt.dev.jjs.ast.JPostfixOperation;
+import com.google.gwt.dev.jjs.ast.JPrefixOperation;
+import com.google.gwt.dev.jjs.ast.JPrimitiveType;
+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.JStatement;
+import com.google.gwt.dev.jjs.ast.JStringLiteral;
+import com.google.gwt.dev.jjs.ast.JSwitchStatement;
+import com.google.gwt.dev.jjs.ast.JThisRef;
+import com.google.gwt.dev.jjs.ast.JThrowStatement;
+import com.google.gwt.dev.jjs.ast.JTryStatement;
+import com.google.gwt.dev.jjs.ast.JType;
+import com.google.gwt.dev.jjs.ast.JUnaryOperator;
+import com.google.gwt.dev.jjs.ast.JVariable;
+import com.google.gwt.dev.jjs.ast.JWhileStatement;
+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.js.JsAbstractSymbolResolver;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsExpression;
+import com.google.gwt.dev.js.ast.JsFunction;
+import com.google.gwt.dev.js.ast.JsModVisitor;
+import com.google.gwt.dev.js.ast.JsName;
+import com.google.gwt.dev.js.ast.JsNameRef;
+import com.google.gwt.dev.js.ast.JsNode;
+import com.google.gwt.dev.js.ast.JsParameter;
+import com.google.gwt.dev.util.StringInterner;
+
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.internal.compiler.ast.AND_AND_Expression;
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
+import org.eclipse.jdt.internal.compiler.ast.AnnotationMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Argument;
+import org.eclipse.jdt.internal.compiler.ast.ArrayAllocationExpression;
+import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
+import org.eclipse.jdt.internal.compiler.ast.ArrayReference;
+import org.eclipse.jdt.internal.compiler.ast.AssertStatement;
+import org.eclipse.jdt.internal.compiler.ast.Assignment;
+import org.eclipse.jdt.internal.compiler.ast.BinaryExpression;
+import org.eclipse.jdt.internal.compiler.ast.Block;
+import org.eclipse.jdt.internal.compiler.ast.BreakStatement;
+import org.eclipse.jdt.internal.compiler.ast.CaseStatement;
+import org.eclipse.jdt.internal.compiler.ast.CastExpression;
+import org.eclipse.jdt.internal.compiler.ast.CharLiteral;
+import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess;
+import org.eclipse.jdt.internal.compiler.ast.Clinit;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.CompoundAssignment;
+import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression;
+import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.ContinueStatement;
+import org.eclipse.jdt.internal.compiler.ast.DoStatement;
+import org.eclipse.jdt.internal.compiler.ast.DoubleLiteral;
+import org.eclipse.jdt.internal.compiler.ast.EmptyStatement;
+import org.eclipse.jdt.internal.compiler.ast.EqualExpression;
+import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.ExtendedStringLiteral;
+import org.eclipse.jdt.internal.compiler.ast.FalseLiteral;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.FieldReference;
+import org.eclipse.jdt.internal.compiler.ast.FloatLiteral;
+import org.eclipse.jdt.internal.compiler.ast.ForStatement;
+import org.eclipse.jdt.internal.compiler.ast.ForeachStatement;
+import org.eclipse.jdt.internal.compiler.ast.IfStatement;
+import org.eclipse.jdt.internal.compiler.ast.Initializer;
+import org.eclipse.jdt.internal.compiler.ast.InstanceOfExpression;
+import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
+import org.eclipse.jdt.internal.compiler.ast.LabeledStatement;
+import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.LongLiteral;
+import org.eclipse.jdt.internal.compiler.ast.MarkerAnnotation;
+import org.eclipse.jdt.internal.compiler.ast.MessageSend;
+import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.NameReference;
+import org.eclipse.jdt.internal.compiler.ast.NormalAnnotation;
+import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
+import org.eclipse.jdt.internal.compiler.ast.OR_OR_Expression;
+import org.eclipse.jdt.internal.compiler.ast.PostfixExpression;
+import org.eclipse.jdt.internal.compiler.ast.PrefixExpression;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedAllocationExpression;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedSuperReference;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedThisReference;
+import org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
+import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation;
+import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
+import org.eclipse.jdt.internal.compiler.ast.Statement;
+import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
+import org.eclipse.jdt.internal.compiler.ast.StringLiteralConcatenation;
+import org.eclipse.jdt.internal.compiler.ast.SuperReference;
+import org.eclipse.jdt.internal.compiler.ast.SwitchStatement;
+import org.eclipse.jdt.internal.compiler.ast.SynchronizedStatement;
+import org.eclipse.jdt.internal.compiler.ast.ThisReference;
+import org.eclipse.jdt.internal.compiler.ast.ThrowStatement;
+import org.eclipse.jdt.internal.compiler.ast.TrueLiteral;
+import org.eclipse.jdt.internal.compiler.ast.TryStatement;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.UnaryExpression;
+import org.eclipse.jdt.internal.compiler.ast.WhileStatement;
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.Binding;
+import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
+import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
+import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
+import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
+import org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
+import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
+import org.eclipse.jdt.internal.compiler.lookup.NestedTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
+import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.SyntheticArgumentBinding;
+import org.eclipse.jdt.internal.compiler.lookup.SyntheticMethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
+import org.eclipse.jdt.internal.compiler.lookup.VariableBinding;
+import org.eclipse.jdt.internal.compiler.util.Util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * Constructs a GWT Java AST from a single isolated compilation unit. The AST is
+ * not associated with any {@link com.google.gwt.dev.jjs.ast.JProgram} and will
+ * contain unresolved references.
+ */
+public class GwtAstBuilder {
+
+  /**
+   * Visit the JDT AST and produce our own AST. By the end of this pass, the
+   * produced AST should contain every piece of information we'll ever need
+   * about the code. The JDT nodes should never again be referenced after this.
+   * 
+   * NOTE ON JDT FORCED OPTIMIZATIONS - If JDT statically determines that a
+   * section of code in unreachable, it won't fully resolve that section of
+   * code. This invalid-state code causes us major problems. As a result, we
+   * have to optimize out those dead blocks early and never try to translate
+   * them to our AST.
+   */
+  class AstVisitor extends SafeASTVisitor {
+
+    /**
+     * Resolves local references to function parameters, and JSNI references.
+     * 
+     * TODO: move more error reporting to
+     * {@link com.google.gwt.dev.javac.JsniChecker}.
+     */
+    private class JsniResolver extends JsModVisitor {
+      private final JsniMethodBody nativeMethodBody;
+
+      private JsniResolver(JsniMethodBody nativeMethodBody) {
+        this.nativeMethodBody = nativeMethodBody;
+      }
+
+      @Override
+      public void endVisit(JsNameRef x, JsContext ctx) {
+        String ident = x.getIdent();
+        if (ident.charAt(0) == '@') {
+          Binding binding = jsniRefs.get(ident);
+          SourceInfo info = x.getSourceInfo();
+          if (binding == null) {
+            assert ident.startsWith("@null::");
+          } else if (binding instanceof TypeBinding) {
+            JType type = typeMap.get((TypeBinding) binding);
+            processClassLiteral(type, info, ctx);
+          } else if (binding instanceof FieldBinding) {
+            JField field = typeMap.get((FieldBinding) binding);
+            processField(x, info, field, ctx);
+          } else {
+            JMethod method = typeMap.get((MethodBinding) binding);
+            processMethod(x, info, method);
+          }
+        }
+      }
+
+      private void processClassLiteral(JType type, SourceInfo info, JsContext ctx) {
+        assert !ctx.isLvalue();
+        JClassLiteral classLiteral = new JClassLiteral(info, type);
+        nativeMethodBody.addClassRef(classLiteral);
+      }
+
+      private void processField(JsNameRef nameRef, SourceInfo info, JField field, JsContext ctx) {
+        /*
+         * We must replace any compile-time constants with the constant value of
+         * the field.
+         */
+        if (field.isCompileTimeConstant()) {
+          assert !ctx.isLvalue();
+          JLiteral initializer = field.getConstInitializer();
+          JType type = initializer.getType();
+          if (type instanceof JPrimitiveType || initializer instanceof JStringLiteral) {
+            GenerateJavaScriptLiterals generator = new GenerateJavaScriptLiterals();
+            generator.accept(initializer);
+            JsExpression result = generator.peek();
+            assert (result != null);
+            ctx.replaceMe(result);
+            return;
+          }
+        }
+
+        // Normal: create a jsniRef.
+        JsniFieldRef fieldRef =
+            new JsniFieldRef(info, nameRef.getIdent(), field, curClass.type, ctx.isLvalue());
+        nativeMethodBody.addJsniRef(fieldRef);
+      }
+
+      private void processMethod(JsNameRef nameRef, SourceInfo info, JMethod method) {
+        JsniMethodRef methodRef =
+            new JsniMethodRef(info, nameRef.getIdent(), method, javaLangObject);
+        nativeMethodBody.addJsniRef(methodRef);
+      }
+    }
+
+    /**
+     * Resolves the scope of JS identifiers solely within the scope of a method.
+     */
+    private class JsParameterResolver extends JsAbstractSymbolResolver {
+      private final JsFunction jsFunction;
+
+      public JsParameterResolver(JsFunction jsFunction) {
+        this.jsFunction = jsFunction;
+      }
+
+      @Override
+      public void resolve(JsNameRef x) {
+        // Only resolve unqualified names
+        if (x.getQualifier() == null) {
+          JsName name = getScope().findExistingName(x.getIdent());
+
+          // Ensure that we're resolving a name from the function's parameters
+          JsNode node = name == null ? null : name.getStaticRef();
+          if (node instanceof JsParameter) {
+            JsParameter param = (JsParameter) node;
+            if (jsFunction.getParameters().contains(param)) {
+              x.resolve(name);
+            }
+          }
+        }
+      }
+    }
+
+    private final Stack<ClassInfo> classStack = new Stack<ClassInfo>();
+
+    private ClassInfo curClass = null;
+
+    private MethodInfo curMethod = null;
+
+    private final Stack<MethodInfo> methodStack = new Stack<MethodInfo>();
+
+    private ArrayList<JNode> nodeStack = new ArrayList<JNode>();
+
+    @Override
+    public void endVisit(AllocationExpression x, BlockScope scope) {
+      try {
+        List<JExpression> arguments = popCallArgs(x.arguments, x.binding);
+        pushNewExpression(x, null, arguments, scope);
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(AND_AND_Expression x, BlockScope scope) {
+      pushBinaryOp(x, JBinaryOperator.AND);
+    }
+
+    @Override
+    public void endVisit(AnnotationMethodDeclaration x, ClassScope classScope) {
+      endVisit((MethodDeclaration) x, classScope);
+    }
+
+    @Override
+    public void endVisit(ArrayAllocationExpression x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JArrayType type = (JArrayType) typeMap.get(x.resolvedType);
+
+        if (x.initializer != null) {
+          // handled by ArrayInitializer.
+        } else {
+          // Annoyingly, JDT only visits non-null dims, so we can't popList().
+          List<JExpression> dims = new ArrayList<JExpression>();
+          for (int i = x.dimensions.length - 1; i >= 0; --i) {
+            JExpression dimension = pop(x.dimensions[i]);
+            // can be null if index expression was empty
+            if (dimension == null) {
+              dimension = JAbsentArrayDimension.INSTANCE;
+            }
+            dims.add(dimension);
+          }
+          // Undo the stack reversal.
+          Collections.reverse(dims);
+          push(JNewArray.createDims(info, type, dims));
+        }
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(ArrayInitializer x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JArrayType type = (JArrayType) typeMap.get(x.resolvedType);
+        List<JExpression> expressions = pop(x.expressions);
+        push(JNewArray.createInitializers(info, type, expressions));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(ArrayReference x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JExpression position = pop(x.position);
+        JExpression receiver = pop(x.receiver);
+        push(new JArrayRef(info, receiver, position));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(AssertStatement x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JExpression exceptionArgument = pop(x.exceptionArgument);
+        JExpression assertExpression = pop(x.assertExpression);
+        push(new JAssertStatement(info, assertExpression, exceptionArgument));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(Assignment x, BlockScope scope) {
+      pushBinaryOp(x, JBinaryOperator.ASG);
+    }
+
+    @Override
+    public void endVisit(BinaryExpression x, BlockScope scope) {
+      JBinaryOperator op;
+      int binOp = (x.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT;
+      switch (binOp) {
+        case BinaryExpression.LEFT_SHIFT:
+          op = JBinaryOperator.SHL;
+          break;
+        case BinaryExpression.RIGHT_SHIFT:
+          op = JBinaryOperator.SHR;
+          break;
+        case BinaryExpression.UNSIGNED_RIGHT_SHIFT:
+          op = JBinaryOperator.SHRU;
+          break;
+        case BinaryExpression.PLUS:
+          if (x.resolvedType instanceof ReferenceBinding) {
+            op = JBinaryOperator.CONCAT;
+          } else {
+            op = JBinaryOperator.ADD;
+          }
+          break;
+        case BinaryExpression.MINUS:
+          op = JBinaryOperator.SUB;
+          break;
+        case BinaryExpression.REMAINDER:
+          op = JBinaryOperator.MOD;
+          break;
+        case BinaryExpression.XOR:
+          op = JBinaryOperator.BIT_XOR;
+          break;
+        case BinaryExpression.AND:
+          op = JBinaryOperator.BIT_AND;
+          break;
+        case BinaryExpression.MULTIPLY:
+          op = JBinaryOperator.MUL;
+          break;
+        case BinaryExpression.OR:
+          op = JBinaryOperator.BIT_OR;
+          break;
+        case BinaryExpression.DIVIDE:
+          op = JBinaryOperator.DIV;
+          break;
+        case BinaryExpression.LESS_EQUAL:
+          op = JBinaryOperator.LTE;
+          break;
+        case BinaryExpression.GREATER_EQUAL:
+          op = JBinaryOperator.GTE;
+          break;
+        case BinaryExpression.GREATER:
+          op = JBinaryOperator.GT;
+          break;
+        case BinaryExpression.LESS:
+          op = JBinaryOperator.LT;
+          break;
+        default:
+          throw translateException(x, new InternalCompilerException(
+              "Unexpected operator for BinaryExpression"));
+      }
+      pushBinaryOp(x, op);
+    }
+
+    @Override
+    public void endVisit(Block x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JBlock block = popBlock(info, x.statements);
+        push(block);
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(BreakStatement x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        push(new JBreakStatement(info, getOrCreateLabel(info, x.label)));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(CaseStatement x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JExpression constantExpression = pop(x.constantExpression);
+        JLiteral caseLiteral;
+        if (constantExpression == null) {
+          caseLiteral = null;
+        } else if (constantExpression instanceof JLiteral) {
+          caseLiteral = (JLiteral) constantExpression;
+        } else {
+          // Adapted from CaseStatement.resolveCase().
+          assert x.constantExpression.resolvedType.isEnum();
+          NameReference reference = (NameReference) x.constantExpression;
+          FieldBinding field = reference.fieldBinding();
+          caseLiteral = JIntLiteral.get(field.original().id);
+        }
+        push(new JCaseStatement(info, caseLiteral));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(CastExpression x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JType type = typeMap.get(x.resolvedType);
+        JExpression expression = pop(x.expression);
+        if (x.type instanceof NameReference) {
+          pop(x.type);
+        }
+        push(new JCastOperation(info, type, expression));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(CharLiteral x, BlockScope scope) {
+      try {
+        push(JCharLiteral.get(x.constant.charValue()));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(ClassLiteralAccess x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JType type = typeMap.get(x.targetType);
+        push(new JClassLiteral(info, type));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(CompoundAssignment x, BlockScope scope) {
+      JBinaryOperator op;
+      switch (x.operator) {
+        case CompoundAssignment.PLUS:
+          if (x.resolvedType instanceof ReferenceBinding) {
+            op = JBinaryOperator.ASG_CONCAT;
+          } else {
+            op = JBinaryOperator.ASG_ADD;
+          }
+          break;
+        case CompoundAssignment.MINUS:
+          op = JBinaryOperator.ASG_SUB;
+          break;
+        case CompoundAssignment.MULTIPLY:
+          op = JBinaryOperator.ASG_MUL;
+          break;
+        case CompoundAssignment.DIVIDE:
+          op = JBinaryOperator.ASG_DIV;
+          break;
+        case CompoundAssignment.AND:
+          op = JBinaryOperator.ASG_BIT_AND;
+          break;
+        case CompoundAssignment.OR:
+          op = JBinaryOperator.ASG_BIT_OR;
+          break;
+        case CompoundAssignment.XOR:
+          op = JBinaryOperator.ASG_BIT_XOR;
+          break;
+        case CompoundAssignment.REMAINDER:
+          op = JBinaryOperator.ASG_MOD;
+          break;
+        case CompoundAssignment.LEFT_SHIFT:
+          op = JBinaryOperator.ASG_SHL;
+          break;
+        case CompoundAssignment.RIGHT_SHIFT:
+          op = JBinaryOperator.ASG_SHR;
+          break;
+        case CompoundAssignment.UNSIGNED_RIGHT_SHIFT:
+          op = JBinaryOperator.ASG_SHRU;
+          break;
+        default:
+          throw translateException(x, new InternalCompilerException(
+              "Unexpected operator for CompoundAssignment"));
+      }
+      pushBinaryOp(x, op);
+    }
+
+    @Override
+    public void endVisit(ConditionalExpression x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JType type = typeMap.get(x.resolvedType);
+        JExpression valueIfFalse = pop(x.valueIfFalse);
+        JExpression valueIfTrue = pop(x.valueIfTrue);
+        JExpression condition = pop(x.condition);
+        push(new JConditional(info, type, condition, valueIfTrue, valueIfFalse));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(ConstructorDeclaration x, ClassScope scope) {
+      try {
+        List<JStatement> statements = pop(x.statements);
+        JStatement constructorCall = pop(x.constructorCall);
+        JBlock block = curMethod.body.getBlock();
+        SourceInfo info = curMethod.method.getSourceInfo();
+
+        /*
+         * Determine if we have an explicit this call. The presence of an
+         * explicit this call indicates we can skip certain initialization steps
+         * (as the callee will perform those steps for us). These skippable
+         * steps are 1) assigning synthetic args to fields and 2) running
+         * initializers.
+         */
+        boolean hasExplicitThis = (x.constructorCall != null) && !x.constructorCall.isSuperAccess();
+
+        /*
+         * All synthetic fields must be assigned, unless we have an explicit
+         * this constructor call, in which case the callee will assign them for
+         * us.
+         */
+        if (!hasExplicitThis) {
+          ReferenceBinding declaringClass = (ReferenceBinding) x.binding.declaringClass.erasure();
+          if (declaringClass instanceof NestedTypeBinding) {
+            NestedTypeBinding nestedBinding = (NestedTypeBinding) declaringClass;
+            if (nestedBinding.enclosingInstances != null) {
+              for (SyntheticArgumentBinding arg : nestedBinding.enclosingInstances) {
+                JBinaryOperation asg = assignSyntheticField(info, arg);
+                block.addStmt(asg.makeStatement());
+              }
+            }
+
+            if (nestedBinding.outerLocalVariables != null) {
+              for (SyntheticArgumentBinding arg : nestedBinding.outerLocalVariables) {
+                JBinaryOperation asg = assignSyntheticField(info, arg);
+                block.addStmt(asg.makeStatement());
+              }
+            }
+          }
+        }
+
+        if (constructorCall != null) {
+          block.addStmt(constructorCall);
+        }
+
+        /*
+         * Call the synthetic instance initializer method, unless we have an
+         * explicit this constructor call, in which case the callee will.
+         */
+        if (!hasExplicitThis) {
+          // $init is always in position 1 (clinit is in 0)
+          JMethod initMethod = curClass.type.getMethods().get(1);
+          JMethodCall initCall = new JMethodCall(info, makeThisRef(info), initMethod);
+          block.addStmt(initCall.makeStatement());
+        }
+
+        // user code (finally!)
+        block.addStmts(statements);
+        popMethodInfo();
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(ContinueStatement x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        push(new JContinueStatement(info, getOrCreateLabel(info, x.label)));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(DoStatement x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JExpression condition = pop(x.condition);
+        JStatement action = pop(x.action);
+        push(new JDoStatement(info, condition, action));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(DoubleLiteral x, BlockScope scope) {
+      try {
+        push(JDoubleLiteral.get(x.constant.doubleValue()));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(EmptyStatement x, BlockScope scope) {
+      push(null);
+    }
+
+    @Override
+    public void endVisit(EqualExpression x, BlockScope scope) {
+      JBinaryOperator op;
+      switch ((x.bits & BinaryExpression.OperatorMASK) >> BinaryExpression.OperatorSHIFT) {
+        case BinaryExpression.EQUAL_EQUAL:
+          op = JBinaryOperator.EQ;
+          break;
+        case BinaryExpression.NOT_EQUAL:
+          op = JBinaryOperator.NEQ;
+          break;
+        default:
+          throw translateException(x, new InternalCompilerException(
+              "Unexpected operator for EqualExpression"));
+      }
+      pushBinaryOp(x, op);
+    }
+
+    @Override
+    public void endVisit(ExplicitConstructorCall x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JConstructor ctor = (JConstructor) typeMap.get(x.binding);
+        JExpression trueQualifier = makeThisRef(info);
+        JMethodCall call = new JMethodCall(info, trueQualifier, ctor);
+        List<JExpression> callArgs = popCallArgs(x.arguments, x.binding);
+
+        if (curClass.classType.isEnumOrSubclass() != null) {
+          // Enums: wire up synthetic name/ordinal params to the super method.
+          JParameterRef enumNameRef = new JParameterRef(info, curMethod.method.getParams().get(0));
+          call.addArg(enumNameRef);
+          JParameterRef enumOrdinalRef =
+              new JParameterRef(info, curMethod.method.getParams().get(1));
+          call.addArg(enumOrdinalRef);
+        }
+
+        if (x.isSuperAccess()) {
+          JExpression qualifier = pop(x.qualification);
+          ReferenceBinding superClass = x.binding.declaringClass;
+          boolean nestedSuper = superClass.isNestedType() && !superClass.isStatic();
+          if (nestedSuper) {
+            processSuperCallThisArgs(superClass, call, qualifier, x.qualification);
+          }
+          call.addArgs(callArgs);
+          if (nestedSuper) {
+            processSuperCallLocalArgs(superClass, call);
+          }
+        } else {
+          assert (x.qualification == null);
+          ReferenceBinding declaringClass = x.binding.declaringClass;
+          boolean nested = declaringClass.isNestedType() && !declaringClass.isStatic();
+          if (nested) {
+            processThisCallThisArgs(declaringClass, call);
+          }
+          call.addArgs(callArgs);
+          if (nested) {
+            processThisCallLocalArgs(declaringClass, call);
+          }
+        }
+        call.setStaticDispatchOnly();
+        push(call.makeStatement());
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      } finally {
+        scope.methodScope().isConstructorCall = false;
+      }
+    }
+
+    @Override
+    public void endVisit(ExtendedStringLiteral x, BlockScope scope) {
+      endVisit((StringLiteral) x, scope);
+    }
+
+    @Override
+    public void endVisit(FalseLiteral x, BlockScope scope) {
+      push(JBooleanLiteral.FALSE);
+    }
+
+    @Override
+    public void endVisit(FieldDeclaration x, MethodScope scope) {
+      try {
+        JExpression initialization = pop(x.initialization);
+        JField field = typeMap.get(x.binding);
+        if (field instanceof JEnumField) {
+          // An enum field must be initialized!
+          assert (initialization instanceof JNewInstance);
+        }
+
+        if (initialization != null) {
+          SourceInfo info = makeSourceInfo(x);
+          JExpression instance = null;
+          if (!x.isStatic()) {
+            instance = makeThisRef(info);
+          }
+          // JDeclarationStatement's ctor sets up the field's initializer.
+          JStatement decl =
+              new JDeclarationStatement(info, new JFieldRef(info, instance, field, curClass.type),
+                  initialization);
+          // will either be init or clinit
+          curMethod.body.getBlock().addStmt(decl);
+        }
+        popMethodInfo();
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(FieldReference x, BlockScope scope) {
+      try {
+        FieldBinding fieldBinding = x.binding;
+        SourceInfo info = makeSourceInfo(x);
+        JExpression instance = pop(x.receiver);
+        JExpression expr;
+        if (fieldBinding.declaringClass == null) {
+          if (!ARRAY_LENGTH_FIELD.equals(String.valueOf(fieldBinding.name))) {
+            throw new InternalCompilerException("Expected [array].length.");
+          }
+          expr = new JArrayLength(info, instance);
+        } else {
+          JField field = typeMap.get(fieldBinding);
+          expr = new JFieldRef(info, instance, field, curClass.type);
+        }
+
+        if (x.genericCast != null) {
+          JType castType = typeMap.get(x.genericCast);
+          /*
+           * Note, this may result in an invalid AST due to an LHS cast
+           * operation. We fix this up in FixAssignmentToUnbox.
+           */
+          expr = maybeCast(castType, expr);
+        }
+        push(expr);
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(FloatLiteral x, BlockScope scope) {
+      try {
+        push(JFloatLiteral.get(x.constant.floatValue()));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(ForeachStatement x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+
+        JBlock body = popBlock(info, x.action);
+        JExpression collection = pop(x.collection);
+        JDeclarationStatement elementDecl = pop(x.elementVariable);
+        assert (elementDecl.initializer == null);
+
+        JLocal elementVar = (JLocal) curMethod.locals.get(x.elementVariable.binding);
+        String elementVarName = elementVar.getName();
+
+        JForStatement result;
+        if (x.collectionVariable != null) {
+          /**
+           * <pre>
+         * for (final T[] i$array = collection,
+         *          int i$index = 0,
+         *          final int i$max = i$array.length;
+         *      i$index < i$max; ++i$index) {
+         *   T elementVar = i$array[i$index];
+         *   // user action
+         * }
+         * </pre>
+           */
+          JLocal arrayVar =
+              JProgram.createLocal(info, elementVarName + "$array", collection.getType(), true,
+                  curMethod.body);
+          JLocal indexVar =
+              JProgram.createLocal(info, elementVarName + "$index", JPrimitiveType.INT, false,
+                  curMethod.body);
+          JLocal maxVar =
+              JProgram.createLocal(info, elementVarName + "$max", JPrimitiveType.INT, true,
+                  curMethod.body);
+
+          List<JStatement> initializers = new ArrayList<JStatement>(3);
+          // T[] i$array = arr
+          initializers.add(makeDeclaration(info, arrayVar, collection));
+          // int i$index = 0
+          initializers.add(makeDeclaration(info, indexVar, JIntLiteral.get(0)));
+          // int i$max = i$array.length
+          initializers.add(makeDeclaration(info, maxVar, new JArrayLength(info, new JLocalRef(info,
+              arrayVar))));
+
+          // i$index < i$max
+          JExpression condition =
+              new JBinaryOperation(info, JPrimitiveType.BOOLEAN, JBinaryOperator.LT, new JLocalRef(
+                  info, indexVar), new JLocalRef(info, maxVar));
+
+          // ++i$index
+          List<JExpressionStatement> increments = new ArrayList<JExpressionStatement>(1);
+          increments.add(new JPrefixOperation(info, JUnaryOperator.INC, new JLocalRef(info,
+              indexVar)).makeStatement());
+
+          // T elementVar = i$array[i$index];
+          elementDecl.initializer =
+              new JArrayRef(info, new JLocalRef(info, arrayVar), new JLocalRef(info, indexVar));
+          body.addStmt(0, elementDecl);
+
+          result = new JForStatement(info, initializers, condition, increments, body);
+        } else {
+          /**
+           * <pre>
+           * for (Iterator&lt;T&gt; i$iterator = collection.iterator(); i$iterator.hasNext();) {
+           *   T elementVar = i$iterator.next();
+           *   // user action
+           * }
+           * </pre>
+           */
+          CompilationUnitScope cudScope = scope.compilationUnitScope();
+          ReferenceBinding javaUtilIterator = scope.getJavaUtilIterator();
+          ReferenceBinding javaLangIterable = scope.getJavaLangIterable();
+          MethodBinding iterator = javaLangIterable.getExactMethod(ITERATOR, NO_TYPES, cudScope);
+          MethodBinding hasNext = javaUtilIterator.getExactMethod(HAS_NEXT, NO_TYPES, cudScope);
+          MethodBinding next = javaUtilIterator.getExactMethod(NEXT, NO_TYPES, cudScope);
+          JLocal iteratorVar =
+              JProgram.createLocal(info, (elementVarName + "$iterator"), typeMap
+                  .get(javaUtilIterator), false, curMethod.body);
+
+          List<JStatement> initializers = new ArrayList<JStatement>(1);
+          // Iterator<T> i$iterator = collection.iterator()
+          initializers.add(makeDeclaration(info, iteratorVar, new JMethodCall(info, collection,
+              typeMap.get(iterator))));
+
+          // i$iterator.hasNext()
+          JExpression condition =
+              new JMethodCall(info, new JLocalRef(info, iteratorVar), typeMap.get(hasNext));
+
+          // T elementVar = (T) i$iterator.next();
+          elementDecl.initializer =
+              new JMethodCall(info, new JLocalRef(info, iteratorVar), typeMap.get(next));
+
+          // Perform any implicit reference type casts (due to generics).
+          // Note this occurs before potential unboxing.
+          if (elementVar.getType() != javaLangObject) {
+            /*
+             * Compute the collection element type by walking the iterator()
+             * method, which may be parameterized.
+             */
+            ReferenceBinding collectionType = (ReferenceBinding) x.collection.resolvedType;
+            MethodBinding iteratorMethod =
+                collectionType.getExactMethod(ITERATOR, NO_TYPES, cudScope);
+            ReferenceBinding iteratorType = (ReferenceBinding) iteratorMethod.returnType;
+            MethodBinding nextMethod = iteratorType.getMethods(NEXT)[0];
+            TypeBinding collectionElementType = nextMethod.returnType;
+            JType toType = typeMap.get(collectionElementType);
+            assert (toType instanceof JReferenceType);
+            elementDecl.initializer = maybeCast(toType, elementDecl.initializer);
+          }
+
+          body.addStmt(0, elementDecl);
+
+          result =
+              new JForStatement(info, initializers, condition, Collections
+                  .<JExpressionStatement> emptyList(), body);
+        }
+
+        // May need to box or unbox the element assignment.
+        elementDecl.initializer =
+            maybeBoxOrUnbox(elementDecl.initializer, x.elementVariableImplicitWidening);
+        push(result);
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(ForStatement x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JStatement action = pop(x.action);
+        List<JExpressionStatement> increments = pop(x.increments);
+        JExpression condition = pop(x.condition);
+        List<JStatement> initializations = pop(x.initializations);
+        push(new JForStatement(info, initializations, condition, increments, action));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(IfStatement x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JStatement elseStatement = pop(x.elseStatement);
+        JStatement thenStatement = pop(x.thenStatement);
+        JExpression condition = pop(x.condition);
+        push(new JIfStatement(info, condition, thenStatement, elseStatement));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(Initializer x, MethodScope scope) {
+      try {
+        JBlock block = pop(x.block);
+        if (block != null) {
+          curMethod.body.getBlock().addStmt(block);
+        }
+        popMethodInfo();
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(InstanceOfExpression x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JExpression expr = pop(x.expression);
+        JReferenceType testType = (JReferenceType) typeMap.get(x.type.resolvedType);
+        push(new JInstanceOf(info, testType, expr));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(IntLiteral x, BlockScope scope) {
+      try {
+        push(JIntLiteral.get(x.constant.intValue()));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(LabeledStatement x, BlockScope scope) {
+      try {
+        JStatement statement = pop(x.statement);
+        if (statement == null) {
+          push(null);
+          return;
+        }
+        SourceInfo info = makeSourceInfo(x);
+        push(new JLabeledStatement(info, getOrCreateLabel(info, x.label), statement));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(LocalDeclaration x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JLocal local = (JLocal) curMethod.locals.get(x.binding);
+        assert local != null;
+        JLocalRef localRef = new JLocalRef(info, local);
+        JExpression initialization = pop(x.initialization);
+        push(new JDeclarationStatement(info, localRef, initialization));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(LongLiteral x, BlockScope scope) {
+      try {
+        push(JLongLiteral.get(x.constant.longValue()));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(MessageSend x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JMethod method = typeMap.get(x.binding);
+
+        List<JExpression> arguments = popCallArgs(x.arguments, x.binding);
+        JExpression receiver = pop(x.receiver);
+        if (x.receiver instanceof ThisReference) {
+          if (method.isStatic()) {
+            // don't bother qualifying it, it's a no-op
+            receiver = null;
+          } else if ((x.bits & ASTNode.DepthMASK) != 0) {
+            // outer method can be reached through emulation if implicit access
+            ReferenceBinding targetType =
+                scope.enclosingSourceType().enclosingTypeAt(
+                    (x.bits & ASTNode.DepthMASK) >> ASTNode.DepthSHIFT);
+            receiver = makeThisReference(info, targetType, true, scope);
+          }
+        }
+
+        JMethodCall call = new JMethodCall(info, receiver, method);
+
+        // On a super ref, don't allow polymorphic dispatch. Oddly enough,
+        // QualifiedSuperReference not derived from SuperReference!
+        boolean isSuperRef =
+            x.receiver instanceof SuperReference || x.receiver instanceof QualifiedSuperReference;
+        if (isSuperRef) {
+          call.setStaticDispatchOnly();
+        }
+
+        // The arguments come first...
+        call.addArgs(arguments);
+
+        if (x.valueCast != null) {
+          JType castType = typeMap.get(x.valueCast);
+          push(maybeCast(castType, call));
+        } else {
+          push(call);
+        }
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(MethodDeclaration x, ClassScope scope) {
+      try {
+        if (x.isNative()) {
+          processNativeMethod(x);
+        } else {
+          List<JStatement> statements = pop(x.statements);
+          curMethod.body.getBlock().addStmts(statements);
+        }
+        popMethodInfo();
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(NullLiteral x, BlockScope scope) {
+      push(JNullLiteral.INSTANCE);
+    }
+
+    @Override
+    public void endVisit(OR_OR_Expression x, BlockScope scope) {
+      pushBinaryOp(x, JBinaryOperator.OR);
+    }
+
+    @Override
+    public void endVisit(PostfixExpression x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JUnaryOperator op;
+        switch (x.operator) {
+          case PostfixExpression.MINUS:
+            op = JUnaryOperator.DEC;
+            break;
+
+          case PostfixExpression.PLUS:
+            op = JUnaryOperator.INC;
+            break;
+
+          default:
+            throw new InternalCompilerException("Unexpected postfix operator");
+        }
+
+        JExpression lhs = pop(x.lhs);
+        push(new JPostfixOperation(info, op, lhs));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(PrefixExpression x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JUnaryOperator op;
+        switch (x.operator) {
+          case PostfixExpression.MINUS:
+            op = JUnaryOperator.DEC;
+            break;
+
+          case PostfixExpression.PLUS:
+            op = JUnaryOperator.INC;
+            break;
+
+          default:
+            throw new InternalCompilerException("Unexpected prefix operator");
+        }
+
+        JExpression lhs = pop(x.lhs);
+        push(new JPrefixOperation(info, op, lhs));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(QualifiedAllocationExpression x, BlockScope scope) {
+      try {
+        List<JExpression> arguments = popCallArgs(x.arguments, x.binding);
+        pushNewExpression(x, x.enclosingInstance(), arguments, scope);
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(QualifiedNameReference x, BlockScope scope) {
+      try {
+        JExpression curRef = resolveNameReference(x, scope);
+        if (curRef == null) {
+          push(null);
+          return;
+        }
+        if (x.genericCast != null) {
+          JType castType = typeMap.get(x.genericCast);
+          curRef = maybeCast(castType, curRef);
+        }
+        SourceInfo info = curRef.getSourceInfo();
+
+        /*
+         * JDT represents multiple field access as an array of fields, each
+         * qualified by everything to the left. So each subsequent item in
+         * otherBindings takes the current expression as a qualifier.
+         */
+        if (x.otherBindings != null) {
+          for (int i = 0; i < x.otherBindings.length; ++i) {
+            FieldBinding fieldBinding = x.otherBindings[i];
+            if (fieldBinding.declaringClass == null) {
+              // probably array.length
+              if (!ARRAY_LENGTH_FIELD.equals(String.valueOf(fieldBinding.name))) {
+                throw new InternalCompilerException("Expected [array].length.");
+              }
+              curRef = new JArrayLength(info, curRef);
+            } else {
+              JField field = typeMap.get(fieldBinding);
+              curRef = new JFieldRef(info, curRef, field, curClass.type);
+            }
+            if (x.otherGenericCasts != null && x.otherGenericCasts[i] != null) {
+              JType castType = typeMap.get(x.otherGenericCasts[i]);
+              curRef = maybeCast(castType, curRef);
+            }
+          }
+        }
+        push(curRef);
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(QualifiedSuperReference x, BlockScope scope) {
+      try {
+        // Oddly enough, super refs can be modeled as this refs, because
+        // whatever expression they qualify has already been resolved.
+        endVisit((QualifiedThisReference) x, scope);
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(QualifiedThisReference x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        ReferenceBinding targetType = (ReferenceBinding) x.qualification.resolvedType;
+        push(makeThisReference(info, targetType, true, scope));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(ReturnStatement x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JExpression expression = pop(x.expression);
+        push(new JReturnStatement(info, expression));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(SingleNameReference x, BlockScope scope) {
+      try {
+        JExpression result = resolveNameReference(x, scope);
+        if (result == null) {
+          push(null);
+          return;
+        }
+        if (x.genericCast != null) {
+          JType castType = typeMap.get(x.genericCast);
+          result = maybeCast(castType, result);
+        }
+        push(result);
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(StringLiteral x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        push(getStringLiteral(info, x.constant.stringValue()));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(StringLiteralConcatenation x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        push(getStringLiteral(info, x.constant.stringValue()));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(SuperReference x, BlockScope scope) {
+      try {
+        assert (typeMap.get(x.resolvedType) == curClass.classType.getSuperClass());
+        // Super refs can be modeled as a this ref.
+        push(makeThisRef(makeSourceInfo(x)));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(SwitchStatement x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+
+        JBlock block = popBlock(info, x.statements);
+        JExpression expression = pop(x.expression);
+
+        if (x.expression.resolvedType instanceof ReferenceBinding) {
+          // Must be an enum; synthesize a call to ordinal().
+          ReferenceBinding javaLangEnum = scope.getJavaLangEnum();
+          MethodBinding ordinal = javaLangEnum.getMethods(ORDINAL)[0];
+          expression = new JMethodCall(info, expression, typeMap.get(ordinal));
+        }
+        push(new JSwitchStatement(info, expression, block));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(SynchronizedStatement x, BlockScope scope) {
+      try {
+        JBlock block = pop(x.block);
+        JExpression expression = pop(x.expression);
+        block.addStmt(0, expression.makeStatement());
+        push(block);
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(ThisReference x, BlockScope scope) {
+      try {
+        assert (typeMap.get(x.resolvedType) == curClass.classType);
+        push(makeThisRef(makeSourceInfo(x)));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(ThrowStatement x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JExpression exception = pop(x.exception);
+        push(new JThrowStatement(info, exception));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(TrueLiteral x, BlockScope scope) {
+      push(JBooleanLiteral.TRUE);
+    }
+
+    @Override
+    public void endVisit(TryStatement x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+
+        JBlock finallyBlock = pop(x.finallyBlock);
+        List<JBlock> catchBlocks = pop(x.catchBlocks);
+        JBlock tryBlock = pop(x.tryBlock);
+
+        List<JLocalRef> catchArgs = new ArrayList<JLocalRef>();
+        if (x.catchBlocks != null) {
+          for (Argument argument : x.catchArguments) {
+            JLocal local = (JLocal) curMethod.locals.get(argument.binding);
+            catchArgs.add(new JLocalRef(info, local));
+          }
+        }
+        push(new JTryStatement(info, tryBlock, catchArgs, catchBlocks, finallyBlock));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(TypeDeclaration x, ClassScope scope) {
+      endVisit(x);
+    }
+
+    @Override
+    public void endVisit(TypeDeclaration x, CompilationUnitScope scope) {
+      endVisit(x);
+    }
+
+    @Override
+    public void endVisit(UnaryExpression x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JUnaryOperator op;
+        int operator = ((x.bits & UnaryExpression.OperatorMASK) >> UnaryExpression.OperatorSHIFT);
+
+        switch (operator) {
+          case UnaryExpression.MINUS:
+            op = JUnaryOperator.NEG;
+            break;
+
+          case UnaryExpression.NOT:
+            op = JUnaryOperator.NOT;
+            break;
+
+          case UnaryExpression.PLUS:
+            // Odd case.. useless + operator; just leave the operand on the
+            // stack.
+            return;
+
+          case UnaryExpression.TWIDDLE:
+            op = JUnaryOperator.BIT_NOT;
+            break;
+
+          default:
+            throw new InternalCompilerException("Unexpected operator for unary expression");
+        }
+
+        JExpression expression = pop(x.expression);
+        push(new JPrefixOperation(info, op, expression));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisit(WhileStatement x, BlockScope scope) {
+      try {
+        SourceInfo info = makeSourceInfo(x);
+        JStatement action = pop(x.action);
+        JExpression condition = pop(x.condition);
+        push(new JWhileStatement(info, condition, action));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public void endVisitValid(TypeDeclaration x, BlockScope scope) {
+      endVisit(x);
+      if (!x.binding.isAnonymousType()) {
+        // Class declaration as a statement; insert a dummy statement.
+        push(null);
+      }
+    }
+
+    public boolean isJavaScriptObject(JClassType type) {
+      if (type == null) {
+        return false;
+      }
+      if ("com.google.gwt.core.client.JavaScriptObject".equals(type.getName())) {
+        return true;
+      }
+      return isJavaScriptObject(type.getSuperClass());
+    }
+
+    @Override
+    public boolean visit(AnnotationMethodDeclaration x, ClassScope classScope) {
+      return visit((MethodDeclaration) x, classScope);
+    }
+
+    @Override
+    public boolean visit(Argument x, BlockScope scope) {
+      // handled by parents
+      return true;
+    }
+
+    @Override
+    public boolean visit(Block x, BlockScope scope) {
+      x.statements = reduceToReachable(x.statements);
+      return true;
+    }
+
+    @Override
+    public boolean visit(ConstructorDeclaration x, ClassScope scope) {
+      try {
+        JConstructor method = (JConstructor) typeMap.get(x.binding);
+        assert !method.getEnclosingType().isExternal();
+        JMethodBody body = new JMethodBody(method.getSourceInfo());
+        method.setBody(body);
+        pushMethodInfo(new MethodInfo(method, body, x.scope));
+
+        // Map all arguments.
+        Iterator<JParameter> it = method.getParams().iterator();
+
+        // Enum arguments have no mapping.
+        if (curClass.classType.isEnumOrSubclass() != null) {
+          // Skip past name and ordinal.
+          it.next();
+          it.next();
+        }
+
+        // Map synthetic arguments for outer this.
+        ReferenceBinding declaringClass = (ReferenceBinding) x.binding.declaringClass.erasure();
+        if (declaringClass.isNestedType() && !declaringClass.isStatic()) {
+          NestedTypeBinding nestedBinding = (NestedTypeBinding) declaringClass;
+          if (nestedBinding.enclosingInstances != null) {
+            for (int i = 0; i < nestedBinding.enclosingInstances.length; ++i) {
+              SyntheticArgumentBinding arg = nestedBinding.enclosingInstances[i];
+              curMethod.locals.put(arg, it.next());
+            }
+          }
+        }
+
+        // Map user arguments.
+        if (x.arguments != null) {
+          for (Argument argument : x.arguments) {
+            curMethod.locals.put(argument.binding, it.next());
+          }
+        }
+
+        // Map synthetic arguments for locals.
+        if (declaringClass.isNestedType() && !declaringClass.isStatic()) {
+          // add synthetic args for locals
+          NestedTypeBinding nestedBinding = (NestedTypeBinding) declaringClass;
+          // add synthetic args for outer this and locals
+          if (nestedBinding.outerLocalVariables != null) {
+            for (int i = 0; i < nestedBinding.outerLocalVariables.length; ++i) {
+              SyntheticArgumentBinding arg = nestedBinding.outerLocalVariables[i];
+              curMethod.locals.put(arg, it.next());
+            }
+          }
+        }
+
+        x.statements = reduceToReachable(x.statements);
+        return true;
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public boolean visit(ExplicitConstructorCall explicitConstructor, BlockScope scope) {
+      scope.methodScope().isConstructorCall = true;
+      return true;
+    }
+
+    @Override
+    public boolean visit(FieldDeclaration x, MethodScope scope) {
+      try {
+        assert !typeMap.get(x.binding).getEnclosingType().isExternal();
+        pushInitializerMethodInfo(x, scope);
+        return true;
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    public boolean visit(ForStatement x, BlockScope scope) {
+      // SEE NOTE ON JDT FORCED OPTIMIZATIONS
+      if (isOptimizedFalse(x.condition)) {
+        x.action = null;
+      }
+      return true;
+    }
+
+    public boolean visit(IfStatement x, BlockScope scope) {
+      // SEE NOTE ON JDT FORCED OPTIMIZATIONS
+      if (isOptimizedFalse(x.condition)) {
+        x.thenStatement = null;
+      } else if (isOptimizedTrue(x.condition)) {
+        x.elseStatement = null;
+      }
+      return true;
+    }
+
+    @Override
+    public boolean visit(Initializer x, MethodScope scope) {
+      try {
+        pushInitializerMethodInfo(x, scope);
+        return true;
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public boolean visit(LocalDeclaration x, BlockScope scope) {
+      try {
+        createLocal(x);
+        return true;
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public boolean visit(MarkerAnnotation annotation, BlockScope scope) {
+      return false;
+    }
+
+    @Override
+    public boolean visit(MethodDeclaration x, ClassScope scope) {
+      try {
+        JMethod method = typeMap.get(x.binding);
+        assert !method.getEnclosingType().isExternal();
+        JMethodBody body = null;
+        if (!method.isNative()) {
+          body = new JMethodBody(method.getSourceInfo());
+          method.setBody(body);
+        }
+        pushMethodInfo(new MethodInfo(method, body, x.scope));
+
+        // Map user arguments.
+        Iterator<JParameter> it = method.getParams().iterator();
+        if (x.arguments != null) {
+          for (Argument argument : x.arguments) {
+            curMethod.locals.put(argument.binding, it.next());
+          }
+        }
+        x.statements = reduceToReachable(x.statements);
+        return true;
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public boolean visit(NormalAnnotation annotation, BlockScope scope) {
+      return false;
+    }
+
+    @Override
+    public boolean visit(SingleMemberAnnotation annotation, BlockScope scope) {
+      return false;
+    }
+
+    @Override
+    public boolean visit(SwitchStatement x, BlockScope scope) {
+      x.statements = reduceToReachable(x.statements);
+      return true;
+    }
+
+    @Override
+    public boolean visit(TryStatement x, BlockScope scope) {
+      try {
+        if (x.catchBlocks != null) {
+          for (Argument argument : x.catchArguments) {
+            createLocal(argument);
+          }
+        }
+        return true;
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    @Override
+    public boolean visit(TypeDeclaration x, ClassScope scope) {
+      return visit(x);
+    }
+
+    @Override
+    public boolean visit(TypeDeclaration x, CompilationUnitScope scope) {
+      return visit(x);
+    }
+
+    public boolean visit(WhileStatement x, BlockScope scope) {
+      // SEE NOTE ON JDT FORCED OPTIMIZATIONS
+      if (isOptimizedFalse(x.condition)) {
+        x.action = null;
+      }
+      return true;
+    }
+
+    @Override
+    public boolean visitValid(TypeDeclaration x, BlockScope scope) {
+      // Local types actually need to be created now.
+      createTypes(x);
+      resolveTypeRefs(x);
+      createMembers(x);
+      return visit(x);
+    }
+
+    protected void endVisit(TypeDeclaration x) {
+      JDeclaredType type = curClass.type;
+      /*
+       * Make clinits chain to super class (JDT doesn't write code to do this).
+       * Call super class $clinit; $clinit is always in position 0.
+       */
+      if (type.getSuperClass() != null) {
+        JMethod myClinit = type.getMethods().get(0);
+        JMethod superClinit = type.getSuperClass().getMethods().get(0);
+        JMethodCall superClinitCall = new JMethodCall(myClinit.getSourceInfo(), null, superClinit);
+        JMethodBody body = (JMethodBody) myClinit.getBody();
+        body.getBlock().addStmt(0, superClinitCall.makeStatement());
+      }
+
+      // Implement getClass() implementation for all non-Object classes.
+      if (type.getSuperClass() != null && !JSORestrictionsChecker.isJsoSubclass(x.binding)) {
+        implementGetClass(type);
+      }
+
+      // Reimplement GWT.isClient(), GWT.isProdMode(), GWT.isScript().
+      if ("com.google.gwt.core.client.GWT".equals(type.getName())) {
+        implementGwtMethods(type);
+      }
+
+      if (type instanceof JEnumType) {
+        processEnumType((JEnumType) type);
+      }
+
+      if (type instanceof JClassType) {
+        addBridgeMethods(x.binding);
+      }
+
+      // TODO: uprefs???
+
+      curClass = classStack.pop();
+    }
+
+    protected JBlock pop(Block x) {
+      return (x == null) ? null : (JBlock) pop();
+    }
+
+    protected JExpression pop(Expression x) {
+      if (x == null) {
+        return null;
+      }
+      JExpression result = (JExpression) pop();
+      if (result == null) {
+        assert x instanceof NameReference;
+        return null;
+      }
+      result = simplify(result, x);
+      return result;
+    }
+
+    @SuppressWarnings("unchecked")
+    protected <T extends JExpression> List<T> pop(Expression[] expressions) {
+      if (expressions == null) {
+        return Collections.emptyList();
+      }
+      List<T> result = (List<T>) popList(expressions.length);
+      for (int i = 0; i < expressions.length; ++i) {
+        result.set(i, (T) simplify(result.get(i), expressions[i]));
+      }
+      return result;
+    }
+
+    protected JDeclarationStatement pop(LocalDeclaration decl) {
+      return (decl == null) ? null : (JDeclarationStatement) pop();
+    }
+
+    protected JStatement pop(Statement x) {
+      JNode pop = (x == null) ? null : pop();
+      if (x instanceof Expression) {
+        return simplify((JExpression) pop, (Expression) x).makeStatement();
+      }
+      return (JStatement) pop;
+    }
+
+    @SuppressWarnings("unchecked")
+    protected <T extends JStatement> List<T> pop(Statement[] statements) {
+      if (statements == null) {
+        return Collections.emptyList();
+      }
+      List<T> result = (List<T>) popList(statements.length);
+      int i = 0;
+      for (ListIterator<T> it = result.listIterator(); it.hasNext(); ++i) {
+        Object element = it.next();
+        if (element == null) {
+          it.remove();
+        } else if (element instanceof JExpression) {
+          it.set((T) simplify((JExpression) element, (Expression) statements[i]).makeStatement());
+        }
+      }
+      return result;
+    }
+
+    protected JBlock popBlock(SourceInfo info, Statement statement) {
+      JStatement stmt = pop(statement);
+      if (stmt instanceof JBlock) {
+        return (JBlock) stmt;
+      }
+      JBlock block = new JBlock(info);
+      if (stmt != null) {
+        block.addStmt(stmt);
+      }
+      return block;
+    }
+
+    protected JBlock popBlock(SourceInfo info, Statement[] statements) {
+      List<JStatement> stmts = pop(statements);
+      JBlock block = new JBlock(info);
+      block.addStmts(stmts);
+      return block;
+    }
+
+    protected void pushBinaryOp(Assignment x, JBinaryOperator op) {
+      pushBinaryOp(x, op, x.lhs, x.expression);
+    }
+
+    protected void pushBinaryOp(BinaryExpression x, JBinaryOperator op) {
+      pushBinaryOp(x, op, x.left, x.right);
+    }
+
+    protected boolean visit(TypeDeclaration x) {
+      JDeclaredType type = (JDeclaredType) typeMap.get(x.binding);
+      assert !type.isExternal();
+      classStack.push(curClass);
+      curClass = new ClassInfo(type, x);
+
+      /*
+       * It's okay to defer creation of synthetic fields, they can't be
+       * referenced until we analyze the code.
+       */
+      int index = 0;
+      SourceTypeBinding binding = x.binding;
+      if (binding.isNestedType() && !binding.isStatic()) {
+        // add synthetic fields for outer this and locals
+        assert (type instanceof JClassType);
+        NestedTypeBinding nestedBinding = (NestedTypeBinding) binding;
+        if (nestedBinding.enclosingInstances != null) {
+          for (int i = 0; i < nestedBinding.enclosingInstances.length; ++i) {
+            SyntheticArgumentBinding arg = nestedBinding.enclosingInstances[i];
+            createSyntheticField(arg, type, index++, Disposition.THIS_REF);
+          }
+        }
+
+        if (nestedBinding.outerLocalVariables != null) {
+          for (int i = 0; i < nestedBinding.outerLocalVariables.length; ++i) {
+            SyntheticArgumentBinding arg = nestedBinding.outerLocalVariables[i];
+            // See InnerClassTest.testOuterThisFromSuperCall().
+            boolean isReallyThisRef = false;
+            if (arg.actualOuterLocalVariable instanceof SyntheticArgumentBinding) {
+              SyntheticArgumentBinding outer =
+                  (SyntheticArgumentBinding) arg.actualOuterLocalVariable;
+              if (outer.matchingField != null) {
+                JField field = typeMap.get(outer.matchingField);
+                if (field.isThisRef()) {
+                  isReallyThisRef = true;
+                }
+              }
+            }
+            createSyntheticField(arg, type, index++, isReallyThisRef ? Disposition.THIS_REF
+                : Disposition.FINAL);
+          }
+        }
+      }
+      return true;
+    }
+
+    /**
+     * <p>
+     * Add a bridge method to <code>clazzBinding</code> for any method it
+     * inherits that implements an interface method but that has a different
+     * erased signature from the interface method.
+     * </p>
+     * 
+     * <p>
+     * The need for these bridges was pointed out in issue 3064. The goal is
+     * that virtual method calls through an interface type are translated to
+     * JavaScript that will function correctly. If the interface signature
+     * matches the signature of the implementing method, then nothing special
+     * needs to be done. If they are different, due to the use of generics, then
+     * GenerateJavaScriptAST is careful to do the right thing. There is a
+     * remaining case, though, that GenerateJavaScriptAST is not in a good
+     * position to fix: a method could be inherited from a superclass, used to
+     * implement an interface method that has a different type signature, and
+     * does not have the interface method in its list of overrides. In that
+     * case, a bridge method should be added that overrides the interface method
+     * and then calls the implementation method.
+     * </p>
+     * 
+     * <p>
+     * This method should only be called once all regular, non-bridge methods
+     * have been installed on the GWT types.
+     * </p>
+     */
+    private void addBridgeMethods(SourceTypeBinding clazzBinding) {
+      /*
+       * JDT adds bridge methods in all the places GWT needs them. Use JDT's
+       * bridge methods.
+       */
+      if (clazzBinding.syntheticMethods() != null) {
+        for (SyntheticMethodBinding synthmeth : clazzBinding.syntheticMethods()) {
+          if (synthmeth.purpose == SyntheticMethodBinding.BridgeMethod && !synthmeth.isStatic()) {
+            createBridgeMethod(synthmeth);
+          }
+        }
+      }
+    }
+
+    private JBinaryOperation assignSyntheticField(SourceInfo info, SyntheticArgumentBinding arg) {
+      JParameter param = (JParameter) curMethod.locals.get(arg);
+      assert param != null;
+      JField field = curClass.syntheticFields.get(arg);
+      assert field != null;
+      JFieldRef lhs = makeInstanceFieldRef(info, field);
+      JParameterRef rhs = new JParameterRef(info, param);
+      JBinaryOperation asg =
+          new JBinaryOperation(info, lhs.getType(), JBinaryOperator.ASG, lhs, rhs);
+      return asg;
+    }
+
+    private JExpression box(JExpression original, int implicitConversion) {
+      int typeId = (implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK) >> 4;
+      ClassScope scope = curClass.scope;
+      BaseTypeBinding primitiveType = (BaseTypeBinding) TypeBinding.wellKnownType(scope, typeId);
+      ReferenceBinding boxType = (ReferenceBinding) scope.boxing(primitiveType);
+      MethodBinding valueOfMethod =
+          boxType.getExactMethod(VALUE_OF, new TypeBinding[]{primitiveType}, scope
+              .compilationUnitScope());
+      assert valueOfMethod != null;
+
+      // Add a cast to the correct primitive type if needed.
+      JType targetPrimitiveType = typeMap.get(primitiveType);
+      if (original.getType() != targetPrimitiveType) {
+        original = new JCastOperation(original.getSourceInfo(), targetPrimitiveType, original);
+      }
+
+      JMethod boxMethod = typeMap.get(valueOfMethod);
+      JMethodCall call = new JMethodCall(original.getSourceInfo(), null, boxMethod);
+      call.addArg(original);
+      return call;
+    }
+
+    /**
+     * Create a bridge method. It calls a same-named method with the same
+     * arguments, but with a different type signature.
+     */
+    private void createBridgeMethod(SyntheticMethodBinding jdtBridgeMethod) {
+      SourceInfo info = curClass.classType.getSourceInfo();
+      JMethod implMethod = typeMap.get(jdtBridgeMethod.targetMethod);
+      String[] paramNames = null;
+      List<JParameter> implParams = implMethod.getParams();
+      if (jdtBridgeMethod.parameters != null) {
+        int paramCount = implParams.size();
+        assert paramCount == jdtBridgeMethod.parameters.length;
+        paramNames = new String[paramCount];
+        for (int i = 0; i < paramCount; ++i) {
+          paramNames[i] = implParams.get(i).getName();
+        }
+      }
+      JMethod bridgeMethod = createSynthicMethodFromBinding(info, jdtBridgeMethod, paramNames);
+      if (implMethod.isFinal()) {
+        bridgeMethod.setFinal();
+      }
+
+      // create a call and pass all arguments through, casting if necessary
+      JMethodCall call = new JMethodCall(info, makeThisRef(info), implMethod);
+      for (int i = 0; i < bridgeMethod.getParams().size(); i++) {
+        JParameter param = bridgeMethod.getParams().get(i);
+        JParameterRef paramRef = new JParameterRef(info, param);
+        call.addArg(maybeCast(implParams.get(i).getType(), paramRef));
+      }
+
+      JMethodBody body = (JMethodBody) bridgeMethod.getBody();
+      if (bridgeMethod.getType() == JPrimitiveType.VOID) {
+        body.getBlock().addStmt(call.makeStatement());
+      } else {
+        body.getBlock().addStmt(new JReturnStatement(info, call));
+      }
+    }
+
+    private JField createEnumValuesField(JEnumType type) {
+      // $VALUES = new E[]{A,B,B};
+      JArrayType enumArrayType = new JArrayType(type);
+      JField valuesField =
+          new JField(type.getSourceInfo(), "$VALUES", type, enumArrayType, true, Disposition.FINAL);
+      type.addField(valuesField);
+      SourceInfo info = type.getSourceInfo();
+      List<JExpression> initializers = new ArrayList<JExpression>();
+      for (JEnumField field : type.getEnumList()) {
+        JFieldRef fieldRef = new JFieldRef(info, null, field, type);
+        initializers.add(fieldRef);
+      }
+      JNewArray newExpr = JNewArray.createInitializers(info, enumArrayType, initializers);
+      JFieldRef valuesRef = new JFieldRef(info, null, valuesField, type);
+      JDeclarationStatement declStmt = new JDeclarationStatement(info, valuesRef, newExpr);
+      JBlock clinitBlock = ((JMethodBody) type.getMethods().get(0).getBody()).getBlock();
+
+      /*
+       * HACKY: the $VALUES array must be initialized immediately after all of
+       * the enum fields, but before any user initialization (which might rely
+       * on $VALUES). The "1 + " is the statement containing the call to
+       * Enum.$clinit().
+       */
+      int insertionPoint = 1 + type.getEnumList().size();
+      assert clinitBlock.getStatements().size() >= initializers.size() + 1;
+      clinitBlock.addStmt(insertionPoint, declStmt);
+      return valuesField;
+    }
+
+    private JLocal createLocal(LocalDeclaration x) {
+      LocalVariableBinding b = x.binding;
+      TypeBinding resolvedType = x.type.resolvedType;
+      JType localType;
+      if (resolvedType.constantPoolName() != null) {
+        localType = typeMap.get(resolvedType);
+      } else {
+        // Special case, a statically unreachable local type.
+        localType = JNullType.INSTANCE;
+      }
+      SourceInfo info = makeSourceInfo(x);
+      JLocal newLocal =
+          JProgram.createLocal(info, intern(x.name), localType, b.isFinal(), curMethod.body);
+      curMethod.locals.put(b, newLocal);
+      curMethod.body.addLocal(newLocal);
+      return newLocal;
+    }
+
+    private JField createSyntheticField(SyntheticArgumentBinding arg, JDeclaredType enclosingType,
+        int index, Disposition disposition) {
+      JType type = typeMap.get(arg.type);
+      SourceInfo info = enclosingType.getSourceInfo();
+      JField field = new JField(info, intern(arg.name), enclosingType, type, false, disposition);
+      // TODO: remove me, source identical for now!
+      enclosingType.addField(index, field);
+      // enclosingType.addField(field);
+      curClass.syntheticFields.put(arg, field);
+      if (arg.matchingField != null) {
+        typeMap.setField(arg.matchingField, field);
+      }
+      return field;
+    }
+
+    private JExpression getConstant(SourceInfo info, Constant constant) {
+      switch (constant.typeID()) {
+        case Constant.T_int:
+          return JIntLiteral.get(constant.intValue());
+        case Constant.T_byte:
+          return JIntLiteral.get(constant.byteValue());
+        case Constant.T_short:
+          return JIntLiteral.get(constant.shortValue());
+        case Constant.T_char:
+          return JCharLiteral.get(constant.charValue());
+        case Constant.T_float:
+          return JFloatLiteral.get(constant.floatValue());
+        case Constant.T_double:
+          return JDoubleLiteral.get(constant.doubleValue());
+        case Constant.T_boolean:
+          return JBooleanLiteral.get(constant.booleanValue());
+        case Constant.T_long:
+          return JLongLiteral.get(constant.longValue());
+        case Constant.T_JavaLangString:
+          return getStringLiteral(info, constant.stringValue());
+        case Constant.T_null:
+          return JNullLiteral.INSTANCE;
+        default:
+          throw new InternalCompilerException("Unknown Constant type: " + constant.typeID());
+      }
+    }
+
+    /**
+     * Get a new label of a particular name, or create a new one if it doesn't
+     * exist already.
+     */
+    private JLabel getOrCreateLabel(SourceInfo info, char[] name) {
+      if (name == null) {
+        return null;
+      }
+      String sname = intern(name);
+      JLabel jlabel = curMethod.labels.get(sname);
+      if (jlabel == null) {
+        jlabel = new JLabel(info, sname);
+        curMethod.labels.put(sname, jlabel);
+      }
+      return jlabel;
+    }
+
+    private JStringLiteral getStringLiteral(SourceInfo info, char[] chars) {
+      return new JStringLiteral(info, intern(chars), javaLangString);
+    }
+
+    private JStringLiteral getStringLiteral(SourceInfo info, String string) {
+      return new JStringLiteral(info, intern(string), javaLangString);
+    }
+
+    private void implementGetClass(JDeclaredType type) {
+      JMethod method = type.getMethods().get(2);
+      assert ("getClass".equals(method.getName()));
+      SourceInfo info = method.getSourceInfo();
+      if ("com.google.gwt.lang.Array".equals(type.getName())) {
+        // Special implementation: return this.arrayClass
+        JField arrayClassField = null;
+        for (JField field : type.getFields()) {
+          if ("arrayClass".equals(field.getName())) {
+            arrayClassField = field;
+          }
+        }
+        assert arrayClassField != null;
+        implementMethod(method, new JFieldRef(info, makeThisRef(info), arrayClassField, type));
+      } else {
+        implementMethod(method, new JClassLiteral(info, type));
+      }
+    }
+
+    private void implementGwtMethods(JDeclaredType type) {
+      for (JMethod method : type.getMethods()) {
+        if (method.getOriginalParamTypes().size() != 0) {
+          continue;
+        }
+        if ("isClient".equals(method.getName())) {
+          implementMethod(method, JBooleanLiteral.TRUE);
+        } else if ("isProdMode".equals(method.getName())) {
+          implementMethod(method, JBooleanLiteral.TRUE);
+        } else if ("isScript".equals(method.getName())) {
+          implementMethod(method, JBooleanLiteral.TRUE);
+        }
+      }
+    }
+
+    private void implementMethod(JMethod method, JExpression returnValue) {
+      JMethodBody body = (JMethodBody) method.getBody();
+      JBlock block = body.getBlock();
+      SourceInfo info;
+      if (block.getStatements().size() > 0) {
+        info = block.getStatements().get(0).getSourceInfo();
+      } else {
+        info = method.getSourceInfo();
+      }
+      block.clear();
+      block.addStmt(new JReturnStatement(info, returnValue));
+    }
+
+    private JDeclarationStatement makeDeclaration(SourceInfo info, JLocal local, JExpression value) {
+      return new JDeclarationStatement(info, new JLocalRef(info, local), value);
+    }
+
+    private JFieldRef makeInstanceFieldRef(SourceInfo info, JField field) {
+      return new JFieldRef(info, makeThisRef(info), field, curClass.classType);
+    }
+
+    private JExpression makeLocalRef(SourceInfo info, LocalVariableBinding b) {
+      JVariable variable = curMethod.locals.get(b);
+      assert variable != null;
+      if (variable instanceof JLocal) {
+        return new JLocalRef(info, (JLocal) variable);
+      } else {
+        return new JParameterRef(info, (JParameter) variable);
+      }
+    }
+
+    private JThisRef makeThisRef(SourceInfo info) {
+      return new JThisRef(info, curClass.classType);
+    }
+
+    private JExpression makeThisReference(SourceInfo info, ReferenceBinding targetType,
+        boolean exactMatch, BlockScope scope) {
+      targetType = (ReferenceBinding) targetType.erasure();
+      Object[] path = scope.getEmulationPath(targetType, exactMatch, false);
+      if (path == null) {
+        throw new InternalCompilerException("No emulation path.");
+      }
+      if (path == BlockScope.EmulationPathToImplicitThis) {
+        return makeThisRef(info);
+      }
+      JExpression ref;
+      ReferenceBinding type;
+      if (curMethod.scope.isInsideInitializer() && path[0] instanceof SyntheticArgumentBinding) {
+        SyntheticArgumentBinding b = (SyntheticArgumentBinding) path[0];
+        JField field = curClass.syntheticFields.get(b);
+        assert field != null;
+        ref = makeInstanceFieldRef(info, field);
+        type = (ReferenceBinding) b.type.erasure();
+      } else if (path[0] instanceof SyntheticArgumentBinding) {
+        SyntheticArgumentBinding b = (SyntheticArgumentBinding) path[0];
+        JParameter param = (JParameter) curMethod.locals.get(b);
+        assert param != null;
+        ref = new JParameterRef(info, param);
+        type = (ReferenceBinding) b.type.erasure();
+      } else if (path[0] instanceof FieldBinding) {
+        FieldBinding b = (FieldBinding) path[0];
+        JField field = typeMap.get(b);
+        assert field != null;
+        ref = makeInstanceFieldRef(info, field);
+        type = (ReferenceBinding) b.type.erasure();
+      } else {
+        throw new InternalCompilerException("Unknown emulation path.");
+      }
+      for (int i = 1; i < path.length; ++i) {
+        SyntheticMethodBinding b = (SyntheticMethodBinding) path[i];
+        assert type == b.declaringClass.erasure();
+        FieldBinding fieldBinding = b.targetReadField;
+        JField field = typeMap.get(fieldBinding);
+        assert field != null;
+        ref = new JFieldRef(info, ref, field, curClass.classType);
+        type = (ReferenceBinding) fieldBinding.type.erasure();
+      }
+      return ref;
+    }
+
+    private JExpression maybeBoxOrUnbox(JExpression original, int implicitConversion) {
+      if (implicitConversion != -1) {
+        if ((implicitConversion & TypeIds.BOXING) != 0) {
+          return box(original, implicitConversion);
+        } else if ((implicitConversion & TypeIds.UNBOXING) != 0) {
+          return unbox(original, implicitConversion);
+        }
+      }
+      return original;
+    }
+
+    private JExpression maybeCast(JType expected, JExpression expression) {
+      if (expected != expression.getType()) {
+        // Must be a generic; insert a cast operation.
+        JReferenceType toType = (JReferenceType) expected;
+        return new JCastOperation(expression.getSourceInfo(), toType, expression);
+      } else {
+        return expression;
+      }
+    }
+
+    private JNode pop() {
+      return nodeStack.remove(nodeStack.size() - 1);
+    }
+
+    private List<JExpression> popCallArgs(Expression[] jdtArgs, MethodBinding binding) {
+      List<JExpression> args = pop(jdtArgs);
+      if (!binding.isVarargs()) {
+        return args;
+      }
+
+      // Handle the odd var-arg case.
+      if (jdtArgs == null) {
+        // Get writable collection (args is currently Collections.emptyList()).
+        args = new ArrayList<JExpression>(1);
+      }
+
+      TypeBinding[] params = binding.parameters;
+      int varArg = params.length - 1;
+
+      // See if there's a single varArg which is already an array.
+      if (args.size() == params.length) {
+        if (jdtArgs[varArg].resolvedType.isCompatibleWith(params[varArg])) {
+          // Already the correct array type.
+          return args;
+        }
+      }
+
+      // Need to synthesize an appropriately-typed array.
+      List<JExpression> tail = args.subList(varArg, args.size());
+      ArrayList<JExpression> initializers = new ArrayList<JExpression>(tail);
+      tail.clear();
+      JArrayType lastParamType = (JArrayType) typeMap.get(params[varArg]);
+      JNewArray newArray =
+          JNewArray.createInitializers(SourceOrigin.UNKNOWN, lastParamType, initializers);
+      args.add(newArray);
+      return args;
+    }
+
+    private List<? extends JNode> popList(int count) {
+      List<JNode> tail = nodeStack.subList(nodeStack.size() - count, nodeStack.size());
+      // Make a copy.
+      List<JNode> result = new ArrayList<JNode>(tail);
+      // Causes the tail to be removed.
+      tail.clear();
+      return result;
+    }
+
+    private void popMethodInfo() {
+      curMethod = methodStack.pop();
+    }
+
+    private void processEnumType(JEnumType type) {
+      JField valuesField = createEnumValuesField(type);
+
+      // $clinit, $init, getClass, valueOf, values
+      {
+        JMethod valueOfMethod = type.getMethods().get(3);
+        assert "valueOf".equals(valueOfMethod.getName());
+        writeEnumValueOfMethod(type, valueOfMethod, valuesField);
+      }
+      {
+        JMethod valuesMethod = type.getMethods().get(4);
+        assert "values".equals(valuesMethod.getName());
+        writeEnumValuesMethod(type, valuesMethod, valuesField);
+      }
+    }
+
+    private void processNativeMethod(MethodDeclaration x) {
+      JMethod method = curMethod.method;
+      JsniMethod jsniMethod = jsniMethods.get(x);
+      assert jsniMethod != null;
+      SourceInfo info = method.getSourceInfo();
+      JsFunction jsFunction = jsniMethod.function();
+      JsniMethodBody body = new JsniMethodBody(info);
+      method.setBody(body);
+      jsFunction.setFromJava(true);
+      body.setFunc(jsFunction);
+      // Resolve locals, params, and JSNI.
+      JsParameterResolver localResolver = new JsParameterResolver(jsFunction);
+      localResolver.accept(jsFunction);
+      JsniResolver jsniResolver = new JsniResolver(body);
+      jsniResolver.accept(jsFunction);
+    }
+
+    private void processSuperCallLocalArgs(ReferenceBinding superClass, JMethodCall call) {
+      if (superClass.syntheticOuterLocalVariables() != null) {
+        for (SyntheticArgumentBinding arg : superClass.syntheticOuterLocalVariables()) {
+          // TODO: use emulation path here.
+          // Got to be one of my params
+          JType varType = typeMap.get(arg.type);
+          String varName = intern(arg.name);
+          JParameter param = null;
+          for (JParameter paramIt : curMethod.method.getParams()) {
+            if (varType == paramIt.getType() && varName.equals(paramIt.getName())) {
+              param = paramIt;
+            }
+          }
+          if (param == null) {
+            throw new InternalCompilerException(
+                "Could not find matching local arg for explicit super ctor call.");
+          }
+          call.addArg(new JParameterRef(call.getSourceInfo(), param));
+        }
+      }
+    }
+
+    private void processSuperCallThisArgs(ReferenceBinding superClass, JMethodCall call,
+        JExpression qualifier, Expression qualification) {
+      if (superClass.syntheticEnclosingInstanceTypes() != null) {
+        for (ReferenceBinding targetType : superClass.syntheticEnclosingInstanceTypes()) {
+          if (qualification != null && superClass.enclosingType() == targetType) {
+            assert qualification.resolvedType.erasure().isCompatibleWith(targetType);
+            call.addArg(qualifier);
+          } else {
+            call.addArg(makeThisReference(call.getSourceInfo(), targetType, false, curMethod.scope));
+          }
+        }
+      }
+    }
+
+    private void processThisCallLocalArgs(ReferenceBinding binding, JMethodCall call) {
+      if (binding.syntheticOuterLocalVariables() != null) {
+        for (SyntheticArgumentBinding arg : binding.syntheticOuterLocalVariables()) {
+          JParameter param = (JParameter) curMethod.locals.get(arg);
+          assert param != null;
+          call.addArg(new JParameterRef(call.getSourceInfo(), param));
+        }
+      }
+    }
+
+    private void processThisCallThisArgs(ReferenceBinding binding, JMethodCall call) {
+      if (binding.syntheticEnclosingInstanceTypes() != null) {
+        Iterator<JParameter> paramIt = curMethod.method.getParams().iterator();
+        if (curClass.classType.isEnumOrSubclass() != null) {
+          // Skip past the enum args.
+          paramIt.next();
+          paramIt.next();
+        }
+        for (@SuppressWarnings("unused")
+        ReferenceBinding argType : binding.syntheticEnclosingInstanceTypes()) {
+          JParameter param = paramIt.next();
+          call.addArg(new JParameterRef(call.getSourceInfo(), param));
+        }
+      }
+    }
+
+    private void push(JNode node) {
+      nodeStack.add(node);
+    }
+
+    private void pushBinaryOp(Expression x, JBinaryOperator op, Expression lhs, Expression rhs) {
+      try {
+        JType type = typeMap.get(x.resolvedType);
+        SourceInfo info = makeSourceInfo(x);
+        JExpression exprArg2 = pop(rhs);
+        JExpression exprArg1 = pop(lhs);
+        push(new JBinaryOperation(info, type, op, exprArg1, exprArg2));
+      } catch (Throwable e) {
+        throw translateException(x, e);
+      }
+    }
+
+    private void pushInitializerMethodInfo(FieldDeclaration x, MethodScope scope) {
+      JMethod initMeth;
+      if (x.isStatic()) {
+        initMeth = curClass.type.getMethods().get(0);
+      } else {
+        initMeth = curClass.type.getMethods().get(1);
+      }
+      pushMethodInfo(new MethodInfo(initMeth, (JMethodBody) initMeth.getBody(), scope));
+    }
+
+    private void pushMethodInfo(MethodInfo newInfo) {
+      methodStack.push(curMethod);
+      curMethod = newInfo;
+    }
+
+    private void pushNewExpression(AllocationExpression x, Expression qualifier,
+        List<JExpression> arguments, BlockScope scope) {
+      TypeBinding typeBinding = x.resolvedType;
+      if (typeBinding.constantPoolName() == null) {
+        /*
+         * Weird case: if JDT determines that this local class is totally
+         * uninstantiable, it won't bother allocating a local name.
+         */
+        push(JNullLiteral.INSTANCE);
+        return;
+      }
+      assert typeBinding.isClass() || typeBinding.isEnum();
+
+      SourceInfo info = makeSourceInfo(x);
+      MethodBinding b = x.binding;
+      assert b.isConstructor();
+      JConstructor ctor = (JConstructor) typeMap.get(b);
+      JMethodCall call = new JNewInstance(info, ctor, curClass.type);
+      JExpression qualExpr = pop(qualifier);
+
+      // Enums: hidden arguments for the name and id.
+      if (x.enumConstant != null) {
+        call.addArgs(getStringLiteral(info, x.enumConstant.name), JIntLiteral
+            .get(x.enumConstant.binding.original().id));
+      }
+
+      // Synthetic args for inner classes
+      ReferenceBinding targetBinding = (ReferenceBinding) b.declaringClass.erasure();
+      NestedTypeBinding nestedBinding = null;
+      if (targetBinding.isNestedType() && !targetBinding.isStatic()) {
+        nestedBinding = (NestedTypeBinding) targetBinding;
+      }
+      if (nestedBinding != null) {
+        // Synthetic this args for inner classes
+        if (nestedBinding.enclosingInstances != null) {
+          ReferenceBinding checkedTargetType =
+              targetBinding.isAnonymousType() ? (ReferenceBinding) targetBinding.superclass()
+                  .erasure() : targetBinding;
+          ReferenceBinding targetEnclosingType = checkedTargetType.enclosingType();
+          for (SyntheticArgumentBinding arg : nestedBinding.enclosingInstances) {
+            TypeBinding argType = arg.type.erasure();
+            if (qualifier != null && argType == targetEnclosingType) {
+              call.addArg(qualExpr);
+            } else {
+              JExpression thisRef =
+                  makeThisReference(info, (ReferenceBinding) argType, false, scope);
+              call.addArg(thisRef);
+            }
+          }
+        }
+      }
+
+      // Plain old regular user arguments
+      call.addArgs(arguments);
+
+      // Synthetic args for inner classes
+      if (nestedBinding != null) {
+        // Synthetic locals for local classes
+        if (nestedBinding.outerLocalVariables != null) {
+          for (SyntheticArgumentBinding arg : nestedBinding.outerLocalVariables) {
+            LocalVariableBinding targetVariable = arg.actualOuterLocalVariable;
+            VariableBinding[] path = scope.getEmulationPath(targetVariable);
+            assert path.length == 1;
+            if (curMethod.scope.isInsideInitializer()
+                && path[0] instanceof SyntheticArgumentBinding) {
+              SyntheticArgumentBinding sb = (SyntheticArgumentBinding) path[0];
+              JField field = curClass.syntheticFields.get(sb);
+              assert field != null;
+              call.addArg(makeInstanceFieldRef(info, field));
+            } else if (path[0] instanceof LocalVariableBinding) {
+              JExpression localRef = makeLocalRef(info, (LocalVariableBinding) path[0]);
+              call.addArg(localRef);
+            } else if (path[0] instanceof FieldBinding) {
+              JField field = typeMap.get((FieldBinding) path[0]);
+              assert field != null;
+              call.addArg(makeInstanceFieldRef(info, field));
+            } else {
+              throw new InternalCompilerException("Unknown emulation path.");
+            }
+          }
+        }
+      }
+
+      if (ctor.getEnclosingType() == javaLangString) {
+        /*
+         * MAGIC: java.lang.String is implemented as a JavaScript String
+         * primitive with a modified prototype. This requires funky handling of
+         * constructor calls. We find a method named _String() whose signature
+         * matches the requested constructor
+         * 
+         * TODO(scottb): consider moving this to a later pass.
+         */
+        MethodBinding staticBinding =
+            targetBinding.getExactMethod(_STRING, b.parameters, curCud.scope);
+        assert staticBinding.isStatic();
+        JMethod staticMethod = typeMap.get(staticBinding);
+        JMethodCall newCall = new JMethodCall(info, null, staticMethod);
+        newCall.addArgs(call.getArgs());
+        call = newCall;
+      }
+
+      push(call);
+    }
+
+    /**
+     * Don't process unreachable statements, because JDT doesn't always fully
+     * resolve them, which can crash us.
+     */
+    private Statement[] reduceToReachable(Statement[] statements) {
+      if (statements == null) {
+        return null;
+      }
+      int reachableCount = 0;
+      for (Statement statement : statements) {
+        if ((statement.bits & ASTNode.IsReachable) != 0) {
+          ++reachableCount;
+        }
+      }
+      if (reachableCount == statements.length) {
+        return statements;
+      }
+      Statement[] newStatments = new Statement[reachableCount];
+      int index = 0;
+      for (Statement statement : statements) {
+        if ((statement.bits & ASTNode.IsReachable) != 0) {
+          newStatments[index++] = statement;
+        }
+      }
+      return newStatments;
+    }
+
+    private JExpression resolveNameReference(NameReference x, BlockScope scope) {
+      SourceInfo info = makeSourceInfo(x);
+      if (x.constant != Constant.NotAConstant) {
+        return getConstant(info, x.constant);
+      }
+      Binding binding = x.binding;
+      JExpression result = null;
+      if (binding instanceof LocalVariableBinding) {
+        LocalVariableBinding b = (LocalVariableBinding) binding;
+        if ((x.bits & ASTNode.DepthMASK) != 0) {
+          VariableBinding[] path = scope.getEmulationPath(b);
+          if (path == null) {
+            /*
+             * Don't like this, but in rare cases (e.g. the variable is only
+             * ever used as an unnecessary qualifier) JDT provides no emulation
+             * to the desired variable.
+             */
+            // throw new InternalCompilerException("No emulation path.");
+            return null;
+          }
+          assert path.length == 1;
+          if (curMethod.scope.isInsideInitializer() && path[0] instanceof SyntheticArgumentBinding) {
+            SyntheticArgumentBinding sb = (SyntheticArgumentBinding) path[0];
+            JField field = curClass.syntheticFields.get(sb);
+            assert field != null;
+            result = makeInstanceFieldRef(info, field);
+          } else if (path[0] instanceof LocalVariableBinding) {
+            result = makeLocalRef(info, (LocalVariableBinding) path[0]);
+          } else if (path[0] instanceof FieldBinding) {
+            FieldBinding fb = (FieldBinding) path[0];
+            assert curClass.typeDecl.binding.isCompatibleWith(x.actualReceiverType.erasure());
+            JField field = typeMap.get(fb);
+            assert field != null;
+            result = makeInstanceFieldRef(info, field);
+          } else {
+            throw new InternalCompilerException("Unknown emulation path.");
+          }
+        } else {
+          result = makeLocalRef(info, b);
+        }
+      } else if (binding instanceof FieldBinding) {
+        FieldBinding b = ((FieldBinding) x.binding).original();
+        JField field = typeMap.get(b);
+        assert field != null;
+        JExpression thisRef = null;
+        if (!b.isStatic()) {
+          thisRef = makeThisReference(info, (ReferenceBinding) x.actualReceiverType, false, scope);
+        }
+        result = new JFieldRef(info, thisRef, field, curClass.type);
+      } else {
+        return null;
+      }
+      assert result != null;
+      return result;
+    }
+
+    private JExpression simplify(JExpression result, Expression x) {
+      if (x.constant != null && x.constant != Constant.NotAConstant) {
+        // Prefer JDT-computed constant value to the actual written expression.
+        result = getConstant(result.getSourceInfo(), x.constant);
+      }
+      return maybeBoxOrUnbox(result, x.implicitConversion);
+    }
+
+    private JExpression unbox(JExpression original, int implicitConversion) {
+      int typeId = implicitConversion & TypeIds.COMPILE_TYPE_MASK;
+      ClassScope scope = curClass.scope;
+      BaseTypeBinding primitiveType = (BaseTypeBinding) TypeBinding.wellKnownType(scope, typeId);
+      ReferenceBinding boxType = (ReferenceBinding) scope.boxing(primitiveType);
+      char[] selector = CharOperation.concat(primitiveType.simpleName, VALUE);
+      MethodBinding valueMethod =
+          boxType.getExactMethod(selector, NO_TYPES, scope.compilationUnitScope());
+      assert valueMethod != null;
+      JMethod unboxMethod = typeMap.get(valueMethod);
+      JMethodCall call = new JMethodCall(original.getSourceInfo(), original, unboxMethod);
+      return call;
+    }
+
+    private void writeEnumValueOfMethod(JEnumType type, JMethod method, JField valuesField) {
+      JField mapField;
+      TypeBinding mapType;
+      ReferenceBinding enumType = curCud.scope.getJavaLangEnum();
+      {
+        /*
+         * Make an inner class to hold a lazy-init name-value map. We use a
+         * class to take advantage of its clinit.
+         * 
+         * class Map { $MAP = Enum.createValueOfMap($VALUES); }
+         */
+        SourceInfo info = type.getSourceInfo();
+        JClassType mapClass = new JClassType(info, intern(type.getName() + "$Map"), false, true);
+        mapClass.setSuperClass(javaLangObject);
+        mapClass.setEnclosingType(type);
+        newTypes.add(mapClass);
+
+        MethodBinding[] createValueOfMapBindings = enumType.getMethods(CREATE_VALUE_OF_MAP);
+        assert createValueOfMapBindings.length == 1;
+        MethodBinding createValueOfMapBinding = createValueOfMapBindings[0];
+        mapType = createValueOfMapBinding.returnType;
+
+        mapField =
+            new JField(info, "$MAP", mapClass, typeMap.get(mapType), true, Disposition.FINAL);
+        mapClass.addField(mapField);
+
+        JMethodCall call = new JMethodCall(info, null, typeMap.get(createValueOfMapBinding));
+        call.addArg(new JFieldRef(info, null, valuesField, mapClass));
+        JFieldRef mapRef = new JFieldRef(info, null, mapField, mapClass);
+        JDeclarationStatement declStmt = new JDeclarationStatement(info, mapRef, call);
+        JMethod clinit =
+            createSyntheticMethod(info, "$clinit", mapClass, JPrimitiveType.VOID, false, true,
+                true, true);
+        JBlock clinitBlock = ((JMethodBody) clinit.getBody()).getBlock();
+        clinitBlock.addStmt(declStmt);
+      }
+
+      /*
+       * return Enum.valueOf(Enum$Map.Map.$MAP, name);
+       */
+      {
+        SourceInfo info = method.getSourceInfo();
+
+        MethodBinding valueOfBinding =
+            enumType.getExactMethod(VALUE_OF, new TypeBinding[]{
+                mapType, curCud.scope.getJavaLangString()}, curCud.scope);
+        assert valueOfBinding != null;
+
+        JFieldRef mapRef = new JFieldRef(info, null, mapField, type);
+        JParameterRef nameRef = new JParameterRef(info, method.getParams().get(0));
+        JMethodCall call = new JMethodCall(info, null, typeMap.get(valueOfBinding));
+        call.addArgs(mapRef, nameRef);
+        implementMethod(method, call);
+      }
+    }
+
+    private void writeEnumValuesMethod(JEnumType type, JMethod method, JField valuesField) {
+      // return $VALUES;
+      JFieldRef valuesRef = new JFieldRef(method.getSourceInfo(), null, valuesField, type);
+      implementMethod(method, valuesRef);
+    }
+  }
+
+  static class ClassInfo {
+    public final JClassType classType;
+    public final ClassScope scope;
+    public final Map<SyntheticArgumentBinding, JField> syntheticFields =
+        new IdentityHashMap<SyntheticArgumentBinding, JField>();
+    public final JDeclaredType type;
+    public final TypeDeclaration typeDecl;
+
+    public ClassInfo(JDeclaredType type, TypeDeclaration x) {
+      this.type = type;
+      this.classType = (type instanceof JClassType) ? (JClassType) type : null;
+      this.typeDecl = x;
+      this.scope = x.scope;
+    }
+  }
+
+  static class CudInfo {
+    public final String fileName;
+    public final CompilationUnitScope scope;
+    public final int[] separatorPositions;
+
+    public CudInfo(CompilationUnitDeclaration cud) {
+      fileName = intern(cud.getFileName());
+      separatorPositions = cud.compilationResult().getLineSeparatorPositions();
+      scope = cud.scope;
+    }
+  }
+
+  static class MethodInfo {
+    public final JMethodBody body;
+    public final Map<String, JLabel> labels = new HashMap<String, JLabel>();
+    public final Map<LocalVariableBinding, JVariable> locals =
+        new IdentityHashMap<LocalVariableBinding, JVariable>();
+    public final JMethod method;
+    public final MethodScope scope;
+
+    public MethodInfo(JMethod method, JMethodBody methodBody, MethodScope methodScope) {
+      this.method = method;
+      this.body = methodBody;
+      this.scope = methodScope;
+    }
+  }
+
+  public static boolean ENABLED = false;
+
+  private static final char[] _STRING = "_String".toCharArray();
+  private static final String ARRAY_LENGTH_FIELD = "length";
+  private static final char[] CREATE_VALUE_OF_MAP = "createValueOfMap".toCharArray();
+  private static final char[] HAS_NEXT = "hasNext".toCharArray();
+  private static final char[] ITERATOR = "iterator".toCharArray();
+  private static final char[] NEXT = "next".toCharArray();
+  private static final TypeBinding[] NO_TYPES = new TypeBinding[0];
+  private static final char[] ORDINAL = "ordinal".toCharArray();
+  private static final StringInterner stringInterner = StringInterner.get();
+  private static final char[] VALUE = "Value".toCharArray();
+  private static final char[] VALUE_OF = "valueOf".toCharArray();
+  private static final char[] VALUES = "values".toCharArray();
+
+  static {
+    InternalCompilerException.preload();
+  }
+
+  static String dotify(char[][] name) {
+    StringBuffer result = new StringBuffer();
+    for (int i = 0; i < name.length; ++i) {
+      if (i > 0) {
+        result.append('.');
+      }
+
+      result.append(name[i]);
+    }
+    return result.toString();
+  }
+
+  static String intern(char[] cs) {
+    return intern(String.valueOf(cs));
+  }
+
+  static String intern(String s) {
+    return stringInterner.intern(s);
+  }
+
+  /**
+   * Returns <code>true</code> if JDT optimized the condition to
+   * <code>false</code>.
+   */
+  private static boolean isOptimizedFalse(Expression condition) {
+    if (condition != null) {
+      Constant cst = condition.optimizedBooleanConstant();
+      if (cst != Constant.NotAConstant) {
+        if (cst.booleanValue() == false) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns <code>true</code> if JDT optimized the condition to
+   * <code>true</code>.
+   */
+  private static boolean isOptimizedTrue(Expression condition) {
+    if (condition != null) {
+      Constant cst = condition.optimizedBooleanConstant();
+      if (cst != Constant.NotAConstant) {
+        if (cst.booleanValue()) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  CudInfo curCud = null;
+
+  JClassType javaLangClass = null;
+
+  JClassType javaLangObject = null;
+
+  JClassType javaLangString = null;
+
+  Map<MethodDeclaration, JsniMethod> jsniMethods;
+
+  Map<String, Binding> jsniRefs;
+
+  final ReferenceMapper typeMap = new ReferenceMapper();
+
+  private final AstVisitor astVisitor = new AstVisitor();
+
+  private List<JDeclaredType> newTypes;
+
+  public List<JDeclaredType> process(CompilationUnitDeclaration cud,
+      Map<MethodDeclaration, JsniMethod> jsniMethods, Map<String, Binding> jsniRefs) {
+    if (cud.types == null) {
+      return Collections.emptyList();
+    }
+    this.jsniRefs = jsniRefs;
+    this.jsniMethods = jsniMethods;
+    newTypes = new ArrayList<JDeclaredType>();
+    curCud = new CudInfo(cud);
+
+    for (TypeDeclaration typeDecl : cud.types) {
+      createTypes(typeDecl);
+    }
+
+    // Now that types exist, cache Object, String, etc.
+    javaLangObject = (JClassType) typeMap.get(cud.scope.getJavaLangObject());
+    javaLangString = (JClassType) typeMap.get(cud.scope.getJavaLangString());
+    javaLangClass = (JClassType) typeMap.get(cud.scope.getJavaLangClass());
+
+    for (TypeDeclaration typeDecl : cud.types) {
+      // Resolve super type / interface relationships.
+      resolveTypeRefs(typeDecl);
+    }
+    for (TypeDeclaration typeDecl : cud.types) {
+      // Create fields and empty methods.
+      createMembers(typeDecl);
+    }
+    for (TypeDeclaration typeDecl : cud.types) {
+      // Build the code.
+      typeDecl.traverse(astVisitor, cud.scope);
+    }
+
+    List<JDeclaredType> result = newTypes;
+
+    // Clean up.
+    typeMap.clearSource();
+    this.jsniRefs = jsniRefs;
+    this.jsniMethods = jsniMethods;
+    newTypes = null;
+    curCud = null;
+    javaLangObject = null;
+    javaLangString = null;
+    javaLangClass = null;
+
+    return result;
+  }
+
+  SourceInfo makeSourceInfo(ASTNode x) {
+    int startLine =
+        Util.getLineNumber(x.sourceStart, curCud.separatorPositions, 0,
+            curCud.separatorPositions.length - 1);
+    return SourceOrigin.create(x.sourceStart, x.sourceEnd, startLine, curCud.fileName);
+  }
+
+  InternalCompilerException translateException(ASTNode node, Throwable e) {
+    if (e instanceof VirtualMachineError) {
+      // Always rethrow VM errors (an attempt to wrap may fail).
+      throw (VirtualMachineError) e;
+    }
+    InternalCompilerException ice;
+    if (e instanceof InternalCompilerException) {
+      ice = (InternalCompilerException) e;
+    } else {
+      ice = new InternalCompilerException("Error constructing Java AST", e);
+    }
+    if (node != null) {
+      ice.addNode(node.getClass().getName(), node.toString(), makeSourceInfo(node));
+    }
+    return ice;
+  }
+
+  private void createField(FieldDeclaration x) {
+    if (x instanceof Initializer) {
+      return;
+    }
+    SourceInfo info = makeSourceInfo(x);
+    FieldBinding binding = x.binding;
+    JType type = typeMap.get(binding.type);
+    JDeclaredType enclosingType = (JDeclaredType) typeMap.get(binding.declaringClass);
+
+    JField field;
+    if (x.initialization != null && x.initialization instanceof AllocationExpression
+        && ((AllocationExpression) x.initialization).enumConstant != null) {
+      field =
+          new JEnumField(info, intern(binding.name), binding.original().id,
+              (JEnumType) enclosingType, (JClassType) type);
+    } else {
+      boolean isCompileTimeConstant =
+          binding.isStatic() && (binding.isFinal())
+              && (binding.constant() != Constant.NotAConstant) && (binding.type.isBaseType());
+      assert (type instanceof JPrimitiveType || !isCompileTimeConstant);
+
+      assert (!binding.isFinal() || !binding.isVolatile());
+      Disposition disposition;
+      if (isCompileTimeConstant) {
+        disposition = Disposition.COMPILE_TIME_CONSTANT;
+      } else if (binding.isFinal()) {
+        disposition = Disposition.FINAL;
+      } else if (binding.isVolatile()) {
+        disposition = Disposition.VOLATILE;
+      } else {
+        disposition = Disposition.NONE;
+      }
+
+      field =
+          new JField(info, intern(binding.name), enclosingType, type, binding.isStatic(),
+              disposition);
+    }
+    enclosingType.addField(field);
+    typeMap.setField(binding, field);
+  }
+
+  private void createMembers(TypeDeclaration x) {
+    SourceTypeBinding binding = x.binding;
+    JDeclaredType type = (JDeclaredType) typeMap.get(binding);
+    SourceInfo info = type.getSourceInfo();
+    try {
+      /**
+       * 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. Clinit is always in slot 0, init (if it exists)
+       * is always in slot 1.
+       */
+      assert type.getMethods().size() == 0;
+      createSyntheticMethod(info, "$clinit", type, JPrimitiveType.VOID, false, true, true, true);
+
+      if (type instanceof JClassType) {
+        assert type.getMethods().size() == 1;
+        createSyntheticMethod(info, "$init", type, JPrimitiveType.VOID, false, false, true, true);
+
+        // Add a getClass() implementation for all non-Object classes.
+        if (type != javaLangObject && !JSORestrictionsChecker.isJsoSubclass(binding)) {
+          assert type.getMethods().size() == 2;
+          createSyntheticMethod(info, "getClass", type, javaLangClass, false, false, false, false);
+        }
+      }
+
+      if (type instanceof JEnumType) {
+        {
+          assert type.getMethods().size() == 3;
+          MethodBinding valueOfBinding =
+              binding.getExactMethod(VALUE_OF, new TypeBinding[]{x.scope.getJavaLangString()},
+                  curCud.scope);
+          assert valueOfBinding != null;
+          createSynthicMethodFromBinding(info, valueOfBinding, new String[]{"name"});
+        }
+        {
+          assert type.getMethods().size() == 4;
+          MethodBinding valuesBinding = binding.getExactMethod(VALUES, NO_TYPES, curCud.scope);
+          assert valuesBinding != null;
+          createSynthicMethodFromBinding(info, valuesBinding, null);
+        }
+      }
+
+      if (x.fields != null) {
+        for (FieldDeclaration field : x.fields) {
+          if (x.binding.isLocalType() && field.isStatic()) {
+            /*
+             * Source compatibility with genJavaAst; static fields in local
+             * types don't get visited.
+             */
+          } else {
+            createField(field);
+          }
+        }
+      }
+
+      if (x.methods != null) {
+        for (AbstractMethodDeclaration method : x.methods) {
+          createMethod(method);
+        }
+      }
+
+      if (x.memberTypes != null) {
+        for (TypeDeclaration memberType : x.memberTypes) {
+          createMembers(memberType);
+        }
+      }
+    } catch (Throwable e) {
+      InternalCompilerException ice = translateException(null, e);
+      StringBuffer sb = new StringBuffer();
+      x.printHeader(0, sb);
+      ice.addNode(x.getClass().getName(), sb.toString(), type.getSourceInfo());
+      throw ice;
+    }
+  }
+
+  private void createMethod(AbstractMethodDeclaration x) {
+    if (x instanceof Clinit) {
+      return;
+    }
+    SourceInfo info = makeSourceInfo(x);
+    MethodBinding b = x.binding;
+    ReferenceBinding declaringClass = (ReferenceBinding) b.declaringClass.erasure();
+    Set<String> alreadyNamedVariables = new HashSet<String>();
+    JDeclaredType enclosingType = (JDeclaredType) typeMap.get(declaringClass);
+    assert !enclosingType.isExternal();
+    JMethod method;
+    if (x.isConstructor()) {
+      method = new JConstructor(info, (JClassType) enclosingType);
+      if (x.binding.declaringClass.isEnum()) {
+        // Enums have hidden arguments for name and value
+        method.addParam(new JParameter(info, "enum$name", typeMap.get(x.scope.getJavaLangString()),
+            true, false, method));
+        method.addParam(new JParameter(info, "enum$ordinal", JPrimitiveType.INT, true, false,
+            method));
+      }
+      // add synthetic args for outer this
+      if (declaringClass.isNestedType() && !declaringClass.isStatic()) {
+        NestedTypeBinding nestedBinding = (NestedTypeBinding) declaringClass;
+        if (nestedBinding.enclosingInstances != null) {
+          for (int i = 0; i < nestedBinding.enclosingInstances.length; ++i) {
+            SyntheticArgumentBinding arg = nestedBinding.enclosingInstances[i];
+            String argName = String.valueOf(arg.name);
+            if (alreadyNamedVariables.contains(argName)) {
+              argName += "_" + i;
+            }
+            createParameter(info, arg, argName, method);
+            alreadyNamedVariables.add(argName);
+          }
+        }
+      }
+    } else {
+      method =
+          new JMethod(info, intern(b.selector), enclosingType, typeMap.get(b.returnType), b
+              .isAbstract(), b.isStatic(), b.isFinal(), b.isPrivate());
+    }
+
+    // User args.
+    createParameters(method, x);
+
+    if (x.isConstructor()) {
+      if (declaringClass.isNestedType() && !declaringClass.isStatic()) {
+        // add synthetic args for locals
+        NestedTypeBinding nestedBinding = (NestedTypeBinding) declaringClass;
+        // add synthetic args for outer this and locals
+        if (nestedBinding.outerLocalVariables != null) {
+          for (int i = 0; i < nestedBinding.outerLocalVariables.length; ++i) {
+            SyntheticArgumentBinding arg = nestedBinding.outerLocalVariables[i];
+            String argName = String.valueOf(arg.name);
+            if (alreadyNamedVariables.contains(argName)) {
+              argName += "_" + i;
+            }
+            createParameter(info, arg, argName, method);
+            alreadyNamedVariables.add(argName);
+          }
+        }
+      }
+    }
+
+    mapExceptions(method, b);
+
+    if (b.isSynthetic()) {
+      method.setSynthetic();
+    }
+    enclosingType.addMethod(method);
+    typeMap.setMethod(b, method);
+  }
+
+  private void createParameter(SourceInfo info, LocalVariableBinding binding, JMethod method) {
+    createParameter(info, binding, intern(binding.name), method);
+  }
+
+  private void createParameter(SourceInfo info, LocalVariableBinding binding, String name,
+      JMethod method) {
+    JParameter param =
+        new JParameter(info, name, typeMap.get(binding.type), binding.isFinal(), false, method);
+    method.addParam(param);
+  }
+
+  private void createParameters(JMethod method, AbstractMethodDeclaration x) {
+    if (x.arguments != null) {
+      for (Argument argument : x.arguments) {
+        SourceInfo info = makeSourceInfo(argument);
+        LocalVariableBinding binding = argument.binding;
+        createParameter(info, binding, method);
+      }
+    }
+    method.freezeParamTypes();
+  }
+
+  private JMethod createSyntheticMethod(SourceInfo info, String name, JDeclaredType enclosingType,
+      JType returnType, boolean isAbstract, boolean isStatic, boolean isFinal, boolean isPrivate) {
+    JMethod method =
+        new JMethod(info, name, enclosingType, returnType, isAbstract, isStatic, isFinal, isPrivate);
+    method.freezeParamTypes();
+    method.setSynthetic();
+    method.setBody(new JMethodBody(info));
+    enclosingType.addMethod(method);
+    return method;
+  }
+
+  private JMethod createSynthicMethodFromBinding(SourceInfo info, MethodBinding binding,
+      String[] paramNames) {
+    JMethod method = typeMap.createMethod(info, binding, paramNames);
+    assert !method.getEnclosingType().isExternal();
+    method.setBody(new JMethodBody(info));
+    typeMap.setMethod(binding, method);
+    return method;
+  }
+
+  private void createTypes(TypeDeclaration x) {
+    SourceInfo info = makeSourceInfo(x);
+    try {
+      SourceTypeBinding binding = x.binding;
+      String name;
+      if (binding instanceof LocalTypeBinding) {
+        char[] localName = binding.constantPoolName();
+        name = new String(localName).replace('/', '.');
+      } else {
+        name = dotify(binding.compoundName);
+      }
+      name = intern(name);
+      JDeclaredType type;
+      if (binding.isClass()) {
+        type = new JClassType(info, name, binding.isAbstract(), binding.isFinal());
+      } else if (binding.isInterface() || binding.isAnnotationType()) {
+        type = new JInterfaceType(info, name);
+      } else if (binding.isEnum()) {
+        if (binding.isAnonymousType()) {
+          // Don't model an enum subclass as a JEnumType.
+          type = new JClassType(info, name, false, true);
+        } else {
+          type = new JEnumType(info, name, binding.isAbstract());
+        }
+      } else {
+        throw new InternalCompilerException("ReferenceBinding is not a class, interface, or enum.");
+      }
+      typeMap.setSourceType(binding, type);
+      newTypes.add(type);
+      if (x.memberTypes != null) {
+        for (TypeDeclaration memberType : x.memberTypes) {
+          createTypes(memberType);
+        }
+      }
+    } catch (Throwable e) {
+      InternalCompilerException ice = translateException(null, e);
+      StringBuffer sb = new StringBuffer();
+      x.printHeader(0, sb);
+      ice.addNode(x.getClass().getName(), sb.toString(), info);
+      throw ice;
+    }
+  }
+
+  private void mapExceptions(JMethod method, MethodBinding binding) {
+    for (ReferenceBinding thrownBinding : binding.thrownExceptions) {
+      JClassType type = (JClassType) typeMap.get(thrownBinding);
+      method.addThrownException(type);
+    }
+  }
+
+  private void resolveTypeRefs(TypeDeclaration x) {
+    SourceTypeBinding binding = x.binding;
+    JDeclaredType type = (JDeclaredType) typeMap.get(binding);
+    try {
+      ReferenceBinding superClassBinding = binding.superclass();
+      if (type instanceof JClassType && superClassBinding != null) {
+        assert (binding.superclass().isClass() || binding.superclass().isEnum());
+        JClassType superClass = (JClassType) typeMap.get(superClassBinding);
+        ((JClassType) type).setSuperClass(superClass);
+      }
+
+      ReferenceBinding[] superInterfaces = binding.superInterfaces();
+      for (ReferenceBinding superInterfaceBinding : superInterfaces) {
+        assert (superInterfaceBinding.isInterface());
+        JInterfaceType superInterface = (JInterfaceType) typeMap.get(superInterfaceBinding);
+        type.addImplements(superInterface);
+      }
+
+      ReferenceBinding enclosingBinding = binding.enclosingType();
+      if (enclosingBinding != null) {
+        type.setEnclosingType((JDeclaredType) typeMap.get(enclosingBinding));
+      }
+      if (x.memberTypes != null) {
+        for (TypeDeclaration memberType : x.memberTypes) {
+          resolveTypeRefs(memberType);
+        }
+      }
+    } catch (Throwable e) {
+      InternalCompilerException ice = translateException(null, e);
+      StringBuffer sb = new StringBuffer();
+      x.printHeader(0, sb);
+      ice.addNode(x.getClass().getName(), sb.toString(), type.getSourceInfo());
+      throw ice;
+    }
+  }
+
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ReferenceMapper.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ReferenceMapper.java
new file mode 100644
index 0000000..e0e2612
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ReferenceMapper.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright 2010 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.impl;
+
+import com.google.gwt.dev.jjs.InternalCompilerException;
+import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.SourceOrigin;
+import com.google.gwt.dev.jjs.ast.JArrayType;
+import com.google.gwt.dev.jjs.ast.JClassType;
+import com.google.gwt.dev.jjs.ast.JConstructor;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
+import com.google.gwt.dev.jjs.ast.JEnumType;
+import com.google.gwt.dev.jjs.ast.JField;
+import com.google.gwt.dev.jjs.ast.JField.Disposition;
+import com.google.gwt.dev.jjs.ast.JInterfaceType;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JNullType;
+import com.google.gwt.dev.jjs.ast.JParameter;
+import com.google.gwt.dev.jjs.ast.JPrimitiveType;
+import com.google.gwt.dev.jjs.ast.JType;
+import com.google.gwt.dev.util.StringInterner;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
+import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
+import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
+import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.SyntheticArgumentBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Creates unresolved references to types, fields, and methods.
+ */
+public class ReferenceMapper {
+
+  private final List<String> argNames = new ArrayList<String>();
+  private final Map<String, JField> fields = new HashMap<String, JField>();
+  private final Map<String, JMethod> methods = new HashMap<String, JMethod>();
+  private final Map<String, JField> sourceFields = new HashMap<String, JField>();
+  private final Map<String, JMethod> sourceMethods = new HashMap<String, JMethod>();
+  private final Map<String, JDeclaredType> sourceTypes = new HashMap<String, JDeclaredType>();
+  private final StringInterner stringInterner = StringInterner.get();
+  private final Map<String, JType> types = new HashMap<String, JType>();
+
+  {
+    put(JPrimitiveType.BOOLEAN, JPrimitiveType.BYTE, JPrimitiveType.CHAR, JPrimitiveType.DOUBLE,
+        JPrimitiveType.FLOAT, JPrimitiveType.INT, JPrimitiveType.LONG, JPrimitiveType.SHORT,
+        JPrimitiveType.VOID, JNullType.INSTANCE);
+  }
+
+  public void clearSource() {
+    sourceFields.clear();
+    sourceMethods.clear();
+    sourceTypes.clear();
+  }
+
+  public JField get(FieldBinding binding) {
+    binding = binding.original();
+    String key = signature(binding);
+    JField sourceField = sourceFields.get(key);
+    if (sourceField != null) {
+      assert !sourceField.getEnclosingType().isExternal();
+      return sourceField;
+    }
+    JField field = fields.get(key);
+    if (field == null) {
+      field = createField(binding);
+      assert field.getEnclosingType().isExternal();
+      fields.put(key, field);
+    }
+    return field;
+  }
+
+  public JMethod get(MethodBinding binding) {
+    binding = binding.original();
+    String key = signature(binding);
+    JMethod sourceMethod = sourceMethods.get(key);
+    if (sourceMethod != null) {
+      assert !sourceMethod.getEnclosingType().isExternal();
+      return sourceMethod;
+    }
+    JMethod method = methods.get(key);
+    if (method == null) {
+      if (binding.isConstructor()) {
+        method = createConstructor(SourceOrigin.UNKNOWN, binding);
+      } else {
+        method = createMethod(SourceOrigin.UNKNOWN, binding, null);
+      }
+      assert method.getEnclosingType().isExternal();
+      methods.put(key, method);
+    }
+    return method;
+  }
+
+  public JType get(TypeBinding binding) {
+    binding = binding.erasure();
+    String key = signature(binding);
+    JDeclaredType sourceType = sourceTypes.get(key);
+    if (sourceType != null) {
+      assert !sourceType.isExternal();
+      return sourceType;
+    }
+    JType type = types.get(key);
+    if (type == null) {
+      assert !(binding instanceof BaseTypeBinding);
+      if (binding instanceof ArrayBinding) {
+        ArrayBinding arrayBinding = (ArrayBinding) binding;
+        type = new JArrayType(get(arrayBinding.elementsType()));
+      } else {
+        ReferenceBinding refBinding = (ReferenceBinding) binding;
+        type = createType(refBinding);
+        if (type instanceof JClassType) {
+          ReferenceBinding superclass = refBinding.superclass();
+          if (superclass != null) {
+            ((JClassType) type).setSuperClass((JClassType) get(superclass));
+          }
+        }
+        if (type instanceof JDeclaredType) {
+          ReferenceBinding[] superInterfaces = refBinding.superInterfaces();
+          JDeclaredType declType = (JDeclaredType) type;
+          if (superInterfaces != null) {
+            for (ReferenceBinding intf : superInterfaces) {
+              declType.addImplements((JInterfaceType) get(intf));
+            }
+          }
+          declType.setExternal(true);
+          // Emulate clinit method for super clinit calls.
+          JMethod clinit =
+              new JMethod(SourceOrigin.UNKNOWN, "$clinit", declType, JPrimitiveType.VOID, false,
+                  true, true, true);
+          clinit.freezeParamTypes();
+          clinit.setSynthetic();
+          declType.addMethod(clinit);
+        }
+      }
+      types.put(key, type);
+    }
+    return type;
+  }
+
+  public void setField(FieldBinding binding, JField field) {
+    String key = signature(binding);
+    sourceFields.put(key, field);
+  }
+
+  public void setMethod(MethodBinding binding, JMethod method) {
+    String key = signature(binding);
+    sourceMethods.put(key, method);
+  }
+
+  public void setSourceType(SourceTypeBinding binding, JDeclaredType type) {
+    String key = signature(binding);
+    sourceTypes.put(key, type);
+  }
+
+  JMethod createConstructor(SourceInfo info, MethodBinding b) {
+    JDeclaredType enclosingType = (JDeclaredType) get(b.declaringClass);
+    JMethod method = new JConstructor(info, (JClassType) enclosingType);
+    enclosingType.addMethod(method);
+
+    /*
+     * Don't need to synthesize enum intrinsic args because enum ctors can only
+     * be called locally.
+     */
+
+    int argPosition = 0;
+
+    ReferenceBinding declaringClass = b.declaringClass;
+    if (declaringClass.isNestedType() && !declaringClass.isStatic()) {
+      // add synthetic args for outer this
+      if (declaringClass.syntheticEnclosingInstanceTypes() != null) {
+        for (ReferenceBinding argType : declaringClass.syntheticEnclosingInstanceTypes()) {
+          createParameter(info, argType, method, argPosition++);
+        }
+      }
+    }
+
+    // User args.
+    argPosition = mapParameters(info, method, b, argPosition);
+
+    if (declaringClass.isNestedType() && !declaringClass.isStatic()) {
+      // add synthetic args for locals
+      if (declaringClass.syntheticOuterLocalVariables() != null) {
+        for (SyntheticArgumentBinding arg : declaringClass.syntheticOuterLocalVariables()) {
+          createParameter(info, arg.type, method, argPosition++);
+        }
+      }
+    }
+
+    mapExceptions(method, b);
+    if (b.isSynthetic()) {
+      method.setSynthetic();
+    }
+    return method;
+  }
+
+  JMethod createMethod(SourceInfo info, MethodBinding b, String[] paramNames) {
+    JDeclaredType enclosingType = (JDeclaredType) get(b.declaringClass);
+    JMethod method =
+        new JMethod(info, intern(b.selector), enclosingType, get(b.returnType), b.isAbstract(), b
+            .isStatic(), b.isFinal(), b.isPrivate());
+    enclosingType.addMethod(method);
+    if (paramNames == null) {
+      mapParameters(info, method, b, 0);
+    } else {
+      mapParameters(info, method, b, paramNames);
+    }
+    mapExceptions(method, b);
+    if (b.isSynthetic()) {
+      method.setSynthetic();
+    }
+    return method;
+  }
+
+  private JField createField(FieldBinding binding) {
+    JDeclaredType enclosingType = (JDeclaredType) get(binding.declaringClass);
+
+    boolean isCompileTimeConstant =
+        binding.isStatic() && (binding.isFinal()) && (binding.constant() != Constant.NotAConstant)
+            && (binding.type.isBaseType());
+    assert (get(binding.type) instanceof JPrimitiveType || !isCompileTimeConstant);
+
+    assert (!binding.isFinal() || !binding.isVolatile());
+    Disposition disposition;
+    if (isCompileTimeConstant) {
+      disposition = Disposition.COMPILE_TIME_CONSTANT;
+    } else if (binding.isFinal()) {
+      disposition = Disposition.FINAL;
+    } else if (binding.isVolatile()) {
+      disposition = Disposition.VOLATILE;
+    } else {
+      disposition = Disposition.NONE;
+    }
+
+    JField field =
+        new JField(SourceOrigin.UNKNOWN, intern(binding.name), enclosingType, get(binding.type),
+            binding.isStatic(), disposition);
+    enclosingType.addField(field);
+    return field;
+  }
+
+  private JParameter createParameter(SourceInfo info, TypeBinding paramType,
+      JMethod enclosingMethod, int argPosition) {
+    JType type = get(paramType);
+    ensureArgNames(argPosition);
+    JParameter param =
+        new JParameter(info, argNames.get(argPosition), type, true, false, enclosingMethod);
+    enclosingMethod.addParam(param);
+    return param;
+  }
+
+  private JParameter createParameter(SourceInfo info, TypeBinding paramType,
+      JMethod enclosingMethod, String name) {
+    JParameter param = new JParameter(info, name, get(paramType), true, false, enclosingMethod);
+    enclosingMethod.addParam(param);
+    return param;
+  }
+
+  private JDeclaredType createType(ReferenceBinding binding) {
+    String name = GwtAstBuilder.dotify(binding.compoundName);
+    SourceInfo info = SourceOrigin.UNKNOWN;
+    if (binding.isClass()) {
+      return new JClassType(info, name, binding.isAbstract(), binding.isFinal());
+    } else if (binding.isInterface() || binding.isAnnotationType()) {
+      return new JInterfaceType(info, name);
+    } else if (binding.isEnum()) {
+      if (binding.isAnonymousType()) {
+        // Don't model an enum subclass as a JEnumType.
+        return new JClassType(info, name, false, true);
+      } else {
+        return new JEnumType(info, name, binding.isAbstract());
+      }
+    } else {
+      throw new InternalCompilerException("ReferenceBinding is not a class, interface, or enum.");
+    }
+  }
+
+  private void ensureArgNames(int required) {
+    for (int i = argNames.size(); i <= required; ++i) {
+      argNames.add(intern("arg" + i));
+    }
+  }
+
+  private String intern(char[] cs) {
+    return intern(String.valueOf(cs));
+  }
+
+  private String intern(String s) {
+    return stringInterner.intern(s);
+  }
+
+  private void mapExceptions(JMethod method, MethodBinding binding) {
+    for (ReferenceBinding thrownBinding : binding.thrownExceptions) {
+      JClassType type = (JClassType) get(thrownBinding);
+      method.addThrownException(type);
+    }
+  }
+
+  private int mapParameters(SourceInfo info, JMethod method, MethodBinding binding, int argPosition) {
+    if (binding.parameters != null) {
+      ensureArgNames(argPosition + binding.parameters.length);
+      for (TypeBinding argType : binding.parameters) {
+        createParameter(info, argType, method, argNames.get(argPosition++));
+      }
+    }
+    method.freezeParamTypes();
+    return argPosition;
+  }
+
+  private void mapParameters(SourceInfo info, JMethod method, MethodBinding binding,
+      String[] paramNames) {
+    if (binding.parameters != null) {
+      int i = 0;
+      for (TypeBinding argType : binding.parameters) {
+        createParameter(info, argType, method, paramNames[i++]);
+      }
+    }
+    method.freezeParamTypes();
+  }
+
+  private void put(JType... baseTypes) {
+    for (JType type : baseTypes) {
+      types.put(type.getName(), type);
+    }
+  }
+
+  private String signature(FieldBinding binding) {
+    StringBuilder sb = new StringBuilder();
+    sb.append(binding.declaringClass.constantPoolName());
+    sb.append('.');
+    sb.append(binding.name);
+    sb.append(':');
+    sb.append(binding.type.constantPoolName());
+    return sb.toString();
+  }
+
+  private String signature(MethodBinding binding) {
+    StringBuilder sb = new StringBuilder();
+    sb.append(binding.declaringClass.constantPoolName());
+    sb.append('.');
+    sb.append(binding.selector);
+    sb.append('(');
+    for (TypeBinding paramType : binding.parameters) {
+      sb.append(paramType.constantPoolName());
+    }
+    sb.append(')');
+    sb.append(binding.returnType.constantPoolName());
+    return sb.toString();
+  }
+
+  private String signature(TypeBinding binding) {
+    if (binding.isBaseType()) {
+      return String.valueOf(binding.sourceName());
+    } else {
+      return String.valueOf(binding.constantPoolName());
+    }
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/javac/impl/JavaResourceBase.java b/dev/core/test/com/google/gwt/dev/javac/impl/JavaResourceBase.java
index 18d7465..016e933 100644
--- a/dev/core/test/com/google/gwt/dev/javac/impl/JavaResourceBase.java
+++ b/dev/core/test/com/google/gwt/dev/javac/impl/JavaResourceBase.java
@@ -143,6 +143,8 @@
       code.append("import java.io.Serializable;\n");
       code.append("public abstract class Enum<E extends Enum<E>> implements Serializable {\n");
       code.append("  protected Enum(String name, int ordinal) {}\n");
+      code.append("  protected static Object createValueOfMap(Enum[] constants) { return null; }\n");
+      code.append("  protected static Enum valueOf(Object map, String name) { return null; }\n");
       code.append("}\n");
       return code;
     }
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/PrunerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/PrunerTest.java
index bb44cd6..19ee872 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/PrunerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/PrunerTest.java
@@ -112,7 +112,7 @@
     assertNull(result.findClass("UninstantiatedClass"));
 
     assertEquals(
-        "public static <null> returnUninstantiatedClass(){\n" + 
+        "public static null returnUninstantiatedClass(){\n" + 
         "  return null;\n" +
         "}", 
         result.findMethod("returnUninstantiatedClass").toSource());
@@ -123,11 +123,11 @@
         result.findMethod("methodWithUninstantiatedParam").toSource());
 
     assertEquals(
-        "[final <null> nullField, int field2]", 
+        "[final null nullField, int field2]", 
         ((JsniMethodBody) result.findMethod("usedNativeMethod").getBody()).
         getJsniFieldRefs().toString());
     assertEquals(
-        "[public final <null> nullMethod(), public void method2()]", 
+        "[public final null nullMethod(), public void method2()]", 
         ((JsniMethodBody) result.findMethod("usedNativeMethod").getBody()).
         getJsniMethodRefs().toString());
 
diff --git a/eclipse/samples/Hello/Hello-compModule.launch b/eclipse/samples/Hello/Hello-compModule.launch
new file mode 100644
index 0000000..48d2178
--- /dev/null
+++ b/eclipse/samples/Hello/Hello-compModule.launch
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>

+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">

+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">

+<listEntry value="/Hello"/>

+</listAttribute>

+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">

+<listEntry value="4"/>

+</listAttribute>

+<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>

+<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">

+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER&quot; javaProject=&quot;Hello&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>

+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/Hello/core/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>

+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gwt-user/core/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>

+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gwt-user/core/super&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>

+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gwt-dev/core/super&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>

+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;Hello&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>

+</listAttribute>

+<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>

+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.CompileModule"/>

+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="com.google.gwt.sample.hello.Hello"/>

+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="Hello"/>

+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea&#13;&#10;-Xmx512M&#13;&#10;-Dgwt.devjar=${gwt_devjar}&#13;&#10;-Dgwt.speedtracerlog=log.html"/>

+</launchConfiguration>

diff --git a/user/build.xml b/user/build.xml
index 6d4a2dc..f8f3236 100755
--- a/user/build.xml
+++ b/user/build.xml
@@ -35,6 +35,9 @@
   <property name="gwt.tck.testcase.dev.includes" value="com/google/gwt/validation/tck/**/*GwtSuite.class" />
   <property name="gwt.tct.testcase.dev.excludes" value="" />
 
+  <property name="gwt.nongwt.testcase.includes" value="com/google/gwt/dev/jjs/GwtAstBuilderTest.class" />
+  <property name="gwt.nongwt.testcase.excludes" value="" />
+
   <!--
     Test args can be specified per test target type.
   -->
@@ -522,6 +525,7 @@
       <antcall target="test.web.htmlunit"/>
       <antcall target="test.draft.htmlunit"/>
       <antcall target="test.nometa.htmlunit"/>
+      <antcall target="test.nongwt"/>
     </parallel>
     </limit>
   </target>
@@ -547,6 +551,24 @@
     </limit>
   </target>
 
+  <target name="test.nongwt"
+      depends="compile, compile.tests"
+      description="Run JRE-only tests."
+      unless="test.nongwt.disable">
+    <fileset id="test.nongwt.tests" dir="${javac.junit.out}"
+        includes="${gwt.nongwt.testcase.includes}"
+        excludes="${gwt.nongwt.testcase.excludes}" />
+    <gwt.junit test.name="test.nongwt"
+        test.args="${test.args}"
+        test.jvmargs="${test.jvmargs}"
+        test.out="${junit.out}/nongwt"
+        test.cases="test.nongwt.tests" >
+      <extraclasspaths>
+        <path refid="test.extraclasspath" />
+      </extraclasspaths>
+    </gwt.junit>
+  </target>
+
   <target name="test.dev"
       depends="compile, compile.tests"
       description="Run dev-mode tests for this project.">
diff --git a/user/test/com/google/gwt/dev/jjs/GwtAstBuilderTest.java b/user/test/com/google/gwt/dev/jjs/GwtAstBuilderTest.java
new file mode 100644
index 0000000..6cc32c9
--- /dev/null
+++ b/user/test/com/google/gwt/dev/jjs/GwtAstBuilderTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2011 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;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.CompileModule;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+
+import junit.framework.TestCase;
+
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Massive test for {@link com.google.gwt.dev.jjs.impl.GwtAstBuilder}, uses
+ * CompilerSuite under the hood to test source compatibility between
+ * {@link com.google.gwt.dev.jjs.impl.GwtAstBuilder} and
+ * {@link com.google.gwt.dev.jjs.impl.GenerateJavaAST}.
+ */
+public class GwtAstBuilderTest extends TestCase {
+
+  private static TreeLogger createLogger() {
+    PrintWriterTreeLogger logger = new PrintWriterTreeLogger(new PrintWriter(System.err, true));
+    logger.setMaxDetail(TreeLogger.ERROR);
+    return logger;
+  }
+
+  public void testGwtAstBuilder() throws UnableToCompleteException {
+    TreeLogger logger = createLogger();
+    ModuleDef module =
+        ModuleDefLoader.createSyntheticModule(logger,
+            "com.google.gwt.dev.jjs.CompilerSuite.GwtAstBuilderTest", new String[]{
+                "com.google.gwt.junit.JUnit", "com.google.gwt.dev.jjs.CompilerSuite"}, false);
+    CompilationState compilationState = CompileModule.buildGwtAst(logger, module);
+    assertFalse(compilationState.hasErrors());
+    JProgram jprogram = CompileModule.buildGenerateJavaAst(logger, module, compilationState);
+
+    Map<String, JDeclaredType> compStateTypes = new HashMap<String, JDeclaredType>();
+    for (CompilationUnit unit : compilationState.getCompilationUnits()) {
+      for (JDeclaredType type : unit.getTypes()) {
+        compStateTypes.put(type.getName(), type);
+      }
+    }
+
+    for (JDeclaredType genJavaAstType : jprogram.getDeclaredTypes()) {
+      String typeName = genJavaAstType.getName();
+      if ("com.google.gwt.core.client.JavaScriptObject".equals(typeName)) {
+        // Known mismatch; genJavaAst version implements all JSO interfaces.
+        continue;
+      }
+      if (typeName.startsWith("com.google.gwt.lang.asyncloaders")) {
+        // GwtAstBuilder doesn't build these; added later.
+        continue;
+      }
+      if ("com.google.gwt.dev.jjs.test.B$1".equals(typeName)) {
+        // Known mismatch; genJavaAst is "wrong".
+        continue;
+      }
+      if (typeName.startsWith("com.google.gwt.dev.jjs.test.CoverageTest$Inner$1")) {
+        // Known mismatch; two different emulation paths do the same thing.
+        continue;
+      }
+      JDeclaredType compStateType = compStateTypes.get(typeName);
+      assertNotNull("No matching prebuilt type for '" + typeName + "'", compStateType);
+      String oldSource = genJavaAstType.toSource();
+      String newSource = compStateType.toSource();
+      assertEquals("Mismatched output for '" + typeName + "'", oldSource, newSource);
+    }
+  }
+}