Towards synthesizing Kotlin @Metadata: supertypes.

Bug: 143687784, 70169921
Change-Id: I8b955570f9d7864979c139d642e984c52d6a5f47
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index acb6c55..9f77f7a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -4,17 +4,16 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmType;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Box;
-import com.android.tools.r8.utils.DescriptorUtils;
-import java.util.ListIterator;
+import java.util.List;
 import kotlinx.metadata.KmClass;
 import kotlinx.metadata.KmType;
-import kotlinx.metadata.KmTypeVisitor;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
@@ -42,35 +41,18 @@
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    ListIterator<KmType> superTypeIterator = kmClass.getSupertypes().listIterator();
-    while (superTypeIterator.hasNext()) {
-      KmType kmType = superTypeIterator.next();
-      Box<Boolean> isLive = new Box<>(false);
-      Box<DexType> renamed = new Box<>(null);
-      kmType.accept(new KmTypeVisitor() {
-        @Override
-        public void visitClass(String name) {
-          String descriptor = DescriptorUtils.getDescriptorFromKotlinClassifier(name);
-          DexType type = appView.dexItemFactory().createType(descriptor);
-          isLive.set(appView.appInfo().isLiveProgramType(type));
-          DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
-          if (renamedType != type) {
-            renamed.set(renamedType);
-          }
-        }
-      });
-      if (!isLive.get()) {
-        superTypeIterator.remove();
-        continue;
+    List<KmType> superTypes = kmClass.getSupertypes();
+    superTypes.clear();
+    for (DexType itfType : clazz.interfaces.values) {
+      KmType kmType = toRenamedKmType(itfType, appView, lens);
+      if (kmType != null) {
+        superTypes.add(kmType);
       }
-      if (renamed.get() != null) {
-        // TODO(b/70169921): need a general util to convert the current clazz's access flag.
-        KmType renamedKmType = new KmType(kmType.getFlags());
-        renamedKmType.visitClass(
-            DescriptorUtils.descriptorToInternalName(renamed.get().toDescriptorString()));
-        superTypeIterator.remove();
-        superTypeIterator.add(renamedKmType);
-      }
+    }
+    assert clazz.superType != null;
+    KmType kmTypeForSupertype = toRenamedKmType(clazz.superType, appView, lens);
+    if (kmTypeForSupertype != null) {
+      superTypes.add(kmTypeForSupertype);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
new file mode 100644
index 0000000..0b061f3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import kotlinx.metadata.KmType;
+
+class KotlinMetadataSynthesizer {
+  static KmType toRenamedKmType(
+      DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    DexClass clazz = appView.definitionFor(type);
+    if (clazz == null) {
+      return null;
+    }
+    // For library or classpath class, synthesize @Metadata always.
+    if (clazz.isNotProgramClass()) {
+      KmType kmType = new KmType(clazz.accessFlags.getAsCfAccessFlags());
+      assert type == lens.lookupType(type, appView.dexItemFactory());
+      kmType.visitClass(DescriptorUtils.descriptorToInternalName(type.toDescriptorString()));
+      return kmType;
+    }
+    // From now on, it is a program class. First, make sure it is live.
+    if (!appView.appInfo().isLiveProgramType(type)) {
+      return null;
+    }
+    KmType kmType = new KmType(clazz.accessFlags.getAsCfAccessFlags());
+    DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
+    kmType.visitClass(DescriptorUtils.descriptorToInternalName(renamedType.toDescriptorString()));
+    return kmType;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java
new file mode 100644
index 0000000..d5297d1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java
@@ -0,0 +1,168 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRenameInClasspathTypeTest extends KotlinMetadataTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRenameInClasspathTypeTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static Path baseLibJar;
+  private static Path extLibJar;
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    String baseLibFolder = PKG_PREFIX + "/classpath_lib_base";
+    baseLibJar =
+        kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addSourceFiles(getKotlinFileInTest(baseLibFolder, "itf"))
+            .compile();
+    String extLibFolder = PKG_PREFIX + "/classpath_lib_ext";
+    extLibJar =
+        kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addClasspathFiles(baseLibJar)
+            .addSourceFiles(getKotlinFileInTest(extLibFolder, "impl"))
+            .compile();
+  }
+
+  @Test
+  public void testMetadataInClasspathType_merged() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addClasspathFiles(baseLibJar)
+            .addProgramFiles(extLibJar)
+            // Keep the Extra class and its interface (which has the method).
+            .addKeepRules("-keep class **.Extra")
+            // Keep the ImplKt extension method which requires metadata
+            // to be called with Kotlin syntax from other kotlin code.
+            .addKeepRules("-keep class **.ImplKt { <methods>; }")
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .compile();
+    String pkg = getClass().getPackage().getName();
+    final String implClassName = pkg + ".classpath_lib_ext.Impl";
+    final String extraClassName = pkg + ".classpath_lib_ext.Extra";
+    compileResult.inspect(inspector -> {
+      ClassSubject impl = inspector.clazz(implClassName);
+      assertThat(impl, not(isPresent()));
+
+      ClassSubject extra = inspector.clazz(extraClassName);
+      assertThat(extra, isPresent());
+      assertThat(extra, not(isRenamed()));
+      // API entry is kept, hence the presence of Metadata.
+      KmClassSubject kmClass = extra.getKmClass();
+      assertThat(kmClass, isPresent());
+      List<ClassSubject> superTypes = kmClass.getSuperTypes();
+      assertTrue(superTypes.stream().noneMatch(
+          supertype -> supertype.getFinalDescriptor().contains("Impl")));
+      // Can't build ClassSubject with Itf in classpath. Instead, check if the reference to Itf is
+      // not altered via descriptors.
+      List<String> superTypeDescriptors = kmClass.getSuperTypeDescriptors();
+      assertTrue(superTypeDescriptors.stream().noneMatch(supertype -> supertype.contains("Impl")));
+      assertTrue(superTypeDescriptors.stream().anyMatch(supertype -> supertype.contains("Itf")));
+    });
+
+    Path libJar = compileResult.writeToZip();
+
+    String appFolder = PKG_PREFIX + "/classpath_app";
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addClasspathFiles(baseLibJar, libJar)
+            .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), baseLibJar, libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), pkg + ".classpath_app.MainKt")
+        .assertSuccessWithOutputLines("Impl::foo");
+  }
+
+  @Test
+  public void testMetadataInClasspathType_renamed() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addClasspathFiles(baseLibJar)
+            .addProgramFiles(extLibJar)
+            // Keep the Extra class and its interface (which has the method).
+            .addKeepRules("-keep class **.Extra")
+            // Keep Super, but allow minification.
+            .addKeepRules("-keep,allowobfuscation class **.Impl")
+            // Keep the ImplKt extension method which requires metadata
+            // to be called with Kotlin syntax from other kotlin code.
+            .addKeepRules("-keep class **.ImplKt { <methods>; }")
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .compile();
+    String pkg = getClass().getPackage().getName();
+    final String implClassName = pkg + ".classpath_lib_ext.Impl";
+    final String extraClassName = pkg + ".classpath_lib_ext.Extra";
+    compileResult.inspect(inspector -> {
+      ClassSubject impl = inspector.clazz(implClassName);
+      assertThat(impl, isPresent());
+      assertThat(impl, isRenamed());
+
+      ClassSubject extra = inspector.clazz(extraClassName);
+      assertThat(extra, isPresent());
+      assertThat(extra, not(isRenamed()));
+      // API entry is kept, hence the presence of Metadata.
+      KmClassSubject kmClass = extra.getKmClass();
+      assertThat(kmClass, isPresent());
+      List<ClassSubject> superTypes = kmClass.getSuperTypes();
+      assertTrue(superTypes.stream().noneMatch(
+          supertype -> supertype.getFinalDescriptor().contains("Impl")));
+      assertTrue(superTypes.stream().anyMatch(
+          supertype -> supertype.getFinalDescriptor().equals(impl.getFinalDescriptor())));
+    });
+
+    Path libJar = compileResult.writeToZip();
+
+    String appFolder = PKG_PREFIX + "/classpath_app";
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addClasspathFiles(baseLibJar, libJar)
+            .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), baseLibJar, libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), pkg + ".classpath_app.MainKt")
+        .assertSuccessWithOutputLines("Impl::foo");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
index 640b18b..e7692bc 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
@@ -6,17 +6,14 @@
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
@@ -90,16 +87,18 @@
     Path libJar = compileResult.writeToZip();
 
     String appFolder = PKG_PREFIX + "/extension_app";
