Allow overloads in native JsType methods.

Change-Id: I39cc4b82949eb37f544db17abbd0788f694b18ac
diff --git a/dev/core/src/com/google/gwt/dev/javac/JsInteropUtil.java b/dev/core/src/com/google/gwt/dev/javac/JsInteropUtil.java
index cf653ef..0a0087e 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JsInteropUtil.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JsInteropUtil.java
@@ -42,6 +42,7 @@
   public static final String JSPROPERTY_CLASS = "com.google.gwt.core.client.js.JsProperty";
   public static final String JSTYPE_CLASS = "com.google.gwt.core.client.js.JsType";
   public static final String UNUSABLE_BY_JS = "unusable-by-js";
+  public static final String INVALID_JSNAME = "<invalid>";
 
   public static void maybeSetJsInteropProperties(JDeclaredType type, Annotation... annotations) {
     AnnotationBinding jsType = JdtUtil.getAnnotation(annotations, JSTYPE_CLASS);
@@ -172,7 +173,7 @@
       String jsName = Introspector.decapitalize(methodName.substring(2));
       method.setJsPropertyInfo(jsName, JsPropertyAccessorType.GETTER);
     } else {
-      method.setJsPropertyInfo("<invalid>", JsPropertyAccessorType.UNDEFINED);
+      method.setJsPropertyInfo(INVALID_JSNAME, JsPropertyAccessorType.UNDEFINED);
     }
   }
 
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 6f3dc0f..13ae108 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
@@ -16,6 +16,7 @@
 package com.google.gwt.dev.jjs.ast;
 
 import com.google.gwt.dev.common.InliningMode;
+import com.google.gwt.dev.javac.JsInteropUtil;
 import com.google.gwt.dev.jjs.InternalCompilerException;
 import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.jjs.SourceOrigin;
@@ -161,7 +162,7 @@
         continue;
       }
       if (jsMemberName != null && !jsMemberName.equals(jsMemberOverrideName)) {
-        return null;
+        return JsInteropUtil.INVALID_JSNAME;
       }
       jsMemberName = jsMemberOverrideName;
     }
@@ -223,7 +224,7 @@
     return JsPropertyAccessorType.NONE;
   }
 
