Allow @Metadata from multi-file class.

Bug: 145941814, 70169921
Change-Id: I0cfe3a669e3a7b7842cb1ed2e0ba71a44e94c102
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
index a24055e..f8c1c12 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.kotlin;
 
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.naming.NamingLens;
@@ -35,12 +34,15 @@
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    throw new Unreachable(toString());
+    // TODO(b/70169921): no idea yet!
+    assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
+        : toString();
   }
 
   @Override
   KotlinClassHeader createHeader() {
-    throw new Unreachable(toString());
+    // TODO(b/70169921): may need to update if `rewrite` is implemented.
+    return metadata.getHeader();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index c05b48f..a195f97 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -83,10 +83,13 @@
     if (kMetadata instanceof KotlinClassMetadata.Class) {
       return KotlinClass.fromKotlinClassMetadata(kMetadata, clazz);
     } else if (kMetadata instanceof KotlinClassMetadata.FileFacade) {
+      // e.g., B.kt becomes class `BKt`
       return KotlinFile.fromKotlinClassMetadata(kMetadata, clazz);
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
+      // multi-file class with the same @JvmName.
       return KotlinClassFacade.fromKotlinClassMetadata(kMetadata, clazz);
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) {
+      // A single file, which is part of multi-file class.
       return KotlinClassPart.fromKotlinClassMetadata(kMetadata, clazz);
     } else if (kMetadata instanceof KotlinClassMetadata.SyntheticClass) {
       return KotlinSyntheticClass.fromKotlinClassMetadata(kMetadata, kotlin, clazz);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index 054c5e2..5658b0d 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -66,9 +66,6 @@
         clazz -> {
           KotlinInfo<?> kotlinInfo = clazz.getKotlinInfo();
           if (kotlinInfo != null) {
-            assert kotlinInfo.isClass()
-                || kotlinInfo.isSyntheticClass()
-                || kotlinInfo.isFile(); // e.g., B.kt becomes class `BKt`
             // If @Metadata is still associated, this class should not be renamed
             // (by {@link ClassNameMinifier} of course).
             assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInMultifileClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInMultifileClassTest.java
new file mode 100644
index 0000000..9280756
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInMultifileClassTest.java
@@ -0,0 +1,176 @@
+// 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.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+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.AnnotationSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRenameInMultifileClassTest 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 MetadataRenameInMultifileClassTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static Path multifileLibJar;
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    String multifileLibFolder = PKG_PREFIX + "/multifileclass_lib";
+    multifileLibJar =
+        kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addSourceFiles(
+                getKotlinFileInTest(multifileLibFolder, "signed"),
+                getKotlinFileInTest(multifileLibFolder, "unsigned"))
+            .compile();
+  }
+
+  @Test
+  public void testMetadataInMultifileClass_merged() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(multifileLibJar)
+            // Keep UtilKt#comma*Join*(). Let R8 optimize (inline) others, such as joinOf*(String).
+            .addKeepRules("-keep class **.UtilKt")
+            .addKeepRules("-keepclassmembers class * { ** comma*Join*(...); }")
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .compile();
+    String pkg = getClass().getPackage().getName();
+    final String utilClassName = pkg + ".multifileclass_lib.UtilKt";
+    final String signedClassName = pkg + ".multifileclass_lib.UtilKt__SignedKt";
+    compileResult.inspect(inspector -> {
+      ClassSubject util = inspector.clazz(utilClassName);
+      assertThat(util, isPresent());
+      assertThat(util, not(isRenamed()));
+      MethodSubject commaJoinOfInt = util.uniqueMethodWithName("commaSeparatedJoinOfInt");
+      assertThat(commaJoinOfInt, isPresent());
+      assertThat(commaJoinOfInt, not(isRenamed()));
+      MethodSubject joinOfInt = util.uniqueMethodWithName("joinOfInt");
+      assertThat(joinOfInt, not(isPresent()));
+      // API entry is kept, hence the presence of Metadata.
+      AnnotationSubject annotationSubject = util.annotation(METADATA_TYPE);
+      assertThat(annotationSubject, isPresent());
+      // TODO(b/70169921): need further inspection.
+
+      ClassSubject signed = inspector.clazz(signedClassName);
+      assertThat(signed, isPresent());
+      assertThat(signed, isRenamed());
+      commaJoinOfInt = signed.uniqueMethodWithName("commaSeparatedJoinOfInt");
+      assertThat(commaJoinOfInt, isPresent());
+      assertThat(commaJoinOfInt, not(isRenamed()));
+      joinOfInt = signed.uniqueMethodWithName("joinOfInt");
+      assertThat(joinOfInt, isPresent());
+      assertThat(joinOfInt, isRenamed());
+      // API entry is kept, hence the presence of Metadata.
+      annotationSubject = util.annotation(METADATA_TYPE);
+      assertThat(annotationSubject, isPresent());
+      // TODO(b/70169921): need further inspection.
+    });
+
+    Path libJar = compileResult.writeToZip();
+
+    String appFolder = PKG_PREFIX + "/multifileclass_app";
+    ProcessResult kotlinTestCompileResult =
+        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/70169921): should be able to compile!
+    assertNotEquals(0, kotlinTestCompileResult.exitCode);
+    assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: join"));
+  }
+
+  @Test
+  public void testMetadataInMultifileClass_renamed() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(multifileLibJar)
+            // Keep UtilKt#comma*Join*().
+            .addKeepRules("-keep class **.UtilKt")
+            .addKeepRules("-keepclassmembers class * { ** comma*Join*(...); }")
+            // Keep yet rename joinOf*(String).
+            .addKeepRules(
+                "-keepclassmembers,allowobfuscation class * { ** joinOf*(...); }")
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .compile();
+    String pkg = getClass().getPackage().getName();
+    final String utilClassName = pkg + ".multifileclass_lib.UtilKt";
+    final String signedClassName = pkg + ".multifileclass_lib.UtilKt__SignedKt";
+    compileResult.inspect(inspector -> {
+      ClassSubject util = inspector.clazz(utilClassName);
+      assertThat(util, isPresent());
+      assertThat(util, not(isRenamed()));
+      MethodSubject commaJoinOfInt = util.uniqueMethodWithName("commaSeparatedJoinOfInt");
+      assertThat(commaJoinOfInt, isPresent());
+      assertThat(commaJoinOfInt, not(isRenamed()));
+      MethodSubject joinOfInt = util.uniqueMethodWithName("joinOfInt");
+      assertThat(joinOfInt, isPresent());
+      assertThat(joinOfInt, isRenamed());
+      // API entry is kept, hence the presence of Metadata.
+      AnnotationSubject annotationSubject = util.annotation(METADATA_TYPE);
+      assertThat(annotationSubject, isPresent());
+      // TODO(b/70169921): need further inspection.
+
+      ClassSubject signed = inspector.clazz(signedClassName);
+      assertThat(signed, isPresent());
+      assertThat(signed, isRenamed());
+      commaJoinOfInt = signed.uniqueMethodWithName("commaSeparatedJoinOfInt");
+      assertThat(commaJoinOfInt, isPresent());
+      assertThat(commaJoinOfInt, not(isRenamed()));
+      joinOfInt = signed.uniqueMethodWithName("joinOfInt");
+      assertThat(joinOfInt, isPresent());
+      assertThat(joinOfInt, isRenamed());
+      // API entry is kept, hence the presence of Metadata.
+      annotationSubject = util.annotation(METADATA_TYPE);
+      assertThat(annotationSubject, isPresent());
+      // TODO(b/70169921): need further inspection.
+    });
+
+    Path libJar = compileResult.writeToZip();
+
+    String appFolder = PKG_PREFIX + "/multifileclass_app";
+    ProcessResult kotlinTestCompileResult =
+        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/70169921): should be able to compile!
+    assertNotEquals(0, kotlinTestCompileResult.exitCode);
+    assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: join"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/multifileclass_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/multifileclass_app/main.kt
new file mode 100644
index 0000000..ce098bf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/multifileclass_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.multifileclass_app
+
+import com.android.tools.r8.kotlin.metadata.multifileclass_lib.join
+
+fun main() {
+  val s = sequenceOf(1, 2, 3)
+  println(s.join())
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/multifileclass_lib/signed.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/multifileclass_lib/signed.kt
new file mode 100644
index 0000000..81c4994
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/multifileclass_lib/signed.kt
@@ -0,0 +1,16 @@
+// 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.
+@file:kotlin.jvm.JvmMultifileClass
+@file:kotlin.jvm.JvmName("UtilKt")
+package com.android.tools.r8.kotlin.metadata.multifileclass_lib
+
+@kotlin.jvm.JvmName("joinOfInt")
+public fun Sequence<Int>.join(separator: String): String {
+  return fold("") { acc, i -> "$acc$separator$i" }
+}
+
+@kotlin.jvm.JvmName("commaSeparatedJoinOfInt")
+public fun Sequence<Int>.join(): String {
+  return join(", ")
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/multifileclass_lib/unsigned.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/multifileclass_lib/unsigned.kt
new file mode 100644
index 0000000..25dac69
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/multifileclass_lib/unsigned.kt
@@ -0,0 +1,20 @@
+// 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.
+@file:kotlin.jvm.JvmMultifileClass
+@file:kotlin.jvm.JvmName("UtilKt")
+package com.android.tools.r8.kotlin.metadata.multifileclass_lib
+
+@SinceKotlin("1.3")
+@ExperimentalUnsignedTypes
+@kotlin.jvm.JvmName("joinOfUInt")
+public fun Sequence<UInt>.join(separator: String): String {
+  return fold("") { acc, i -> "$acc$separator$i" }
+}
+
+@SinceKotlin("1.3")
+@ExperimentalUnsignedTypes
+@kotlin.jvm.JvmName("commaSeparatedJoinOfUInt")
+public fun Sequence<UInt>.join(): String {
+  return join(", ")
+}