Rewrite Kotlin @Metadata on renamed classes.

Bug: 70169921, 147447503, 143687784
Change-Id: I616f1c1adef5408c84854dbfd8b7d3451509e700
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 0edb554..9ac3a82 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -48,6 +48,16 @@
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    if (appView.options().enableKotlinMetadataRewritingForRenamedClasses
+        && lens.lookupType(clazz.type, appView.dexItemFactory()) != clazz.type) {
+      String renamedClassifier = toRenamedClassifier(clazz.type, appView, lens);
+      if (renamedClassifier != null) {
+        assert !kmClass.getName().equals(renamedClassifier);
+        kmClass.setName(renamedClassifier);
+      }
+    }
+
+    // Rewriting upward hierarchy.
     List<KmType> superTypes = kmClass.getSupertypes();
     superTypes.clear();
     for (DexType itfType : clazz.interfaces.values) {
@@ -66,7 +76,8 @@
       superTypes.add(toKmType(addKotlinPrefix("Any;")));
     }
 
-    if (!appView.options().enableKotlinMetadataRewriting) {
+    // TODO(b/70169921): downward hierarchy is harmless. Move above member-rewriting flag.
+    if (!appView.options().enableKotlinMetadataRewritingForMembers) {
       return;
     }
     List<KmConstructor> constructors = kmClass.getConstructors();
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 f8c1c12..07ec533 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -36,6 +36,7 @@
   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();
   }
 
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 94a993b..58c5aff 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -37,7 +37,7 @@
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    if (!appView.options().enableKotlinMetadataRewriting) {
+    if (!appView.options().enableKotlinMetadataRewritingForMembers) {
       return;
     }
     rewriteDeclarationContainer(kmPackage, appView, lens);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
index 9eb70f8..5eac335 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -37,7 +37,7 @@
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    if (!appView.options().enableKotlinMetadataRewriting) {
+    if (!appView.options().enableKotlinMetadataRewritingForMembers) {
       return;
     }
     rewriteDeclarationContainer(kmPackage, appView, lens);
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 5658b0d..b295da3 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -68,7 +68,10 @@
           if (kotlinInfo != null) {
             // If @Metadata is still associated, this class should not be renamed
             // (by {@link ClassNameMinifier} of course).
+            // Or, we start maintaining @Metadata for renamed classes.
+            // TODO(b/70169921): if this option is settled down, this assertion is meaningless.
             assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
+                    || appView.options().enableKotlinMetadataRewritingForRenamedClasses
                 : clazz.toSourceString() + " != "
                     + lens.lookupType(clazz.type, appView.dexItemFactory());
 
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 8a8773a..c46b94a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -128,7 +128,7 @@
     }
     DexMethod renamedMethod = lens.lookupMethod(method.method, appView.dexItemFactory());
     // For a library method override, we should not have renamed it.
-    assert !method.isLibraryMethodOverride().isTrue() || renamedMethod == method.method
+    assert !method.isLibraryMethodOverride().isTrue() || renamedMethod.name == method.method.name
         : method.toSourceString() + " -> " + renamedMethod.toSourceString();
     // TODO(b/70169921): Should we keep kotlin-specific flags only while synthesizing the base
     //  value from general JVM flags?
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
index 59f7dce..28a6eb1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -53,6 +53,7 @@
   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();
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index d13cc3b..1c75720 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -115,7 +115,9 @@
       if (!renaming.containsKey(clazz.type)) {
         DexString renamed = computeName(clazz.type);
         renaming.put(clazz.type, renamed);
-        KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, clazz);
+        if (!appView.options().enableKotlinMetadataRewritingForRenamedClasses) {
+          KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, clazz);
+        }
         // If the class is a member class and it has used $ separator, its renamed name should have
         // the same separator (as long as inner-class attribute is honored).
         assert !keepInnerClassStructure
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 5b0400f..259d74a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -224,7 +224,11 @@
   public boolean enablePropagationOfDynamicTypesAtCallSites = true;
   // TODO(b/69963623): enable if everything is ready, including signature rewriting at call sites.
   public boolean enablePropagationOfConstantsAtCallSites = false;
-  public boolean enableKotlinMetadataRewriting = true;
+  // At 2.0, part of @Metadata up to this flag is rewritten, which is super-type hierarchy.
+  public boolean enableKotlinMetadataRewritingForMembers = true;
+  // Up to 2.0, Kotlin @Metadata is removed if the associated class is renamed.
+  // Under this flag, Kotlin @Metadata is generally kept and modified for all program classes.
+  public boolean enableKotlinMetadataRewritingForRenamedClasses = true;
   public boolean encodeChecksums = false;
   public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
   public boolean enableCfInterfaceMethodDesugaring = false;
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
index cd74f9b..6bfafc4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
@@ -4,11 +4,13 @@
 package com.android.tools.r8.kotlin.metadata;
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKotlinClassifier;
 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.equalTo;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
@@ -116,7 +118,7 @@
     // API entry is kept, hence @Metadata exists.
     KmClassSubject kmClass = kept.getKmClass();
     assertThat(kmClass, isPresent());
-    // TODO(b/70169921): check if `name` in @Metadata is equal to the (kept) class name.
+    assertEquals(kept.getFinalDescriptor(), getDescriptorFromKotlinClassifier(kmClass.getName()));
     // @Anno is kept.
     String annoName = pkg + ".anno.Anno";
     AnnotationSubject anno = kept.annotation(annoName);
@@ -127,9 +129,10 @@
     // @Anno is kept.
     anno = renamed.annotation(annoName);
     assertThat(anno, isPresent());
-    // TODO(b/147447503): But, R8 treats @Metadata specially.
+    // @Metadata is kept even though the class is renamed.
     kmClass = renamed.getKmClass();
-    assertThat(kmClass, not(isPresent()));
-    // TODO(b/70169921): check if `name` in @Metadata is equal to the (renamed) class name.
+    assertThat(kmClass, isPresent());
+    assertEquals(
+        renamed.getFinalDescriptor(), getDescriptorFromKotlinClassifier(kmClass.getName()));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index f93179f..31cd07d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -52,6 +52,8 @@
             .addKeepMainRule(mainClassName)
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
             .addKeepRules("-keep class kotlin.Metadata")
+            // TODO(b/70169921): if this option is settled down, this test is meaningless.
+            .addOptionsModification(o -> o.enableKotlinMetadataRewritingForRenamedClasses = false)
             .allowDiagnosticWarningMessages()
             .setMinApi(parameters.getApiLevel())
             .compile()
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
index 1a640ed..af619ea 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
@@ -9,6 +9,11 @@
 public class AbsentKmClassSubject extends KmClassSubject {
 
   @Override
+  public String getName() {
+    return null;
+  }
+
+  @Override
   public DexClass getDexClass() {
     return null;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
index 6c65c2d..39a3ce7 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
@@ -24,6 +24,11 @@
   }
 
   @Override
+  public String getName() {
+    return kmClass.getName();
+  }
+
+  @Override
   public DexClass getDexClass() {
     return clazz;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
index 97b233d..191956c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
@@ -7,6 +7,8 @@
 import java.util.List;
 
 public abstract class KmClassSubject extends Subject implements KmDeclarationContainerSubject {
+  public abstract String getName();
+
   public abstract DexClass getDexClass();
 
   public abstract List<String> getSuperTypeDescriptors();