Allow native JsMethods coexist with implementations with the same name.

Since there is no overloading in JavaScript, J2CL can only have one
member providing the implementation for a name. Based on that rule,
multiple native members using the same name are ok as long as at the
end there is only one method exists with an implementation.
We call this "one live implementation" rule and we won't emit an error
as long as the class follows the rule.

This allows user to override an implementation with a native override,
and provide a new member to take over the implementation.

For example;

  @JsType
  class A {
    public void m() { }
    public native void m(int i);
  }

  @JsType
  class B {
    @Override
    public native void m();
    public void m(Object o) { }
  }

is allowed because at class "B", "void m()" is overridden the previous
implementation in "A" making it native hence no existing implementation
remains except the new provided overload in "B". This makes the class B
fit into one live implementation rule.

Change-Id: I4fb2217231b97dee9f7e9c67bd4acb6f210c5b16
diff --git a/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java b/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java
index 4fb23b7..eea0341 100644
--- a/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java
+++ b/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java
@@ -158,7 +158,7 @@
   private final Set<String> deletedDiskSourcePaths = Sets.newHashSet();
   private final Set<String> deletedResourcePaths = Sets.newHashSet();
   private final Set<String> dualJsoImplInterfaceNames = Sets.newHashSet();
-  private final Map<String, String> descriptionByexportedGlobalNames = Maps.newHashMap();
+  private final Map<String, String> descriptionByExportedGlobalNames = Maps.newHashMap();
   private final Multimap<String, String> exportedGlobalNamesByTypeName = HashMultimap.create();
   private final ArtifactSet generatedArtifacts = new ArtifactSet();
   private final Multimap<String, String> generatedCompilationUnitNamesByReboundTypeNames =
