Fixes a bug in JavaScriptObject support in DevMode that prevents dispatching
through an interface to a method defined in a super-interface. The change
also includes a new test to serve as a regression test.

Review at http://gwt-code-reviews.appspot.com/1287801
Review by: bobv, scottb, cromwellian


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9542 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteSingleJsoImplDispatches.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteSingleJsoImplDispatches.java
index e676987..215dcc6 100644
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteSingleJsoImplDispatches.java
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteSingleJsoImplDispatches.java
@@ -34,8 +34,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
+import java.util.SortedSet;
+import java.util.TreeSet;
 
 /**
  * Effects the renaming of {@code @SingleJsoImpl} methods from their original
@@ -181,9 +181,9 @@
      * may be referenced via a more specific interface.
      */
     if (inSingleJsoImplInterfaceType) {
-      for (Map.Entry<String, List<Method>> entry : toImplement(currentTypeName).entrySet()) {
-        for (Method method : entry.getValue()) {
-          writeEmptyMethod(entry.getKey(), method);
+      for (String mangledName : toImplement(currentTypeName)) {
+        for (Method method : jsoData.getDeclarations(mangledName)) {
+          writeEmptyMethod(mangledName, method);
         }
       }
     }
@@ -265,29 +265,23 @@
    * Given a resource name of a class, find all mangled method names that must
    * be implemented.
    */
-  private SortedMap<String, List<Method>> toImplement(String typeName) {
+  private SortedSet<String> toImplement(String typeName) {
     String name = typeName.replace('/', '_');
     String prefix = name + "_";
     String suffix = name + "`";
-    SortedMap<String, List<Method>> toReturn = new TreeMap<String, List<Method>>();
-
+    SortedSet<String> toReturn = new TreeSet<String>();
     for (String mangledName : jsoData.getMangledNames().subSet(prefix, suffix)) {
-      toReturn.put(mangledName, jsoData.getImplementations(mangledName));
+      if (!implementedMethods.contains(mangledName)) {
+        toReturn.add(mangledName);
+      }
     }
-    toReturn.keySet().removeAll(implementedMethods);
     return toReturn;
   }
-
-  private void writeEmptyMethod(String mangledMethodName, Method method) {
-    assert method.getArgumentTypes().length > 0;
-    // Remove the first argument, which would be the implementing JSO type
-    String descriptor = "("
-        + method.getDescriptor().substring(
-            1 + method.getArgumentTypes()[0].getDescriptor().length());
-
-    // Create the stub method entry in the interface
+  
+  private void writeEmptyMethod(String mangledMethodName, Method declMethod) {
     MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC
-        | Opcodes.ACC_ABSTRACT, mangledMethodName, descriptor, null, null);
+        | Opcodes.ACC_ABSTRACT, mangledMethodName, declMethod.getDescriptor(),
+        null, null);
     mv.visitEnd();
   }
 
@@ -303,20 +297,14 @@
      * semantics of the dispatches that would make a common implementation far
      * more awkward than the duplication of code.
      */
-    for (Map.Entry<String, List<Method>> entry : toImplement(stubIntr).entrySet()) {
-      for (Method method : entry.getValue()) {
-        String mangledName = entry.getKey();
+    for (String mangledName : toImplement(stubIntr)) {
+      for (Method method : jsoData.getDeclarations(mangledName)) {
 
-        String descriptor = "("
-            + method.getDescriptor().substring(
-                1 + method.getArgumentTypes()[0].getDescriptor().length());
-        String localName = method.getName().substring(0,
-            method.getName().length() - 1);
-        Method toCall = new Method(localName, descriptor);
+        Method toCall = new Method(method.getName(), method.getDescriptor());
 
         // Must not be final
         MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC
-            | Opcodes.ACC_SYNTHETIC, mangledName, descriptor, null, null);
+            | Opcodes.ACC_SYNTHETIC, mangledName, method.getDescriptor(), null, null);
         if (mv != null) {
           mv.visitCode();
 
diff --git a/user/test/com/google/gwt/dev/jjs/test/singlejso/TypeHierarchyTest.java b/user/test/com/google/gwt/dev/jjs/test/singlejso/TypeHierarchyTest.java
index c0f93d4..03d38ac 100644
--- a/user/test/com/google/gwt/dev/jjs/test/singlejso/TypeHierarchyTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/singlejso/TypeHierarchyTest.java
@@ -123,6 +123,45 @@
   static class Wide {
   }
 
+  private interface Element extends Node {
+  }
+
+  private static class JvmNode implements Node {
+    @Override
+    public JvmNode appendChild(Node node) {
+      return (JvmNode)node;
+    }
+  }
+  
+  private final static class JvmElement extends JvmNode implements Element {
+    static Element create() {
+      return new JvmElement();
+    }
+  }
+  
+  private final static class JsElement extends JsNode implements Element {
+    static Element create() {
+      return (Element) JavaScriptObject.createObject();
+    }
+
+    protected JsElement() {
+    }
+  }
+
+  private static class JsNode extends JavaScriptObject implements Node {
+    @Override
+    public final native JsNode appendChild(Node node) /*-{
+      return node;
+    }-*/;
+
+    protected JsNode() {
+    }
+  }
+
+  private interface Node {
+    Node appendChild(Node node);
+  }
+
   @Override
   public String getModuleName() {
     return "com.google.gwt.dev.jjs.CompilerSuite";
@@ -149,7 +188,7 @@
     IA b2 = new B2();
     assertEquals("B2", b2.whoAmI());
   }
-
+  
   public void testCase3() {
     IA a = A.create();
     assertEquals("A", a.whoAmI());
@@ -171,6 +210,17 @@
     IDiamond2B d2b = DiamondImpl.create();
     assertEquals(42, d2b.size());
   }
+  
+  /**
+   * Tests that dispatches through a hierarchy of interfaces works properly.
+   */
+  public void testInterfaceHierarchyDispatch() {
+    Element jsElement = JsElement.create();
+    assertEquals(jsElement, jsElement.appendChild(jsElement));
+    
+    Element jvmElement = JvmElement.create();
+    assertEquals(jvmElement, jvmElement.appendChild(jvmElement));
+  }
 
   public void testVirtualOverrides() {
     Arrayish array = PlainJsoWithInterface.create();