Restore links between multi-file class facade and parts

Bug: 70169921
Change-Id: I3385ae359e99503977505c2a0134fc860170b4fa
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 07ec533..fee14fa 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -4,15 +4,25 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedBinaryName;
+
 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 java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 public final class KotlinClassFacade extends KotlinInfo<KotlinClassMetadata.MultiFileClassFacade> {
 
+  // TODO(b/70169921): is it better to maintain List<DexType>?
+  List<String> partClassNames;
+
   static KotlinClassFacade fromKotlinClassMetadata(
       KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
     assert kotlinClassMetadata instanceof KotlinClassMetadata.MultiFileClassFacade;
@@ -29,21 +39,31 @@
   void processMetadata() {
     assert !isProcessed;
     isProcessed = true;
-    // No API to explore metadata details, hence nothing to do further.
+    // Part Class names are stored in `d1`, which is immutable. Make a copy instead.
+    partClassNames = new ArrayList<>(metadata.getPartClassNames());
+    // No API to explore metadata details, hence nothing further to do.
   }
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    // TODO(b/70169921): no idea yet!
-    assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
-            || appView.options().enableKotlinMetadataRewritingForRenamedClasses
-        : toString();
+    ListIterator<String> partClassIterator = partClassNames.listIterator();
+    while (partClassIterator.hasNext()) {
+      String partClassName = partClassIterator.next();
+      partClassIterator.remove();
+      DexType partClassType = appView.dexItemFactory().createType(
+          DescriptorUtils.getDescriptorFromClassBinaryName(partClassName));
+      String renamedPartClassName = toRenamedBinaryName(partClassType, appView, lens);
+      if (renamedPartClassName != null) {
+        partClassIterator.add(renamedPartClassName);
+      }
+    }
   }
 
   @Override
   KotlinClassHeader createHeader() {
-    // TODO(b/70169921): may need to update if `rewrite` is implemented.
-    return metadata.getHeader();
+    KotlinClassMetadata.MultiFileClassFacade.Writer writer =
+        new KotlinClassMetadata.MultiFileClassFacade.Writer();
+    return writer.write(partClassNames).getHeader();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index 58c5aff..17a30ef 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -4,10 +4,14 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedBinaryName;
+
 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.KmPackage;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -15,6 +19,8 @@
 public final class KotlinClassPart extends KotlinInfo<KotlinClassMetadata.MultiFileClassPart> {
 
   KmPackage kmPackage;
+  // TODO(b/70169921): is it better to maintain DexType?
+  String facadeClassName;
 
   static KotlinClassPart fromKotlinClassMetadata(
       KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
@@ -33,10 +39,14 @@
     assert !isProcessed;
     isProcessed = true;
     kmPackage = metadata.toKmPackage();
+    facadeClassName = metadata.getFacadeClassName();
   }
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    DexType facadeClassType = appView.dexItemFactory().createType(
+        DescriptorUtils.getDescriptorFromClassBinaryName(facadeClassName));
+    facadeClassName = toRenamedBinaryName(facadeClassType, appView, lens);
     if (!appView.options().enableKotlinMetadataRewritingForMembers) {
       return;
     }
@@ -45,10 +55,17 @@
 
   @Override
   KotlinClassHeader createHeader() {
-    KotlinClassMetadata.MultiFileClassPart.Writer writer =
-        new KotlinClassMetadata.MultiFileClassPart.Writer();
-    kmPackage.accept(writer);
-    return writer.write(metadata.getFacadeClassName()).getHeader();
+    if (facadeClassName != null) {
+      KotlinClassMetadata.MultiFileClassPart.Writer writer =
+          new KotlinClassMetadata.MultiFileClassPart.Writer();
+      kmPackage.accept(writer);
+      return writer.write(facadeClassName).getHeader();
+    } else {
+      // It's no longer part of multi-file class.
+      KotlinClassMetadata.FileFacade.Writer writer = new KotlinClassMetadata.FileFacade.Writer();
+      kmPackage.accept(writer);
+      return writer.write().getHeader();
+    }
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
index 6ff14a3..b16d90a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmFieldSignature;
 import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmMethodSignature;
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromDescriptor;
 import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKmType;
 import static com.android.tools.r8.kotlin.Kotlin.NAME;
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToKotlinClassifier;
@@ -44,6 +45,30 @@
     return kmType;
   }
 
+  static DexType toRenamedType(
+      DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    // For library or classpath class, synthesize @Metadata always.
+    // For a program class, make sure it is live.
+    if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(type)) {
+      return null;
+    }
+    DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
+    // For library or classpath class, we should not have renamed it.
+    DexClass clazz = appView.definitionFor(type);
+    assert clazz == null || clazz.isProgramClass() || renamedType == type
+        : type.toSourceString() + " -> " + renamedType.toSourceString();
+    return renamedType;
+  }
+
+  static String toRenamedBinaryName(
+      DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    DexType renamedType = toRenamedType(type, appView, lens);
+    if (renamedType == null) {
+      return null;
+    }
+    return getBinaryNameFromDescriptor(renamedType.toDescriptorString());
+  }
+
   static String toRenamedClassifier(
       DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
     // E.g., V -> kotlin/Unit, J -> kotlin/Long, [J -> kotlin/LongArray
@@ -56,16 +81,10 @@
     if (type.isArrayType()) {
       return NAME + "/Array";
     }
-    // For library or classpath class, synthesize @Metadata always.
-    // For a program class, make sure it is live.
-    if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(type)) {
+    DexType renamedType = toRenamedType(type, appView, lens);
+    if (renamedType == null) {
       return null;
     }
-    DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
-    // For library or classpath class, we should not have renamed it.
-    DexClass clazz = appView.definitionFor(type);
-    assert clazz == null || clazz.isProgramClass() || renamedType == type
-        : type.toSourceString() + " -> " + renamedType.toSourceString();
     return descriptorToKotlinClassifier(renamedType.toDescriptorString());
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
index 2dc57bf..5b9dfed 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
@@ -4,25 +4,34 @@
 package com.android.tools.r8.kotlin.metadata;
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
 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.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 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.DescriptorUtils;
 import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
+import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -88,7 +97,6 @@
 
   private void inspectMerged(CodeInspector inspector) {
     String utilClassName = PKG + ".multifileclass_lib.UtilKt";
-    String signedClassName = PKG + ".multifileclass_lib.UtilKt__SignedKt";
 
     ClassSubject util = inspector.clazz(utilClassName);
     assertThat(util, isPresent());
@@ -98,22 +106,10 @@
     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, isRenamed());
-    commaJoinOfInt = signed.uniqueMethodWithName("commaSeparatedJoinOfInt");
-    assertThat(commaJoinOfInt, isPresent());
-    assertThat(commaJoinOfInt, not(isRenamed()));
-    joinOfInt = signed.uniqueMethodWithName("joinOfInt");
-    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.
+    inspectMetadataForFacade(inspector, util);
+
+    inspectSignedKt(inspector);
   }
 
   @Test
@@ -146,7 +142,6 @@
 
   private void inspectRenamed(CodeInspector inspector) {
     String utilClassName = PKG + ".multifileclass_lib.UtilKt";
-    String signedClassName = PKG + ".multifileclass_lib.UtilKt__SignedKt";
 
     ClassSubject util = inspector.clazz(utilClassName);
     assertThat(util, isPresent());
@@ -157,21 +152,48 @@
     MethodSubject joinOfInt = util.uniqueMethodWithName("joinOfInt");
     assertThat(joinOfInt, isPresent());
     assertThat(joinOfInt, isRenamed());
+
+    inspectMetadataForFacade(inspector, util);
+
+    inspectSignedKt(inspector);
+  }
+
+  private void inspectMetadataForFacade(CodeInspector inspector, ClassSubject util) {
     // API entry is kept, hence the presence of Metadata.
     AnnotationSubject annotationSubject = util.annotation(METADATA_TYPE);
     assertThat(annotationSubject, isPresent());
-    // TODO(b/70169921): need further inspection.
+    KotlinClassMetadata metadata = util.getKotlinClassMetadata();
+    assertNotNull(metadata);
+    assertTrue(metadata instanceof KotlinClassMetadata.MultiFileClassFacade);
+    KotlinClassMetadata.MultiFileClassFacade facade =
+        (KotlinClassMetadata.MultiFileClassFacade) metadata;
+    List<String> partClassNames = facade.getPartClassNames();
+    assertEquals(2, partClassNames.size());
+    for (String partClassName : partClassNames) {
+      ClassSubject partClass =
+          inspector.clazz(DescriptorUtils.getJavaTypeFromBinaryName(partClassName));
+      assertThat(partClass, isRenamed());
+    }
+  }
 
+  private void inspectSignedKt(CodeInspector inspector) {
+    String signedClassName = PKG + ".multifileclass_lib.UtilKt__SignedKt";
     ClassSubject signed = inspector.clazz(signedClassName);
     assertThat(signed, isRenamed());
-    commaJoinOfInt = signed.uniqueMethodWithName("commaSeparatedJoinOfInt");
+    MethodSubject commaJoinOfInt = signed.uniqueMethodWithName("commaSeparatedJoinOfInt");
     assertThat(commaJoinOfInt, isPresent());
     assertThat(commaJoinOfInt, not(isRenamed()));
-    joinOfInt = signed.uniqueMethodWithName("joinOfInt");
+    MethodSubject joinOfInt = signed.uniqueMethodWithName("joinOfInt");
     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.
+    KmPackageSubject kmPackage = signed.getKmPackage();
+    assertThat(kmPackage, isPresent());
+    KmFunctionSubject kmFunction =
+        kmPackage.kmFunctionExtensionWithUniqueName("commaSeparatedJoinOfInt");
+    assertThat(kmFunction, isPresent());
+    assertThat(kmFunction, isExtensionFunction());
+    // TODO(b/70169921): Inspect that parameter type has a correct type argument, Int.
+    // TODO(b/70169921): Inspect that the name in KmFunction is still 'join' so that apps can refer.
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index a471b18..7331a22 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexClass;
 import java.util.List;
 import java.util.function.Consumer;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 public class AbsentClassSubject extends ClassSubject {
 
@@ -147,4 +148,9 @@
   public KmPackageSubject getKmPackage() {
     return null;
   }
+
+  @Override
+  public KotlinClassMetadata getKotlinClassMetadata() {
+    return null;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index d72d1a8..b693ad9 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -18,6 +18,7 @@
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 public abstract class ClassSubject extends Subject {
 
@@ -174,4 +175,6 @@
   public abstract KmClassSubject getKmClass();
 
   public abstract KmPackageSubject getKmPackage();
+
+  public abstract KotlinClassMetadata getKotlinClassMetadata();
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index c0dba89..dc65b71 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -355,8 +355,25 @@
     KotlinClassMetadata metadata =
         KotlinClassMetadataReader.toKotlinClassMetadata(
             codeInspector.getFactory().kotlin, annotationSubject.getAnnotation());
-    assertTrue(metadata instanceof KotlinClassMetadata.FileFacade);
-    KotlinClassMetadata.FileFacade kFile = (KotlinClassMetadata.FileFacade) metadata;
-    return new FoundKmPackageSubject(codeInspector, getDexClass(), kFile.toKmPackage());
+    assertTrue(metadata instanceof KotlinClassMetadata.FileFacade
+        || metadata instanceof KotlinClassMetadata.MultiFileClassPart);
+    if (metadata instanceof KotlinClassMetadata.FileFacade) {
+      KotlinClassMetadata.FileFacade kFile = (KotlinClassMetadata.FileFacade) metadata;
+      return new FoundKmPackageSubject(codeInspector, getDexClass(), kFile.toKmPackage());
+    } else {
+      KotlinClassMetadata.MultiFileClassPart kPart =
+          (KotlinClassMetadata.MultiFileClassPart) metadata;
+      return new FoundKmPackageSubject(codeInspector, getDexClass(), kPart.toKmPackage());
+    }
+  }
+
+  @Override
+  public KotlinClassMetadata getKotlinClassMetadata() {
+    AnnotationSubject annotationSubject = annotation(METADATA_TYPE);
+    if (!annotationSubject.isPresent()) {
+      return null;
+    }
+    return KotlinClassMetadataReader.toKotlinClassMetadata(
+        codeInspector.getFactory().kotlin, annotationSubject.getAnnotation());
   }
 }