@@ -195,7 +195,7 @@
   public String addExportedGlobalName(
       String exportedGlobalName, String description, String inTypeName) {
     exportedGlobalNamesByTypeName.put(inTypeName, exportedGlobalName);
-    return descriptionByexportedGlobalNames.put(exportedGlobalName, description);
+    return descriptionByExportedGlobalNames.put(exportedGlobalName, description);
   }
 
   /**
@@ -463,7 +463,7 @@
     copyMap(that.compilationUnitTypeNameByNestedTypeName,
         this.compilationUnitTypeNameByNestedTypeName);
     copyMap(that.contentHashByGeneratedTypeName, this.contentHashByGeneratedTypeName);
-    copyMap(that.descriptionByexportedGlobalNames, this.descriptionByexportedGlobalNames);
+    copyMap(that.descriptionByExportedGlobalNames, this.descriptionByExportedGlobalNames);
     copyMap(that.jsByTypeName, this.jsByTypeName);
     copyMap(that.lastModifiedByDiskSourcePath, this.lastModifiedByDiskSourcePath);
     copyMap(that.lastModifiedByResourcePath, this.lastModifiedByResourcePath);
@@ -680,7 +680,7 @@
   public void removeExportedNames(String inTypeName) {
     Collection<String> exportedGlobalNamesForType =
         exportedGlobalNamesByTypeName.removeAll(inTypeName);
-    descriptionByexportedGlobalNames.keySet().removeAll(exportedGlobalNamesForType);
+    descriptionByExportedGlobalNames.keySet().removeAll(exportedGlobalNamesForType);
   }
 
   public void removeReferencesFrom(String fromTypeName) {
@@ -766,8 +766,8 @@
         && Objects.equal(this.deletedResourcePaths, that.deletedResourcePaths)
         && Objects.equal(this.dualJsoImplInterfaceNames, that.dualJsoImplInterfaceNames)
         && Objects.equal(this.generatedArtifacts, that.generatedArtifacts)
-        && Objects.equal(this.descriptionByexportedGlobalNames,
-            that.descriptionByexportedGlobalNames)
+        && Objects.equal(this.descriptionByExportedGlobalNames,
+            that.descriptionByExportedGlobalNames)
         && Objects.equal(this.exportedGlobalNamesByTypeName, that.exportedGlobalNamesByTypeName)
         && Objects.equal(this.generatedCompilationUnitNamesByReboundTypeNames,
             that.generatedCompilationUnitNamesByReboundTypeNames)
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 b8bb599..02c86fb 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
@@ -51,15 +51,16 @@
 import com.google.gwt.dev.js.ast.JsNameRef;
 import com.google.gwt.dev.js.ast.JsParameter;
 import com.google.gwt.dev.js.ast.JsVisitor;
-import com.google.gwt.dev.util.Pair;
+import com.google.gwt.thirdparty.guava.common.base.Preconditions;
 import com.google.gwt.thirdparty.guava.common.base.Predicate;
 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.LinkedHashMultimap;
+import com.google.gwt.thirdparty.guava.common.collect.Multimap;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
 
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
+import java.util.Set;
 
 /**
  * Checks and throws errors for invalid JsInterop constructs.
@@ -216,8 +217,8 @@
         getDescription(superPrimaryConsructor));
   }
 
-  private void checkMember(
-      JMember member, Map<String, JsMember> localNames, Map<String, JsMember> ownGlobalNames) {
+  private void checkMember(JMember member, Multimap<String, JMember> instanceMembersByJsName) {
+
     if (member.getEnclosingType().isJsNative()) {
       checkMemberOfNativeJsType(member);
     }
@@ -249,12 +250,12 @@
 
     checkMemberQualifiedJsName(member);
 
-    if (isCheckedLocalName(member)) {
-      checkLocalName(localNames, member);
+    if (isInstanceJsMember(member)) {
+      checkInstanceNameConsistency(instanceMembersByJsName, member);
     }
 
-    if (isCheckedGlobalName(member)) {
-      checkGlobalName(ownGlobalNames, member);
+    if (isStaticJsMember(member)) {
+      checkStaticNameCollisions(member);
     }
   }
 
@@ -600,41 +601,74 @@
     }
   }
 
-  private void checkLocalName(Map<String, JsMember> localNames, JMember member) {
-    Pair<JsMember, JsMember> oldAndNewJsMember = updateJsMembers(localNames, member);
-    JsMember oldJsMember = oldAndNewJsMember.left;
-    JsMember newJsMember = oldAndNewJsMember.right;
+  private void checkInstanceNameConsistency(
+      Multimap<String, JMember> instanceMembersByJsName, JMember member) {
+    checkOverrideConsistency(member);
 
-    checkNameConsistency(member);
-    checkJsPropertyConsistency(member, newJsMember);
-
-    if (oldJsMember == null || oldJsMember == newJsMember) {
+    if (member.isJsNative()) {
       return;
     }
 
-    if (oldJsMember.isJsNative() && newJsMember.isJsNative()) {
+    String name = member.getJsName();
+
+    Set<JMember> potentiallyCollidingMembers =
+        Sets.newLinkedHashSet(instanceMembersByJsName.get(name));
+
+    // Remove self.
+    boolean removed = potentiallyCollidingMembers.remove(member);
+    Preconditions.checkState(removed);
+
+    // Remove native members as they don't cause collisions.
+    Iterables.removeIf(potentiallyCollidingMembers,
+        new Predicate<JMember>() {
+          @Override
+          public boolean apply(JMember member) {
+            return member.isJsNative();
+          }
+        });
+
+    if (potentiallyCollidingMembers.isEmpty()) {
+      // No colliding members.
       return;
     }
 
-    logError(member, "%s and %s cannot both use the same JavaScript name '%s'.",
-        getMemberDescription(member), getMemberDescription(oldJsMember.member), member.getJsName());
+    JMember potentiallyCollidingMember = potentiallyCollidingMembers.iterator().next();
+    if (potentiallyCollidingMembers.size() == 1
+         && isJsPropertyAccessorPair(member, potentiallyCollidingMember)) {
+      if (!checkPropertyConsistency(member, potentiallyCollidingMember)) {
+        // Remove colliding member to avoid duplicate error messages.
+        instanceMembersByJsName.get(name).remove(member);
+      }
+      return;
+    }
+
+    logError(member,
+        "%s and %s cannot both use the same JavaScript name '%s'.",
+        getMemberDescription(member),
+        getMemberDescription(potentiallyCollidingMember),
+        member.getJsName());
+
+    // Remove colliding member avoid duplicate error messages.
+    instanceMembersByJsName.get(name).remove(member);
   }
 
-  private void checkGlobalName(Map<String, JsMember> ownGlobalNames, JMember member) {
-    Pair<JsMember, JsMember> oldAndNewJsMember = updateJsMembers(ownGlobalNames, member);
-    JsMember oldJsMember = oldAndNewJsMember.left;
-    JsMember newJsMember = oldAndNewJsMember.right;
+  private boolean isJsPropertyAccessorPair(JMember thisMember, JMember thatMember) {
+    return (thisMember.getJsMemberType() == JsMemberType.GETTER
+            && thatMember.getJsMemberType() == JsMemberType.SETTER)
+        || (thatMember.getJsMemberType() == JsMemberType.GETTER
+            && thisMember.getJsMemberType() == JsMemberType.SETTER);
+  }
 
-    if (oldJsMember == newJsMember) {
-      // We allow setter-getter to share the name if they are both defined in the same class, so
-      // skipping the global name check. However still need to do a consistency check.
-      checkJsPropertyConsistency(member, newJsMember);
+  private void checkStaticNameCollisions(JMember member) {
+    if (member.isJsNative()) {
       return;
     }
-
+    // TODO(rluble): Add static property consistency check here if static property accessors are
+    // ever allowed.
     String currentGlobalNameDescription =
         minimalRebuildCache.addExportedGlobalName(member.getQualifiedJsName(),
             JjsUtils.getReadableDescription(member), member.getEnclosingType().getName());
+
     if (currentGlobalNameDescription == null) {
       return;
     }
@@ -642,14 +676,18 @@
         getMemberDescription(member), member.getQualifiedJsName(), currentGlobalNameDescription);
   }
 
-  private void checkJsPropertyConsistency(JMember member, JsMember newMember) {
-    if (newMember.setter != null && newMember.getter != null) {
-      List<JParameter> setterParams = ((JMethod) newMember.setter).getParams();
-      if (isSameType(newMember.getter.getType(), setterParams.get(0).getType())) {
+  private boolean checkPropertyConsistency(JMember member, JMember otherMember) {
+    JMember setter = member.getJsMemberType() == JsMemberType.SETTER ? member : otherMember;
+    JMember getter = member.getJsMemberType() == JsMemberType.GETTER ? member : otherMember;
+    if (setter != null && getter != null) {
+      List<JParameter> setterParams = ((JMethod) setter).getParams();
+      if (isSameType(getter.getType(), setterParams.get(0).getType())) {
         logError(member, "JsProperty setter %s and getter %s cannot have inconsistent types.",
-            getMemberDescription(newMember.setter), getMemberDescription(newMember.getter));
+            getMemberDescription(setter), getMemberDescription(getter));
+        return false;
       }
     }
+    return true;
   }
 
   /**
@@ -661,18 +699,33 @@
     return !thisType.getJavahSignatureName().equals(thatType.getJavahSignatureName());
   }
 
-  private void checkNameConsistency(JMember member) {
+  private void checkOverrideConsistency(JMember member) {
     if (member instanceof JMethod) {
       String jsName = member.getJsName();
-      for (JMethod jMethod : ((JMethod) member).getOverriddenMethods()) {
-        String parentName = jMethod.getJsName();
-        if (parentName != null && !parentName.equals(jsName)) {
+      for (JMethod overridenMethod : ((JMethod) member).getOverriddenMethods()) {
+        String parentName = overridenMethod.getJsName();
+        if (parentName == null) {
+          continue;
+        }
+
+        if (!parentName.equals(jsName)) {
           logError(
               member,
               "%s cannot be assigned a different JavaScript name than the method it overrides.",
               getMemberDescription(member));
           break;
         }
+
+        if (overridenMethod.getJsMemberType() != member.getJsMemberType()) {
+          // Overrides can not change JsMethod to JsProperty nor vice versa.
+          logError(
+              member,
+              "%s %s cannot override %s %s.",
+              member.getJsMemberType() == JsMemberType.METHOD ? "JsMethod" : "JsProperty",
+              getMemberDescription(member),
+              overridenMethod.getJsMemberType() == JsMemberType.METHOD ? "JsMethod" : "JsProperty",
+              getMemberDescription(overridenMethod));
+        }
       }
     }
   }
@@ -932,10 +985,9 @@
       checkJsConstructorSubtype(type);
     }
 
-    Map<String, JsMember> ownGlobalNames = Maps.newHashMap();
-    Map<String, JsMember> localNames = collectLocalNames(type.getSuperClass());
+    Multimap<String, JMember> instanceJsNames = collectInstanceMembersByJsNames(type);
     for (JMember member : type.getMembers()) {
-      checkMember(member, localNames, ownGlobalNames);
+      checkMember(member, instanceJsNames);
     }
   }
 
@@ -986,46 +1038,36 @@
     wasUnusableByJsWarningReported = true;
   }
 
-  private static class JsMember {
-    private JMember member;
-    private JMember setter;
-    private JMember getter;
-
-    public JsMember(JMember member) {
-      this.member = member;
-    }
-
-    public JsMember(JMember member, JMember setter, JMember getter) {
-      this.member = member;
-      this.setter = setter;
-      this.getter = getter;
-    }
-
-    public boolean isJsNative() {
-      return member.isJsNative();
-    }
-
-    public boolean isPropertyAccessor() {
-      return setter != null || getter != null;
-    }
-  }
-
-  private LinkedHashMap<String, JsMember> collectLocalNames(JDeclaredType type) {
+  private Multimap<String, JMember> collectInstanceMembersByJsNames(JDeclaredType type) {
     if (type == null) {
-      return Maps.newLinkedHashMap();
+      return LinkedHashMultimap.create();
     }
 
-    LinkedHashMap<String, JsMember> memberByLocalMemberNames =
-        collectLocalNames(type.getSuperClass());
+    Multimap<String, JMember> instanceMembersByJsName =
+        collectInstanceMembersByJsNames(type.getSuperClass());
     for (JMember member : type.getMembers()) {
-      if (isCheckedLocalName(member)) {
-        updateJsMembers(memberByLocalMemberNames, member);
+      if (isInstanceJsMember(member)) {
+        addMember(instanceMembersByJsName, member);
       }
     }
-    return memberByLocalMemberNames;
+    return instanceMembersByJsName;
   }
 
-  private boolean isCheckedLocalName(JMember method) {
+  private static void addMember(
+      Multimap<String, JMember> instanceMembersByJsName, final JMember member) {
+    String name = member.getJsName();
+    Iterables.removeIf(instanceMembersByJsName.get(name),
+        new Predicate<JMember>() {
+          @Override
+          public boolean apply(JMember m) {
+            return overrides(member, m);
+          }
+        });
+
+    instanceMembersByJsName.put(name, member);
+  }
+
+  private boolean isInstanceJsMember(JMember method) {
     return method.needsDynamicDispatch() && method.getJsMemberType() != JsMemberType.NONE
         && !isSyntheticBridgeMethod(method);
   }
@@ -1044,50 +1086,11 @@
     return member.isSynthetic() && !((JMethod) member).isForwarding();
   }
 
-  private boolean isCheckedGlobalName(JMember member) {
-    return !member.needsDynamicDispatch() && !member.isJsNative();
+  private boolean isStaticJsMember(JMember member) {
+    return !member.needsDynamicDispatch() && member.getJsMemberType() != JsMemberType.NONE;
   }
 
-  private Pair<JsMember, JsMember> updateJsMembers(
-      Map<String, JsMember> memberByNames, JMember member) {
-    JsMember oldJsMember = memberByNames.get(member.getJsName());
-    JsMember newJsMember = createOrUpdateJsMember(oldJsMember, member);
-    memberByNames.put(member.getJsName(), newJsMember);
-    return Pair.create(oldJsMember, newJsMember);
-  }
-
-  private JsMember createOrUpdateJsMember(JsMember jsMember, JMember member) {
-    switch (member.getJsMemberType()) {
-      case GETTER:
-        if (jsMember != null && jsMember.isPropertyAccessor()) {
-          if (jsMember.getter == null || overrides(member, jsMember.getter)) {
-            jsMember.getter = member;
-            jsMember.member = member;
-            return jsMember;
-          }
-        }
-        return new JsMember(member, jsMember == null ? null : jsMember.setter, member);
-      case SETTER:
-        if (jsMember != null && jsMember.isPropertyAccessor()) {
-          if (jsMember.setter == null || overrides(member, jsMember.setter)) {
-            jsMember.setter = member;
-            jsMember.member = member;
-            return jsMember;
-          }
-        }
-        return new JsMember(member, member, jsMember == null ? null : jsMember.getter);
-      default:
-        if (jsMember != null && !jsMember.isPropertyAccessor()) {
-          if (overrides(member, jsMember.member)) {
-            jsMember.member = member;
-            return jsMember;
-          }
-        }
-        return new JsMember(member);
-    }
-  }
-
-  private boolean overrides(JMember member, JMember potentiallyOverriddenMember) {
+  private static boolean overrides(JMember member, JMember potentiallyOverriddenMember) {
     if (member instanceof JField || potentiallyOverriddenMember instanceof JField) {
       return false;
     }
@@ -1100,7 +1103,7 @@
     // 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.
+    // for instance name collision checking.
     JMethod potentiallyOverriddenMethod = (JMethod) potentiallyOverriddenMember;
 
     // TODO(goktug): make this more precise to handle package visibilities.
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 9e9af72..a928f10 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
@@ -51,9 +51,9 @@
         "}");
 
     assertBuggyFails(
-        "Line 14: 'void EntryPoint.ParentBuggy.doIt(EntryPoint.Bar)' "
+        "Line 13: 'void EntryPoint.ParentBuggy.doIt(EntryPoint.Foo)' "
             + "(exposed by 'EntryPoint.Buggy') and "
-            + "'void EntryPoint.ParentBuggy.doIt(EntryPoint.Foo)' (exposed by 'EntryPoint.Buggy') "
+            + "'void EntryPoint.ParentBuggy.doIt(EntryPoint.Bar)' (exposed by 'EntryPoint.Buggy') "
             + "cannot both use the same JavaScript name 'doIt'.");
   }
 
@@ -75,8 +75,8 @@
         "public static class Buggy {}  // Unrelated class");
 
     assertBuggyFails(
-        "Line 14: 'void EntryPoint.Baz.doIt(EntryPoint.Bar)' and "
-            + "'void EntryPoint.Baz.doIt(EntryPoint.Foo)' cannot both use the same "
+        "Line 13: 'void EntryPoint.Baz.doIt(EntryPoint.Foo)' and "
+            + "'void EntryPoint.Baz.doIt(EntryPoint.Bar)' cannot both use the same "
             + "JavaScript name 'doIt'.");
   }
 
@@ -259,7 +259,7 @@
         "}");
 
     assertBuggyFails(
-        "Line 10: 'boolean EntryPoint.Buggy.getX()' and 'boolean EntryPoint.Buggy.isX()' "
+        "Line 8: 'boolean EntryPoint.Buggy.isX()' and 'boolean EntryPoint.Buggy.getX()' "
             + "cannot both use the same JavaScript name 'x'.");
   }
 
@@ -300,7 +300,7 @@
         "}");
 
     assertBuggyFails(
-        "Line 10: 'void EntryPoint.Buggy.setX(int)' and 'void EntryPoint.Buggy.setX(boolean)' "
+        "Line 8: 'void EntryPoint.Buggy.setX(boolean)' and 'void EntryPoint.Buggy.setX(int)' "
             + "cannot both use the same JavaScript name 'x'.");
   }
 
@@ -320,9 +320,9 @@
         "}");
 
     assertBuggyFails(
-        "Line 9: 'int EntryPoint.IBuggy.getX()' and 'boolean EntryPoint.IBuggy.x(boolean)' "
+        "Line 7: 'boolean EntryPoint.IBuggy.x(boolean)' and 'int EntryPoint.IBuggy.getX()' "
             + "cannot both use the same JavaScript name 'x'.",
-        "Line 13: 'int EntryPoint.Buggy.getX()' and 'boolean EntryPoint.Buggy.x(boolean)' "
+        "Line 12: 'boolean EntryPoint.Buggy.x(boolean)' and 'int EntryPoint.Buggy.getX()' "
             + "cannot both use the same JavaScript name 'x'.");
   }
 
@@ -342,9 +342,9 @@
         "}");
 
     assertBuggyFails(
-        "Line 9: 'void EntryPoint.IBuggy.setX(int)' and 'boolean EntryPoint.IBuggy.x(boolean)' "
+        "Line 7: 'boolean EntryPoint.IBuggy.x(boolean)' and 'void EntryPoint.IBuggy.setX(int)' "
             + "cannot both use the same JavaScript name 'x'.",
-        "Line 13: 'void EntryPoint.Buggy.setX(int)' and 'boolean EntryPoint.Buggy.x(boolean)' "
+        "Line 12: 'boolean EntryPoint.Buggy.x(boolean)' and 'void EntryPoint.Buggy.setX(int)' "
             + "cannot both use the same JavaScript name 'x'.");
   }
 
@@ -426,7 +426,7 @@
         "}");
 
     assertBuggyFails(
-        "Line 6: 'void EntryPoint.Buggy.show()' and 'int EntryPoint.Buggy.show' cannot both use "
+        "Line 7: 'int EntryPoint.Buggy.show' and 'void EntryPoint.Buggy.show()' cannot both use "
             + "the same JavaScript name 'show'.");
   }
 
@@ -440,7 +440,7 @@
         "}");
 
     assertBuggyFails(
-        "Line 7: 'void EntryPoint.Buggy.show()' and 'void EntryPoint.Buggy.show(int)' cannot both "
+        "Line 6: 'void EntryPoint.Buggy.show(int)' and 'void EntryPoint.Buggy.show()' cannot both "
             + "use the same JavaScript name 'show'.");
   }
 
@@ -844,9 +844,9 @@
         "}");
 
     assertBuggyFails(
-        "Line 10: JsProperty setter 'void EntryPoint.IBuggy.setFoo(Integer)' and "
+        "Line 8: JsProperty setter 'void EntryPoint.IBuggy.setFoo(Integer)' and "
             + "getter 'int EntryPoint.IBuggy.getFoo()' cannot have inconsistent types.",
-        "Line 14: JsProperty setter 'void EntryPoint.Buggy.setFoo(Integer)' and "
+        "Line 13: JsProperty setter 'void EntryPoint.Buggy.setFoo(Integer)' and "
             + "getter 'int EntryPoint.Buggy.getFoo()' cannot have inconsistent types.");
   }
 
@@ -867,9 +867,9 @@
         "}");
 
     assertBuggyFails(
-        "Line 10: JsProperty setter 'void EntryPoint.IBuggy.setFoo(Object)' and "
+        "Line 8: JsProperty setter 'void EntryPoint.IBuggy.setFoo(Object)' and "
             + "getter 'boolean EntryPoint.IBuggy.isFoo()' cannot have inconsistent types.",
-        "Line 14: JsProperty setter 'void EntryPoint.Buggy.setFoo(Object)' and "
+        "Line 13: JsProperty setter 'void EntryPoint.Buggy.setFoo(Object)' and "
             + "getter 'boolean EntryPoint.Buggy.isFoo()' cannot have inconsistent types.");
   }
 
@@ -965,10 +965,10 @@
         "}");
 
     assertBuggyFails(
-        "Line 10: 'int EntryPoint.Buggy.getY()' and 'int EntryPoint.Super.getY()' cannot "
-            + "both use the same JavaScript name 'getY'.",
-        "Line 11: 'void EntryPoint.Buggy.setZ(int)' and 'void EntryPoint.Super.setZ(int)' cannot "
-           + "both use the same JavaScript name 'z'.");
+        "Line 10: JsProperty 'int EntryPoint.Buggy.getY()' cannot override "
+            + "JsMethod 'int EntryPoint.Super.getY()'.",
+        "Line 11: JsMethod 'void EntryPoint.Buggy.setZ(int)' cannot override "
+            + "JsProperty 'void EntryPoint.Super.setZ(int)'.");
   }
 
   public void testJsMethodJSNIVarargsWithNoReferenceSucceeds()
@@ -2257,7 +2257,8 @@
     assertBuggySucceeds();
   }
 
-  public void testNonJsTypeExtendingNativeJsTypeWithInstanceMethodOverloadsFails() {
+  public void testNonJsTypeExtendingNativeJsTypeWithInstanceMethodOverloadsSucceeds()
+      throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) public static class Super {",
@@ -2268,9 +2269,7 @@
         "  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'.");
+    assertBuggySucceeds();
   }
 
   public void testNonJsTypeWithNativeStaticMethodOverloadsSucceeds() throws Exception {
@@ -2284,16 +2283,46 @@
     assertBuggySucceeds();
   }
 
-  public void testNonJsTypeWithNativeInstanceMethodOverloadsFails() throws Exception {
+  public void testNonJsTypeWithNativeInstanceMethodOverloadsSucceeds() throws Exception {
+    addSnippetImport("jsinterop.annotations.JsMethod");
+    addSnippetImport("jsinterop.annotations.JsProperty");
+    addSnippetClassDecl(
+        "class Top {",
+        "  @JsMethod public void m(int o) {}",
+        "}",
+        "class SubTop extends Top {",
+        // Redefines m to be a setter
+        "  @JsMethod public native void m(int o);",
+        "  @JsProperty public void setM(int m) { }",
+        "}",
+        "class SubSubTop extends SubTop {",
+        //  Adds a getter
+        "  @JsProperty public int getM() { return 0; }",
+        "}",
+        "public class Buggy extends SubSubTop {",
+        // makes setter/getter pair native to define a different overload for the
+        // JavaScript name
+        "  @JsProperty public native void setM(int m);",
+        "  @JsProperty public native int getM();",
+        "  @JsMethod public void m(int o, Object opt_o) { }",
+        "}");
+
+    assertBuggySucceeds();
+  }
+
+  public void testNonSingleOverloadImplementationFails() throws Exception {
     addSnippetImport("jsinterop.annotations.JsMethod");
     addSnippetClassDecl(
-        "public static class Buggy {",
-        "  @JsMethod public native void m(Object o);",
+        "class Super {",
         "  @JsMethod public void m(int o) { }",
+        "}",
+        "public class Buggy extends Super {",
+        "  @JsMethod public native void m(Object o);",
+        "  @JsMethod public void m(int o, Object opt_o) { }",
         "}");
 
     assertBuggyFails(
-        "Line 6: 'void EntryPoint.Buggy.m(int)' and 'void EntryPoint.Buggy.m(Object)' "
+        "Line 9: 'void EntryPoint.Buggy.m(int, Object)' and 'void EntryPoint.Super.m(int)' "
             + "cannot both use the same JavaScript name 'm'.");
   }