-  public boolean isJsPropertyAccessor() {
+  private boolean isJsPropertyAccessor() {
     return jsPropertyType != JsPropertyAccessorType.NONE;
   }
 
@@ -561,7 +562,8 @@
   }
 
   /**
-   * Returns the transitive closure of all the methods this method overrides.
+   * Returns the transitive closure of all the methods this method overrides; this set is ordered
+   * from most specific to least specific, where class methods appear before interface methods.
    */
   public Set<JMethod> getOverriddenMethods() {
     return overriddenMethods;
@@ -569,7 +571,9 @@
 
   /**
    * Returns the transitive closure of all the methods that override this method; caveat this
-   * list is only complete in monolithic compiles and should not be used in incremental compiles..
+   * list is only complete in monolithic compiles and should not be used in incremental compiles.
+   * The returned set is ordered in such a way that most specific overriding methods appear after
+   * less specific ones.
    */
   public Set<JMethod> getOverridingMethods() {
     return overridingMethods;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JType.java
index aa8d20a..82a40f5 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JType.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JType.java
@@ -132,7 +132,7 @@
    * "class Name { .. }"), i.e. it is a name that does not include enclosing type names nor package.
    */
   public String[] getCompoundName() {
-    return new String[] { shortName };
+    return new String[] { getShortName() };
   }
 
   /**
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 5b7a3e1..ddaec08 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
@@ -868,6 +868,12 @@
     return subclassesByClass.containsEntry(type.getName(), possibleSubType.getName());
   }
 
+  public boolean isSubType(JDeclaredType type, JDeclaredType possibleSubType) {
+    return subclassesByClass.containsEntry(type.getName(), possibleSubType.getName())
+        || classesByImplementingInterface.containsEntry(type.getName(), possibleSubType.getName())
+        || subInterfacesByInterface.containsEntry(type.getName(), possibleSubType.getName());
+  }
+
   public Iterable<String> getSubTypeNames(String typeName) {
     return Iterables.concat(classesByImplementingInterface.get(typeName),
         subclassesByClass.get(typeName), subInterfacesByInterface.get(typeName));
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 1f0bd91..f8c997a 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
@@ -1472,6 +1472,7 @@
         for (JField field : type.getFields()) {
           if (field.isJsInteropEntryPoint()) {
             if (!field.isFinal()) {
+              // TODO(rluble): move waring to JsInteropRestrictionChecker.
               logger.log(
                   TreeLogger.Type.WARN,
                   "Exporting effectively non-final field "
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JavaAstVerifier.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JavaAstVerifier.java
index 4d0d6c9..7537714 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JavaAstVerifier.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JavaAstVerifier.java
@@ -30,9 +30,11 @@
 import com.google.gwt.dev.jjs.ast.js.JsniFieldRef;
 import com.google.gwt.dev.jjs.ast.js.JsniMethodRef;
 import com.google.gwt.thirdparty.guava.common.collect.HashMultimap;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
 import com.google.gwt.thirdparty.guava.common.collect.Multimap;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
 
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -48,8 +50,10 @@
   private Set<String> seenTypeNames = Sets.newHashSet();
   private Multimap<JDeclaredType, String> seenMethodsByType = HashMultimap.create();
   private Multimap<JDeclaredType, String> seenFieldsByType = HashMultimap.create();
+  private JProgram program;
 
   JavaAstVerifier(JProgram program) {
+    this.program = program;
     for (JDeclaredType type :program.getModuleDeclaredTypes()) {
       membersByType.putAll(type, type.getMethods());
       membersByType.putAll(type, type.getFields());
@@ -65,6 +69,40 @@
     }
   }
 
+  public static void assertCorrectOverriddenOrder(JProgram program, JMethod method) {
+    // The order of in the overriden set is most specific to least.
+    List<JMethod> seenMethods = Lists.newArrayList(method);
+    JMethod lastMethod = method;
+    for (JMethod overriden : method.getOverriddenMethods()) {
+      for (JMethod seenMethod : seenMethods) {
+        assert !program.typeOracle.isSubType(
+            seenMethod.getEnclosingType(), overriden.getEnclosingType())
+            : "Superclass method '" + seenMethod.getQualifiedName()
+                + "' appeared before subclass method '" + overriden.getQualifiedName()
+                + "' in '" + method.getQualifiedName() + "' overridden list";
+      }
+      assert overriden.getEnclosingType() instanceof JInterfaceType
+          || lastMethod.getEnclosingType() instanceof JClassType
+          : "Class method '" + overriden.getQualifiedName()
+          + "' appeared before after interface method '" + lastMethod.getQualifiedName()
+          + "' in '" + method.getQualifiedName() + "' overridden list";
+    }
+  }
+
+  public static void assertCorrectOverridingOrder(JProgram program, JMethod method) {
+    // The order of in the overriden set is most specific to least.
+    List<JMethod> seenMethods = Lists.newArrayList(method);
+    for (JMethod overriden : method.getOverridingMethods()) {
+      for (JMethod seenMethod : seenMethods) {
+        assert !program.typeOracle.isSubType(
+            overriden.getEnclosingType(), seenMethod.getEnclosingType())
+            : "Subclass method '" + seenMethod.getQualifiedName()
+                + "' appeared before superclass method '" + overriden.getQualifiedName()
+                + "' in '" + method.getQualifiedName() + "' overriding list";
+      }
+    }
+  }
+
   @Override
   public void endVisit(JClassType x, Context ctx) {
     assertNotSeenBefore(x);
@@ -97,6 +135,8 @@
     assert !seenMethodsByType.containsEntry(enclosingType, methodSignature) :
         "Method " + x + " is duplicated.";
     seenMethodsByType.put(enclosingType, methodSignature);
+    assertCorrectOverriddenOrder(program, x);
+    assertCorrectOverridingOrder(program, x);
   }
 
   @Override
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JjsUtils.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JjsUtils.java
index 003f7fb..34b776f 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JjsUtils.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JjsUtils.java
@@ -20,6 +20,7 @@
 import com.google.gwt.dev.jjs.SourceOrigin;
 import com.google.gwt.dev.jjs.ast.HasName;
 import com.google.gwt.dev.jjs.ast.HasType;
+import com.google.gwt.dev.jjs.ast.JArrayType;
 import com.google.gwt.dev.jjs.ast.JBinaryOperation;
 import com.google.gwt.dev.jjs.ast.JBinaryOperator;
 import com.google.gwt.dev.jjs.ast.JBlock;
@@ -30,6 +31,7 @@
 import com.google.gwt.dev.jjs.ast.JDeclaredType;
 import com.google.gwt.dev.jjs.ast.JDoubleLiteral;
 import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JField;
 import com.google.gwt.dev.jjs.ast.JFloatLiteral;
 import com.google.gwt.dev.jjs.ast.JIntLiteral;
 import com.google.gwt.dev.jjs.ast.JInterfaceType;
@@ -66,6 +68,7 @@
 import com.google.gwt.thirdparty.guava.common.base.Joiner;
 import com.google.gwt.thirdparty.guava.common.base.Predicate;
 import com.google.gwt.thirdparty.guava.common.base.Predicates;
+import com.google.gwt.thirdparty.guava.common.base.Strings;
 import com.google.gwt.thirdparty.guava.common.collect.Collections2;
 import com.google.gwt.thirdparty.guava.common.collect.FluentIterable;
 import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
@@ -297,6 +300,48 @@
     return createEmptyMethodFromExample(type, superTypeMethod, true);
   }
 
+  /**
+   * Returns a description for a type suitable for reporting errors to the users.
+   */
+  public static String getReadableDescription(JType type) {
+    if (type instanceof JArrayType) {
+      JArrayType arrayType = (JArrayType) type;
+      return getReadableDescription(arrayType.getLeafType()) + Strings.repeat("[]",
+          arrayType.getDims());
+    }
+    return Joiner.on(".").join(type.getCompoundName());
+  }
+
+  /**
+   * Returns a description for a member suitable for reporting errors to the users.
+   */
+  public static String getReadableDescription(JMember member) {
+    if (member instanceof JField) {
+      return String.format("%s %s.%s",
+          getReadableDescription(member.getType()),
+          getReadableDescription(member.getEnclosingType()),
+          member.getName());
+    }
+
+    JMethod method = (JMethod) member;
+    String printableDescription = "";
+    if (!method.isConstructor()) {
+      printableDescription += getReadableDescription(method.getType()) + " ";
+    }
+    printableDescription += String.format("%s.%s(%s)",
+        getReadableDescription(method.getEnclosingType()),
+        method.getName(),
+        Joiner.on(", ").join(
+            Iterables.transform(method.getOriginalParamTypes(), new Function<JType, String>() {
+                  @Override
+                  public String apply(JType type) {
+                    return getReadableDescription(type);
+                  }
+                }
+            )));
+    return printableDescription;
+  }
+
   public static void replaceMethodBody(JMethod method, JExpression returnValue) {
     JMethodBody body = (JMethodBody) method.getBody();
     JBlock block = body.getBlock();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java
index 220af42..328936e 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java
@@ -14,9 +14,11 @@
 package com.google.gwt.dev.jjs.impl;
 
 import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.TreeLogger.Type;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.MinimalRebuildCache;
 import com.google.gwt.dev.javac.JsInteropUtil;
+import com.google.gwt.dev.jjs.HasSourceInfo;
 import com.google.gwt.dev.jjs.ast.CanHaveSuppressedWarnings;
 import com.google.gwt.dev.jjs.ast.Context;
 import com.google.gwt.dev.jjs.ast.JClassType;
@@ -44,11 +46,14 @@
 import com.google.gwt.thirdparty.guava.common.collect.FluentIterable;
 import com.google.gwt.thirdparty.guava.common.collect.Iterables;
 import com.google.gwt.thirdparty.guava.common.collect.Maps;
+import com.google.gwt.thirdparty.guava.common.collect.Multimap;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
+import com.google.gwt.thirdparty.guava.common.collect.TreeMultimap;
 
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
+import java.util.TreeSet;
 
 /**
  * Checks and throws errors for invalid JsInterop constructs.
@@ -58,30 +63,22 @@
   public static void exec(TreeLogger logger, JProgram jprogram,
       MinimalRebuildCache minimalRebuildCache) throws UnableToCompleteException {
     JsInteropRestrictionChecker jsInteropRestrictionChecker =
-        new JsInteropRestrictionChecker(logger, jprogram, minimalRebuildCache);
-
-    jsInteropRestrictionChecker.checkProgram();
-    if (jsInteropRestrictionChecker.hasErrors) {
+        new JsInteropRestrictionChecker(jprogram, minimalRebuildCache);
+    boolean success = jsInteropRestrictionChecker.checkProgram(logger);
+    if (!success) {
       throw new UnableToCompleteException();
     }
   }
 
-  private Map<String, String> currentJsMethodNameByGetterNames;
-  private Map<String, String> currentJsMethodNameBySetterNames;
-  private Map<String, JType> currentJsPropertyTypeByName;
-  private Map<String, String> currentLocalNameByMemberNames;
-  private Set<JMethod> currentProcessedMethods;
-  private JDeclaredType currentType;
-  private boolean hasErrors;
+  private Multimap<String, String> errorsByFilename = TreeMultimap.create();
+  private Multimap<String, String> warningsByFilename = TreeMultimap.create();
   private final JProgram jprogram;
-  private final TreeLogger logger;
   private final MinimalRebuildCache minimalRebuildCache;
 
   // TODO review any use of word export
 
-  private JsInteropRestrictionChecker(TreeLogger logger, JProgram jprogram,
+  private JsInteropRestrictionChecker(JProgram jprogram,
       MinimalRebuildCache minimalRebuildCache) {
-    this.logger = logger;
     this.jprogram = jprogram;
     this.minimalRebuildCache = minimalRebuildCache;
   }
@@ -185,12 +182,16 @@
           }
         }).toList();
 
+    if (x.isJsNative()) {
+      return;
+    }
+
     if (jsConstructors.isEmpty()) {
       return;
     }
 
     if (jsConstructors.size() > 1) {
-      logError("More than one JsConstructor exists for %s.", x.getName());
+      logError(x, "More than one JsConstructor exists for %s.", JjsUtils.getReadableDescription(x));
     }
 
     final JConstructor jsConstructor = (JConstructor) jsConstructors.get(0);
@@ -204,8 +205,9 @@
     });
 
     if (anyNonDelegatingConstructor) {
-      logError("Constructor '%s' can be a JsConstructor only if all constructors in the class are "
-          + "delegating to it.", jsConstructor.getQualifiedName());
+      logError(jsConstructor,
+          "Constructor %s can be a JsConstructor only if all constructors in the class are "
+          + "delegating to it.", getMemberDescription(jsConstructor));
     }
   }
 
@@ -217,70 +219,127 @@
     return call.getTarget().equals(targetCtor);
   }
 
-  private void checkField(JField x) {
-    if (x.getEnclosingType().isJsNative()) {
-      checkMemberOfNativeJsType(x);
+  private void checkField(Map<String, JsMember> localNames, JField field) {
+    if (field.getEnclosingType().isJsNative()) {
+      checkMemberOfNativeJsType(field);
     }
-    checkUnusableByJs(x);
 
-    if (!x.isJsProperty()) {
+    checkUnusableByJs(field);
+
+    if (!field.isJsProperty()) {
       return;
     }
 
-    if (x.needsDynamicDispatch()) {
-      checkLocalName(x);
-    } else if (!x.isJsNative() && currentType == x.getEnclosingType()) {
-      checkGlobalName(x);
+    if (field.needsDynamicDispatch()) {
+      checkLocalName(localNames, field);
+    } else if (!field.isJsNative()) {
+      checkGlobalName(field);
     }
   }
 
-  private void checkMethod(JMethod x) {
-    if (!currentProcessedMethods.add(x)) {
-      return;
-    }
-    currentProcessedMethods.addAll(x.getOverriddenMethods());
-
-    if (x.getEnclosingType().isJsNative()) {
-      checkMemberOfNativeJsType(x);
+  private void checkMethod(Map<String, JsMember> localNames, JMethod method) {
+    if (method.getEnclosingType().isJsNative()) {
+      checkMemberOfNativeJsType(method);
     }
 
-    if (x.isJsOverlay()) {
-      checkJsOverlay(x);
+    if (method.isJsOverlay()) {
+      checkJsOverlay(method);
     }
 
-    checkUnusableByJs(x);
+    checkUnusableByJs(method);
 
-    if (!x.isOrOverridesJsMethod()) {
+    if (!method.isOrOverridesJsMethod()) {
       return;
     }
 
-    if (x.needsDynamicDispatch()) {
-      checkJsMethod(x);
-    } else if (!x.isJsNative() && currentType == x.getEnclosingType()) {
-      checkGlobalName(x);
+    if (method.needsDynamicDispatch()) {
+      if (!isSyntheticBridgeMethod(method)) {
+        checkLocalName(localNames, method);
+      }
+    } else if (!method.isJsNative()) {
+      checkGlobalName(method);
     }
   }
 
-  private void checkGlobalName(JMember x) {
-    if (!minimalRebuildCache.addExportedGlobalName(x.getQualifiedJsName(), currentType.getName())) {
-      logError("'%s' can't be exported because the global name '%s' is already taken.",
-          x.getQualifiedName(), x.getQualifiedJsName());
+  private void checkGlobalName(JMember member) {
+    if (!minimalRebuildCache.addExportedGlobalName(member.getQualifiedJsName(),
+        member.getEnclosingType().getName())) {
+      logError(member, "%s cannot be exported because the global name '%s' is already taken.",
+          getMemberDescription(member), member.getQualifiedJsName());
     }
   }
 
-  private void checkLocalName(JMember member) {
-    String jsName = member.getJsName();
-    if (currentLocalNameByMemberNames.put(jsName, member.getQualifiedName()) != null) {
-      logError("'%s' can't be exported in type '%s' because the name '%s' is already taken.",
-          member.getQualifiedName(), currentType.getName(), jsName);
+  private void checkLocalName(Map<String, JsMember> localNames, JMember member) {
+    if (member.getJsName().equals(JsInteropUtil.INVALID_JSNAME)) {
+      if (member instanceof JMethod
+          && ((JMethod) member).getJsPropertyAccessorType() == JsPropertyAccessorType.UNDEFINED) {
+        logError(member, "JsProperty %s doesn't follow Java Bean naming conventions.",
+            getMemberDescription(member));
+      } else {
+        logError(
+            member, "%s cannot be assigned a different JavaScript name than the method it overrides.",
+            getMemberDescription(member));
+      }
+      return;
+    }
+
+    JsMember oldMember = localNames.get(member.getJsName());
+    JsMember newMember = createOrUpdateJsMember(oldMember, member);
+
+    checkJsPropertyAccessor(member, newMember);
+
+    if (oldMember == null || newMember == oldMember) {
+      localNames.put(member.getJsName(), newMember);
+      return;
+    }
+
+    if (oldMember.isNativeMethod() && newMember.isNativeMethod()) {
+      return;
+    }
+
+    logError(member, "%s and %s cannot both use the same JavaScript name '%s'.",
+        getMemberDescription(member), getMemberDescription(oldMember.member), member.getJsName());
+  }
+
+  void checkJsPropertyAccessor(JMember x, JsMember newMember) {
+    if (newMember.setter != null) {
+      checkValidSetter(newMember.setter);
+    }
+    if (newMember.getter != null) {
+      checkValidGetter(newMember.getter);
+    }
+    if (newMember.setter != null && newMember.getter != null) {
+      checkJsPropertyGetterConsistentWithSetter(
+          x.getEnclosingType(), newMember.setter, newMember.getter);
     }
   }
 
-  private void checkJsPropertyType(String propertyName, String enclosingTypeName, JType type) {
-    JType recordedType = currentJsPropertyTypeByName.put(propertyName, type);
-    if (recordedType != null && recordedType != type) {
-      logError("The setter and getter for JsProperty '%s' in type '%s' must have consistent types.",
-          propertyName, enclosingTypeName);
+  private void checkJsPropertyGetterConsistentWithSetter(
+      JType type, JMethod setter, JMethod getter) {
+    if (setter.getParams().size() == 1
+        &&  getter.getType() != setter.getParams().get(0).getType()) {
+      logError(setter,
+          "The setter and getter for JsProperty '%s' in type '%s' must have consistent types.",
+          setter.getJsName(), JjsUtils.getReadableDescription(type));
+    }
+  }
+
+  private void checkValidSetter(JMethod setter) {
+    if (setter.getParams().size() != 1 || setter.getType() != JPrimitiveType.VOID) {
+      logError(setter, "There needs to be single parameter and void return type for the "
+          + "JsProperty setter %s.", getMemberDescription(setter));
+    }
+  }
+
+  private void checkValidGetter(JMethod getter) {
+    if (!getter.getParams().isEmpty() || getter.getType() == JPrimitiveType.VOID) {
+      logError(getter,
+          "There cannot be void return type or any parameters for the JsProperty getter"
+          + " %s.", getMemberDescription(getter));
+    }
+    if (getter.getType() != JPrimitiveType.BOOLEAN && getter.getName().startsWith("is")) {
+      logError(getter, "There cannot be non-boolean return for the JsProperty 'is' getter %s.",
+          getMemberDescription(getter));
     }
   }
 
@@ -289,19 +348,22 @@
       return;
     }
 
-    String qualifiedName = method.getQualifiedName();
+    String methodDescription = JjsUtils.getReadableDescription(method);
 
     if (!method.getEnclosingType().isJsNative()) {
-      logError("Method '%s' in non-native type cannot be @JsOverlay.", qualifiedName);
+      logError(method,
+          "Method '%s' in non-native type cannot be @JsOverlay.", methodDescription);
     }
 
     if (!method.getOverriddenMethods().isEmpty()) {
-      logError("JsOverlay method '%s' cannot override a supertype method.", qualifiedName);
+      logError(method,
+          "JsOverlay method '%s' cannot override a supertype method.", methodDescription);
       return;
     }
 
     if (method.isJsNative() || method.isJsniMethod() || method.isStatic() || !method.isFinal()) {
-      logError("JsOverlay method '%s' cannot be non-final, static, nor native.", qualifiedName);
+      logError(method,
+          "JsOverlay method '%s' cannot be non-final, static, nor native.", methodDescription);
     }
   }
 
@@ -311,96 +373,12 @@
     }
 
     if (member.getJsName() == null && !member.isJsOverlay()) {
-      logError("Native JsType member '%s' is not public or has @JsIgnore.",
-          member.getQualifiedName());
+      logError(member, "Native JsType member %s is not public or has @JsIgnore.",
+          getMemberDescription(member));
       return;
     }
   }
 
-  private void checkJsMethod(JMethod method) {
-    if (method.isSynthetic() && !method.isForwarding()) {
-      // A name slot taken up by a synthetic method, such as a bridge method for a generic method,
-      // is not the fault of the user and so should not be reported as an error. JS generation
-      // should take responsibility for ensuring that only the correct method version (in this
-      // particular set of colliding method names) is exported. Forwarding synthetic methods
-      // (such as an accidental override forwarding method that occurs when a JsType interface
-      // starts exposing a method in class B that is only ever implemented in its parent class A)
-      // though should be checked since they are exported and do take up an name slot.
-      return;
-    }
-
-    String jsMemberName = method.getJsName();
-    String qualifiedMethodName = method.getQualifiedName();
-    String typeName = method.getEnclosingType().getName();
-    JsPropertyAccessorType accessorType = method.getJsPropertyAccessorType();
-
-    if (jsMemberName == null) {
-      logError("'%s' can't be exported because the method overloads multiple methods with "
-          + "different names.", qualifiedMethodName);
-    }
-
-    if (accessorType == JsPropertyAccessorType.GETTER) {
-      if (!method.getParams().isEmpty() || method.getType() == JPrimitiveType.VOID) {
-        logError("There can't be void return type or any parameters for the JsProperty getter"
-            + " '%s'.", qualifiedMethodName);
-        return;
-      }
-      if (method.getType() != JPrimitiveType.BOOLEAN && method.getName().startsWith("is")) {
-        logError("There can't be non-boolean return for the JsProperty 'is' getter '%s'.",
-            qualifiedMethodName);
-        return;
-      }
-      if (currentJsMethodNameByGetterNames.put(jsMemberName, qualifiedMethodName) != null) {
-        // Don't allow multiple getters for the same property name.
-        logError("There can't be more than one getter for JsProperty '%s' in type '%s'.",
-            jsMemberName, typeName);
-        return;
-      }
-      checkNameCollisionForGetterAndRegular(jsMemberName, typeName);
-      checkJsPropertyType(jsMemberName, typeName, method.getOriginalReturnType());
-    } else if (accessorType == JsPropertyAccessorType.SETTER) {
-      if (method.getParams().size() != 1 || method.getType() != JPrimitiveType.VOID) {
-        logError("There needs to be single parameter and void return type for the JsProperty setter"
-            + " '%s'.", qualifiedMethodName);
-        return;
-      }
-      if (currentJsMethodNameBySetterNames.put(jsMemberName, qualifiedMethodName) != null) {
-        // Don't allow multiple setters for the same property name.
-        logError("There can't be more than one setter for JsProperty '%s' in type '%s'.",
-            jsMemberName, typeName);
-        return;
-      }
-      checkNameCollisionForSetterAndRegular(jsMemberName, typeName);
-      checkJsPropertyType(jsMemberName, typeName,
-          Iterables.getOnlyElement(method.getParams()).getType());
-    } else if (accessorType == JsPropertyAccessorType.UNDEFINED) {
-      // We couldn't extract the JsPropertyType.
-      logError("JsProperty '%s' doesn't follow Java Bean naming conventions.", qualifiedMethodName);
-    } else {
-      checkLocalName(method);
-      checkNameCollisionForGetterAndRegular(jsMemberName, typeName);
-      checkNameCollisionForSetterAndRegular(jsMemberName, typeName);
-    }
-  }
-
-  private void checkNameCollisionForGetterAndRegular(String getterName, String typeName) {
-    if (currentJsMethodNameByGetterNames.containsKey(getterName)
-        && currentLocalNameByMemberNames.containsKey(getterName)) {
-      logError("'%s' and '%s' can't both be named '%s' in type '%s'.",
-          currentLocalNameByMemberNames.get(getterName),
-          currentJsMethodNameByGetterNames.get(getterName), getterName, typeName);
-    }
-  }
-
-  private void checkNameCollisionForSetterAndRegular(String setterName, String typeName) {
-    if (currentJsMethodNameBySetterNames.containsKey(setterName)
-        && currentLocalNameByMemberNames.containsKey(setterName)) {
-      logError("'%s' and '%s' can't both be named '%s' in type '%s'.",
-          currentLocalNameByMemberNames.get(setterName),
-          currentJsMethodNameBySetterNames.get(setterName), setterName, typeName);
-    }
-  }
-
   private void checkStaticJsPropertyCalls() {
     new JVisitor() {
       @Override
@@ -412,11 +390,10 @@
       @Override
       public void endVisit(JMethodCall x, Context ctx) {
         JMethod target = x.getTarget();
-        if (x.isStaticDispatchOnly() && target.isJsPropertyAccessor()) {
-          logError("Cannot call property accessor '%s' via super (%s:%d).",
-              target.getQualifiedName(),
-              x.getSourceInfo().getFileName(),
-              x.getSourceInfo().getStartLine());
+        if (x.isStaticDispatchOnly() &&
+            target.getJsPropertyAccessorType() != JsPropertyAccessorType.NONE) {
+          logError(x, "Cannot call property accessor %s via super.",
+              getMemberDescription(target));
         }
       }
     }.accept(jprogram);
@@ -428,10 +405,8 @@
       public boolean visit(JInstanceOf x, Context ctx) {
         JReferenceType type = x.getTestType();
         if (type.isJsNative() && type instanceof JInterfaceType) {
-          logError("Cannot do instanceof against native JsType interface %s (%s:%d).",
-              type.getName(),
-              x.getSourceInfo().getFileName(),
-              x.getSourceInfo().getStartLine());
+          logError(x, "Cannot do instanceof against native JsType interface '%s'.",
+              JjsUtils.getReadableDescription(type));
         }
         return true;
       }
@@ -450,8 +425,8 @@
 
     for (JConstructor constructor : type.getConstructors()) {
       if (!isConstructorEmpty(constructor)) {
-        logError("Native JsType constructor '%s' cannot have non-empty method body.",
-            constructor.getQualifiedName());
+        logError(constructor, "Native JsType constructor %s cannot have non-empty method body.",
+            getMemberDescription(constructor));
       }
     }
   }
@@ -468,16 +443,12 @@
     if (type.isJsType()) {
       logError("'%s' cannot be both a JsFunction and a JsType at the same time.", type);
     }
-
-    Set<String> subTypes = jprogram.typeOracle.getSubInterfaceNames(type.getName());
-    if (!subTypes.isEmpty()) {
-      logError("JsFunction '%s' cannot be extended by other interfaces:%s", type, subTypes);
-    }
   }
 
   private void checkJsFunctionImplementation(JDeclaredType type) {
     if (type.getImplements().size() != 1) {
-      logError("JsFunction implementation '%s' cannot implement more than one interface.", type);
+      logError("JsFunction implementation '%s' cannot implement more than one interface.",
+          type);
     }
 
     if (type.isJsType()) {
@@ -488,29 +459,34 @@
     if (type.getSuperClass() != jprogram.getTypeJavaLangObject()) {
       logError("JsFunction implementation '%s' cannot extend a class.", type);
     }
+  }
 
-    Set<String> subTypes = jprogram.typeOracle.getSubClassNames(type.getName());
-    if (!subTypes.isEmpty()) {
-      logError("Implementation of JsFunction '%s' cannot be extended by other classes:%s", type,
-          subTypes);
+  private void checkJsFunctionSubtype(JDeclaredType type) {
+    JClassType superClass = type.getSuperClass();
+    if (superClass != null && superClass.isJsFunctionImplementation()) {
+      logError(type, "'%s' cannot extend JsFunction implementation '%s'.",
+          JjsUtils.getReadableDescription(type), JjsUtils.getReadableDescription(superClass));
+    }
+    for (JInterfaceType superInterface : type.getImplements()) {
+      if (superInterface.isJsFunction()) {
+        logError(type, "'%s' cannot extend JsFunction '%s'.",
+            JjsUtils.getReadableDescription(type), JjsUtils.getReadableDescription(superInterface));
+      }
     }
   }
 
-  private void checkProgram() {
+  private boolean checkProgram(TreeLogger logger) {
     for (JDeclaredType type : jprogram.getModuleDeclaredTypes()) {
       checkType(type);
     }
     checkStaticJsPropertyCalls();
     checkInstanceOfNativeJsTypes();
+
+    boolean hasErrors = reportErrorsAndWarnings(logger);
+    return !hasErrors;
   }
 
   private void checkType(JDeclaredType type) {
-    currentProcessedMethods = Sets.newHashSet();
-    currentLocalNameByMemberNames = Maps.newHashMap();
-    currentJsMethodNameByGetterNames = Maps.newHashMap();
-    currentJsMethodNameBySetterNames = Maps.newHashMap();
-    currentJsPropertyTypeByName = Maps.newHashMap();
-    currentType = type;
     minimalRebuildCache.removeExportedNames(type.getName());
 
     if (type.isJsNative()) {
@@ -522,16 +498,17 @@
     } else if (type.isJsFunctionImplementation()) {
       checkJsFunctionImplementation(type);
     } else {
+      checkJsFunctionSubtype(type);
       checkJsConstructors(type);
     }
 
-    for (;type != null; type = type.getSuperClass())  {
-      for (JField field : type.getFields()) {
-        checkField(field);
-      }
-      for (JMethod method : type.getMethods()) {
-        checkMethod(method);
-      }
+    Map<String, JsMember> localNames = collectNames(type.getSuperClass());
+
+    for (JField field : type.getFields()) {
+      checkField(localNames, field);
+    }
+    for (JMethod method : type.getMethods()) {
+      checkMethod(localNames, method);
     }
   }
 
@@ -540,22 +517,22 @@
         || isUnusableByJsSuppressed(method)) {
       return;
     }
-    String methodName = method.getQualifiedName();
     // check parameters.
     for (JParameter parameter : method.getParams()) {
       if (!parameter.getType().canBeReferencedExternally()
           && !isUnusableByJsSuppressed(parameter)) {
         logWarning(
-            "[unusable-by-js] Type of parameter '%s' in method '%s' is not usable by but exposed to"
-            + " JavaScript",
-            parameter.getName(), methodName);
+            parameter,
+            "[unusable-by-js] Type of parameter '%s' in method %s is not usable by but exposed to"
+            + " JavaScript.",
+            parameter.getName(), getMemberDescription(method));
       }
     }
     // check return type.
     if (!method.getType().canBeReferencedExternally()) {
       logWarning(
-          "[unusable-by-js] Return type of '%s' is not usable by but exposed to JavaScript",
-          methodName);
+          method, "[unusable-by-js] Return type of %s is not usable by but exposed to JavaScript.",
+          getMemberDescription(method));
     }
   }
 
@@ -566,35 +543,183 @@
     }
     if (!field.getType().canBeReferencedExternally()) {
       logWarning(
-          "[unusable-by-js] Type of field '%s' in type '%s' is not usable by but exposed to "
-          + "JavaScript",
-          field.getName(), field.getEnclosingType().getName());
+          field, "[unusable-by-js] Type of field '%s' in type '%s' is not usable by but exposed to "
+              + "JavaScript.",
+          field.getName(), JjsUtils.getReadableDescription(field.getEnclosingType()));
     }
   }
 
+  private static class JsMember {
+    private JMember member;
+    private JMethod setter;
+    private JMethod getter;
+
+    public JsMember(JMember member) {
+      this.member = member;
+    }
+
+    public JsMember(JMethod member, JMethod setter, JMethod getter) {
+      this.member = member;
+      this.setter = setter;
+      this.getter = getter;
+    }
+
+    public boolean isNativeMethod() {
+      return member instanceof JMethod && member.isJsNative() && !isPropertyAccessor();
+    }
+
+    public boolean isPropertyAccessor() {
+      return setter != null || getter != null;
+    }
+  }
+
+  private LinkedHashMap<String, JsMember> collectNames(JDeclaredType type) {
+    if (type == null) {
+      return Maps.newLinkedHashMap();
+    }
+
+    LinkedHashMap<String, JsMember> memberByLocalMemberNames = collectNames(type.getSuperClass());
+
+    for (JField field : type.getFields()) {
+      if (!field.isJsProperty() || !field.needsDynamicDispatch()) {
+        continue;
+      }
+      updateJsMembers(memberByLocalMemberNames, field);
+    }
+    for (JMethod method : type.getMethods()) {
+      if (!method.isOrOverridesJsMethod() || !method.needsDynamicDispatch()
+          || isSyntheticBridgeMethod(method)) {
+        continue;
+      }
+      updateJsMembers(memberByLocalMemberNames, method);
+    }
+    return memberByLocalMemberNames;
+  }
+
+  private boolean isSyntheticBridgeMethod(JMethod method) {
+    // A name slot taken up by a synthetic method, such as a bridge method for a generic method,
+    // is not the fault of the user and so should not be reported as an error. JS generation
+    // should take responsibility for ensuring that only the correct method version (in this
+    // particular set of colliding method names) is exported. Forwarding synthetic methods
+    // (such as an accidental override forwarding method that occurs when a JsType interface
+    // starts exposing a method in class B that is only ever implemented in its parent class A)
+    // though should be checked since they are exported and do take up an name slot.
+    return method.isSynthetic() && !method.isForwarding();
+  }
+
+  private void updateJsMembers(Map<String, JsMember> memberByLocalMemberNames, JMember member) {
+    JsMember oldJsMember = memberByLocalMemberNames.get(member.getJsName());
+    JsMember updatedJsMember = createOrUpdateJsMember(oldJsMember, member);
+    memberByLocalMemberNames.put(member.getJsName(), updatedJsMember);
+  }
+
+  private JsMember createOrUpdateJsMember(JsMember jsMember, JMember member) {
+    if (member instanceof JField) {
+      return new JsMember(member);
+    }
+
+    JMethod method = (JMethod) member;
+    switch (method.getJsPropertyAccessorType()) {
+      case GETTER:
+        if (jsMember != null && jsMember.isPropertyAccessor()) {
+          if (jsMember.getter == null || overrides(method, jsMember.getter)) {
+            jsMember.getter = method;
+            jsMember.member = method;
+            return jsMember;
+          }
+        }
+        return new JsMember(method, jsMember == null ? null : jsMember.setter, method);
+      case SETTER:
+        if (jsMember != null && jsMember.isPropertyAccessor()) {
+          if (jsMember.setter == null || overrides(method, jsMember.setter)) {
+            jsMember.setter = method;
+            jsMember.member = method;
+            return jsMember;
+          }
+        }
+        return new JsMember(method, method, jsMember == null ? null : jsMember.getter);
+      default:
+        if (jsMember != null) {
+          if (overrides(method, jsMember.member)) {
+            jsMember.member = method;
+            return jsMember;
+          }
+        }
+        return new JsMember(method);
+    }
+  }
+
+  private boolean overrides(JMethod method, JMember potentiallyOverriddenMember) {
+    if (potentiallyOverriddenMember instanceof JField) {
+      return false;
+    }
+    if (method.getOverriddenMethods().contains(potentiallyOverriddenMember)) {
+      return true;
+    }
+
+    // Consider methods that have the same name and parameter signature to be overrides.
+    // GWT models overrides similar to the JVM (not Java) in the sense that for a method to override
+    // another they must have identical signatures (includes parameters and return type).
+    // Methods that only differ in return types are Java overrides and need to be considered so
+    // for local name collision checking.
+    JMethod potentiallyOverriddenMethod = (JMethod) potentiallyOverriddenMember;
+    return method.getJsniSignature(false, false)
+        .equals(potentiallyOverriddenMethod.getJsniSignature(false, false));
+  }
+
+  private static String getMemberDescription(JMember member) {
+    if (member instanceof JField) {
+      return String.format("'%s'", JjsUtils.getReadableDescription(member));
+    }
+    JMethod method = (JMethod) member;
+    if ((method.isSyntheticAccidentalOverride() || method.isSynthetic())
+        // Some synthetic methods are created by JDT, it is not save to assume
+        // that they will always be overriding and crash the compiler.
+        && !method.getOverriddenMethods().isEmpty()) {
+      JMethod overridenMethod = method.getOverriddenMethods().iterator().next();
+      return String.format("'%s' (exposed by '%s')",
+          JjsUtils.getReadableDescription(overridenMethod),
+          JjsUtils.getReadableDescription(method.getEnclosingType()));
+    }
+    return String.format("'%s'", JjsUtils.getReadableDescription(method));
+  }
+
   private boolean isUnusableByJsSuppressed(CanHaveSuppressedWarnings x) {
     return x.getSuppressedWarnings() != null &&
         x.getSuppressedWarnings().contains(JsInteropUtil.UNUSABLE_BY_JS);
   }
 
   private void logError(String format, JType type) {
-    logError(format, type.getName());
+    logError(type, format, JjsUtils.getReadableDescription(type));
   }
 
-  private void logError(String format, JType type, Set<String> subTypes) {
-    StringBuilder subTypeNames = new StringBuilder();
-    for (String typeName : subTypes) {
-      subTypeNames.append("\n\t").append(typeName);
+  private void logError(HasSourceInfo hasSourceInfo, String format, Object... args) {
+    errorsByFilename.put(hasSourceInfo.getSourceInfo().getFileName(),
+        String.format("Line %d: ", hasSourceInfo.getSourceInfo().getStartLine())
+            + String.format(format, args));
+  }
+
+  private void logWarning(HasSourceInfo hasSourceInfo, String format, Object... args) {
+    warningsByFilename.put(hasSourceInfo.getSourceInfo().getFileName(),
+        String.format("Line %d: ", hasSourceInfo.getSourceInfo().getStartLine())
+            + String.format(format, args));
+  }
+
+  private boolean reportErrorsAndWarnings(TreeLogger logger) {
+    TreeSet<String> filenamesToReport = Sets.newTreeSet(
+        Iterables.concat(errorsByFilename.keySet(), warningsByFilename.keySet()));
+    for (String fileName : filenamesToReport) {
+      boolean hasErrors = !errorsByFilename.get(fileName).isEmpty();
+      TreeLogger branch = logger.branch(
+          hasErrors ? Type.ERROR : Type.WARN,
+          (hasErrors ? "Errors" : "Warnings") + " in " + fileName);
+      for (String message : errorsByFilename.get(fileName)) {
+        branch.log(Type.ERROR, message);
+      }
+      for (String message :warningsByFilename.get(fileName)) {
+        branch.log(Type.WARN, message);
+      }
     }
-    logError(format, type.getName(), subTypeNames);
-  }
-
-  private void logError(String format, Object... args) {
-    logger.log(TreeLogger.ERROR, String.format(format, args));
-    hasErrors = true;
-  }
-
-  private void logWarning(String format, Object... args) {
-    logger.log(TreeLogger.WARN, String.format(format, args));
+    return !errorsByFilename.isEmpty();
   }
 }
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java
index a345792..259daaa 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java
@@ -16,11 +16,14 @@
 package com.google.gwt.dev.jjs.impl;
 
 import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.MinimalRebuildCache;
 import com.google.gwt.dev.javac.testing.impl.MockJavaResource;
 import com.google.gwt.dev.jjs.ast.JMethod;
 import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
+
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * Tests for the JsInteropRestrictionChecker.
@@ -47,8 +50,10 @@
         "}");
 
     assertBuggyFails(
-        "'test.EntryPoint$Buggy.doIt(Ltest/EntryPoint$Bar;)V' can't be exported in type "
-        + "'test.EntryPoint$Buggy' because the name 'doIt' is already taken.");
+        "Line 14: 'void EntryPoint.ParentBuggy.doIt(EntryPoint.Bar)' "
+            + "(exposed by 'EntryPoint.Buggy') and "
+            + "'void EntryPoint.ParentBuggy.doIt(EntryPoint.Foo)' (exposed by 'EntryPoint.Buggy') "
+            + "cannot both use the same JavaScript name 'doIt'.");
   }
 
   public void testCollidingAccidentalOverrideAbstractMethodFails() throws Exception {
@@ -69,8 +74,9 @@
         "public static class Buggy {}  // Unrelated class");
 
     assertBuggyFails(
-        "'test.EntryPoint$Baz.doIt(Ltest/EntryPoint$Bar;)V' can't be exported in type "
-            + "'test.EntryPoint$Baz' because the name 'doIt' is already taken.");
+        "Line 14: 'void EntryPoint.Baz.doIt(EntryPoint.Bar)' and "
+            + "'void EntryPoint.Baz.doIt(EntryPoint.Foo)' cannot both use the same "
+            + "JavaScript name 'doIt'.");
   }
 
   public void testCollidingAccidentalOverrideHalfAndHalfFails() throws Exception {
@@ -93,14 +99,15 @@
         "public static class Buggy extends Parent implements Bar {}");
 
     assertBuggyFails(
-        "'test.EntryPoint$Parent.doIt(Ltest/EntryPoint$Foo;)V' can't be exported in type "
-        + "'test.EntryPoint$Buggy' because the name 'doIt' is already taken.");
+        "Line 12: 'void EntryPoint.ParentParent.doIt(EntryPoint.Bar)' "
+            + "(exposed by 'EntryPoint.Buggy') and 'void EntryPoint.Parent.doIt(EntryPoint.Foo)' "
+            + "cannot both use the same JavaScript name 'doIt'.");
   }
 
   public void testCollidingFieldExportsFails() throws Exception {
     addSnippetImport("jsinterop.annotations.JsProperty");
     addSnippetClassDecl(
-        "public static class Buggy {",
+       "public static class Buggy {",
         "  @JsProperty",
         "  public static final int show = 0;",
         "  @JsProperty(name = \"show\")",
@@ -108,8 +115,8 @@
         "}");
 
     assertBuggyFails(
-        "'test.EntryPoint$Buggy.display' can't be exported because the "
-            + "global name 'test.EntryPoint.Buggy.show' is already taken.");
+        "Line 8: 'int EntryPoint.Buggy.display' cannot be exported because the global "
+            + "name 'test.EntryPoint.Buggy.show' is already taken.");
   }
 
   public void testJsPropertyGetterStyleSucceeds() throws Exception {
@@ -142,18 +149,20 @@
         "}");
 
     assertBuggyFails(
-        "There can't be non-boolean return for the JsProperty 'is' getter"
-            + " 'test.EntryPoint$Buggy.isX()I'.",
-        "There can't be void return type or any parameters for the JsProperty getter"
-            + " 'test.EntryPoint$Buggy.getY(I)I'.",
-        "There can't be void return type or any parameters for the JsProperty getter"
-            + " 'test.EntryPoint$Buggy.getZ()V'.",
-        "There needs to be single parameter and void return type for the JsProperty setter"
-            + " 'test.EntryPoint$Buggy.setX(II)V'.",
-        "There needs to be single parameter and void return type for the JsProperty setter"
-            + " 'test.EntryPoint$Buggy.setY()V'.",
-        "There needs to be single parameter and void return type for the JsProperty setter"
-            + " 'test.EntryPoint$Buggy.setZ(I)I'.");
+        "Line 10: There needs to be single parameter and void return type for the JsProperty "
+            + "setter 'void EntryPoint.Buggy.setX(int, int)'.",
+        "Line 8: There cannot be void return type or any parameters for the JsProperty getter "
+            + "'int EntryPoint.Buggy.getY(int)'.",
+        "Line 9: There cannot be void return type or any parameters for the JsProperty getter "
+            + "'void EntryPoint.Buggy.getZ()'.",
+        "Line 7: There cannot be non-boolean return for the JsProperty 'is' "
+            + "getter 'int EntryPoint.Buggy.isX()'.",
+        "Line 11: There needs to be single parameter and void return type for the JsProperty "
+            + "setter 'void EntryPoint.Buggy.setY()'.",
+        "Line 12: There needs to be single parameter and void return type for the JsProperty "
+            + "setter 'int EntryPoint.Buggy.setZ(int)'.",
+        "Line 12: The setter and getter for JsProperty 'z' in type 'EntryPoint.Buggy' must "
+            + "have consistent types.");
   }
 
   public void testJsPropertyNonGetterStyleFails() throws Exception {
@@ -168,9 +177,12 @@
         "}");
 
     assertBuggyFails(
-        "JsProperty 'test.EntryPoint$Buggy.hasX()Z' doesn't follow Java Bean naming conventions.",
-        "JsProperty 'test.EntryPoint$Buggy.x()I' doesn't follow Java Bean naming conventions.",
-        "JsProperty 'test.EntryPoint$Buggy.x(I)V' doesn't follow Java Bean naming conventions.");
+        "Line 7: JsProperty 'boolean EntryPoint.Buggy.hasX()' doesn't follow Java Bean "
+            + "naming conventions.",
+        "Line 8: JsProperty 'int EntryPoint.Buggy.x()' doesn't follow Java Bean naming"
+            + " conventions.",
+        "Line 9: JsProperty 'void EntryPoint.Buggy.x(int)' doesn't follow Java Bean naming "
+            + "conventions.");
   }
 
   public void testCollidingJsPropertiesTwoGettersFails() throws Exception {
@@ -178,20 +190,16 @@
     addSnippetImport("jsinterop.annotations.JsProperty");
     addSnippetClassDecl(
         "@JsType",
-        "public static interface IBuggy {",
+        "public static interface Buggy {",
         "  @JsProperty",
         "  boolean isX();",
         "  @JsProperty",
         "  boolean getX();",
-        "}",
-        "public static class Buggy implements IBuggy {",
-        "  public boolean isX() {return false;}",
-        "  public boolean getX() {return false;}",
         "}");
 
     assertBuggyFails(
-        "There can't be more than one getter for JsProperty 'x' in type 'test.EntryPoint$IBuggy'.",
-        "There can't be more than one getter for JsProperty 'x' in type 'test.EntryPoint$Buggy'.");
+        "Line 10: 'boolean EntryPoint.Buggy.getX()' and 'boolean EntryPoint.Buggy.isX()' "
+            + "cannot both use the same JavaScript name 'x'.");
   }
 
   public void testCollidingJsPropertiesTwoSettersFails() throws Exception {
@@ -199,23 +207,18 @@
     addSnippetImport("jsinterop.annotations.JsProperty");
     addSnippetClassDecl(
         "@JsType",
-        "public static interface IBuggy {",
+        "public static interface Buggy {",
         "  @JsProperty",
         "  void setX(boolean x);",
         "  @JsProperty",
         "  void setX(int x);",
-        "}",
-        "public static class Buggy implements IBuggy {",
-        "  public void setX(boolean x) {}",
-        "  public void setX(int x) {}",
         "}");
 
     assertBuggyFails(
-        "There can't be more than one setter for JsProperty 'x' in type 'test.EntryPoint$IBuggy'.",
-        "There can't be more than one setter for JsProperty 'x' in type 'test.EntryPoint$Buggy'.");
+        "Line 10: 'void EntryPoint.Buggy.setX(int)' and 'void EntryPoint.Buggy.setX(boolean)' "
+            + "cannot both use the same JavaScript name 'x'.");
   }
 
-  // TODO: duplicate this check with two @JsType interfaces.
   public void testCollidingJsTypeAndJsPropertyGetterFails() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetImport("jsinterop.annotations.JsProperty");
@@ -232,10 +235,10 @@
         "}");
 
     assertBuggyFails(
-        "'test.EntryPoint$IBuggy.x(Z)Z' and 'test.EntryPoint$IBuggy.getX()I' "
-        + "can't both be named 'x' in type 'test.EntryPoint$IBuggy'.",
-        "'test.EntryPoint$Buggy.x(Z)Z' and 'test.EntryPoint$Buggy.getX()I' "
-        + "can't both be named 'x' in type 'test.EntryPoint$Buggy'.");
+        "Line 9: 'int EntryPoint.IBuggy.getX()' and 'boolean EntryPoint.IBuggy.x(boolean)' "
+            + "cannot both use the same JavaScript name 'x'.",
+        "Line 13: 'int EntryPoint.Buggy.getX()' and 'boolean EntryPoint.Buggy.x(boolean)' "
+            + "cannot both use the same JavaScript name 'x'.");
   }
 
   public void testCollidingJsTypeAndJsPropertySetterFails() throws Exception {
@@ -254,10 +257,10 @@
         "}");
 
     assertBuggyFails(
-        "'test.EntryPoint$IBuggy.x(Z)Z' and 'test.EntryPoint$IBuggy.setX(I)V' "
-        + "can't both be named 'x' in type 'test.EntryPoint$IBuggy'.",
-        "'test.EntryPoint$Buggy.x(Z)Z' and 'test.EntryPoint$Buggy.setX(I)V' "
-        + "can't both be named 'x' in type 'test.EntryPoint$Buggy'.");
+        "Line 9: 'void EntryPoint.IBuggy.setX(int)' and 'boolean EntryPoint.IBuggy.x(boolean)' "
+            + "cannot both use the same JavaScript name 'x'.",
+        "Line 13: 'void EntryPoint.Buggy.setX(int)' and 'boolean EntryPoint.Buggy.x(boolean)' "
+            + "cannot both use the same JavaScript name 'x'.");
   }
 
   public void testCollidingMethodExportsFails() throws Exception {
@@ -271,8 +274,8 @@
         "}");
 
     assertBuggyFails(
-        "'test.EntryPoint$Buggy.display()V' can't be exported "
-            + "because the global name 'test.EntryPoint.Buggy.show' is already taken.");
+        "Line 8: 'void EntryPoint.Buggy.display()' cannot be exported because the global name "
+            + "'test.EntryPoint.Buggy.show' is already taken.");
   }
 
   public void testCollidingMethodToFieldExportsFails() throws Exception {
@@ -287,8 +290,8 @@
         "}");
 
     assertBuggyFails(
-        "'test.EntryPoint$Buggy.show()V' can't be exported because the "
-            + "global name 'test.EntryPoint.Buggy.show' is already taken.");
+        "Line 7: 'void EntryPoint.Buggy.show()' cannot be exported because the global name "
+            + "'test.EntryPoint.Buggy.show' is already taken.");
   }
 
   public void testCollidingMethodToFieldJsTypeFails() throws Exception {
@@ -301,8 +304,8 @@
         "}");
 
     assertBuggyFails(
-        "'test.EntryPoint$Buggy.show()V' can't be exported in type "
-            + "'test.EntryPoint$Buggy' because the name 'show' is already taken.");
+        "Line 6: 'void EntryPoint.Buggy.show()' and 'int EntryPoint.Buggy.show' cannot both use "
+            + "the same JavaScript name 'show'.");
   }
 
   public void testCollidingMethodToMethodJsTypeFails() throws Exception {
@@ -315,8 +318,8 @@
         "}");
 
     assertBuggyFails(
-        "'test.EntryPoint$Buggy.show()V' can't be exported in type "
-            + "'test.EntryPoint$Buggy' because the name 'show' is already taken.");
+        "Line 7: 'void EntryPoint.Buggy.show()' and 'void EntryPoint.Buggy.show(int)' cannot both "
+            + "use the same JavaScript name 'show'.");
   }
 
   public void testCollidingSubclassExportedFieldToFieldJsTypeSucceeds() throws Exception {
@@ -402,8 +405,8 @@
         "}");
 
     assertBuggyFails(
-        "'test.EntryPoint$ParentBuggy.foo' can't be exported in type "
-            + "'test.EntryPoint$Buggy' because the name 'foo' is already taken.");
+        "Line 10: 'int EntryPoint.Buggy.foo' and 'int EntryPoint.ParentBuggy.foo' cannot both use "
+            + "the same JavaScript name 'foo'.");
   }
 
   public void testCollidingSubclassFieldToMethodJsTypeFails() throws Exception {
@@ -419,8 +422,8 @@
         "}");
 
     assertBuggyFails(
-        "'test.EntryPoint$ParentBuggy.foo' can't be exported in type "
-            + "'test.EntryPoint$Buggy' because the name 'foo' is already taken.");
+        "Line 10: 'void EntryPoint.Buggy.foo(int)' and 'int EntryPoint.ParentBuggy.foo' cannot "
+            + "both use the same JavaScript name 'foo'.");
   }
 
   public void testCollidingSubclassMethodToExportedMethodJsTypeSucceeds() throws Exception {
@@ -456,8 +459,8 @@
         "}");
 
     assertBuggyFails(
-        "'test.EntryPoint$Buggy.show()V' can't be exported in type "
-            + "'test.EntryPoint$Buggy2' because the name 'show' is already taken.");
+        "Line 16: 'void EntryPoint.Buggy2.show(boolean)' and 'void EntryPoint.Buggy.show()' cannot "
+            + "both use the same JavaScript name 'show'.");
   }
 
   public void testCollidingSubclassMethodToMethodJsTypeFails() throws Exception {
@@ -473,8 +476,8 @@
         "}");
 
     assertBuggyFails(
-        "'test.EntryPoint$ParentBuggy.foo()V' can't be exported in type "
-            + "'test.EntryPoint$Buggy' because the name 'foo' is already taken.");
+        "Line 10: 'void EntryPoint.Buggy.foo(int)' and 'void EntryPoint.ParentBuggy.foo()' "
+            + "cannot both use the same JavaScript name 'foo'.");
   }
 
   public void testCollidingSubclassMethodToMethodTwoLayerInterfaceJsTypeFails() throws Exception {
@@ -500,11 +503,11 @@
         "}");
 
     assertBuggyFails(
-        "'test.EntryPoint$Buggy.show()V' can't be exported in type "
-            + "'test.EntryPoint$Buggy2' because the name 'show' is already taken.");
+        "Line 20: 'void EntryPoint.Buggy2.show(boolean)' and 'void EntryPoint.Buggy.show()' "
+            + "cannot both use the same JavaScript name 'show'.");
   }
 
-  public void testCollidingSyntheticBridgeMethodSucceeds() throws Exception {
+  public void testNonCollidingSyntheticBridgeMethodSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetImport("jsinterop.annotations.JsProperty");
     addSnippetClassDecl(
@@ -520,6 +523,52 @@
     assertBuggySucceeds();
   }
 
+  public void testCollidingSyntheticBridgeMethodSucceeds() throws Exception {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetClassDecl(
+        "@JsType",
+        "public static interface Comparable<T> {",
+        "  int compareTo(T other);",
+        "}",
+        "@JsType",
+        "public static class Enum<E extends Enum<E>> implements Comparable<E> {",
+        "  public int compareTo(E other) {return 0;}",
+        "}",
+        "public static class Buggy {}");
+
+    assertBuggySucceeds();
+  }
+
+  public void testSpecializeReturnTypeInImplementorSucceeds() throws Exception {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetClassDecl(
+        "@JsType",
+        "interface I {",
+        "  I m();",
+        "}",
+        "@JsType",
+        "public static class Buggy implements I {",
+        "  public Buggy m() { return null; } ",
+        "}");
+
+    assertBuggySucceeds();
+  }
+
+  public void testSpecializeReturnTypeInSubclassSucceeds() throws Exception {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetClassDecl(
+        "@JsType",
+        "public static class S {",
+        "  public S m() { return null; }",
+        "}",
+        "@JsType",
+        "public static class Buggy extends S {",
+        "  public Buggy m() { return null; } ",
+        "}");
+
+    assertBuggySucceeds();
+  }
+
   public void testCollidingTwoLayerSubclassFieldToFieldJsTypeFails() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
@@ -536,8 +585,93 @@
         "}");
 
     assertBuggyFails(
-        "'test.EntryPoint$ParentParentBuggy.foo' can't be exported in type "
-            + "'test.EntryPoint$Buggy' because the name 'foo' is already taken.");
+        "Line 13: 'int EntryPoint.Buggy.foo' and 'int EntryPoint.ParentParentBuggy.foo' cannot "
+            + "both use the same JavaScript name 'foo'.");
+  }
+
+  public void testRenamedSuperclassJsMethodFails() {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetImport("jsinterop.annotations.JsMethod");
+    addSnippetClassDecl(
+        "@JsType",
+        "public static class ParentBuggy {",
+        "  public void foo() {}",
+        "}",
+        "public static class Buggy extends ParentBuggy {",
+        "  @JsMethod(name = \"bar\") public void foo() {}",
+        "}");
+
+    assertBuggyFails("Line 10: 'void EntryPoint.Buggy.foo()' cannot be assigned a different "
+        + "JavaScript name than the method it overrides.");
+  }
+
+  public void testRenamedSuperInterfaceJsMethodFails() {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetImport("jsinterop.annotations.JsMethod");
+    addSnippetClassDecl(
+        "@JsType",
+        "public interface ParentBuggy {",
+        "  void foo();;",
+        "}",
+        "public interface Buggy extends ParentBuggy {",
+        "  @JsMethod(name = \"bar\") void foo();",
+        "}");
+
+    assertBuggyFails("Line 10: 'void EntryPoint.Buggy.foo()' cannot be assigned a different "
+        + "JavaScript name than the method it overrides.");
+  }
+
+  public void testAccidentallyRenamedSuperInterfaceJsMethodFails() {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetImport("jsinterop.annotations.JsMethod");
+    addSnippetClassDecl(
+        "@JsType",
+        "public interface IBuggy {",
+        "  void foo();",
+        "}",
+        "@JsType",
+        "public static class ParentBuggy {",
+        "  @JsMethod(name = \"bar\") public void foo() {}",
+        "}",
+        "public static class Buggy extends ParentBuggy implements IBuggy {",
+        "}");
+
+    assertBuggyFails("Line 11: 'void EntryPoint.ParentBuggy.foo()' (exposed by 'EntryPoint.Buggy') "
+        + "cannot be assigned a different JavaScript name than the method it overrides.");
+  }
+
+  // TODO(goktug): enable once the property names are handled.
+  public void __disabled__testRenamedSuperclassJsPropertyFails() {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetImport("jsinterop.annotations.JsProperty");
+    addSnippetClassDecl(
+        "@JsType",
+        "public static class ParentBuggy {",
+        "  @JsProperty public int getFoo() { return 0; }",
+        "}",
+        "public static class Buggy extends ParentBuggy {",
+        "  @JsProperty(name = \"bar\") public int getFoo() { return 0;}",
+        "}");
+
+    assertBuggyFails("'EntryPoint.Buggy.getFoo()I' cannot be exported because the method "
+        + "overrides a method with different name.");
+  }
+
+  public void testJsPropertyDifferentFlavourInSubclassFails() {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetImport("jsinterop.annotations.JsProperty");
+    addSnippetClassDecl(
+        "@JsType",
+        "public static class ParentBuggy {",
+        "  @JsProperty public boolean isFoo() { return false; }",
+        "}",
+        "public static class Buggy extends ParentBuggy {",
+        "  @JsProperty public boolean getFoo() { return false;}",
+        "}");
+
+    assertBuggyFails(
+        "Line 10: 'boolean EntryPoint.Buggy.getFoo()' and 'boolean EntryPoint.ParentBuggy.isFoo()' "
+            + "cannot both use the same JavaScript name 'foo'.");
   }
 
   public void testConsistentPropertyTypeSucceeds() throws Exception {
@@ -576,9 +710,9 @@
         "}");
 
     assertBuggyFails(
-        "The setter and getter for JsProperty 'foo' in type 'test.EntryPoint$IBuggy' "
+        "Line 10: The setter and getter for JsProperty 'foo' in type 'EntryPoint.IBuggy' "
             + "must have consistent types.",
-        "The setter and getter for JsProperty 'foo' in type 'test.EntryPoint$Buggy' "
+        "Line 14: The setter and getter for JsProperty 'foo' in type 'EntryPoint.Buggy' "
             + "must have consistent types.");
   }
 
@@ -599,14 +733,13 @@
         "}");
 
     assertBuggyFails(
-        "The setter and getter for JsProperty 'foo' in type 'test.EntryPoint$IBuggy' "
+        "Line 10: The setter and getter for JsProperty 'foo' in type 'EntryPoint.IBuggy' "
             + "must have consistent types.",
-        "The setter and getter for JsProperty 'foo' in type 'test.EntryPoint$Buggy' "
+        "Line 14: The setter and getter for JsProperty 'foo' in type 'EntryPoint.Buggy' "
             + "must have consistent types.");
   }
 
-  public void testJsPropertySuperCallFails()
-      throws UnableToCompleteException {
+  public void testJsPropertySuperCallFails() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetImport("jsinterop.annotations.JsProperty");
     addSnippetClassDecl(
@@ -618,12 +751,10 @@
         "}");
 
     assertBuggyFails(
-        "Cannot call property accessor 'test.EntryPoint$Super.getX()I' via super "
-            + "(test/EntryPoint.java:9).");
+        "Line 9: Cannot call property accessor 'int EntryPoint.Super.getX()' via super.");
   }
 
-  public void testJsPropertyCallSucceeds()
-      throws UnableToCompleteException {
+  public void testJsPropertyCallSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetImport("jsinterop.annotations.JsProperty");
     addSnippetClassDecl(
@@ -638,7 +769,7 @@
   }
 
   public void testJsPropertyAccidentalSuperCallSucceeds()
-      throws UnableToCompleteException {
+      throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetImport("jsinterop.annotations.JsProperty");
     addSnippetClassDecl(
@@ -655,6 +786,23 @@
     assertBuggySucceeds();
   }
 
+  public void testJsPropertyOverrideSucceeds()
+      throws Exception {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetImport("jsinterop.annotations.JsProperty");
+    addSnippetClassDecl(
+        "@JsType public static class Super {",
+        "  @JsProperty public void setX(int x) {  }",
+        "  @JsProperty public int getX() { return 5; }",
+        "}",
+
+        "@JsType public static class Buggy extends Super {",
+        "  @JsProperty public void setX(int x) {  }",
+        "}");
+
+    assertBuggySucceeds();
+  }
+
   public void testMultiplePrivateConstructorsExportSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
@@ -667,8 +815,7 @@
     assertBuggySucceeds();
   }
 
-  public void testMultiplePublicConstructorsAllDelegatesToJsConstructorSucceeds()
-      throws Exception {
+  public void testMultiplePublicConstructorsAllDelegatesToJsConstructorSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetImport("jsinterop.annotations.JsIgnore");
     addSnippetClassDecl(
@@ -697,8 +844,8 @@
         "}");
 
     assertBuggyFails(
-        "Constructor 'test.EntryPoint$Buggy.EntryPoint$Buggy() <init>' can be a JsConstructor only "
-            + "if all constructors in the class are delegating to it.");
+        "Line 6: Constructor 'EntryPoint.Buggy.EntryPoint$Buggy()' can be a JsConstructor only if "
+            + "all constructors in the class are delegating to it.");
   }
 
   public void testMultiplePublicConstructorsExportFails() throws Exception {
@@ -713,9 +860,9 @@
         "}");
 
     assertBuggyFails(
-        "More than one JsConstructor exists for test.EntryPoint$Buggy.",
-        "'test.EntryPoint$Buggy.EntryPoint$Buggy(I) <init>' can't be "
-            + "exported because the global name 'test.EntryPoint.Buggy' is already taken.");
+        "Line 5: More than one JsConstructor exists for EntryPoint.Buggy.",
+        "Line 7: 'EntryPoint.Buggy.EntryPoint$Buggy(int)' cannot be exported because the global "
+            + "name 'test.EntryPoint.Buggy' is already taken.");
   }
 
   public void testNonCollidingAccidentalOverrideSucceeds() throws Exception {
@@ -768,7 +915,7 @@
         "  void foo();",
         "}");
 
-    assertBuggyFails("JsFunction 'test.EntryPoint$Buggy' cannot extend other interfaces.");
+    assertBuggyFails("Line 6: JsFunction 'EntryPoint.Buggy' cannot extend other interfaces.");
   }
 
   public void testJsFunctionExtendedByInterfaceFails() throws Exception {
@@ -776,8 +923,8 @@
 
     addSnippetClassDecl("public interface Buggy extends MyJsFunctionInterface {}");
 
-    assertBuggyFails("JsFunction 'test.MyJsFunctionInterface' cannot be extended by other "
-        + "interfaces:\n\ttest.EntryPoint$Buggy");
+    assertBuggyFails(
+        "Line 3: 'EntryPoint.Buggy' cannot extend JsFunction 'MyJsFunctionInterface'.");
   }
 
   public void testJsFunctionMarkedAsJsTypeFails() throws Exception {
@@ -790,7 +937,7 @@
         "}");
 
     assertBuggyFails(
-        "'test.EntryPoint$Buggy' cannot be both a JsFunction and a JsType at the same time.");
+        "Line 6: 'EntryPoint.Buggy' cannot be both a JsFunction and a JsType at the same time.");
   }
 
   public void testJsFunctionImplementationWithSingleInterfaceSucceeds() throws Exception {
@@ -812,8 +959,8 @@
         "  public int bar(int x) { return 0; }",
         "}");
 
-    assertBuggyFails("JsFunction implementation 'test.EntryPoint$Buggy' cannot implement more than "
-        + "one interface.");
+    assertBuggyFails("Line 4: JsFunction implementation 'EntryPoint.Buggy' cannot "
+        + "implement more than one interface.");
   }
 
   public void testJsFunctionImplementationWithSuperClassFails() throws Exception {
@@ -824,7 +971,8 @@
         "  public int foo(int x) { return 0; }",
         "}");
 
-    assertBuggyFails("JsFunction implementation 'test.EntryPoint$Buggy' cannot extend a class.");
+    assertBuggyFails("Line 4: JsFunction implementation 'EntryPoint.Buggy' cannot "
+        + "extend a class.");
   }
 
   public void testJsFunctionImplementationWithSubclassesFails() throws Exception {
@@ -836,8 +984,8 @@
         "public static class Buggy extends BaseClass  {",
         "}");
 
-    assertBuggyFails("Implementation of JsFunction 'test.EntryPoint$BaseClass' cannot be extended "
-        + "by other classes:\n\ttest.EntryPoint$Buggy");
+    assertBuggyFails("Line 6: 'EntryPoint.Buggy' cannot extend "
+        + "JsFunction implementation 'EntryPoint.BaseClass'.");
   }
 
   public void testJsFunctionImplementationMarkedAsJsTypeFails() throws Exception {
@@ -850,8 +998,8 @@
         "}");
 
     assertBuggyFails(
-        "'test.EntryPoint$Buggy' cannot be both a JsFunction implementation and a JsType at the "
-            + "same time.");
+        "Line 5: 'EntryPoint.Buggy' cannot be both a JsFunction implementation and a JsType "
+            + "at the same time.");
   }
 
   public void testJsFunctionStaticInitializerFails() {
@@ -865,7 +1013,7 @@
         "}");
 
     assertBuggyFails(
-        "JsFunction 'test.EntryPoint$Buggy' cannot have static initializer.");
+        "Line 6: JsFunction 'EntryPoint.Buggy' cannot have static initializer.");
   }
 
   public void testNativeJsTypeStaticInitializerFails() {
@@ -877,7 +1025,7 @@
         "}");
 
     assertBuggyFails(
-        "Native JsType 'test.EntryPoint$Buggy' cannot have static initializer.");
+        "Line 4: Native JsType 'EntryPoint.Buggy' cannot have static initializer.");
   }
 
   public void testNativeJsTypeNonEmptyConstructorFails() {
@@ -890,8 +1038,8 @@
         "}");
 
     assertBuggyFails(
-        "Native JsType constructor 'test.EntryPoint$Buggy.EntryPoint$Buggy(I) <init>' "
-            + "cannot have non-empty method body.");
+        "Line 5: Native JsType constructor 'EntryPoint.Buggy.EntryPoint$Buggy(int)' cannot have "
+            + "non-empty method body.");
   }
 
   public void testNativeJsTypeInstanceInitializerFails() {
@@ -904,11 +1052,11 @@
         "}");
 
     assertBuggyFails(
-        "Native JsType constructor 'test.EntryPoint$Buggy.EntryPoint$Buggy(I) <init>' "
+        "Line 6: Native JsType constructor 'EntryPoint.Buggy.EntryPoint$Buggy(int)' "
             + "cannot have non-empty method body.");
   }
 
-  public void testNativeJsTypeImplicitSuperSucceeds() throws UnableToCompleteException {
+  public void testNativeJsTypeImplicitSuperSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) public static class Super {",
@@ -923,7 +1071,7 @@
     assertBuggySucceeds();
   }
 
-  public void testNativeJsTypeExplicitSuperSucceeds() throws UnableToCompleteException {
+  public void testNativeJsTypeExplicitSuperSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) public static class Super {",
@@ -939,7 +1087,7 @@
     assertBuggySucceeds();
   }
 
-  public void testNativeJsTypeExplicitSuperWithEffectSucceeds() throws UnableToCompleteException {
+  public void testNativeJsTypeExplicitSuperWithEffectSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) public static class Super {",
@@ -963,7 +1111,7 @@
         "}");
 
     assertBuggyFails(
-        "Native JsType 'test.EntryPoint$Buggy' cannot have static initializer.");
+        "Line 4: Native JsType 'EntryPoint.Buggy' cannot have static initializer.");
   }
 
   public void testNativeJsTypeInterfaceInlineInitializerFails() {
@@ -974,10 +1122,10 @@
         "}");
 
     assertBuggyFails(
-        "Native JsType 'test.EntryPoint$Buggy' cannot have static initializer.");
+        "Line 4: Native JsType 'EntryPoint.Buggy' cannot have static initializer.");
   }
 
-  public void testNativeJsTypeCompileTimeConstantSucceeds() throws UnableToCompleteException {
+  public void testNativeJsTypeCompileTimeConstantSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) public static class Buggy {",
@@ -995,8 +1143,8 @@
         "  public Buggy() { if (new Object() instanceof IBuggy) {} }",
         "}");
 
-    assertBuggyFails("Cannot do instanceof against native JsType interface test.EntryPoint$IBuggy "
-        + "(test/EntryPoint.java:6).");
+    assertBuggyFails("Line 6: Cannot do instanceof against native JsType interface "
+        + "'EntryPoint.IBuggy'.");
   }
 
   public void testNativeJsTypeEnumFails() {
@@ -1005,11 +1153,10 @@
         "@JsType(isNative=true) public enum Buggy { A, B }");
 
     assertBuggyFails(
-        "Enum 'test.EntryPoint$Buggy' cannot be a native JsType.");
+        "Line 4: Enum 'EntryPoint.Buggy' cannot be a native JsType.");
   }
 
-  public void testNativeJsTypeInterfaceCompileTimeConstantSucceeds()
-      throws UnableToCompleteException {
+  public void testNativeJsTypeInterfaceCompileTimeConstantSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) public interface Buggy {",
@@ -1019,7 +1166,7 @@
     assertBuggySucceeds();
   }
 
-  public void testNativeJsTypeExtendsNativeJsTypeSucceeds() throws UnableToCompleteException {
+  public void testNativeJsTypeExtendsNativeJsTypeSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) public static class Super {",
@@ -1030,7 +1177,7 @@
     assertBuggySucceeds();
   }
 
-  public void testNativeJsTypeImplementsNativeJsTypeSucceeds() throws UnableToCompleteException {
+  public void testNativeJsTypeImplementsNativeJsTypeSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) public interface Super {",
@@ -1041,8 +1188,7 @@
     assertBuggySucceeds();
   }
 
-  public void testNativeJsTypeInterfaceImplementsNativeJsTypeSucceeds()
-      throws UnableToCompleteException {
+  public void testNativeJsTypeInterfaceImplementsNativeJsTypeSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) public interface Super {",
@@ -1053,7 +1199,7 @@
     assertBuggySucceeds();
   }
 
-  public void testJsOverlayOnNativeJsTypeMemberSucceeds() throws UnableToCompleteException {
+  public void testJsOverlayOnNativeJsTypeMemberSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetImport("jsinterop.annotations.JsOverlay");
     addSnippetClassDecl(
@@ -1075,8 +1221,8 @@
         "  @JsOverlay public static final void m() { }",
         "}");
 
-    assertBuggyFails(
-        "JsOverlay method 'test.EntryPoint$Buggy.m()V' cannot be non-final, static, nor native.");
+    assertBuggyFails("Line 6: JsOverlay method 'void EntryPoint.Buggy.m()' cannot be "
+        + "non-final, static, nor native.");
   }
 
   public void testJsOverlayImplementingInterfaceMethodFails() {
@@ -1090,8 +1236,8 @@
         "  @JsOverlay public void m() { }",
         "}");
 
-    assertBuggyFails(
-        "JsOverlay method 'test.EntryPoint$Buggy.m()V' cannot override a supertype method.");
+    assertBuggyFails("Line 9: JsOverlay method 'void EntryPoint.Buggy.m()' cannot override a "
+        + "supertype method.");
   }
 
   public void testJsOverlayOverridingSuperclassMethodFails() {
@@ -1106,7 +1252,7 @@
         "}");
 
     assertBuggyFails(
-        "JsOverlay method 'test.EntryPoint$Buggy.m()V' cannot override a supertype method.");
+        "Line 9: JsOverlay method 'void EntryPoint.Buggy.m()' cannot override a supertype method.");
   }
 
   public void testJsOverlayOnNonFinalFails() {
@@ -1118,7 +1264,7 @@
         "}");
 
     assertBuggyFails(
-        "JsOverlay method 'test.EntryPoint$Buggy.m()V' cannot be non-final, static, nor native.");
+        "Line 6: JsOverlay method 'void EntryPoint.Buggy.m()' cannot be non-final, static, nor native.");
   }
 
   public void testJsOverlayOnNativeMethodFails() {
@@ -1130,10 +1276,10 @@
         "}");
 
     assertBuggyFails(
-        "JsOverlay method 'test.EntryPoint$Buggy.m()V' cannot be non-final, static, nor native.");
+        "Line 6: JsOverlay method 'void EntryPoint.Buggy.m()' cannot be non-final, static, nor native.");
   }
 
-  public void testJsOverlayOnJsoMethodSucceeds() throws UnableToCompleteException {
+  public void testJsOverlayOnJsoMethodSucceeds() throws Exception {
     addSnippetImport("com.google.gwt.core.client.JavaScriptObject");
     addSnippetImport("jsinterop.annotations.JsOverlay");
     addSnippetClassDecl(
@@ -1145,7 +1291,7 @@
     assertBuggySucceeds();
   }
 
-  public void testImplicitJsOverlayOnJsoMethodSucceeds() throws UnableToCompleteException {
+  public void testImplicitJsOverlayOnJsoMethodSucceeds() throws Exception {
     addSnippetImport("com.google.gwt.core.client.JavaScriptObject");
     addSnippetImport("jsinterop.annotations.JsOverlay");
     addSnippetClassDecl(
@@ -1166,7 +1312,7 @@
         "}");
 
     assertBuggyFails(
-        "Method 'test.EntryPoint$Buggy.m()V' in non-native type cannot be @JsOverlay.");
+        "Line 6: Method 'void EntryPoint.Buggy.m()' in non-native type cannot be @JsOverlay.");
   }
 
   public void testJsOverlayOnNonJsTypeFails() {
@@ -1177,10 +1323,10 @@
         "}");
 
     assertBuggyFails(
-        "Method 'test.EntryPoint$Buggy.m()V' in non-native type cannot be @JsOverlay.");
+        "Line 5: Method 'void EntryPoint.Buggy.m()' in non-native type cannot be @JsOverlay.");
   }
 
-  public void testJsTypeExtendsNativeJsTypeSucceeds() throws UnableToCompleteException {
+  public void testJsTypeExtendsNativeJsTypeSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) public static class Super {",
@@ -1191,7 +1337,7 @@
     assertBuggySucceeds();
   }
 
-  public void testJsTypeExtendsNonJsTypeSucceeds() throws UnableToCompleteException {
+  public void testJsTypeExtendsNonJsTypeSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "public static class Super {",
@@ -1202,7 +1348,7 @@
     assertBuggySucceeds();
   }
 
-  public void testJsTypeImplementsNativeJsTypeInterfaceSucceeds() throws UnableToCompleteException {
+  public void testJsTypeImplementsNativeJsTypeInterfaceSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) public interface Interface {",
@@ -1213,7 +1359,7 @@
     assertBuggySucceeds();
   }
 
-  public void testJsTypeImplementsNonJsTypeInterfaceSucceeds() throws UnableToCompleteException {
+  public void testJsTypeImplementsNonJsTypeInterfaceSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "public interface Interface {",
@@ -1224,8 +1370,7 @@
     assertBuggySucceeds();
   }
 
-  public void testJsTypeIntefaceExtendsNativeJsTypeInterfaceSucceeds()
-      throws UnableToCompleteException {
+  public void testJsTypeIntefaceExtendsNativeJsTypeInterfaceSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) public interface Interface {",
@@ -1237,7 +1382,7 @@
   }
 
   public void testJsTypeInterfaceExtendsNonJsTypeInterfaceSucceeds()
-      throws UnableToCompleteException {
+      throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "public interface Interface {",
@@ -1248,7 +1393,7 @@
     assertBuggySucceeds();
   }
 
-  public void testNativeJsTypeExtendsNaiveJsTypeSucceeds() throws UnableToCompleteException {
+  public void testNativeJsTypeExtendsNaiveJsTypeSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) public static class Super {",
@@ -1267,7 +1412,7 @@
         "}");
 
     assertBuggyFails(
-        "Native JsType member 'test.EntryPoint$Buggy.f' is not public or has @JsIgnore.");
+        "Line 5: Native JsType member 'int EntryPoint.Buggy.f' is not public or has @JsIgnore.");
   }
 
   public void testNativeJsTypeJsIgnoredFieldFails() {
@@ -1279,7 +1424,7 @@
         "}");
 
     assertBuggyFails(
-        "Native JsType member 'test.EntryPoint$Buggy.x' is not public or has @JsIgnore.");
+        "Line 6: Native JsType member 'int EntryPoint.Buggy.x' is not public or has @JsIgnore.");
   }
 
   public void testNativeJsTypeNonPublicMethodFails() {
@@ -1290,7 +1435,7 @@
         "}");
 
     assertBuggyFails(
-        "Native JsType member 'test.EntryPoint$Buggy.m()V' is not public or has @JsIgnore.");
+        "Line 5: Native JsType member 'void EntryPoint.Buggy.m()' is not public or has @JsIgnore.");
   }
 
   public void testNativeJsTypeJsIgnoredMethodFails() {
@@ -1302,7 +1447,7 @@
         "}");
 
     assertBuggyFails(
-        "Native JsType member 'test.EntryPoint$Buggy.m()V' is not public or has @JsIgnore.");
+        "Line 6: Native JsType member 'void EntryPoint.Buggy.m()' is not public or has @JsIgnore.");
   }
 
   public void testNativeJsTypeJsIgnoredConstructorFails() {
@@ -1314,11 +1459,22 @@
         "}");
 
     assertBuggyFails(
-        "Native JsType member 'test.EntryPoint$Buggy.EntryPoint$Buggy() <init>' "
-          + "is not public or has @JsIgnore.");
+        "Line 6: Native JsType member 'EntryPoint.Buggy.EntryPoint$Buggy()' is not public or "
+            + "has @JsIgnore.");
   }
 
-  public void testNativeJsTypeNonPublicConstructorSucceeds() throws UnableToCompleteException {
+  public void testNativeJsTypeMutlipleConstructorSucceeds() throws Exception {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetClassDecl(
+        "@JsType(isNative=true) static class Buggy {",
+        "  public Buggy(int i) { }",
+        "  public Buggy() { }",
+        "}");
+
+    assertBuggySucceeds();
+  }
+
+  public void testNativeJsTypeNonPublicConstructorSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) static class Buggy {",
@@ -1328,7 +1484,7 @@
     assertBuggySucceeds();
   }
 
-  public void testNativeJsTypeDefaultConstructorSucceeds() throws UnableToCompleteException {
+  public void testNativeJsTypeDefaultConstructorSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) static class Buggy {",
@@ -1337,7 +1493,86 @@
     assertBuggySucceeds();
   }
 
-  public void testNonJsTypeExtendsJsTypeSucceeds() throws UnableToCompleteException {
+  public void testNativeJsTypeInstanceMethodOverloadSucceeds() throws Exception {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetClassDecl(
+        "@SuppressWarnings(\"unusable-by-js\")",
+        "@JsType(isNative=true) public static class Buggy {",
+        "  public native void m(Object o);",
+        "  public native void m(Object[] o);",
+        "}");
+
+    assertBuggySucceeds();
+  }
+
+  public void testNativeJsTypeStaticMethodOverloadSucceeds() throws Exception {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetClassDecl(
+        "@SuppressWarnings(\"unusable-by-js\")",
+        "@JsType(isNative=true) public static class Buggy {",
+        "  public static native void m(Object o);",
+        "  public static native void m(Object[] o);",
+        "}");
+
+    assertBuggySucceeds();
+  }
+
+  public void testNonJsTypeExtendingNativeJsTypeWithInstanceMethodSucceeds() throws Exception {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetClassDecl(
+        "@SuppressWarnings(\"unusable-by-js\")",
+        "@JsType(isNative=true) public static class Super {",
+        "  public native void m(Object o);",
+        "  public native void m(Object[] o);",
+        "}",
+        "@JsType public static class Buggy extends Super {",
+        "  public void n(Object o) { }",
+        "}");
+
+    assertBuggySucceeds();
+  }
+
+  public void testNonJsTypeExtendingNativeJsTypeWithInstanceMethodOverloadsFails() {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetClassDecl(
+        "@JsType(isNative=true) public static class Super {",
+        "  public native void m(Object o);",
+        "  public native void m(int o);",
+        "}",
+        "public static class Buggy extends Super {",
+        "  public void m(Object o) { }",
+        "}");
+
+    assertBuggyFails(
+        "Line 9: 'void EntryPoint.Buggy.m(Object)' and 'void EntryPoint.Super.m(int)' "
+            + "cannot both use the same JavaScript name 'm'.");
+  }
+
+  public void testNonJsTypeWithNativeStaticMethodOverloadsSucceeds() throws Exception {
+    addSnippetImport("jsinterop.annotations.JsMethod");
+    addSnippetClassDecl(
+        "public static class Buggy {",
+        "  @JsMethod public static native void m(Object o);",
+        "  @JsMethod public static native void m(int o);",
+        "}");
+
+    assertBuggySucceeds();
+  }
+
+  public void testNonJsTypeWithNativeInstanceMethodOverloadsFails() throws Exception {
+    addSnippetImport("jsinterop.annotations.JsMethod");
+    addSnippetClassDecl(
+        "public static class Buggy {",
+        "  @JsMethod public native void m(Object o);",
+        "  @JsMethod public void m(int o) { }",
+        "}");
+
+    assertBuggyFails(
+        "Line 6: 'void EntryPoint.Buggy.m(int)' and 'void EntryPoint.Buggy.m(Object)' "
+            + "cannot both use the same JavaScript name 'm'.");
+  }
+
+  public void testNonJsTypeExtendsJsTypeSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType public static class Super {",
@@ -1348,7 +1583,7 @@
     assertBuggySucceeds();
   }
 
-  public void testNonJsTypeImplementsJsTypeInterfaceSucceeds() throws UnableToCompleteException {
+  public void testNonJsTypeImplementsJsTypeInterfaceSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType public interface Interface {",
@@ -1359,8 +1594,7 @@
     assertBuggySucceeds();
   }
 
-  public void testNonJsTypeInterfaceExtendsJsTypeInterfaceSucceeds()
-      throws UnableToCompleteException {
+  public void testNonJsTypeInterfaceExtendsJsTypeInterfaceSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType public interface Interface {",
@@ -1371,20 +1605,20 @@
     assertBuggySucceeds();
   }
 
-  public void testNonJsTypeExtendsNativeJsTypeSucceeds()
-      throws UnableToCompleteException {
+  public void testNonJsTypeExtendsNativeJsTypeSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) public static class Super {",
+        "  public native void m();",
         "}",
         "public static class Buggy extends Super {",
+        "  public void m() { }",
         "}");
 
     assertBuggySucceeds();
   }
 
-  public void testNonJsTypeImplementsNativeJsTypeInterfaceSucceeds()
-      throws UnableToCompleteException {
+  public void testNonJsTypeImplementsNativeJsTypeInterfaceSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) public interface Interface {",
@@ -1395,8 +1629,7 @@
     assertBuggySucceeds();
   }
 
-  public void testNonJsTypeInterfaceExtendsNativeJsTypeInterfaceSucceeds()
-      throws UnableToCompleteException {
+  public void testNonJsTypeInterfaceExtendsNativeJsTypeInterfaceSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) public interface Interface {",
@@ -1469,7 +1702,7 @@
     assertBuggySucceeds();
   }
 
-  public void testUnusuableByJsFails() throws Exception {
+  public void testUnusuableByJsWarns() throws Exception {
     addSnippetImport("jsinterop.annotations.JsFunction");
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetImport("jsinterop.annotations.JsMethod");
@@ -1499,44 +1732,59 @@
         "}");
 
     assertBuggySucceeds(
-        "[unusable-by-js] Return type of "
-            + "'test.EntryPoint$Buggy.f1(Ltest/EntryPoint$A;)Ltest/EntryPoint$A;' "
-            + "is not usable by but exposed to JavaScript",
-        "[unusable-by-js] Return type of "
-            + "'test.EntryPoint$Buggy.f2([Ltest/EntryPoint$A;)[Ltest/EntryPoint$A;' "
-            + "is not usable by but exposed to JavaScript",
-        "[unusable-by-js] Return type of "
-            + "'test.EntryPoint$Buggy.f3(J)J' is not usable by but exposed to JavaScript",
-        "[unusable-by-js] Return type of "
-            + "'test.EntryPoint$Buggy.f4(Ltest/EntryPoint$B;)Ltest/EntryPoint$B;' "
-            + "is not usable by but exposed to JavaScript",
-        "[unusable-by-js] Type of field 'field' in type 'test.EntryPoint$Buggy' "
-            + "is not usable by but exposed to JavaScript",
-        "[unusable-by-js] Type of parameter 'a' in method "
-            + "'test.EntryPoint$Buggy.f1(Ltest/EntryPoint$A;)Ltest/EntryPoint$A;' "
-            + "is not usable by but exposed to JavaScript",
-        "[unusable-by-js] Type of parameter 'a' in method "
-            + "'test.EntryPoint$Buggy.f2([Ltest/EntryPoint$A;)[Ltest/EntryPoint$A;' "
-            + "is not usable by but exposed to JavaScript",
-        "[unusable-by-js] Type of parameter 'a' in method 'test.EntryPoint$Buggy.f3(J)J' "
-            + "is not usable by but exposed to JavaScript",
-        "[unusable-by-js] Type of parameter 'a' in method "
-            + "'test.EntryPoint$Buggy.f4(Ltest/EntryPoint$B;)Ltest/EntryPoint$B;' "
-            + "is not usable by but exposed to JavaScript",
-        "[unusable-by-js] Type of parameter 'a' in method "
-            + "'test.EntryPoint$Buggy.f5([[Ljava/lang/Object;)V' "
-            + "is not usable by but exposed to JavaScript",
-        "[unusable-by-js] Type of parameter 'a' in method "
-            + "'test.EntryPoint$Buggy.f6([Ljava/lang/Object;)V' "
-            + "is not usable by but exposed to JavaScript",
-        "[unusable-by-js] Type of parameter 'a' in method " // JsFunction method
-            + "'test.EntryPoint$FI.f(Ltest/EntryPoint$A;)V' "
-            + "is not usable by but exposed to JavaScript",
-        "[unusable-by-js] Type of parameter 'a' in method " // JsMethod in non-jstype class.
-            + "'test.EntryPoint$C.fc1(Ltest/EntryPoint$A;)V' "
-            + "is not usable by but exposed to JavaScript",
-        "[unusable-by-js] Type of field 'a' in type " // JsProperty in non-jstype class.
-            + "'test.EntryPoint$D' is not usable by but exposed to JavaScript");
+        "Line 21: [unusable-by-js] Return type of 'EntryPoint.A EntryPoint.Buggy.f1(EntryPoint.A)' "
+            + "is not usable by but exposed to JavaScript.",
+        "Line 22: [unusable-by-js] Return type of "
+            + "'EntryPoint.A[] EntryPoint.Buggy.f2(EntryPoint.A[])' is not usable by but "
+            + "exposed to JavaScript.",
+        "Line 23: [unusable-by-js] Return type of 'long EntryPoint.Buggy.f3(long)' is not "
+            + "usable by but exposed to JavaScript.",
+        "Line 24: [unusable-by-js] Return type of 'EntryPoint.B EntryPoint.Buggy.f4(EntryPoint.B)' "
+            + "is not usable by but exposed to JavaScript.",
+        "Line 20: [unusable-by-js] Type of field 'field' in type 'EntryPoint.Buggy' "
+            + "is not usable by but exposed to JavaScript.",
+        "Line 21: [unusable-by-js] Type of parameter 'a' in method "
+            + "'EntryPoint.A EntryPoint.Buggy.f1(EntryPoint.A)' is not usable by but "
+            + "exposed to JavaScript.",
+        "Line 22: [unusable-by-js] Type of parameter 'a' in method "
+            + "'EntryPoint.A[] EntryPoint.Buggy.f2(EntryPoint.A[])' is not usable by but "
+            + "exposed to JavaScript.",
+        "Line 23: [unusable-by-js] Type of parameter 'a' in method "
+            + "'long EntryPoint.Buggy.f3(long)' is not usable by but exposed to JavaScript.",
+        "Line 24: [unusable-by-js] Type of parameter 'a' in method "
+            + "'EntryPoint.B EntryPoint.Buggy.f4(EntryPoint.B)' is not usable by but "
+            + "exposed to JavaScript.",
+        "Line 25: [unusable-by-js] Type of parameter 'a' in method "
+            + "'void EntryPoint.Buggy.f5(Object[][])' is not usable by but exposed to JavaScript.",
+        "Line 26: [unusable-by-js] Type of parameter 'a' in method "
+            + "'void EntryPoint.Buggy.f6(Object[])' is not usable by but exposed to JavaScript.",
+        "Line 18: [unusable-by-js] Type of parameter 'a' in method "
+            + "'void EntryPoint.FI.f(EntryPoint.A)' is not usable by but exposed to JavaScript.",
+        "Line 12: [unusable-by-js] Type of parameter 'a' in method "
+            // JsMethod in non-jstype class.
+            + "'void EntryPoint.C.fc1(EntryPoint.A)' is not usable by but exposed to JavaScript.",
+        "Line 16: [unusable-by-js] Type of field 'a' in type " // JsProperty in non-jstype class.
+            + "'EntryPoint.D' is not usable by but exposed to JavaScript.");
+  }
+
+  public void testUnusableByJsAccidentalOverrideSuppressionWarns()
+      throws Exception {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetClassDecl(
+        "@JsType",
+        "public static interface Foo {",
+        "  @SuppressWarnings(\"unusable-by-js\") ",
+        "  void doIt(Class foo);",
+        "}",
+        "public static class Parent {",
+        "  public void doIt(Class x) {}",
+        "}",
+        "public static class Buggy extends Parent implements Foo {}");
+
+    assertBuggySucceeds(
+        "Line 10: [unusable-by-js] Type of parameter 'x' in method "
+            + "'void EntryPoint.Parent.doIt(Class)' (exposed by 'EntryPoint.Buggy') is not usable "
+            + "by but exposed to JavaScript.");
   }
 
   private static final MockJavaResource jsFunctionInterface = new MockJavaResource(
@@ -1554,21 +1802,31 @@
   };
 
   public final void assertBuggySucceeds(String... expectedWarnings)
-      throws UnableToCompleteException {
-    Result result = assertCompileSucceeds("Buggy buggy = null;", expectedWarnings);
+      throws Exception {
+    List<String> allWarnings = Lists.newArrayList();
+    if (expectedWarnings.length > 0) {
+      allWarnings.add("Warnings in test/EntryPoint.java");
+      allWarnings.addAll(Arrays.asList(expectedWarnings));
+    }
+    Result result = assertCompileSucceeds("Buggy buggy = null;",
+        allWarnings.toArray(new String[0]));
     assertNotNull(result.findClass("test.EntryPoint$Buggy"));
   }
 
   public final void assertBuggyFails(String... expectedErrors) {
     assertTrue(expectedErrors.length > 0);
-    assertCompileFails("Buggy buggy = null;", expectedErrors);
+
+    List<String> allErrors = Lists.newArrayList();
+    allErrors.add("Errors in test/EntryPoint.java");
+    allErrors.addAll(Arrays.asList(expectedErrors));
+    assertCompileFails("Buggy buggy = null;", allErrors.toArray(new String[0]));
   }
 
   @Override
   protected boolean doOptimizeMethod(TreeLogger logger, JProgram program, JMethod method) {
     try {
       JsInteropRestrictionChecker.exec(logger, program, new MinimalRebuildCache());
-    } catch (UnableToCompleteException e) {
+    } catch (Exception e) {
       throw new RuntimeException(e);
     }
     return false;
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/UnifyAstTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/UnifyAstTest.java
index 305e3f1..9938f40 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/UnifyAstTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/UnifyAstTest.java
@@ -20,6 +20,7 @@
 import com.google.gwt.dev.javac.testing.impl.JavaResourceBase;
 import com.google.gwt.dev.javac.testing.impl.MockJavaResource;
 import com.google.gwt.dev.jjs.ast.JClassType;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
 import com.google.gwt.dev.jjs.ast.JMethod;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.util.arg.SourceLevel;
@@ -92,6 +93,28 @@
     assertOverrides(result, "b.E.m1()Lb/E;");
   }
 
+  public void testOverrides_orderInOverriddenSet() throws Exception {
+    addAll(A_A, A_I, A_J, A_B, B_C, B_D, B_E);
+    Result result = optimize("void", "");
+
+    for (JDeclaredType type : result.getOptimizedProgram().getDeclaredTypes()) {
+      for (JMethod method : type.getMethods()) {
+        JavaAstVerifier.assertCorrectOverriddenOrder(result.getOptimizedProgram(), method);
+      }
+    }
+  }
+
+  public void testOverrides_orderInOverridingnSet() throws Exception {
+    addAll(A_A, A_I, A_J, A_B, B_C, B_D, B_E);
+    Result result = optimize("void", "");
+
+    for (JDeclaredType type : result.getOptimizedProgram().getDeclaredTypes()) {
+      for (JMethod method : type.getMethods()) {
+        JavaAstVerifier.assertCorrectOverridingOrder(result.getOptimizedProgram(), method);
+      }
+    }
+  }
+
   public void testOverrides_packagePrivate() throws Exception {
     addAll(A_A, A_I, A_J, A_B, B_C, B_D, B_E);
     Result result = optimize("void", "");
diff --git a/dev/core/test/com/google/gwt/dev/util/UnitTestTreeLogger.java b/dev/core/test/com/google/gwt/dev/util/UnitTestTreeLogger.java
index 7e38525..b57a254 100644
--- a/dev/core/test/com/google/gwt/dev/util/UnitTestTreeLogger.java
+++ b/dev/core/test/com/google/gwt/dev/util/UnitTestTreeLogger.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.thirdparty.guava.common.collect.ComparisonChain;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
 
 import junit.framework.Assert;
 
@@ -148,6 +149,17 @@
       return type;
     }
 
+    public boolean equals(Object other) {
+      if (!(other instanceof LogEntry)) {
+        return false;
+      }
+      return this.toString().equals(other.toString());
+    }
+
+    public int hashCode() {
+      return toString().hashCode();
+    }
+
     @Override
     public String toString() {
       StringBuilder sb = new StringBuilder();
@@ -248,7 +260,11 @@
     Collections.sort(actualEntries);
 
     if (expectedEntries.size() != actualEntries.size()) {
-      Assert.fail("Wrong log count: expected=" + expectedEntries + ", actual=" + actualEntries);
+      List<LogEntry> missingEntries = Lists.newArrayList(expectedEntries);
+      missingEntries.removeAll(actualEntries);
+      List<LogEntry> unexpectedEntries = Lists.newArrayList(actualEntries);
+      unexpectedEntries.removeAll(expectedEntries);
+      Assert.fail("Wrong log count: missing=" + missingEntries + ", unexpected=" + unexpectedEntries);
     }
 
     for (int i = 0; i < expectedEntries.size(); ++i) {