Towards synthesizing Kotlin @Metadata: sealedSubclasses in KmClass.

Bug: 70169921
Change-Id: Ifb9e36bc06d40fbbb6526d53917f4e0b8d878424
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 861da28..0edb554 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -6,8 +6,10 @@
 
 import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toKmType;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedClassifier;
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmConstructor;
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmType;
+import static kotlinx.metadata.Flag.IS_SEALED;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -81,7 +83,16 @@
 
     rewriteDeclarationContainer(kmClass, appView, lens);
 
-    // TODO(b/70169921): companion
+    List<String> sealedSubclasses = kmClass.getSealedSubclasses();
+    sealedSubclasses.clear();
+    if (IS_SEALED.invoke(kmClass.getFlags())) {
+      for (DexType subtype : appView.appInfo().allImmediateSubtypes(clazz.type)) {
+        String classifier = toRenamedClassifier(subtype, appView, lens);
+        if (classifier != null) {
+          sealedSubclasses.add(classifier);
+        }
+      }
+    }
   }
 
   @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 5ce0e97..8a8773a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
 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.descriptorToInternalName;
 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;
 import static kotlinx.metadata.FlagsKt.flagsOf;
 
 import com.android.tools.r8.graph.AppView;
@@ -40,23 +40,21 @@
 
   static KmType toKmType(String descriptor) {
     KmType kmType = new KmType(flagsOf());
-    kmType.visitClass(descriptorToInternalName(descriptor));
+    kmType.visitClass(descriptorToKotlinClassifier(descriptor));
     return kmType;
   }
 
-  static KmType toRenamedKmType(
+  static String toRenamedClassifier(
       DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    // E.g., [Ljava/lang/String; -> Lkotlin/Array;
+    // E.g., [Ljava/lang/String; -> kotlin/Array
     if (type.isArrayType()) {
-      return toKmType(addKotlinPrefix("Array;"));
+      return NAME + "/Array";
     }
-    // E.g., void -> Lkotlin/Unit;
+    // E.g., void -> kotlin/Unit
     if (appView.dexItemFactory().kotlin.knownTypeConversion.containsKey(type)) {
-      KmType kmType = new KmType(flagsOf());
       DexType convertedType = appView.dexItemFactory().kotlin.knownTypeConversion.get(type);
       assert convertedType != null;
-      kmType.visitClass(descriptorToInternalName(convertedType.toDescriptorString()));
-      return kmType;
+      return descriptorToKotlinClassifier(convertedType.toDescriptorString());
     }
     // For library or classpath class, synthesize @Metadata always.
     // For a program class, make sure it is live.
@@ -68,10 +66,19 @@
     DexClass clazz = appView.definitionFor(type);
     assert clazz == null || clazz.isProgramClass() || renamedType == type
         : type.toSourceString() + " -> " + renamedType.toSourceString();
+    return descriptorToKotlinClassifier(renamedType.toDescriptorString());
+  }
+
+  static KmType toRenamedKmType(
+      DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    String classifier = toRenamedClassifier(type, appView, lens);
+    if (classifier == null) {
+      return null;
+    }
     // TODO(b/70169921): Mysterious, why attempts to properly set flags bothers kotlinc?
     //   and/or why wiping out flags works for KmType but not KmFunction?!
     KmType kmType = new KmType(flagsOf());
-    kmType.visitClass(descriptorToInternalName(renamedType.toDescriptorString()));
+    kmType.visitClass(classifier);
     return kmType;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index fd51973..70d6df0 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -178,6 +178,16 @@
   }
 
   /**
+   * Convert a descriptor to a classifier in Kotlin metadata
+   * @param descriptor like "Lorg/foo/bar/Baz$Nested;"
+   * @return className "org/foo/bar/Baz.Nested"
+   */
+  public static String descriptorToKotlinClassifier(String descriptor) {
+    return getBinaryNameFromDescriptor(descriptor)
+        .replace(INNER_CLASS_SEPARATOR, JAVA_PACKAGE_SEPARATOR);
+  }
+
+  /**
    * Convert a type descriptor to a Java type name. Will also deobfuscate class names if a
    * class mapper is provided.
    *
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSealedClassTest.java
index 725576d..a2d4fa1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSealedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSealedClassTest.java
@@ -11,8 +11,9 @@
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
@@ -90,10 +91,8 @@
       kmClass.getSealedSubclassDescriptors().forEach(sealedSubclassDescriptor -> {
         ClassSubject sealedSubclass =
             inspector.clazz(descriptorToJavaType(sealedSubclassDescriptor));
-        // TODO(b/70169921): sealedSubclasses in KmClass should refer to live classes.
-        // assertThat(sealedSubclass, isRenamed());
-        // TODO(b/70169921): sealedSubclasses in KmClass should refer to renamed classes.
-        assertNotEquals(sealedSubclassDescriptor, sealedSubclass.getFinalDescriptor());
+        assertThat(sealedSubclass, isRenamed());
+        assertEquals(sealedSubclassDescriptor, sealedSubclass.getFinalDescriptor());
       });
 
       ClassSubject libKt = inspector.clazz(libClassName);
@@ -153,8 +152,7 @@
       KmClassSubject kmClass = expr.getKmClass();
       assertThat(kmClass, isPresent());
 
-      // TODO(b/70169921): Then, we can cleanup sealedSubclasses in KmClass
-      assertFalse(kmClass.getSealedSubclassDescriptors().isEmpty());
+      assertTrue(kmClass.getSealedSubclassDescriptors().isEmpty());
 
       ClassSubject libKt = inspector.clazz(libClassName);
       assertThat(expr, isPresent());