Tracks nullness within the compiler by adding a JNonNull type.

Review by: scottb


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7279 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/SizeMapRecorder.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/SizeMapRecorder.java
index 19ea629..d1543ef 100644
--- a/dev/core/src/com/google/gwt/core/ext/soyc/impl/SizeMapRecorder.java
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/SizeMapRecorder.java
@@ -16,8 +16,8 @@
 package com.google.gwt.core.ext.soyc.impl;
 
 import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.jjs.ast.JClassType;
 import com.google.gwt.dev.jjs.ast.JMethod;
-import com.google.gwt.dev.jjs.ast.JReferenceType;
 import com.google.gwt.dev.jjs.ast.JType;
 import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap;
 import com.google.gwt.dev.js.SizeBreakdown;
@@ -182,7 +182,7 @@
       return new TypedProgramReference("method", desc);
     }
 
-    JReferenceType type = jjsmap.nameToType(name);
+    JClassType type = jjsmap.nameToType(name);
     if (type != null) {
       return new TypedProgramReference("type", type.getName());
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/CorrelationFactory.java b/dev/core/src/com/google/gwt/dev/jjs/CorrelationFactory.java
index 4dd00aa..be0fbfb 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/CorrelationFactory.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/CorrelationFactory.java
@@ -17,9 +17,9 @@
 
 import com.google.gwt.dev.jjs.Correlation.Axis;
 import com.google.gwt.dev.jjs.Correlation.Literal;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
 import com.google.gwt.dev.jjs.ast.JField;
 import com.google.gwt.dev.jjs.ast.JMethod;
-import com.google.gwt.dev.jjs.ast.JReferenceType;
 import com.google.gwt.dev.jjs.ast.JType;
 import com.google.gwt.dev.js.ast.JsFunction;
 import com.google.gwt.dev.js.ast.JsName;
@@ -51,7 +51,7 @@
     }
 
     @Override
-    public Correlation by(JReferenceType type) {
+    public Correlation by(JDeclaredType type) {
       return null;
     }
 
@@ -155,7 +155,7 @@
     }
 
     @Override