-    ProcessResult kotlinTestCompileResult =
+    Path output =
         kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
             .setOutputPath(temp.newFolder().toPath())
-            // TODO(b/143687784): update to just .compile() once fixed.
-            .compileRaw();
-    // TODO(b/143687784): should be able to compile!
-    assertNotEquals(0, kotlinTestCompileResult.exitCode);
-    assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: doStuff"));
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), pkg + ".extension_app.MainKt")
+        .assertSuccessWithOutputLines("do stuff", "do stuff");
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt
new file mode 100644
index 0000000..3c1585a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt
@@ -0,0 +1,11 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin.metadata.classpath_app
+
+import com.android.tools.r8.kotlin.metadata.classpath_lib_ext.Extra
+import com.android.tools.r8.kotlin.metadata.classpath_lib_ext.extension
+
+fun main() {
+  Extra().extension()
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_base/itf.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_base/itf.kt
new file mode 100644
index 0000000..9df88be
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_base/itf.kt
@@ -0,0 +1,8 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin.metadata.classpath_lib_base
+
+interface Itf {
+  fun foo()
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt
new file mode 100644
index 0000000..975865b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt
@@ -0,0 +1,18 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin.metadata.classpath_lib_ext
+
+import com.android.tools.r8.kotlin.metadata.classpath_lib_base.Itf
+
+open class Impl : Itf {
+  override fun foo() {
+    println("Impl::foo")
+  }
+}
+
+class Extra : Impl()
+
+fun Extra.extension() {
+  foo()
+}
\ No newline at end of file