Make sure types are compared by signature in JsInteropRestrictionChecker.

In incremental compilation (reference only) types might have more than
one JDeclaredType instance representing the same type.
JsInteropRestrictionChecker needs to look in some cases at reference
only types (e.g. when determining whether setter and getter have
the same property type) and in this case comparing by reference equality
is not correct.

Bug: #9518
Bug-Link: https://github.com/gwtproject/gwt/issues/9518
Change-Id: I3778b15791ae1b5d80d28cb1e641de5cdd41ab50
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 93417bd..b8bb599 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
@@ -42,6 +42,7 @@
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JReferenceType;
 import com.google.gwt.dev.jjs.ast.JStatement;
+import com.google.gwt.dev.jjs.ast.JType;
 import com.google.gwt.dev.jjs.ast.JVisitor;
 import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
 import com.google.gwt.dev.js.JsUtils;
@@ -644,13 +645,22 @@
   private void checkJsPropertyConsistency(JMember member, JsMember newMember) {
     if (newMember.setter != null && newMember.getter != null) {
       List<JParameter> setterParams = ((JMethod) newMember.setter).getParams();
-      if (newMember.getter.getType() != setterParams.get(0).getType()) {
+      if (isSameType(newMember.getter.getType(), setterParams.get(0).getType())) {
         logError(member, "JsProperty setter %s and getter %s cannot have inconsistent types.",
             getMemberDescription(newMember.setter), getMemberDescription(newMember.getter));
       }
     }
   }
 
+  /**
+   * Returns true if {@code thisType} is the same type as {@code thatType}.
+   */
+  private boolean isSameType(JType thisType, JType thatType) {
+    // NOTE: The comparison here is made by signature instead of reference equality because under
+    // incremental compilation this types might be reference only and hence not unique.
+    return !thisType.getJavahSignatureName().equals(thatType.getJavahSignatureName());
+  }
+
   private void checkNameConsistency(JMember member) {
     if (member instanceof JMethod) {
       String jsName = member.getJsName();
diff --git a/dev/core/test/com/google/gwt/dev/CompilerTest.java b/dev/core/test/com/google/gwt/dev/CompilerTest.java
index 196e568..1e56f38 100644
--- a/dev/core/test/com/google/gwt/dev/CompilerTest.java
+++ b/dev/core/test/com/google/gwt/dev/CompilerTest.java
@@ -1495,6 +1495,67 @@
     checkIncrementalRecompile_dateStampChange(JsOutputOption.DETAILED);
   }
 
+  // Repro for bug #9518
+  public void testIncrementalRecompile_jsPropertyConsistencyCheck()
+      throws UnableToCompleteException,
+      IOException, InterruptedException {
+    // Supertype defines the getter.
+    MockJavaResource superType =
+        JavaResourceBase.createMockJavaResource(
+            "com.foo.SuperType",
+            "package com.foo;",
+            "import jsinterop.annotations.JsProperty;",
+            "public class SuperType {",
+            "  @JsProperty String getString() { return null; }",
+            "}");
+
+    // Subtype defines the setter.
+    MockJavaResource subType =
+        JavaResourceBase.createMockJavaResource(
+            "com.foo.SubType",
+            "package com.foo;",
+            "import jsinterop.annotations.JsProperty;",
+            "public class SubType extends SuperType {",
+            "  @JsProperty void setString(String s) {}",
+            "}");
+
+    MockJavaResource testEntryPoint =
+        JavaResourceBase.createMockJavaResource(
+            "com.foo.TestEntryPoint",
+            "package com.foo;",
+            "import com.google.gwt.core.client.EntryPoint;",
+            "public class TestEntryPoint implements EntryPoint {",
+            "  public void onModuleLoad() {",
+            // Create Impl and pass it to JS but do not explicitly call m
+            "    new SubType();",
+            "  }",
+            "}");
+
+    MockResource moduleResource =
+        JavaResourceBase.createMockResource(
+            "com/foo/TestModule.gwt.xml",
+            "<module>",
+            "  <source path=''/>",
+            "  <entry-point class='com.foo.TestEntryPoint'/>",
+            "</module>");
+
+    MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();
+    File relinkApplicationDir = Files.createTempDir();
+
+    // Perform a first compile.
+    compileToJs(relinkApplicationDir, "com.foo.TestModule",
+        Lists.newArrayList(moduleResource, testEntryPoint, subType, superType),
+        relinkMinimalRebuildCache, null, JsOutputOption.OBFUSCATED);
+
+    // Invalidate ONLY the subtype. The types referred by the parameters of supertype methods are
+    // going to be reference only which means that the supertype property will have a reference
+    // to a JClassType for "java.lang.String" that isExternal() and is different from the
+    // (non external) reference in the supertype.
+    relinkMinimalRebuildCache.markSourceFileStale("com/foo/SubType.java");
+    compileToJs(relinkApplicationDir, "com.foo.TestModule", Lists.<MockResource> newArrayList(),
+        relinkMinimalRebuildCache, null, JsOutputOption.OBFUSCATED);
+  }
+
   public void testIncrementalRecompile_invalidatePreamble() throws UnableToCompleteException,
       IOException, InterruptedException {
     MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();