-    public Correlation by(JReferenceType type) {
+    public Correlation by(JDeclaredType type) {
       Correlation toReturn = canonicalMap.get(type);
       if (toReturn == null) {
         toReturn = new Correlation(Axis.CLASS, type.getName(), type);
@@ -225,7 +225,7 @@
 
   public abstract Correlation by(JMethod method);
 
-  public abstract Correlation by(JReferenceType type);
+  public abstract Correlation by(JDeclaredType type);
 
   public abstract Correlation by(JsFunction function);
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index aef1490..d4fbd85 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -724,7 +724,7 @@
       throw new UnableToCompleteException();
     }
 
-    JMethod entryMethod = findMainMethodRecurse(reboundEntryType);
+    JMethod entryMethod = findMainMethodRecurse(entryClass);
     if (entryMethod == null) {
       logger.log(TreeLogger.ERROR,
           "Could not find entry method 'onModuleLoad()' method in entry point class '"
@@ -738,7 +738,7 @@
               + originalMainClassName + "' must not be abstract", null);
       throw new UnableToCompleteException();
     }
-    SourceInfo sourceInfo = reboundEntryType.getSourceInfo().makeChild(
+    SourceInfo sourceInfo = entryClass.getSourceInfo().makeChild(
         JavaToJavaScriptCompiler.class, "Rebound entry point");
 
     JExpression qualifier = null;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/HasSettableType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/HasSettableType.java
deleted file mode 100644
index c56737f..0000000
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/HasSettableType.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright 2007 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.ast;
-
-/**
- * Characteristic interface to be overlaid on AST constructs that have a type
- * that can be explicitly set.
- */
-public interface HasSettableType extends HasType {
-  void setType(JType newType);
-}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JArrayRef.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JArrayRef.java
index 8d8e469..e63722f 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JArrayRef.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JArrayRef.java
@@ -22,8 +22,8 @@
  */
 public class JArrayRef extends JExpression {
 
-  private JExpression instance;
   private JExpression indexExpr;
+  private JExpression instance;
 
   public JArrayRef(SourceInfo info, JExpression instance, JExpression indexExpr) {
     super(info);
@@ -31,6 +31,14 @@
     this.indexExpr = indexExpr;
   }
 
+  public JArrayType getArrayType() {
+    JType type = instance.getType();
+    if (type instanceof JNullType) {
+      return null;
+    }
+    return (JArrayType) ((JReferenceType) type).getUnderlyingType();
+  }
+
   public JExpression getIndexExpr() {
     return indexExpr;
   }
@@ -40,12 +48,9 @@
   }
 
   public JType getType() {
-    JType type = instance.getType();
-    if (type instanceof JNullType) {
-      return JNullType.INSTANCE;
-    }
-    JArrayType arrayType = (JArrayType) type;
-    return arrayType.getElementType();
+    JArrayType arrayType = getArrayType();
+    return (arrayType == null) ? JNullType.INSTANCE
+        : arrayType.getElementType();
   }
 
   public boolean hasSideEffects() {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JBinaryOperation.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JBinaryOperation.java
index 54db51d..61af84f 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JBinaryOperation.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JBinaryOperation.java
@@ -20,7 +20,7 @@
 /**
  * Binary operator expression.
  */
-public class JBinaryOperation extends JExpression implements HasSettableType {
+public class JBinaryOperation extends JExpression {
 
   private JExpression lhs;
   private final JBinaryOperator op;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JConditional.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JConditional.java
index f474f25..b8f4dff 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JConditional.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JConditional.java
@@ -20,7 +20,7 @@
 /**
  * Conditional expression.
  */
-public class JConditional extends JExpression implements HasSettableType {
+public class JConditional extends JExpression {
 
   private JExpression elseExpr;
   private JExpression ifTest;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JGwtCreate.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JGwtCreate.java
index cb46c35..69ebac2 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JGwtCreate.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JGwtCreate.java
@@ -25,7 +25,7 @@
  * finalized. Replaced with the entry call for the appropriate rebind result in
  * that permutation.
  */
-public class JGwtCreate extends JExpression implements HasSettableType {
+public class JGwtCreate extends JExpression {
 
   public static JExpression createInstantiationExpression(SourceInfo info,
       JClassType classType) {
@@ -46,7 +46,8 @@
       return null;
     }
     // Call it, using a new expression as a qualifier
-    JNewInstance newInstance = new JNewInstance(info, classType);
+    JNewInstance newInstance = new JNewInstance(info,
+        (JNonNullType) noArgCtor.getType());
     return new JMethodCall(info, newInstance, noArgCtor);
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
index 0a4a833..e2cb5de 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
@@ -31,7 +31,7 @@
  * A Java method implementation.
  */
 public final class JMethod extends JNode implements HasEnclosingType, HasName,
-    HasSettableType, CanBeAbstract, CanBeSetFinal, CanBeNative, CanBeStatic {
+    HasType, CanBeAbstract, CanBeSetFinal, CanBeNative, CanBeStatic {
 
   private static final String TRACE_METHOD_WILDCARD = "*";
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JNewArray.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JNewArray.java
index 24de34a..5df5e87 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JNewArray.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JNewArray.java
@@ -23,7 +23,7 @@
 /**
  * New array expression.
  */
-public class JNewArray extends JExpression implements HasSettableType {
+public class JNewArray extends JExpression {
 
   public static JNewArray createDims(JProgram program, SourceInfo info,
       JArrayType arrayType, List<JExpression> dims) {
@@ -45,39 +45,40 @@
       classLiterals.add(classLit);
       cur = ((JArrayType) cur).getElementType();
     }
-    return new JNewArray(info, arrayType, dims, null, classLiterals);
+    return new JNewArray(info, program.getNonNullType(arrayType), dims, null,
+        classLiterals);
   }
 
   public static JNewArray createInitializers(JProgram program, SourceInfo info,
       JArrayType arrayType, List<JExpression> initializers) {
     List<JClassLiteral> classLiterals = new ArrayList<JClassLiteral>();
     classLiterals.add(program.getLiteralClass(arrayType));
-    return new JNewArray(info, arrayType, null, initializers, classLiterals);
+    return new JNewArray(info, program.getNonNullType(arrayType), null,
+        initializers, classLiterals);
   }
 
   public final List<JExpression> dims;
 
   public final List<JExpression> initializers;
 
-  private JArrayType arrayType;
+  private JNonNullType type;
 
   /**
    * The list of class literals that will be needed to support this expression.
    */
   private final List<JClassLiteral> classLiterals;
 
-  public JNewArray(SourceInfo info, JArrayType arrayType,
-      List<JExpression> dims, List<JExpression> initializers,
-      List<JClassLiteral> classLits) {
+  public JNewArray(SourceInfo info, JNonNullType type, List<JExpression> dims,
+      List<JExpression> initializers, List<JClassLiteral> classLits) {
     super(info);
-    this.arrayType = arrayType;
+    setType(type);
     this.dims = dims;
     this.initializers = initializers;
     this.classLiterals = classLits;
   }
 
   public JArrayType getArrayType() {
-    return arrayType;
+    return (JArrayType) type.getUnderlyingType();
   }
 
   /**
@@ -98,8 +99,8 @@
     return classLiterals;
   }
 
-  public JType getType() {
-    return arrayType;
+  public JNonNullType getType() {
+    return type;
   }
 
   @Override
@@ -122,8 +123,9 @@
     return false;
   }
 
-  public void setType(JType arrayType) {
-    this.arrayType = (JArrayType) arrayType;
+  public void setType(JNonNullType type) {
+    assert type.getUnderlyingType() instanceof JArrayType;
+    this.type = type;
   }
 
   public void traverse(JVisitor visitor, Context ctx) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JNewInstance.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JNewInstance.java
index f8a11a1..eeea366 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JNewInstance.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JNewInstance.java
@@ -24,21 +24,23 @@
  */
 public class JNewInstance extends JExpression {
 
-  private final JClassType classType;
+  private final JNonNullType type;
 
-  public JNewInstance(SourceInfo info, JClassType classType) {
+  public JNewInstance(SourceInfo info, JNonNullType type) {
     super(info);
-    this.classType = classType;
+    assert type.getUnderlyingType() instanceof JClassType;
+    this.type = type;
   }
 
   public JClassType getClassType() {
-    return classType;
+    return (JClassType) type.getUnderlyingType();
   }
 
-  public JType getType() {
-    return classType;
+  public JNonNullType getType() {
+    return type;
   }
 
+  @Override
   public boolean hasSideEffects() {
     // The actual new operation itself has no side effects (see class comment).
     return false;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JNonNullType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JNonNullType.java
new file mode 100644
index 0000000..1d19196
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JNonNullType.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2008 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.ast;
+
+import com.google.gwt.dev.jjs.InternalCompilerException;
+
+/**
+ * A type including all the values in some other type except for
+ * <code>null</code>.
+ */
+public class JNonNullType extends JReferenceType {
+
+  private final JReferenceType ref;
+
+  JNonNullType(JReferenceType ref) {
+    super(ref.getSourceInfo(), ref.getName());
+    assert ref.canBeNull();
+    this.ref = ref;
+  }
+
+  @Override
+  public boolean canBeNull() {
+    return false;
+  }
+
+  @Override
+  public String getClassLiteralFactoryMethod() {
+    return ref.getClassLiteralFactoryMethod();
+  }
+
+  @Override
+  public JClassType getSuperClass() {
+    return ref.getSuperClass();
+  }
+
+  @Override
+  public JReferenceType getUnderlyingType() {
+    return ref;
+  }
+
+  public boolean isAbstract() {
+    return ref.isAbstract();
+  }
+
+  public boolean isFinal() {
+    return ref.isFinal();
+  }
+
+  @Override
+  public void setSuperClass(JClassType superClass) {
+    throw new InternalCompilerException("should not be called");
+  }
+
+  public void traverse(JVisitor visitor, Context ctx) {
+    visitor.accept(ref);
+  }
+}
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 2a2c513..cac95fb 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
@@ -36,10 +36,12 @@
         "Cannot get class literal for null type");
   }
 
+  @Override
   public String getJavahSignatureName() {
     return "N";
   }
 
+  @Override
   public String getJsniSignatureName() {
     return "N";
   }
@@ -52,6 +54,11 @@
     return true;
   }
 
+  @Override
+  public void setSuperClass(JClassType superClass) {
+    throw new InternalCompilerException("should not be called");
+  }
+
   public void traverse(JVisitor visitor, Context ctx) {
     if (visitor.visit(this, ctx)) {
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
index c6bd9f6..df289af 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
@@ -283,6 +283,8 @@
 
   private List<JsonObject> jsonTypeTable;
 
+  private Map<JReferenceType, JNonNullType> nonNullTypes = new IdentityHashMap<JReferenceType, JNonNullType>();
+
   private JField nullField;
 
   private JMethod nullMethod;
@@ -317,6 +319,8 @@
 
   private final Map<String, JDeclaredType> typeNameMap = new HashMap<String, JDeclaredType>();
 
+  private JNonNullType typeNonNullString;
+
   private JClassType typeSpecialClassLiteralHolder;
 
   private JClassType typeSpecialJavaScriptObject;
@@ -397,6 +401,7 @@
         typeJavaLangObject = x;
       } else if (sname.equals("java.lang.String")) {
         typeString = x;
+        typeNonNullString = getNonNullType(x);
       } else if (sname.equals("java.lang.Enum")) {
         typeJavaLangEnum = x;
       } else if (sname.equals("java.lang.Class")) {
@@ -544,6 +549,10 @@
     return createSourceInfo(0, caller.getName()).makeChild(caller, description);
   }
 
+  /**
+   * Return the least upper bound of a set of types. That is, the smallest type
+   * that is a supertype of all the input types.
+   */
   public JReferenceType generalizeTypes(
       Collection<? extends JReferenceType> types) {
     assert (types != null);
@@ -556,12 +565,31 @@
     return curType;
   }
 
+  /**
+   * Return the least upper bound of two types. That is, the smallest type that
+   * is a supertype of both types.
+   */
   public JReferenceType generalizeTypes(JReferenceType type1,
       JReferenceType type2) {
     if (type1 == type2) {
       return type1;
     }
 
+    if (type1 instanceof JNonNullType && type2 instanceof JNonNullType) {
+      // Neither can be null.
+      type1 = type1.getUnderlyingType();
+      type2 = type2.getUnderlyingType();
+      return getNonNullType(generalizeTypes(type1, type2));
+    } else if (type1 instanceof JNonNullType) {
+      // type2 can be null, so the result can be null
+      type1 = type1.getUnderlyingType();
+    } else if (type2 instanceof JNonNullType) {
+      // type1 can be null, so the result can be null
+      type2 = type2.getUnderlyingType();
+    }
+    assert !(type1 instanceof JNonNullType);
+    assert !(type2 instanceof JNonNullType);
+
     int classify1 = classifyType(type1);
     int classify2 = classifyType(type2);
 
@@ -679,8 +707,8 @@
       int lesser = Math.min(classify1, classify2);
       int greater = Math.max(classify1, classify2);
 
-      JReferenceType tLesser = classify1 > classify2 ? type1 : type2;
-      JReferenceType tGreater = classify1 < classify2 ? type1 : type2;
+      JReferenceType tLesser = classify1 < classify2 ? type1 : type2;
+      JReferenceType tGreater = classify1 > classify2 ? type1 : type2;
 
       if (lesser == IS_INTERFACE && greater == IS_CLASS) {
 
@@ -730,7 +758,7 @@
   }
 
   public JThisRef getExprThisRef(SourceInfo info, JClassType enclosingType) {
-    return new JThisRef(info, enclosingType);
+    return new JThisRef(info, getNonNullType(enclosingType));
   }
 
   public int getFragmentCount() {
@@ -884,13 +912,25 @@
     JStringLiteral toReturn = stringLiteralMap.get(s);
     if (toReturn == null) {
       toReturn = new JStringLiteral(stringPoolSourceInfo.makeChild(
-          JProgram.class, "String literal: " + s), s, getTypeJavaLangString());
+          JProgram.class, "String literal: " + s), s, typeNonNullString);
       stringLiteralMap.put(s, toReturn);
     }
     toReturn.getSourceInfo().merge(sourceInfo);
     return toReturn;
   }
 
+  public JNonNullType getNonNullType(JReferenceType type) {
+    if (type instanceof JNonNullType) {
+      return (JNonNullType) type;
+    }
+    JNonNullType nonNullType = nonNullTypes.get(type);
+    if (nonNullType == null) {
+      nonNullType = new JNonNullType(type);
+      nonNullTypes.put(type, nonNullType);
+    }
+    return nonNullType;
+  }
+
   public JField getNullField() {
     if (nullField == null) {
       nullField = new JField(createSourceInfoSynthetic(JProgram.class,
@@ -910,6 +950,7 @@
   }
 
   public int getQueryId(JReferenceType elementType) {
+    assert (elementType == getRunTimeType(elementType));
     Integer integer = queryIds.get(elementType);
     if (integer == null) {
       return 0;
@@ -922,6 +963,25 @@
     return runAsyncReplacements;
   }
 
+  /**
+   * A run-time type is a type at the granularity that GWT tests at run time.
+   * These include declared types, arrays of declared types, arrays of
+   * primitives, and null. This is also the granularity for the notion of
+   * instantiability recorded in {@link JTypeOracle}. This method returns the
+   * narrowest supertype of <code>type</code> that is a run-time type.
+   */
+  public JReferenceType getRunTimeType(JReferenceType type) {
+    type = type.getUnderlyingType();
+    if (type instanceof JArrayType) {
+      JArrayType typeArray = (JArrayType) type;
+      if (typeArray.getLeafType() instanceof JNonNullType) {
+        JNonNullType leafType = (JNonNullType) typeArray.getLeafType();
+        type = getTypeArray(leafType.getUnderlyingType(), typeArray.getDims());
+      }
+    }
+    return type;
+  }
+
   public List<Integer> getSplitPointInitialSequence() {
     return splitPointInitialSequence;
   }
@@ -931,6 +991,7 @@
   }
 
   public JArrayType getTypeArray(JType leafType, int dimensions) {
+    assert (!(leafType instanceof JArrayType));
     HashMap<JType, JArrayType> typeToArrayType;
 
     // Create typeToArrayType maps for index slots that don't exist yet.
@@ -1014,6 +1075,7 @@
   }
 
   public int getTypeId(JReferenceType referenceType) {
+    assert (referenceType == getRunTimeType(referenceType));
     Integer integer = typeIdMap.get(referenceType);
     if (integer == null) {
       return 0;
@@ -1086,9 +1148,13 @@
     this.jsonTypeTable = jsonObjects;
   }
 
+  public boolean isJavaLangString(JType type) {
+    return type == typeString || type == typeNonNullString;
+  }
+
   public boolean isJavaScriptObject(JType type) {
-    if (type instanceof JClassType && typeSpecialJavaScriptObject != null) {
-      return typeOracle.canTriviallyCast((JClassType) type,
+    if (type instanceof JReferenceType && typeSpecialJavaScriptObject != null) {
+      return typeOracle.canTriviallyCast((JReferenceType) type,
           typeSpecialJavaScriptObject);
     }
     return false;
@@ -1145,11 +1211,20 @@
     return staticToInstanceMap.get(method);
   }
 
+  /**
+   * Return the greatest lower bound of two types. That is, return the largest
+   * type that is a subtype of both inputs.
+   */
   public JReferenceType strongerType(JReferenceType type1, JReferenceType type2) {
     if (type1 == type2) {
       return type1;
     }
 
+    if (type1 instanceof JNonNullType != type2 instanceof JNonNullType) {
+      // If either is non-nullable, the result should be non-nullable.
+      return strongerType(getNonNullType(type1), getNonNullType(type2));
+    }
+
     if (typeOracle.canTriviallyCast(type1, type2)) {
       return type1;
     }
@@ -1171,6 +1246,7 @@
   }
 
   private int classifyType(JReferenceType type) {
+    assert !(type instanceof JNonNullType);
     if (type instanceof JNullType) {
       return IS_NULL;
     } else if (type instanceof JInterfaceType) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JReboundEntryPoint.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JReboundEntryPoint.java
index 41d6045..c93e3ec 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JReboundEntryPoint.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JReboundEntryPoint.java
@@ -28,9 +28,9 @@
 
   private final List<JExpression> entryCalls;
   private final List<JClassType> resultTypes;
-  private final JReferenceType sourceType;
+  private final JDeclaredType sourceType;
 
-  public JReboundEntryPoint(SourceInfo info, JReferenceType sourceType,
+  public JReboundEntryPoint(SourceInfo info, JDeclaredType sourceType,
       List<JClassType> resultTypes, List<JExpression> entryCalls) {
     super(info);
     this.sourceType = sourceType;
@@ -46,7 +46,7 @@
     return resultTypes;
   }
 
-  public JReferenceType getSourceType() {
+  public JDeclaredType getSourceType() {
     return sourceType;
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JReferenceType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JReferenceType.java
index e70f1d7..db07d97 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JReferenceType.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JReferenceType.java
@@ -31,6 +31,16 @@
     super(info, name, JNullLiteral.INSTANCE);
   }
 
+  /**
+   * Returns <code>true</code> if it's possible for this type to be
+   * <code>null</code>.
+   * 
+   * @see JNonNullType
+   */
+  public boolean canBeNull() {
+    return true;
+  }
+
   @Override
   public String getJavahSignatureName() {
     return "L" + name.replaceAll("_", "_1").replace('.', '_') + "_2";
@@ -55,6 +65,13 @@
   }
 
   /**
+   * If this type is a non-null type, returns the underlying (original) type.
+   */
+  public JReferenceType getUnderlyingType() {
+    return this;
+  }
+
+  /**
    * Sets this type's super class.
    */
   public void setSuperClass(JClassType superClass) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JStringLiteral.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JStringLiteral.java
index 9197a91..e38cd41 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JStringLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JStringLiteral.java
@@ -22,13 +22,13 @@
  */
 public class JStringLiteral extends JValueLiteral {
 
-  private final JClassType stringType;
+  private final JNonNullType stringType;
   private final String value;
 
   /**
    * These are only supposed to be constructed by JProgram.
    */
-  JStringLiteral(SourceInfo sourceInfo, String value, JClassType stringType) {
+  JStringLiteral(SourceInfo sourceInfo, String value, JNonNullType stringType) {
     super(sourceInfo);
     this.value = value;
     this.stringType = stringType;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java
index d24101c..6f58f3b 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java
@@ -22,19 +22,20 @@
  */
 public class JThisRef extends JExpression {
 
-  private final JClassType classType;
+  private final JNonNullType type;
 
-  public JThisRef(SourceInfo info, JClassType classType) {
+  public JThisRef(SourceInfo info, JNonNullType type) {
     super(info);
-    this.classType = classType;
+    assert type.getUnderlyingType() instanceof JClassType;
+    this.type = type;
   }
 
   public JClassType getClassType() {
-    return classType;
+    return (JClassType) type.getUnderlyingType();
   }
 
   public JType getType() {
-    return classType;
+    return type;
   }
 
   public boolean hasSideEffects() {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
index 1310630..ebd7a0b 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
@@ -177,35 +177,6 @@
   }
 
   /**
-   * Determine whether a type is instantiated, given an assumed list of
-   * instantiated types.
-   * 
-   * @param type any type
-   * @param instantiatedTypes a set of types assumed to be instantiated. If
-   *          <code>null</code>, then there are no assumptions about which types
-   *          are instantiated.
-   * @return whether the type is instantiated
-   */
-  private static boolean isInstantiatedType(JReferenceType type,
-      Set<JReferenceType> instantiatedTypes) {
-    if (instantiatedTypes == null) {
-      return true;
-    }
-
-    if (type instanceof JNullType) {
-      return true;
-    }
-
-    if (type instanceof JArrayType) {
-      JArrayType arrayType = (JArrayType) type;
-      if (arrayType.getLeafType() instanceof JNullType) {
-        return true;
-      }
-    }
-    return instantiatedTypes.contains(type);
-  }
-
-  /**
    * A map of all interfaces to the set of classes that could theoretically
    * implement them.
    */
@@ -229,6 +200,11 @@
    */
   private final Map<JClassType, Set<JInterfaceType>> implementsMap = new IdentityHashMap<JClassType, Set<JInterfaceType>>();
 
+  /**
+   * The types in the program that are instantiable. All types in this set
+   * should be run-time types as defined at
+   * {@link JProgram#getRunTimeType(JReferenceType)}.
+   */
   private Set<JReferenceType> instantiatedTypes = null;
 
   /**
@@ -291,6 +267,15 @@
   }
 
   public boolean canTheoreticallyCast(JReferenceType type, JReferenceType qType) {
+    if (!type.canBeNull() && qType == program.getTypeNull()) {
+      // Cannot cast non-nullable to null
+      return false;
+    }
+
+    // Compare the underlying types.
+    type = type.getUnderlyingType();
+    qType = qType.getUnderlyingType();
+
     JClassType jlo = program.getTypeJavaLangObject();
     if (type == qType || type == jlo) {
       return true;
@@ -346,6 +331,15 @@
   }
 
   public boolean canTriviallyCast(JReferenceType type, JReferenceType qType) {
+    if (type.canBeNull() && !qType.canBeNull()) {
+      // Cannot reliably cast nullable to non-nullable
+      return false;
+    }
+
+    // Compare the underlying types.
+    type = type.getUnderlyingType();
+    qType = qType.getUnderlyingType();
+
     JClassType jlo = program.getTypeJavaLangObject();
     if (type == qType || qType == jlo) {
       return true;
@@ -419,8 +413,7 @@
     jsoSingleImpls.clear();
     dualImpls.clear();
 
-    for (int i = 0; i < program.getDeclaredTypes().size(); ++i) {
-      JReferenceType type = program.getDeclaredTypes().get(i);
+    for (JDeclaredType type : program.getDeclaredTypes()) {
       if (type instanceof JClassType) {
         recordSuperSubInfo((JClassType) type);
       } else {
@@ -451,20 +444,17 @@
       }
     }
 
-    for (int i = 0; i < program.getDeclaredTypes().size(); ++i) {
-      JReferenceType type = program.getDeclaredTypes().get(i);
+    for (JDeclaredType type : program.getDeclaredTypes()) {
       if (type instanceof JClassType) {
         computeImplements((JClassType) type);
       }
     }
-    for (int i = 0; i < program.getDeclaredTypes().size(); ++i) {
-      JReferenceType type = program.getDeclaredTypes().get(i);
+    for (JDeclaredType type : program.getDeclaredTypes()) {
       if (type instanceof JClassType) {
         computeCouldImplement((JClassType) type);
       }
     }
-    for (int i = 0; i < program.getDeclaredTypes().size(); ++i) {
-      JReferenceType type = program.getDeclaredTypes().get(i);
+    for (JDeclaredType type : program.getDeclaredTypes()) {
       if (type instanceof JClassType) {
         computeVirtualUpRefs((JClassType) type);
       }
@@ -538,12 +528,12 @@
     return results;
   }
 
-  public Set<JInterfaceType> getInterfacesWithJavaAndJsoImpls() {
-    return Collections.unmodifiableSet(dualImpls);
+  public JClassType getSingleJsoImpl(JReferenceType maybeSingleJsoIntf) {
+    return jsoSingleImpls.get(maybeSingleJsoIntf.getUnderlyingType());
   }
 
-  public Map<JInterfaceType, JClassType> getSingleJsoImpls() {
-    return Collections.unmodifiableMap(jsoSingleImpls);
+  public boolean isDualJsoInterface(JReferenceType maybeDualImpl) {
+    return dualImpls.contains(maybeDualImpl.getUnderlyingType());
   }
 
   public boolean isInstantiatedType(JReferenceType type) {
@@ -815,6 +805,37 @@
     return get(implementsMap, type).contains(qType);
   }
 
+  /**
+   * Determine whether a type is instantiated, given an assumed list of
+   * instantiated types.
+   * 
+   * @param type any type
+   * @param instantiatedTypes a set of types assumed to be instantiated. If
+   *          <code>null</code>, then there are no assumptions about which types
+   *          are instantiated.
+   * @return whether the type is instantiated
+   */
+  private boolean isInstantiatedType(JReferenceType type,
+      Set<JReferenceType> instantiatedTypes) {
+    type = program.getRunTimeType(type);
+
+    if (instantiatedTypes == null) {
+      return true;
+    }
+
+    if (type instanceof JNullType) {
+      return true;
+    }
+
+    if (type instanceof JArrayType) {
+      JArrayType arrayType = (JArrayType) type;
+      if (arrayType.getLeafType() instanceof JNullType) {
+        return true;
+      }
+    }
+    return instantiatedTypes.contains(type);
+  }
+
   private boolean isSameOrSuper(JClassType type, JClassType qType) {
     return (type == qType || isSuperClass(type, qType));
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JVariable.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JVariable.java
index f9d46f2..8184366 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JVariable.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JVariable.java
@@ -21,7 +21,7 @@
  * Base class for any storage location.
  */
 public abstract class JVariable extends JNode implements CanBeSetFinal,
-    CanHaveInitializer, HasName, HasSettableType {
+    CanHaveInitializer, HasName, HasType {
 
   protected JDeclarationStatement declStmt = null;
   private boolean isFinal;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsniFieldRef.java b/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsniFieldRef.java
index a6b46b8..0accf75 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsniFieldRef.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsniFieldRef.java
@@ -29,7 +29,7 @@
 public class JsniFieldRef extends JFieldRef {
 
   private final String ident;
-  private boolean isLvalue;
+  private final boolean isLvalue;
 
   public JsniFieldRef(SourceInfo info, String ident, JField field,
       JDeclaredType enclosingType, boolean isLvalue) {
@@ -47,6 +47,7 @@
     return isLvalue;
   }
 
+  @Override
   public void traverse(JVisitor visitor, Context ctx) {
     if (visitor.visit(this, ctx)) {
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java
index a1e2292..14f1f2a 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java
@@ -49,12 +49,11 @@
     public void endVisit(JBinaryOperation x, Context ctx) {
       if (x.getOp() == JBinaryOperator.ASG && x.getLhs() instanceof JArrayRef) {
         JArrayRef arrayRef = (JArrayRef) x.getLhs();
-        if (arrayRef.getType() instanceof JNullType) {
+        JType elementType = arrayRef.getType();
+        if (elementType instanceof JNullType) {
           // will generate a null pointer exception instead
           return;
         }
-        JArrayType arrayType = (JArrayType) arrayRef.getInstance().getType();
-        JType elementType = arrayType.getElementType();
 
         /*
          * See if we need to do a checked store. Primitives and (effectively)
@@ -62,7 +61,9 @@
          */
         if (elementType instanceof JReferenceType) {
           if (!((JReferenceType) elementType).isFinal()
-              || elementType != x.getRhs().getType()) {
+              || !program.typeOracle.canTriviallyCast(
+                  (JReferenceType) x.getRhs().getType(),
+                  (JReferenceType) elementType)) {
             // replace this assignment with a call to setCheck()
             JMethodCall call = new JMethodCall(x.getSourceInfo(), null,
                 setCheckMethod);
@@ -137,10 +138,14 @@
       SourceInfo sourceInfo = x.getSourceInfo().makeChild(ArrayVisitor.class,
           "Creating dimensions");
       JMethodCall call = new JMethodCall(sourceInfo, null, initDims, arrayType);
-      JsonArray classLitList = new JsonArray(sourceInfo, program.getJavaScriptObject());
-      JsonArray typeIdList = new JsonArray(sourceInfo, program.getJavaScriptObject());
-      JsonArray queryIdList = new JsonArray(sourceInfo, program.getJavaScriptObject());
-      JsonArray dimList = new JsonArray(sourceInfo, program.getJavaScriptObject());
+      JsonArray classLitList = new JsonArray(sourceInfo,
+          program.getJavaScriptObject());
+      JsonArray typeIdList = new JsonArray(sourceInfo,
+          program.getJavaScriptObject());
+      JsonArray queryIdList = new JsonArray(sourceInfo,
+          program.getJavaScriptObject());
+      JsonArray dimList = new JsonArray(sourceInfo,
+          program.getJavaScriptObject());
       JType cur = arrayType;
       for (int i = 0; i < dims; ++i) {
         // Walk down each type from most dims to least.
@@ -168,11 +173,13 @@
       // override the type of the called method with the array's type
       SourceInfo sourceInfo = x.getSourceInfo().makeChild(ArrayVisitor.class,
           "Array initializer");
-      JMethodCall call = new JMethodCall(sourceInfo, null, initValues, arrayType);
+      JMethodCall call = new JMethodCall(sourceInfo, null, initValues,
+          arrayType);
       JLiteral classLit = x.getClassLiteral();
       JLiteral typeIdLit = program.getLiteralInt(program.getTypeId(arrayType));
       JLiteral queryIdLit = program.getLiteralInt(tryGetQueryId(arrayType));
-      JsonArray initList = new JsonArray(sourceInfo, program.getJavaScriptObject());
+      JsonArray initList = new JsonArray(sourceInfo,
+          program.getJavaScriptObject());
       for (int i = 0; i < x.initializers.size(); ++i) {
         initList.exprs.add(x.initializers.get(i));
       }
@@ -184,7 +191,7 @@
       JType elementType = type.getElementType();
       int leafTypeId = -1;
       if (elementType instanceof JReferenceType) {
-        leafTypeId = program.getQueryId((JReferenceType) elementType);
+        leafTypeId = program.getQueryId(program.getRunTimeType((JReferenceType) elementType));
       }
       return leafTypeId;
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java b/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
index 81b3b9c..55f54ea 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
@@ -29,6 +29,7 @@
 import com.google.gwt.dev.jjs.ast.JMethodBody;
 import com.google.gwt.dev.jjs.ast.JMethodCall;
 import com.google.gwt.dev.jjs.ast.JNewInstance;
+import com.google.gwt.dev.jjs.ast.JNonNullType;
 import com.google.gwt.dev.jjs.ast.JParameter;
 import com.google.gwt.dev.jjs.ast.JParameterRef;
 import com.google.gwt.dev.jjs.ast.JPrimitiveType;
@@ -168,8 +169,8 @@
         String name = enclosingType.getShortName();
         SourceInfo info = makeSourceInfo(ctorDecl, enclosingType);
         JMethod newMethod = program.createMethod(info, name.toCharArray(),
-            enclosingType, enclosingType, false, false, true, b.isPrivate(),
-            false);
+            enclosingType, program.getNonNullType(enclosingType), false, false,
+            true, b.isPrivate(), false);
 
         // Enums have hidden arguments for name and value
         if (enclosingType.isEnumOrSubclass() != null) {
@@ -400,12 +401,13 @@
       // Define the method
       JMethod synthetic = program.createMethod(type.getSourceInfo().makeChild(
           BuildDeclMapVisitor.class, "Synthetic constructor"),
-          "new".toCharArray(), type, type, false, true, true, false, false);
+          "new".toCharArray(), type, program.getNonNullType(type), false, true,
+          true, false, false);
 
       // new Foo() : Create the instance
       JNewInstance newInstance = new JNewInstance(
           type.getSourceInfo().makeChild(BuildDeclMapVisitor.class,
-              "new instance"), type);
+              "new instance"), (JNonNullType) synthetic.getType());
 
       // (new Foo()).Foo() : Invoke the constructor method on the instance
       JMethodCall call = new JMethodCall(type.getSourceInfo().makeChild(
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
index 4296607..dc0a041 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
@@ -71,7 +71,6 @@
  * </p>
  */
 public class CastNormalizer {
-
   private class AssignTypeIdsVisitor extends JVisitor {
 
     Set<JReferenceType> alreadyRan = new HashSet<JReferenceType>();
@@ -147,17 +146,17 @@
      */
     @Override
     public void endVisit(JBinaryOperation x, Context ctx) {
-      if (x.getOp() == JBinaryOperator.ASG && x.getLhs() instanceof JArrayRef) {
+      if (x.getOp().isAssignment() && x.getLhs() instanceof JArrayRef) {
 
         // first, calculate the transitive closure of all possible runtime types
         // the lhs could be
-        JExpression instance = ((JArrayRef) x.getLhs()).getInstance();
-        if (instance.getType() instanceof JNullType) {
+        JArrayRef lhsArrayRef = (JArrayRef) x.getLhs();
+        JType elementType = lhsArrayRef.getType();
+        if (elementType instanceof JNullType) {
           // will generate a null pointer exception instead
           return;
         }
-        JArrayType lhsArrayType = (JArrayType) instance.getType();
-        JType elementType = lhsArrayType.getElementType();
+        JArrayType lhsArrayType = lhsArrayRef.getArrayType();
 
         // primitives are statically correct
         if (!(elementType instanceof JReferenceType)) {
@@ -183,7 +182,7 @@
           if (typeOracle.canTheoreticallyCast(arrayType, lhsArrayType)) {
             JType itElementType = arrayType.getElementType();
             if (itElementType instanceof JReferenceType) {
-              recordCastInternal((JReferenceType) itElementType, refRhsType);
+              recordCast(itElementType, x.getRhs());
             }
           }
         }
@@ -214,6 +213,7 @@
       if (type == null || alreadyRan.contains(type)) {
         return;
       }
+      assert (type == program.getRunTimeType(type));
 
       alreadyRan.add(type);
 
@@ -288,14 +288,13 @@
 
     private void recordCast(JType targetType, JExpression rhs) {
       if (targetType instanceof JReferenceType) {
+        targetType = program.getRunTimeType((JReferenceType) targetType);
         // unconditional cast b/c it would've been a semantic error earlier
-        JReferenceType rhsType = (JReferenceType) rhs.getType();
+        JReferenceType rhsType = program.getRunTimeType((JReferenceType) rhs.getType());
         // don't record a type for trivial casts that won't generate code
-        if (rhsType instanceof JClassType) {
-          if (program.typeOracle.canTriviallyCast(rhsType,
-              (JReferenceType) targetType)) {
-            return;
-          }
+        if (program.typeOracle.canTriviallyCast(rhsType,
+            (JReferenceType) targetType)) {
+          return;
         }
 
         // If the target type is a JavaScriptObject, don't record an id.
@@ -307,9 +306,10 @@
       }
     }
 
-    private void recordCastInternal(JReferenceType targetType,
+    private void recordCastInternal(JReferenceType toType,
         JReferenceType rhsType) {
-      JReferenceType toType = targetType;
+      toType = program.getRunTimeType(toType);
+      rhsType = program.getRunTimeType(rhsType);
       Set<JReferenceType> querySet = queriedTypes.get(toType);
       if (querySet == null) {
         queryIds.put(toType, nextQueryId++);
@@ -434,7 +434,7 @@
         replaceExpr = call;
       } else if (toType instanceof JReferenceType) {
         JExpression curExpr = expr;
-        JReferenceType refType = (JReferenceType) toType;
+        JReferenceType refType = program.getRunTimeType((JReferenceType) toType);
         JReferenceType argType = (JReferenceType) expr.getType();
         if (program.typeOracle.canTriviallyCast(argType, refType)) {
           // just remove the cast
@@ -442,11 +442,11 @@
         } else {
 
           JMethod method;
-          boolean isJsoCast = program.isJavaScriptObject(toType);
+          boolean isJsoCast = program.isJavaScriptObject(refType);
           if (isJsoCast) {
             // A cast to a concrete JSO subtype
             method = program.getIndexedMethod("Cast.dynamicCastJso");
-          } else if (program.typeOracle.getSingleJsoImpls().containsKey(toType)) {
+          } else if (program.typeOracle.isDualJsoInterface(refType)) {
             // An interface that should succeed when the object is a JSO
             method = program.getIndexedMethod("Cast.dynamicCastAllowJso");
           } else {
@@ -549,6 +549,8 @@
     public void endVisit(JInstanceOf x, Context ctx) {
       JReferenceType argType = (JReferenceType) x.getExpr().getType();
       JReferenceType toType = x.getTestType();
+      // Only tests on run-time types are supported
+      assert (toType == program.getRunTimeType(toType));
       if (program.typeOracle.canTriviallyCast(argType, toType)) {
         // trivially true if non-null; replace with a null test
         JNullLiteral nullLit = program.getLiteralNull();
@@ -559,7 +561,7 @@
       } else {
         JMethod method;
         boolean isJsoCast = false;
-        if (program.typeOracle.getSingleJsoImpls().containsKey(toType)) {
+        if (program.typeOracle.getSingleJsoImpl(toType) != null) {
           method = program.getIndexedMethod("Cast.instanceOfOrJso");
         } else if (program.isJavaScriptObject(toType)) {
           isJsoCast = true;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java
index 5c7de38..6bcc205 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java
@@ -242,7 +242,7 @@
 
   @Override
   public boolean visit(JNewArray x, Context ctx) {
-    expression = new JNewArray(x.getSourceInfo(), x.getArrayType(),
+    expression = new JNewArray(x.getSourceInfo(), x.getType(),
         cloneExpressions(x.dims), cloneExpressions(x.initializers),
         x.getClassLiterals());
     return false;
@@ -250,7 +250,7 @@
 
   @Override
   public boolean visit(JNewInstance x, Context ctx) {
-    expression = new JNewInstance(x.getSourceInfo(), x.getClassType());
+    expression = new JNewInstance(x.getSourceInfo(), x.getType());
     return false;
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
index 0c0d090..0095722 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
@@ -167,7 +167,7 @@
     public Map<JField, Integer> fields = new HashMap<JField, Integer>();
     public Map<JMethod, Integer> methods = new HashMap<JMethod, Integer>();
     public Map<String, Integer> strings = new HashMap<String, Integer>();
-    public Map<JReferenceType, Integer> types = new HashMap<JReferenceType, Integer>();
+    public Map<JDeclaredType, Integer> types = new HashMap<JDeclaredType, Integer>();
   }
 
   /**
@@ -184,6 +184,10 @@
       this.fragment = fragment;
     }
 
+    public boolean isLive(JDeclaredType type) {
+      return checkMap(fragmentMap.types, type);
+    }
+
     public boolean isLive(JField field) {
       return checkMap(fragmentMap.fields, field);
     }
@@ -192,10 +196,6 @@
       return checkMap(fragmentMap.methods, method);
     }
 
-    public boolean isLive(JReferenceType type) {
-      return checkMap(fragmentMap.types, type);
-    }
-
     public boolean isLive(String literal) {
       return checkMap(fragmentMap.strings, literal);
     }
@@ -458,6 +458,19 @@
     return cfa;
   }
 
+  /**
+   * Extract the types from a set that happen to be declared types.
+   */
+  private static Set<JDeclaredType> declaredTypesIn(Set<JReferenceType> types) {
+    Set<JDeclaredType> result = new HashSet<JDeclaredType>();
+    for (JReferenceType type : types) {
+      if (type instanceof JDeclaredType) {
+        result.add((JDeclaredType) type);
+      }
+    }
+    return result;
+  }
+
   private static String fullNameString(JField field) {
     return field.getEnclosingType().getName() + "." + field.getName();
   }
@@ -487,8 +500,8 @@
   private static void installInitialLoadSequenceField(JProgram program,
       LinkedHashSet<Integer> initialLoadSequence) {
     JMethodCall constructorCall = ReplaceRunAsyncs.getBrowserLoaderConstructor(program);
-    assert constructorCall.getArgs().get(1).getType() instanceof JArrayType;
-    assert ((JArrayType) constructorCall.getArgs().get(1).getType()).getElementType() == JPrimitiveType.INT;
+    assert ((JReferenceType) constructorCall.getArgs().get(1).getType()).getUnderlyingType() instanceof JArrayType;
+    assert ((JArrayType) ((JReferenceType) constructorCall.getArgs().get(1).getType()).getUnderlyingType()).getElementType() == JPrimitiveType.INT;
 
     SourceInfo info = program.createSourceInfoSynthetic(ReplaceRunAsyncs.class,
         "array with initial load sequence");
@@ -600,7 +613,6 @@
   }
 
   private final MultipleDependencyGraphRecorder dependencyRecorder;
-
   private final Map<JField, JClassLiteral> fieldToLiteralOfClass;
   private final FragmentExtractor fragmentExtractor;
   private final LinkedHashSet<Integer> initialLoadSequence;
@@ -933,12 +945,12 @@
 
   private void fixUpLoadOrderDependenciesForTypes(ExclusivityMap fragmentMap) {
     int numFixups = 0;
-    Queue<JReferenceType> typesToCheck = new ArrayBlockingQueue<JReferenceType>(
+    Queue<JDeclaredType> typesToCheck = new ArrayBlockingQueue<JDeclaredType>(
         jprogram.getDeclaredTypes().size());
     typesToCheck.addAll(jprogram.getDeclaredTypes());
 
     while (!typesToCheck.isEmpty()) {
-      JReferenceType type = typesToCheck.remove();
+      JDeclaredType type = typesToCheck.remove();
       if (type.getSuperClass() != null) {
         int typeFrag = getOrZero(fragmentMap.types, type);
         int supertypeFrag = getOrZero(fragmentMap.types, type.getSuperClass());
@@ -995,8 +1007,9 @@
           allButOne.getLiveFieldsAndMethods(), allMethods);
       updateMap(entry, fragmentMap.strings, allButOne.getLiveStrings(),
           everything.getLiveStrings());
-      updateMap(entry, fragmentMap.types, allButOne.getInstantiatedTypes(),
-          everything.getInstantiatedTypes());
+      updateMap(entry, fragmentMap.types,
+          declaredTypesIn(allButOne.getInstantiatedTypes()),
+          declaredTypesIn(everything.getInstantiatedTypes()));
     }
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
index f62bba9..6abc9de 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
@@ -464,8 +464,7 @@
      */
     private void maybeRescueJavaScriptObjectPassingIntoJava(JType type) {
       boolean doIt = false;
-      if (program.isJavaScriptObject(type)
-          || type == program.getTypeJavaLangString()) {
+      if (program.isJavaScriptObject(type) || program.isJavaLangString(type)) {
         doIt = true;
       } else if (type instanceof JArrayType) {
         /*
@@ -475,7 +474,7 @@
         JArrayType arrayType = (JArrayType) type;
         JType elementType = arrayType.getElementType();
         if (elementType instanceof JPrimitiveType
-            || elementType == program.getTypeJavaLangString()
+            || program.isJavaLangString(elementType)
             || program.isJavaScriptObject(elementType)) {
           doIt = true;
         }
@@ -527,32 +526,39 @@
 
     private void rescue(JReferenceType type, boolean isReferenced,
         boolean isInstantiated) {
-      if (type != null) {
+      if (type == null) {
+        return;
+      }
 
-        boolean doVisit = false;
-        if (isInstantiated && !instantiatedTypes.contains(type)) {
-          instantiatedTypes.add(type);
-          doVisit = true;
-        }
+      /*
+       * Track references and instantiability at the granularity of run-time
+       * types. For example, ignore nullness.
+       */
+      type = program.getRunTimeType(type);
 
-        if (isReferenced && !referencedTypes.contains(type)) {
-          referencedTypes.add(type);
-          doVisit = true;
-        }
+      boolean doVisit = false;
+      if (isInstantiated && !instantiatedTypes.contains(type)) {
+        instantiatedTypes.add(type);
+        doVisit = true;
+      }
 
-        if (doVisit) {
-          accept(type);
+      if (isReferenced && !referencedTypes.contains(type)) {
+        referencedTypes.add(type);
+        doVisit = true;
+      }
 
-          if (type instanceof JDeclaredType) {
-            for (JNode artificial : ((JDeclaredType) type).getArtificialRescues()) {
-              if (artificial instanceof JReferenceType) {
-                rescue((JReferenceType) artificial, true, true);
-                rescue(program.getLiteralClass((JReferenceType) artificial).getField());
-              } else if (artificial instanceof JVariable) {
-                rescue((JVariable) artificial);
-              } else if (artificial instanceof JMethod) {
-                rescue((JMethod) artificial);
-              }
+      if (doVisit) {
+        accept(type);
+
+        if (type instanceof JDeclaredType) {
+          for (JNode artificial : ((JDeclaredType) type).getArtificialRescues()) {
+            if (artificial instanceof JReferenceType) {
+              rescue((JReferenceType) artificial, true, true);
+              rescue(program.getLiteralClass((JReferenceType) artificial).getField());
+            } else if (artificial instanceof JVariable) {
+              rescue((JVariable) artificial);
+            } else if (artificial instanceof JMethod) {
+              rescue((JMethod) artificial);
             }
           }
         }
@@ -602,10 +608,11 @@
      * Handle special rescues needed implicitly to support concat.
      */
     private void rescueByConcat(JType type) {
-      JClassType stringType = program.getTypeJavaLangString();
       JPrimitiveType charType = program.getTypePrimitiveChar();
-      if (type instanceof JReferenceType && type != stringType
-          && type != program.getTypeNull()) {
+      JClassType stringType = program.getTypeJavaLangString();
+      if (type instanceof JReferenceType
+          && !program.typeOracle.canTriviallyCast((JReferenceType) type,
+              stringType) && type != program.getTypeNull()) {
         /*
          * Any reference types (except String, which works by default) that take
          * part in a concat must rescue java.lang.Object.toString().
@@ -789,7 +796,7 @@
     }
   }
 
-  public void traverseFromReferenceTo(JReferenceType type) {
+  public void traverseFromReferenceTo(JDeclaredType type) {
     rescuer.rescue(type, true, false);
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
index 90505e7..b39364c 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
@@ -1161,11 +1161,7 @@
     }
 
     private boolean isTypeString(JExpression exp) {
-      return isTypeString(exp.getType());
-    }
-
-    private boolean isTypeString(JType type) {
-      return type == program.getTypeJavaLangString();
+      return program.isJavaLangString(exp.getType());
     }
 
     private boolean isUnconditionalBreak(JStatement statement) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/EqualityNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/EqualityNormalizer.java
index 83578f5..fdff227 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/EqualityNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/EqualityNormalizer.java
@@ -28,37 +28,36 @@
 import com.google.gwt.dev.jjs.ast.JType;
 
 /**
- * Handle all of the cases where Java reference identity causes problems in
- * JavaScript due to the <code>null === undefined</code> problem. Reference
- * identity checks will generally fall into one of two cases right now.
- * 
  * <p>
- * If something that may be a String is compared to something that may not be a
- * <code>String</code>, we must use the <code>===</code> to prevent
- * JavaScript compare-as-strings behavior. However, this invites the
- * <code>null === undefined</code> problem, so we must emit calls to mask off
- * <code>undefined</code> as <code>null</code>. These cases include:
+ * Rewrite Java <code>==</code> so that it will execute correctly in JavaScript.
+ * After this pass, Java's <code>==</code> is considered equivalent to
+ * JavaScript's <code>===</code>.
+ *</p>
+ *<p>
+ * Whenever possible, a Java <code>==</code> is replaced by a JavaScript
+ * <code>==</code>. This is shorter than <code>===</code>, and it avoids any
+ * complication due to GWT treating both <code>null</code> and
+ * <code>undefined</code> as a valid translation of a Java <code>null</code>.
+ * </p>
+ * <p>
+ * However, whenever something that may be a String is compared to something
+ * that may not be a <code>String</code>, use <code>===</code>. A Java object
+ * compared to a string should always yield false, but that's not true when
+ * comparing in JavaScript using <code>==</code>. The cases where
+ * <code>===</code> must be used are:
  * </p>
  * <ul>
  * <li>One or both sides have unknown <code>String</code> status.</li>
- * <li>One side is definitely <code>String</code> and one side is definitely !<code>String</code>.
- * <br/>TODO: This case could be optimized as
- * <code>(a == null) &amp; (b == null)</code>. </li>
+ * <li>One side is definitely <code>String</code> and one side is definitely !
+ * <code>String</code>. <br/>
+ * TODO: This case could be optimized as
+ * <code>(a == null) &amp; (b == null)</code>.</li>
  * </ul>
- * 
  * <p>
- * Otherwise, we can use the <code>==</code> operator to avoid having to mask
- * of <code>undefined</code>. These cases include:
+ * Since <code>null !== undefined</code>, it is also necessary to normalize
+ * <code>null</code> vs. <code>undefined</code> if it's possible for one side to
+ * be <code>null</code> and the other to be <code>undefined</code>.
  * </p>
- * <ul>
- * <li>Comparing two things statically typed as <code>String</code>.</li>
- * <li>Comparing two things statically typed as !<code>String</code>.</li>
- * <li>Comparing anything to something that is definitely <code>null</code>.</li>
- * </ul>
- * 
- * TODO: There will be a third case when we can identity things that are
- * definitely not <code>null</code>. The presence of a definitely not null
- * operand allows us to use <code>===</code> with impunity.
  */
 public class EqualityNormalizer {
 
@@ -84,45 +83,60 @@
 
       StringStatus lhsStatus = getStringStatus((JReferenceType) lhsType);
       StringStatus rhsStatus = getStringStatus((JReferenceType) rhsType);
+      int strat = COMPARISON_STRAT[lhsStatus.getIndex()][rhsStatus.getIndex()];
 
-      if ((USE_TRIPLE_EQUALS[lhsStatus.getIndex()][rhsStatus.getIndex()] == 1)) {
-        // Mask each side to prevent null === undefined.
-        lhs = maskUndefined(lhs);
-        rhs = maskUndefined(rhs);
-        JBinaryOperation binOp = new JBinaryOperation(x.getSourceInfo(),
-            x.getType(), x.getOp(), lhs, rhs);
-        ctx.replaceMe(binOp);
-      } else {
-        boolean lhsNullLit = lhs == program.getLiteralNull();
-        boolean rhsNullLit = rhs == program.getLiteralNull();
-        if ((lhsNullLit && rhsStatus == StringStatus.NOTSTRING)
-            || (rhsNullLit && lhsStatus == StringStatus.NOTSTRING)) {
-          /*
-           * If either side is a null literal and the other is non-String,
-           * replace with a null-check.
-           */
-          String methodName;
-          if (op == JBinaryOperator.EQ) {
-            methodName = "Cast.isNull";
-          } else {
-            methodName = "Cast.isNotNull";
+      switch (strat) {
+        case STRAT_TRIPLE: {
+          if (canBeNull(lhs) && canBeNull(rhs)) {
+            /*
+             * If it's possible for one side to be null and the other side
+             * undefined, then mask both sides.
+             */
+            lhs = maskUndefined(lhs);
+            rhs = maskUndefined(rhs);
           }
-          JMethod isNullMethod = program.getIndexedMethod(methodName);
-          JMethodCall call = new JMethodCall(x.getSourceInfo(), null, isNullMethod);
-          call.addArg(lhsNullLit ? rhs : lhs);
-          ctx.replaceMe(call);
-        } else {
-          // Replace with a call to Cast.jsEquals, which does a == internally.
-          String methodName;
-          if (op == JBinaryOperator.EQ) {
-            methodName = "Cast.jsEquals";
+
+          JBinaryOperation binOp = new JBinaryOperation(x.getSourceInfo(),
+              x.getType(), x.getOp(), lhs, rhs);
+          ctx.replaceMe(binOp);
+          break;
+        }
+
+        case STRAT_DOUBLE: {
+          boolean lhsNullLit = lhs == program.getLiteralNull();
+          boolean rhsNullLit = rhs == program.getLiteralNull();
+          if ((lhsNullLit && rhsStatus == StringStatus.NOTSTRING)
+              || (rhsNullLit && lhsStatus == StringStatus.NOTSTRING)) {
+            /*
+             * If either side is a null literal and the other is non-String,
+             * replace with a null-check.
+             */
+            String methodName;
+            if (op == JBinaryOperator.EQ) {
+              methodName = "Cast.isNull";
+            } else {
+              methodName = "Cast.isNotNull";
+            }
+            JMethod isNullMethod = program.getIndexedMethod(methodName);
+            JMethodCall call = new JMethodCall(x.getSourceInfo(), null,
+                isNullMethod);
+            call.addArg(lhsNullLit ? rhs : lhs);
+            ctx.replaceMe(call);
           } else {
-            methodName = "Cast.jsNotEquals";
+            // Replace with a call to Cast.jsEquals, which does a == internally.
+            String methodName;
+            if (op == JBinaryOperator.EQ) {
+              methodName = "Cast.jsEquals";
+            } else {
+              methodName = "Cast.jsNotEquals";
+            }
+            JMethod eqMethod = program.getIndexedMethod(methodName);
+            JMethodCall call = new JMethodCall(x.getSourceInfo(), null,
+                eqMethod);
+            call.addArgs(lhs, rhs);
+            ctx.replaceMe(call);
           }
-          JMethod eqMethod = program.getIndexedMethod(methodName);
-          JMethodCall call = new JMethodCall(x.getSourceInfo(), null, eqMethod);
-          call.addArgs(lhs, rhs);
-          ctx.replaceMe(call);
+          break;
         }
       }
     }
@@ -141,9 +155,11 @@
     }
 
     private JExpression maskUndefined(JExpression lhs) {
+      assert ((JReferenceType) lhs.getType()).canBeNull();
+
       JMethod maskMethod = program.getIndexedMethod("Cast.maskUndefined");
-      JMethodCall lhsCall = new JMethodCall(lhs.getSourceInfo(), null, maskMethod,
-          lhs.getType());
+      JMethodCall lhsCall = new JMethodCall(lhs.getSourceInfo(), null,
+          maskMethod, lhs.getType());
       lhsCall.addArg(lhs);
       return lhsCall;
     }
@@ -152,8 +168,6 @@
   /**
    * Represents what we know about an operand type in terms of its type and
    * <code>null</code> status.
-   * 
-   * TODO: represent definitely non-null things.
    */
   private enum StringStatus {
     NOTSTRING(2), NULL(3), STRING(1), UNKNOWN(0);
@@ -170,20 +184,34 @@
   }
 
   /**
-   * A map of the combinations where <code>===</code> should be used.
+   * A map of the combinations where each comparison strategy should be used.
    */
-  private static int[][] USE_TRIPLE_EQUALS = {
+  private static int[][] COMPARISON_STRAT = {
   // ..U..S.!S..N
-      {1, 1, 1, 0}, // UNKNOWN
-      {1, 0, 1, 0}, // STRING
-      {1, 1, 0, 0}, // NOTSTRING
-      {0, 0, 0, 0}, // NULL
+      {1, 1, 1, 0,}, // UNKNOWN
+      {1, 0, 1, 0,}, // STRING
+      {1, 1, 0, 0,}, // NOTSTRING
+      {0, 0, 0, 0,}, // NULL
   };
 
+  /**
+   * The comparison strategy of using ==.
+   */
+  private static final int STRAT_DOUBLE = 0;
+
+  /**
+   * The comparison strategy of using ===.
+   */
+  private static final int STRAT_TRIPLE = 1;
+
   public static void exec(JProgram program) {
     new EqualityNormalizer(program).execImpl();
   }
 
+  private static boolean canBeNull(JExpression x) {
+    return ((JReferenceType) x.getType()).canBeNull();
+  }
+
   private final JProgram program;
 
   private EqualityNormalizer(JProgram program) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java
index 5ac8a58..fbe8748 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java
@@ -16,10 +16,11 @@
 package com.google.gwt.dev.jjs.impl;
 
 import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.ast.JClassType;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
 import com.google.gwt.dev.jjs.ast.JField;
 import com.google.gwt.dev.jjs.ast.JMethod;
 import com.google.gwt.dev.jjs.ast.JProgram;
-import com.google.gwt.dev.jjs.ast.JReferenceType;
 import com.google.gwt.dev.js.ast.JsBinaryOperation;
 import com.google.gwt.dev.js.ast.JsBinaryOperator;
 import com.google.gwt.dev.js.ast.JsEmpty;
@@ -65,7 +66,7 @@
       return cfa.getLiveFieldsAndMethods().contains(method);
     }
 
-    public boolean isLive(JReferenceType type) {
+    public boolean isLive(JDeclaredType type) {
       return cfa.getInstantiatedTypes().contains(type);
     }
 
@@ -107,7 +108,7 @@
 
     boolean isLive(JMethod method);
 
-    boolean isLive(JReferenceType type);
+    boolean isLive(JDeclaredType type);
 
     boolean isLive(String literal);
 
@@ -133,7 +134,7 @@
       return false;
     }
 
-    public boolean isLive(JReferenceType type) {
+    public boolean isLive(JDeclaredType type) {
       return false;
     }
 
@@ -258,7 +259,7 @@
     /**
      * The type whose vtables can currently be installed.
      */
-    JReferenceType currentVtableType = null;
+    JClassType currentVtableType = null;
 
     for (int frag = 0; frag < jsprogram.getFragmentCount(); frag++) {
       List<JsStatement> stats = jsprogram.getFragmentBlock(frag).getStatements();
@@ -284,7 +285,7 @@
           if (vtableTypeAssigned(stat) != null) {
             currentVtableType = vtableTypeAssigned(stat);
           }
-          JReferenceType vtableType = vtableTypeNeeded(stat);
+          JClassType vtableType = vtableTypeNeeded(stat);
           if (vtableType != null && vtableType != currentVtableType) {
             extractedStats.add(vtableStatFor(vtableType));
             currentVtableType = vtableType;
@@ -379,7 +380,7 @@
   }
 
   private boolean isLive(JsStatement stat, LivenessPredicate livenessPredicate) {
-    JReferenceType type = map.typeForStatement(stat);
+    JClassType type = map.typeForStatement(stat);
     if (type != null) {
       // This is part of the code only needed once a type is instantiable
       return livenessPredicate.isLive(type);
@@ -478,7 +479,7 @@
    * <code>_ = foo.prototype</code>, where <code>foo</code> is the constructor
    * function for <code>vtableType</code>.
    */
-  private JsStatement vtableStatFor(JReferenceType vtableType) {
+  private JsStatement vtableStatFor(JClassType vtableType) {
     JsNameRef prototypeField = new JsNameRef(
         jsprogram.createSourceInfoSynthetic(FragmentExtractor.class,
             "prototype field"), "prototype");
@@ -506,10 +507,8 @@
   /**
    * If <code>state</code> is of the form <code>_ = Foo.prototype = exp</code>,
    * then return <code>Foo</code>. Otherwise return <code>null</code>.
-   * 
-   * TODO(spoon): get this information via source info on the statement
    */
-  private JReferenceType vtableTypeAssigned(JsStatement stat) {
+  private JClassType vtableTypeAssigned(JsStatement stat) {
     if (!(stat instanceof JsExprStmt)) {
       return null;
     }
@@ -552,11 +551,11 @@
     return map.typeForStatement(stat);
   }
 
-  private JReferenceType vtableTypeNeeded(JsStatement stat) {
+  private JClassType vtableTypeNeeded(JsStatement stat) {
     JMethod meth = map.vtableInitToMethod(stat);
     if (meth != null) {
       if (!meth.isStatic()) {
-        return meth.getEnclosingType();
+        return (JClassType) meth.getEnclosingType();
       }
     }
     return null;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
index ace3d27..5e2add6 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
@@ -66,6 +66,7 @@
 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.JNonNullType;
 import com.google.gwt.dev.jjs.ast.JParameter;
 import com.google.gwt.dev.jjs.ast.JParameterRef;
 import com.google.gwt.dev.jjs.ast.JPostfixOperation;
@@ -790,7 +791,8 @@
         }
         call = new JMethodCall(makeSourceInfo(x), null, targetMethod);
       } else {
-        JNewInstance newInstance = new JNewInstance(info, newType);
+        JNewInstance newInstance = new JNewInstance(info,
+            (JNonNullType) ctor.getType());
         call = new JMethodCall(info, newInstance, ctor);
       }
 
@@ -904,7 +906,7 @@
           op = JBinaryOperator.SHRU;
           break;
         case BinaryExpression.PLUS:
-          if (typeMap.get(x.resolvedType) == program.getTypeJavaLangString()) {
+          if (program.isJavaLangString((JType) typeMap.get(x.resolvedType))) {
             op = JBinaryOperator.CONCAT;
           } else {
             op = JBinaryOperator.ADD;
@@ -975,7 +977,7 @@
 
       switch (x.operator) {
         case CompoundAssignment.PLUS:
-          if (typeMap.get(x.resolvedType) == program.getTypeJavaLangString()) {
+          if (program.isJavaLangString((JType) typeMap.get(x.resolvedType))) {
             op = JBinaryOperator.ASG_CONCAT;
           } else {
             op = JBinaryOperator.ASG_ADD;
@@ -1214,8 +1216,8 @@
       SourceInfo info = makeSourceInfo(x);
       MethodBinding b = x.binding;
       JMethod ctor = (JMethod) typeMap.get(b);
-      JClassType enclosingType = (JClassType) ctor.getEnclosingType();
-      JNewInstance newInstance = new JNewInstance(info, enclosingType);
+      JNewInstance newInstance = new JNewInstance(info,
+          (JNonNullType) ctor.getType());
       JMethodCall call = new JMethodCall(info, newInstance, ctor);
       JExpression qualifier = dispProcessExpression(x.enclosingInstance);
       List<JExpression> qualList = new ArrayList<JExpression>();
@@ -2155,7 +2157,7 @@
      * world would we need to do this? It turns out that when making an
      * unqualified explicit super constructor call to something that needs a
      * synthetic outer this arg, the correct value to pass in can be one of
-     * several of the calling constructor's own synthetic ags. The catch is,
+     * several of the calling constructor's own synthetic args. The catch is,
      * it's possible none of the args are exactly the right type-- we have to
      * make one of them the right type by following each of their synthetic this
      * refs up an arbitrarily big tree of enclosing classes and
@@ -2167,6 +2169,8 @@
      * prefer the current expression as one of its supertypes over a synthetic
      * this ref rooted off the current expression that happens to be the correct
      * type. We have observed this to be consistent with how Java handles it.
+     * 
+     * TODO(scottb): could we get this info directly from JDT?
      */
     private JExpression createThisRef(JReferenceType qualType,
         List<JExpression> list) {
@@ -2174,7 +2178,7 @@
       workList.addAll(list);
       while (!workList.isEmpty()) {
         JExpression expr = workList.removeFirst();
-        JClassType classType = (JClassType) expr.getType();
+        JClassType classType = (JClassType) ((JReferenceType) expr.getType()).getUnderlyingType();
         for (; classType != null; classType = classType.getSuperClass()) {
           // prefer myself or myself-as-supertype over any of my this$ fields
           // that may have already been added to the work list
@@ -2226,7 +2230,7 @@
           JClassType fieldEnclosingType = (JClassType) field.getEnclosingType();
           instance = createThisRef(info, fieldEnclosingType);
           if (!program.typeOracle.canTriviallyCast(
-              (JClassType) instance.getType(), fieldEnclosingType)) {
+              (JReferenceType) instance.getType(), fieldEnclosingType)) {
             throw new InternalCompilerException(
                 "FieldRef referencing field in a different type.");
           }
@@ -2344,8 +2348,8 @@
      * expression. Beware that when autoboxing, the type of the expression is
      * not necessarily the same as the type of the box to be created. The JDT
      * figures out what the necessary conversion is, depending on the context
-     * the expression appears in, and stores it in <code>x.implicitConversion</code>,
-     * so extract it from there.
+     * the expression appears in, and stores it in
+     * <code>x.implicitConversion</code>, so extract it from there.
      */
     private JPrimitiveType implicitConversionTargetType(Expression x)
         throws InternalCompilerException {
@@ -2862,8 +2866,7 @@
 
           JLiteral initializer = field.getConstInitializer();
           JType type = initializer.getType();
-          if (type instanceof JPrimitiveType
-              || type == program.getTypeJavaLangString()) {
+          if (type instanceof JPrimitiveType || program.isJavaLangString(type)) {
             GenerateJavaScriptLiterals generator = new GenerateJavaScriptLiterals(
                 jsProgram);
             generator.accept(initializer);
@@ -2882,10 +2885,9 @@
 
       private void processMethod(JsNameRef nameRef, SourceInfo info,
           JMethod method, JsContext<JsExpression> ctx) {
-        JReferenceType enclosingType = method.getEnclosingType();
+        JDeclaredType enclosingType = method.getEnclosingType();
         if (enclosingType != null) {
-          JClassType jsoImplType = program.typeOracle.getSingleJsoImpls().get(
-              enclosingType);
+          JClassType jsoImplType = program.typeOracle.getSingleJsoImpl(enclosingType);
           if (jsoImplType != null) {
             JsniCollector.reportJsniError(info, methodDecl,
                 "Illegal reference to method '" + method.getName()
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index a107a0d..7bc960f 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -1086,7 +1086,7 @@
     @Override
     public void endVisit(JNewInstance x, Context ctx) {
       JsNew newOp = new JsNew(x.getSourceInfo());
-      JsNameRef nameRef = names.get(x.getType()).makeRef(x.getSourceInfo());
+      JsNameRef nameRef = names.get(x.getClassType()).makeRef(x.getSourceInfo());
       newOp.setConstructorExpression(nameRef);
       push(newOp);
     }
@@ -1949,7 +1949,7 @@
    * Because we modify String's prototype, all fields and polymorphic methods on
    * String's super types need special handling.
    */
-  private final Set<JReferenceType> specialObfuscatedTypes = new HashSet<JReferenceType>();
+  private final Set<JDeclaredType> specialObfuscatedTypes = new HashSet<JDeclaredType>();
 
   /**
    * Maps JsNames to machine-usable identifiers.
@@ -1961,7 +1961,7 @@
    */
   private final JsScope topScope;
 
-  private final Map<JsStatement, JReferenceType> typeForStatMap = new HashMap<JsStatement, JReferenceType>();
+  private final Map<JsStatement, JClassType> typeForStatMap = new HashMap<JsStatement, JClassType>();
 
   private final JTypeOracle typeOracle;
 
@@ -2121,11 +2121,11 @@
     generator.accept(program);
     final Map<JsName, JMethod> nameToMethodMap = new HashMap<JsName, JMethod>();
     final HashMap<JsName, JField> nameToFieldMap = new HashMap<JsName, JField>();
-    final HashMap<JsName, JReferenceType> constructorNameToTypeMap = new HashMap<JsName, JReferenceType>();
+    final HashMap<JsName, JClassType> constructorNameToTypeMap = new HashMap<JsName, JClassType>();
     for (JDeclaredType type : program.getDeclaredTypes()) {
       JsName typeName = names.get(type);
-      if (typeName != null) {
-        constructorNameToTypeMap.put(typeName, type);
+      if (type instanceof JClassType && typeName != null) {
+        constructorNameToTypeMap.put(typeName, (JClassType) type);
       }
       for (JField field : type.getFields()) {
         if (field.isStatic()) {
@@ -2152,7 +2152,7 @@
         return names.get(method);
       }
 
-      public JsName nameForType(JReferenceType type) {
+      public JsName nameForType(JClassType type) {
         return names.get(type);
       }
 
@@ -2164,11 +2164,11 @@
         return nameToMethodMap.get(name);
       }
 
-      public JReferenceType nameToType(JsName name) {
+      public JClassType nameToType(JsName name) {
         return constructorNameToTypeMap.get(name);
       }
 
-      public JReferenceType typeForStatement(JsStatement stat) {
+      public JClassType typeForStatement(JsStatement stat) {
         return typeForStatMap.get(stat);
       }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JavaScriptObjectNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JavaScriptObjectNormalizer.java
index 0760216..1eb02f0 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JavaScriptObjectNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JavaScriptObjectNormalizer.java
@@ -22,10 +22,10 @@
 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.JDeclaredType;
 import com.google.gwt.dev.jjs.ast.JExpression;
 import com.google.gwt.dev.jjs.ast.JField;
 import com.google.gwt.dev.jjs.ast.JInstanceOf;
-import com.google.gwt.dev.jjs.ast.JInterfaceType;
 import com.google.gwt.dev.jjs.ast.JLocal;
 import com.google.gwt.dev.jjs.ast.JLocalRef;
 import com.google.gwt.dev.jjs.ast.JMethod;
@@ -33,14 +33,13 @@
 import com.google.gwt.dev.jjs.ast.JMethodCall;
 import com.google.gwt.dev.jjs.ast.JModVisitor;
 import com.google.gwt.dev.jjs.ast.JNewArray;
+import com.google.gwt.dev.jjs.ast.JNonNullType;
 import com.google.gwt.dev.jjs.ast.JParameter;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JReferenceType;
 import com.google.gwt.dev.jjs.ast.JType;
 import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
 
-import java.util.Map;
-import java.util.Set;
 import java.util.Stack;
 
 /**
@@ -110,8 +109,8 @@
      */
     @Override
     public void endVisit(JMethodCall x, Context ctx) {
-      JType targetClass = x.getTarget().getEnclosingType();
-      if (jsoSingleImpls.containsKey(targetClass)) {
+      JDeclaredType targetClass = x.getTarget().getEnclosingType();
+      if (program.typeOracle.getSingleJsoImpl(targetClass) != null) {
 
         SourceInfo info = x.getSourceInfo().makeChild(
             JavaScriptObjectNormalizer.class,
@@ -121,7 +120,7 @@
         JMethod jsoMethod = findJsoMethod(x.getTarget());
         assert jsoMethod != null;
 
-        if (dualImpls.contains(targetClass)) {
+        if (program.typeOracle.isDualJsoInterface(targetClass)) {
           /*
            * This is the special-case code to handle interfaces.
            */
@@ -163,7 +162,7 @@
 
     @Override
     public void endVisit(JNewArray x, Context ctx) {
-      x.setType(translate(x.getType()));
+      x.setType((JNonNullType) translate(x.getType()));
     }
 
     @Override
@@ -198,7 +197,7 @@
     }
 
     private JMethod findJsoMethod(JMethod interfaceMethod) {
-      JClassType jsoClass = jsoSingleImpls.get(interfaceMethod.getEnclosingType());
+      JClassType jsoClass = program.typeOracle.getSingleJsoImpl(interfaceMethod.getEnclosingType());
       assert program.isJavaScriptObject(jsoClass);
       assert jsoClass != null;
 
@@ -245,22 +244,28 @@
     }
 
     private JType translate(JType type) {
-      if (program.isJavaScriptObject(type)) {
-        return program.getJavaScriptObject();
+      if (!(type instanceof JReferenceType)) {
+        return type;
+      }
+      JReferenceType refType = (JReferenceType) type;
+      boolean canBeNull = refType.canBeNull();
+      refType = refType.getUnderlyingType();
 
-      } else if (jsoSingleImpls.containsKey(type) && !dualImpls.contains(type)) {
+      if (program.isJavaScriptObject(refType)) {
+        refType = program.getJavaScriptObject();
+      } else if (program.typeOracle.getSingleJsoImpl(refType) != null
+          && !program.typeOracle.isDualJsoInterface(refType)) {
         // Optimization: narrow to JSO if it's not a dual impl.
-        return program.getJavaScriptObject();
-
-      } else if (type instanceof JArrayType) {
-        JArrayType arrayType = (JArrayType) type;
+        refType = program.getJavaScriptObject();
+      } else if (refType instanceof JArrayType) {
+        JArrayType arrayType = (JArrayType) refType;
         JType leafType = arrayType.getLeafType();
         JType replacement = translate(leafType);
         if (leafType != replacement) {
-          return program.getTypeArray(replacement, arrayType.getDims());
+          refType = program.getTypeArray(replacement, arrayType.getDims());
         }
       }
-      return type;
+      return canBeNull ? refType : program.getNonNullType(refType);
     }
   }
 
@@ -268,22 +273,10 @@
     new JavaScriptObjectNormalizer(program).execImpl();
   }
 
-  /**
-   * Interfaces implemented both by a JSO type and a regular Java type.
-   */
-  private final Set<JInterfaceType> dualImpls;
-
-  /**
-   * Maps SingleJsoImpl interfaces onto the single JSO implementation.
-   */
-  private final Map<JInterfaceType, JClassType> jsoSingleImpls;
-
   private final JProgram program;
 
   private JavaScriptObjectNormalizer(JProgram program) {
     this.program = program;
-    dualImpls = program.typeOracle.getInterfacesWithJavaAndJsoImpls();
-    jsoSingleImpls = program.typeOracle.getSingleJsoImpls();
   }
 
   private void execImpl() {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JavaToJavaScriptMap.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JavaToJavaScriptMap.java
index 3a232b1..89aace3 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JavaToJavaScriptMap.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JavaToJavaScriptMap.java
@@ -15,9 +15,9 @@
  */
 package com.google.gwt.dev.jjs.impl;
 
+import com.google.gwt.dev.jjs.ast.JClassType;
 import com.google.gwt.dev.jjs.ast.JField;
 import com.google.gwt.dev.jjs.ast.JMethod;
-import com.google.gwt.dev.jjs.ast.JReferenceType;
 import com.google.gwt.dev.js.ast.JsName;
 import com.google.gwt.dev.js.ast.JsStatement;
 
@@ -33,7 +33,7 @@
   /**
    * Return the JavaScript name corresponding to a Java type.
    */
-  JsName nameForType(JReferenceType type);
+  JsName nameForType(JClassType type);
 
   /**
    * If <code>name</code> is the name of a
@@ -52,13 +52,13 @@
    * If <code>name</code> is the name of a constructor function corresponding to
    * a Java type, then return that type. Otherwise, return <code>null</code>.
    */
-  JReferenceType nameToType(JsName name);
+  JClassType nameToType(JsName name);
 
   /**
    * If <code>stat</code> is used to set up the definition of some class, return
    * that class. Otherwise, return null.
    */
-  JReferenceType typeForStatement(JsStatement stat);
+  JClassType typeForStatement(JsStatement stat);
 
   /**
    * If <code>stat</code> is used to set up a vtable entry for a method, then
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/LongCastNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/LongCastNormalizer.java
index d5929ac..9e71d88 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/LongCastNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/LongCastNormalizer.java
@@ -59,7 +59,7 @@
       JType resultType = x.getType();
       JBinaryOperator op = x.getOp();
 
-      if (resultType == program.getTypeJavaLangString()) {
+      if (program.isJavaLangString(resultType)) {
         // Don't mess with concat.
         return;
       }
@@ -123,8 +123,8 @@
       if (init != null) {
         init = checkAndReplace(init, x.getVariableRef().getType());
         if (init != x.getInitializer()) {
-          JDeclarationStatement newStmt = new JDeclarationStatement(x.getSourceInfo(),
-              x.getVariableRef(), init);
+          JDeclarationStatement newStmt = new JDeclarationStatement(
+              x.getSourceInfo(), x.getVariableRef(), init);
           ctx.replaceMe(newStmt);
         }
       }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/LongEmulationNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/LongEmulationNormalizer.java
index d35dbc9..4a00988 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/LongEmulationNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/LongEmulationNormalizer.java
@@ -50,7 +50,7 @@
     @Override
     public void endVisit(JBinaryOperation x, Context ctx) {
       // Concats are handled by CastNormalizer.ConcatVisitor.
-      if (x.getType() == program.getTypeJavaLangString()) {
+      if (program.isJavaLangString(x.getType())) {
         return;
       }
       JType lhsType = x.getLhs().getType();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
index 1fe417a..d0bf13f 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
@@ -159,7 +159,7 @@
       // Setup parameters; map from the old params to the new params
       JParameter thisParam = JParameter.create(sourceInfo.makeChild(
           CreateStaticImplsVisitor.class, "Instance parameter"), "this$static",
-          enclosingType, true, true, newMethod);
+          program.getNonNullType(enclosingType), true, true, newMethod);
       Map<JParameter, JParameter> varMap = new IdentityHashMap<JParameter, JParameter>();
       for (int i = 0; i < x.getParams().size(); ++i) {
         JParameter oldVar = x.getParams().get(i);
@@ -171,7 +171,7 @@
 
       // Set the new original param types based on the old original param types
       List<JType> originalParamTypes = new ArrayList<JType>();
-      originalParamTypes.add(enclosingType);
+      originalParamTypes.add(program.getNonNullType(enclosingType));
       originalParamTypes.addAll(x.getOriginalParamTypes());
       newMethod.setOriginalTypes(x.getOriginalReturnType(), originalParamTypes);
 
@@ -186,7 +186,8 @@
       x.setBody(newBody);
       JMethodCall newCall = new JMethodCall(delegateCallSourceInfo, null,
           newMethod);
-      newCall.addArg(new JThisRef(delegateCallSourceInfo, enclosingType));
+      newCall.addArg(new JThisRef(delegateCallSourceInfo,
+          program.getNonNullType(enclosingType)));
       for (int i = 0; i < x.getParams().size(); ++i) {
         JParameter param = x.getParams().get(i);
         newCall.addArg(new JParameterRef(delegateCallSourceInfo, param));
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java
index 4e5cb42..d5ec679 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java
@@ -26,7 +26,6 @@
 import com.google.gwt.dev.jjs.ast.JNullType;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JReferenceType;
-import com.google.gwt.dev.jjs.ast.JType;
 
 /**
  * Update polymorphic method calls to tighter bindings based on the type of the
@@ -54,7 +53,7 @@
         return;
       }
 
-      JType instanceType = instance.getType();
+      JReferenceType instanceType = ((JReferenceType) instance.getType()).getUnderlyingType();
       JReferenceType enclosingType = method.getEnclosingType();
 
       if (instanceType == enclosingType
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRebinds.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRebinds.java
index 841735a..048a7b0 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRebinds.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRebinds.java
@@ -18,11 +18,11 @@
 import com.google.gwt.dev.jjs.InternalCompilerException;
 import com.google.gwt.dev.jjs.ast.Context;
 import com.google.gwt.dev.jjs.ast.JClassType;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
 import com.google.gwt.dev.jjs.ast.JGwtCreate;
 import com.google.gwt.dev.jjs.ast.JModVisitor;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JReboundEntryPoint;
-import com.google.gwt.dev.jjs.ast.JReferenceType;
 import com.google.gwt.dev.jjs.ast.JType;
 
 import java.util.List;
@@ -91,7 +91,7 @@
       throw new InternalCompilerException("Unexpected failure to rebind '"
           + reqType + "'");
     }
-    JReferenceType result = program.getFromTypeMap(reboundClassName);
+    JDeclaredType result = program.getFromTypeMap(reboundClassName);
     assert (result != null);
     return (JClassType) result;
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/SourceGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/SourceGenerationVisitor.java
index abbef1d..6c35360 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/SourceGenerationVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/SourceGenerationVisitor.java
@@ -17,12 +17,12 @@
 
 import com.google.gwt.dev.jjs.ast.Context;
 import com.google.gwt.dev.jjs.ast.JClassType;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
 import com.google.gwt.dev.jjs.ast.JField;
 import com.google.gwt.dev.jjs.ast.JInterfaceType;
 import com.google.gwt.dev.jjs.ast.JMethod;
 import com.google.gwt.dev.jjs.ast.JMethodBody;
 import com.google.gwt.dev.jjs.ast.JProgram;
-import com.google.gwt.dev.jjs.ast.JReferenceType;
 import com.google.gwt.dev.util.TextOutput;
 
 /**
@@ -99,7 +99,7 @@
   @Override
   public boolean visit(JProgram x, Context ctx) {
     for (int i = 0; i < x.getDeclaredTypes().size(); ++i) {
-      JReferenceType type = x.getDeclaredTypes().get(i);
+      JDeclaredType type = x.getDeclaredTypes().get(i);
       accept(type);
       newline();
       newline();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
index e7bb5a4..04ed496 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
@@ -306,7 +306,8 @@
          */
         JMethod staticImplFor = program.staticImplFor(x);
         if (staticImplFor == null
-            || !staticImplFor.getEnclosingType().getMethods().contains(staticImplFor)) {
+            || !staticImplFor.getEnclosingType().getMethods().contains(
+                staticImplFor)) {
           // The instance method has already been pruned.
           return true;
         }
@@ -404,17 +405,18 @@
       if (triviallyTrue) {
         // remove the cast operation
         ctx.replaceMe(x.getExpr());
-      } else if (triviallyFalse) {
-        // replace with a magic NULL cast
+      } else if (triviallyFalse && toType != program.getTypeNull()) {
+        // replace with a magic NULL cast, unless it's already a cast to NULL
         JCastOperation newOp = new JCastOperation(x.getSourceInfo(),
             program.getTypeNull(), x.getExpr());
         ctx.replaceMe(newOp);
       } else {
         // If possible, try to use a narrower cast
-        JClassType concreteType = getSingleConcreteType(toType);
-        if (concreteType != null) {
+        JReferenceType tighterType = getSingleConcreteType(toType);
+
+        if (tighterType != null && tighterType != toType) {
           JCastOperation newOp = new JCastOperation(x.getSourceInfo(),
-              concreteType, x.getExpr());
+              tighterType, x.getExpr());
           ctx.replaceMe(newOp);
         }
       }
@@ -442,10 +444,10 @@
 
     @Override
     public void endVisit(JGwtCreate x, Context ctx) {
-      List<JClassType> typeList = new ArrayList<JClassType>();
+      List<JReferenceType> typeList = new ArrayList<JReferenceType>();
       for (JExpression expr : x.getInstantiationExpressions()) {
-        JType type = expr.getType();
-        typeList.add((JClassType) type);
+        JReferenceType type = (JReferenceType) expr.getType();
+        typeList.add(type);
       }
 
       JReferenceType resultType = program.generalizeTypes(typeList);
@@ -493,7 +495,7 @@
         ctx.replaceMe(program.getLiteralBoolean(false));
       } else {
         // If possible, try to use a narrower cast
-        JClassType concreteType = getSingleConcreteType(toType);
+        JReferenceType concreteType = getSingleConcreteType(toType);
         if (concreteType != null) {
           JInstanceOf newOp = new JInstanceOf(x.getSourceInfo(), concreteType,
               x.getExpr());
@@ -528,7 +530,7 @@
         return;
       }
 
-      JClassType concreteType = getSingleConcreteType(x.getType());
+      JReferenceType concreteType = getSingleConcreteType(x.getType());
       if (concreteType != null) {
         x.setType(concreteType);
         myDidChange = true;
@@ -545,13 +547,6 @@
       // tighten based on both returned types and possible overrides
       List<JReferenceType> typeList = new ArrayList<JReferenceType>();
 
-      /*
-       * Always assume at least one null assignment; if there really aren't any
-       * other assignments, then this variable will get the null type. If there
-       * are, it won't hurt anything because null type will always lose.
-       */
-      typeList.add(typeNull);
-
       Set<JExpression> myReturns = returns.get(x);
       if (myReturns != null) {
         for (JExpression expr : myReturns) {
@@ -565,7 +560,13 @@
         }
       }
 
-      JReferenceType resultType = program.generalizeTypes(typeList);
+      JReferenceType resultType;
+      if (typeList.isEmpty()) {
+        // The method returns nothing
+        resultType = typeNull;
+      } else {
+        resultType = program.generalizeTypes(typeList);
+      }
       resultType = program.strongerType(refType, resultType);
       if (refType != resultType) {
         x.setType(resultType);
@@ -657,14 +658,18 @@
      * Given an abstract type, return the single concrete implementation of that
      * type.
      */
-    private JClassType getSingleConcreteType(JType type) {
+    private JReferenceType getSingleConcreteType(JType type) {
       if (type instanceof JReferenceType) {
         JReferenceType refType = (JReferenceType) type;
         if (refType.isAbstract()) {
-          JClassType singleConcrete = getSingleConcrete((JReferenceType) type,
-              implementors);
+          JClassType singleConcrete = getSingleConcrete(
+              refType.getUnderlyingType(), implementors);
           assert (singleConcrete == null || program.typeOracle.isInstantiatedType(singleConcrete));
-          return singleConcrete;
+          if (singleConcrete == null) {
+            return null;
+          }
+          return refType.canBeNull() ? singleConcrete
+              : program.getNonNullType(singleConcrete);
         }
       }
       return null;
@@ -715,7 +720,7 @@
       }
 
       // tighten based on leaf types
-      JClassType leafType = getSingleConcreteType(refType);
+      JReferenceType leafType = getSingleConcreteType(refType);
       if (leafType != null) {
         x.setType(leafType);
         myDidChange = true;
@@ -726,15 +731,13 @@
       List<JReferenceType> typeList = new ArrayList<JReferenceType>();
 
       /*
-       * For non-parameters, always assume at least one null assignment; if
-       * there really aren't any other assignments, then this variable will get
-       * the null type. If there are, it won't hurt anything because null type
-       * will always lose.
-       * 
-       * For parameters, don't perform any tightening if we can't find any
-       * actual assignments. The method should eventually get pruned.
+       * For fields without an initializer, add a null assignment, because the
+       * field might be accessed before initialized. Technically even a field
+       * with an initializer might be accessed before initialization, but
+       * presumably that is not the programmer's intent, so the compiler cheats
+       * and assumes the initial null will not be seen.
        */
-      if (!(x instanceof JParameter)) {
+      if ((x instanceof JField) && !x.hasInitializer()) {
         typeList.add(typeNull);
       }
 
@@ -758,12 +761,22 @@
         }
       }
 
-      if (typeList.isEmpty()) {
-        return;
+      JReferenceType resultType;
+      if (!typeList.isEmpty()) {
+        resultType = program.generalizeTypes(typeList);
+        resultType = program.strongerType(refType, resultType);
+      } else {
+        if (x instanceof JParameter) {
+          /*
+           * There is no need to tighten unused parameters, because they will be
+           * pruned.
+           */
+          resultType = refType;
+        } else {
+          resultType = typeNull;
+        }
       }
 
-      JReferenceType resultType = program.generalizeTypes(typeList);
-      resultType = program.strongerType(refType, resultType);
       if (refType != resultType) {
         x.setType(resultType);
         myDidChange = true;
diff --git a/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java b/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java
index 1a154a6..1f4bed4 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java
@@ -15,8 +15,8 @@
  */
 package com.google.gwt.dev.js;
 
+import com.google.gwt.dev.jjs.ast.JClassType;
 import com.google.gwt.dev.jjs.ast.JMethod;
-import com.google.gwt.dev.jjs.ast.JReferenceType;
 import com.google.gwt.dev.jjs.impl.FragmentExtractor;
 import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap;
 import com.google.gwt.dev.js.ast.JsBlock;
@@ -121,7 +121,7 @@
   private JsName nameToBillTo(JsVisitable node) {
     if (node instanceof JsStatement) {
       JsStatement stat = (JsStatement) node;
-      JReferenceType type = map.typeForStatement(stat);
+      JClassType type = map.typeForStatement(stat);
       if (type != null) {
         return map.nameForType(type);
       }
diff --git a/dev/core/test/com/google/gwt/dev/jjs/JjsTypeTest.java b/dev/core/test/com/google/gwt/dev/jjs/JjsTypeTest.java
new file mode 100644
index 0000000..38821ac
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/JjsTypeTest.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2009 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.dev.jjs.ast.JArrayType;
+import com.google.gwt.dev.jjs.ast.JClassType;
+import com.google.gwt.dev.jjs.ast.JInterfaceType;
+import com.google.gwt.dev.jjs.ast.JNonNullType;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JReferenceType;
+import com.google.gwt.dev.jjs.ast.JTypeOracle;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jdt.core.compiler.CharOperation;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Test basic operations on the types used by the JJS compiler. See
+ * {@link com.google.gwt.dev.jjs.ast.JType}.
+ */
+public class JjsTypeTest extends TestCase {
+  private JArrayType arrayOfA;
+  private JArrayType arrayOfArrayOfB;
+  private JReferenceType arrayOfArrayOfInt;
+  private JArrayType arrayOfB;
+  private JArrayType arrayOfBSub;
+  private JArrayType arrayOfC;
+  private JReferenceType arrayOfInt;
+  private JArrayType arrayOfObject;
+  private JClassType classA;
+  private JClassType classB;
+  private JClassType classBase;
+  private JNonNullType classBaseNn;
+  private JNonNullType classBnn;
+  private JClassType classBSub;
+  private JClassType classC;
+  private JClassType classJso;
+  private JClassType classJso1;
+  private JClassType classJso2;
+  private JClassType classObject;
+  private JClassType classString;
+  private JInterfaceType intfI;
+  private JInterfaceType intfIBase;
+  private JInterfaceType intfJ;
+  private JProgram program;
+  private SourceInfo synthSource;
+  private JReferenceType typeNull;
+  private JTypeOracle typeOracle;
+
+  public void testCanTheoreticallyCast() {
+    assertFalse(typeOracle.canTheoreticallyCast(classBnn, typeNull));
+
+    assertTrue(typeOracle.canTheoreticallyCast(classBSub, classB));
+    assertTrue(typeOracle.canTheoreticallyCast(classB, classBSub));
+
+    assertTrue(typeOracle.canTheoreticallyCast(classB, classBnn));
+    assertTrue(typeOracle.canTheoreticallyCast(classBnn, classB));
+
+    assertTrue(typeOracle.canTheoreticallyCast(classB, classB));
+
+    assertTrue(typeOracle.canTheoreticallyCast(classObject, arrayOfB));
+    assertFalse(typeOracle.canTheoreticallyCast(arrayOfA, arrayOfArrayOfB));
+
+    assertTrue(typeOracle.canTheoreticallyCast(arrayOfObject, arrayOfArrayOfB));
+
+    assertTrue(typeOracle.canTheoreticallyCast(arrayOfB, arrayOfBSub));
+
+    assertTrue(typeOracle.canTheoreticallyCast(classBase, intfI));
+    assertFalse(typeOracle.canTheoreticallyCast(classA, intfJ));
+
+    assertTrue(typeOracle.canTheoreticallyCast(intfIBase, intfI));
+
+    assertTrue(typeOracle.canTheoreticallyCast(intfIBase, classBase));
+    assertFalse(typeOracle.canTheoreticallyCast(intfJ, classA));
+  }
+
+  public void testCanTriviallyCast() {
+    assertTrue(typeOracle.canTriviallyCast(classB, classB));
+
+    assertTrue(typeOracle.canTriviallyCast(classBSub, classB));
+    assertFalse(typeOracle.canTriviallyCast(classB, classBSub));
+
+    assertFalse(typeOracle.canTriviallyCast(classC, classA));
+    assertFalse(typeOracle.canTriviallyCast(classA, classC));
+
+    assertTrue(typeOracle.canTriviallyCast(classB, intfI));
+    assertFalse(typeOracle.canTriviallyCast(intfI, classB));
+
+    assertTrue(typeOracle.canTriviallyCast(classB, classObject));
+    assertFalse(typeOracle.canTriviallyCast(classObject, classB));
+
+    assertTrue(typeOracle.canTriviallyCast(classB, intfI));
+    assertFalse(typeOracle.canTriviallyCast(intfI, classB));
+
+    assertTrue(typeOracle.canTriviallyCast(classBnn, classB));
+    assertFalse(typeOracle.canTriviallyCast(classB, classBnn));
+
+    assertTrue(typeOracle.canTriviallyCast(typeNull, classB));
+    assertFalse(typeOracle.canTriviallyCast(classB, typeNull));
+
+    assertTrue(typeOracle.canTriviallyCast(arrayOfBSub, arrayOfB));
+    assertFalse(typeOracle.canTriviallyCast(arrayOfB, arrayOfBSub));
+
+    assertFalse(typeOracle.canTriviallyCast(arrayOfA, arrayOfB));
+    assertFalse(typeOracle.canTriviallyCast(arrayOfB, arrayOfA));
+
+    assertFalse(typeOracle.canTriviallyCast(arrayOfArrayOfB, arrayOfB));
+    assertFalse(typeOracle.canTriviallyCast(arrayOfB, arrayOfArrayOfB));
+
+    assertTrue(typeOracle.canTriviallyCast(arrayOfArrayOfB, arrayOfObject));
+    assertFalse(typeOracle.canTriviallyCast(arrayOfObject, arrayOfArrayOfB));
+
+    assertTrue(typeOracle.canTriviallyCast(classJso1, classJso2));
+    assertTrue(typeOracle.canTriviallyCast(classJso2, classJso1));
+
+    assertTrue(typeOracle.canTriviallyCast(classJso, classJso1));
+    assertTrue(typeOracle.canTriviallyCast(classJso, classJso1));
+
+    /*
+     * Test that two types cannot both be trivially castable to each other,
+     * unless they are the same type. Or, unless they are both JSOs.
+     */
+    for (JReferenceType type1 : severalTypes()) {
+      for (JReferenceType type2 : severalTypes()) {
+        if (type1 != type2) {
+          if (!isJso(type1) || !isJso(type2)) {
+            assertFalse(typeOracle.canTriviallyCast(type1, type2)
+                && typeOracle.canTriviallyCast(type2, type1));
+          }
+        }
+      }
+    }
+  }
+
+  public void testGeneralizeTypes() {
+    assertSame(classA, generalizeTypes(classA, classA));
+    assertSame(classB, generalizeTypes(classB, classBnn));
+    assertSame(classB, generalizeTypes(classBnn, classB));
+    assertSame(classBaseNn, generalizeTypes(classBnn, classBaseNn));
+    assertSame(classB, generalizeTypes(classB, typeNull));
+    assertSame(classB, generalizeTypes(typeNull, classB));
+
+    assertSame(intfIBase, generalizeTypes(intfI, intfIBase));
+    assertSame(intfIBase, generalizeTypes(intfIBase, intfI));
+    assertSame(classObject, generalizeTypes(intfJ, intfI));
+
+    assertSame(classObject, generalizeTypes(arrayOfB, arrayOfInt));
+    assertSame(classObject, generalizeTypes(arrayOfC, arrayOfArrayOfB));
+    assertSame(arrayOfObject, generalizeTypes(arrayOfC, arrayOfB));
+    assertSame(arrayOfObject, generalizeTypes(arrayOfObject, arrayOfArrayOfInt));
+
+    assertSame(intfI, generalizeTypes(classB, intfI));
+    assertSame(classObject, generalizeTypes(classB, intfJ));
+
+    assertSame(classObject, generalizeTypes(intfI, arrayOfInt));
+
+    for (JReferenceType type1 : severalTypes()) {
+      for (JReferenceType type2 : severalTypes()) {
+        JReferenceType generalized = generalizeTypes(type1, type2);
+        assertTrue(typeOracle.canTriviallyCast(type1, generalized));
+        assertTrue(typeOracle.canTriviallyCast(type2, generalized));
+      }
+    }
+  }
+
+  public void testStrongerType() {
+    assertSame(classA, program.strongerType(classA, classA));
+    assertSame(classBnn, program.strongerType(classB, classBnn));
+    assertSame(classB, program.strongerType(classB, classBase));
+    assertSame(classB, program.strongerType(classBase, classB));
+    assertSame(intfI, program.strongerType(intfI, intfJ));
+  }
+
+  @Override
+  protected void setUp() {
+    createSampleProgram();
+  }
+
+  private JClassType createClass(String className, JClassType superClass,
+      boolean isAbstract, boolean isFinal) {
+    JClassType clazz = program.createClass(synthSource, CharOperation.splitOn(
+        '.', className.toCharArray()), isAbstract, isFinal);
+    clazz.setSuperClass(superClass);
+    return clazz;
+  }
+
+  private JInterfaceType createInterface(String className) {
+    JInterfaceType intf = program.createInterface(synthSource,
+        CharOperation.splitOn('.', className.toCharArray()));
+    return intf;
+  }
+
+  private void createSampleProgram() {
+    // Make the program itself
+    program = new JProgram();
+    typeOracle = program.typeOracle;
+    synthSource = program.createSourceInfoSynthetic(JjsTypeTest.class,
+        "synthetic node used for testing");
+
+    classObject = createClass("java.lang.Object", null, false, false);
+    classString = createClass("java.lang.String", classObject, false, true);
+    classJso = createClass("com.google.gwt.core.client.JavaScriptObject",
+        classObject, false, false);
+
+    intfIBase = createInterface("IBase");
+
+    intfI = createInterface("I");
+    intfI.addImplements(intfIBase);
+
+    intfJ = createInterface("J");
+
+    classBase = createClass("Base", classObject, false, false);
+
+    classA = createClass("A", classBase, false, false);
+
+    classB = createClass("B", classBase, false, false);
+    classB.addImplements(intfI);
+
+    classC = createClass("C", classObject, false, false);
+    classC.addImplements(intfI);
+
+    classBSub = createClass("BSub", classB, false, false);
+
+    classJso1 = createClass("Jso1", classJso, false, false);
+    classJso2 = createClass("Jso2", classJso, false, false);
+
+    program.typeOracle.computeBeforeAST();
+
+    // Save off some miscellaneous types to test against
+    typeNull = program.getTypeNull();
+
+    classBnn = program.getNonNullType(classB);
+    classBaseNn = program.getNonNullType(classBase);
+
+    arrayOfA = program.getTypeArray(classA, 1);
+    arrayOfB = program.getTypeArray(classB, 1);
+    arrayOfBSub = program.getTypeArray(classBSub, 1);
+    arrayOfC = program.getTypeArray(classC, 1);
+    arrayOfObject = program.getTypeArray(classObject, 1);
+    arrayOfInt = program.getTypeArray(program.getTypePrimitiveInt(), 1);
+    arrayOfArrayOfInt = program.getTypeArray(program.getTypePrimitiveInt(), 2);
+
+    arrayOfArrayOfB = program.getTypeArray(classB, 2);
+  }
+
+  private JReferenceType generalizeTypes(JReferenceType type1,
+      JReferenceType type2) {
+    List<JReferenceType> types = new ArrayList<JReferenceType>(2);
+    types.add(type1);
+    types.add(type2);
+    return program.generalizeTypes(types);
+  }
+
+  private boolean isJso(JReferenceType type) {
+    return typeOracle.canTriviallyCast(type, classJso);
+  }
+
+  /**
+   * Return several types, for exhaustively testing basic properties.
+   */
+  private Collection<JReferenceType> severalTypes() {
+    List<JReferenceType> types = new ArrayList<JReferenceType>();
+    types.add(arrayOfA);
+    types.add(arrayOfB);
+    types.add(arrayOfArrayOfB);
+    types.add(arrayOfBSub);
+    types.add(arrayOfObject);
+    types.add(classA);
+    types.add(classB);
+    types.add(classBSub);
+    types.add(classBnn);
+    types.add(classBase);
+    types.add(classC);
+    types.add(classObject);
+    types.add(classString);
+    types.add(intfI);
+    types.add(intfJ);
+    types.add(intfIBase);
+    types.add(classJso1);
+    types.add(classJso2);
+    types.add(classJso);
+    types.add(typeNull);
+
+    return types;
+  }
+}