[Metadata] Model enum values for fields

Bug: b/259389417
Change-Id: Ibbbb7c6971dc13992469f54236fe97caa8373887
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
index 100d61a..810f2de 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -20,6 +20,8 @@
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -157,7 +159,6 @@
             keepByteCode,
             extensionInformation,
             originalMembersWithKotlinInfo);
-    String companionObjectName = setCompanionObject(kmClass, hostClass, reporter);
     KotlinTypeReference anonymousObjectOrigin = getAnonymousObjectOrigin(kmClass, factory);
     boolean nameCanBeDeducedFromClassOrOrigin =
         kmClass.name.equals(
@@ -176,7 +177,7 @@
         getSuperTypes(kmClass.getSupertypes(), factory, reporter),
         getSealedSubClasses(kmClass.getSealedSubclasses(), factory),
         getNestedClasses(hostClass, kmClass.getNestedClasses(), factory),
-        kmClass.getEnumEntries(),
+        setEnumEntries(kmClass, hostClass),
         KotlinVersionRequirementInfo.create(kmClass.getVersionRequirements()),
         anonymousObjectOrigin,
         packageName,
@@ -187,7 +188,7 @@
         KotlinTypeInfo.create(kmClass.getInlineClassUnderlyingType(), factory, reporter),
         originalMembersWithKotlinInfo,
         JvmExtensionsKt.getJvmFlags(kmClass),
-        companionObjectName);
+        setCompanionObject(kmClass, hostClass, reporter));
   }
 
   private static KotlinTypeReference getAnonymousObjectOrigin(
@@ -248,6 +249,25 @@
     return companionObjectName;
   }
 
+  private static List<String> setEnumEntries(KmClass kmClass, DexClass hostClass) {
+    List<String> enumEntries = kmClass.getEnumEntries();
+    if (enumEntries.isEmpty()) {
+      return enumEntries;
+    }
+    Collection<String> enumEntryStrings =
+        enumEntries.size() < 16 ? enumEntries : Sets.newHashSet(enumEntries);
+    hostClass
+        .fields()
+        .forEach(
+            field -> {
+              String fieldName = field.getName().toString();
+              if (enumEntryStrings.contains(fieldName)) {
+                field.setKotlinMemberInfo(new KotlinEnumEntryInfo(fieldName));
+              }
+            });
+    return enumEntries;
+  }
+
   @Override
   public boolean isClass() {
     return true;
@@ -290,14 +310,24 @@
     }
     // Find a companion object.
     boolean foundCompanion = false;
+    int numberOfEnumEntries = 0;
     for (DexEncodedField field : clazz.fields()) {
-      if (field.getKotlinInfo().isCompanion()) {
+      KotlinFieldLevelInfo kotlinInfo = field.getKotlinInfo();
+      if (kotlinInfo.isCompanion()) {
         rewritten |=
-            field
-                .getKotlinInfo()
+            kotlinInfo
                 .asCompanion()
                 .rewrite(kmClass, field.getReference(), appView.getNamingLens());
         foundCompanion = true;
+      } else if (kotlinInfo.isEnumEntry()) {
+        KotlinEnumEntryInfo kotlinEnumEntryInfo = kotlinInfo.asEnumEntry();
+        rewritten |=
+            kotlinEnumEntryInfo.rewrite(kmClass, field.getReference(), appView.getNamingLens());
+        if (numberOfEnumEntries >= enumEntries.size()
+            || !enumEntries.get(numberOfEnumEntries).equals(kotlinEnumEntryInfo.getEnumEntry())) {
+          rewritten = true;
+        }
+        numberOfEnumEntries += 1;
       }
     }
     // If we did not find a companion but it was there on input we have to emit a new metadata
@@ -305,6 +335,11 @@
     if (!foundCompanion && companionObjectName != null) {
       rewritten = true;
     }
+    // If we could remove enum entries but were unable to rename them we still have to emit a new
+    // metadata object.
+    if (numberOfEnumEntries < enumEntries.size()) {
+      rewritten = true;
+    }
     // Take all not backed constructors because we will never find them in definitions.
     for (KotlinConstructorInfo constructorInfo : constructorsWithNoBacking) {
       rewritten |= constructorInfo.rewrite(kmClass, null, appView);
@@ -373,8 +408,6 @@
               appView,
               null);
     }
-    // TODO(b/154347404): Understand enum entries.
-    kmClass.getEnumEntries().addAll(enumEntries);
     rewritten |= versionRequirements.rewrite(kmClass::visitVersionRequirement);
     if (inlineClassUnderlyingPropertyName != null && inlineClassUnderlyingType != null) {
       kmClass.setInlineClassUnderlyingPropertyName(inlineClassUnderlyingPropertyName);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinEnumEntryInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinEnumEntryInfo.java
new file mode 100644
index 0000000..25e4ca7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinEnumEntryInfo.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2022, 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.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.naming.NamingLens;
+import kotlinx.metadata.KmClassVisitor;
+
+// Structure around a kotlin enum value that can be assigned to a field.
+public class KotlinEnumEntryInfo implements KotlinFieldLevelInfo {
+
+  private final String enumEntry;
+
+  public KotlinEnumEntryInfo(String enumEntry) {
+    this.enumEntry = enumEntry;
+  }
+
+  @Override
+  public boolean isEnumEntry() {
+    return true;
+  }
+
+  @Override
+  public KotlinEnumEntryInfo asEnumEntry() {
+    return this;
+  }
+
+  boolean rewrite(KmClassVisitor visitor, DexField field, NamingLens lens) {
+    DexString dexString = lens.lookupName(field);
+    String finalName = dexString.toString();
+    visitor.visitEnumEntry(finalName);
+    return !finalName.equals(enumEntry);
+  }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    // Do nothing.
+  }
+
+  public String getEnumEntry() {
+    return enumEntry;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberLevelInfo.java
index 41f23c1..7c6a6c1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberLevelInfo.java
@@ -43,4 +43,12 @@
   default KotlinPropertyInfo asProperty() {
     return null;
   }
+
+  default boolean isEnumEntry() {
+    return false;
+  }
+
+  default KotlinEnumEntryInfo asEnumEntry() {
+    return null;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteEnumTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteEnumTest.java
index 939eb30..b7c8c16 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteEnumTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteEnumTest.java
@@ -3,16 +3,23 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
+import static com.android.tools.r8.cf.methodhandles.fields.ClassFieldMethodHandleTest.Main.assertEquals;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import java.nio.file.Path;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
+import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -58,7 +65,7 @@
   }
 
   @Test
-  public void testR8() throws Exception {
+  public void testKotlincFailsRenamed() throws Exception {
     R8TestCompileResult r8libResult =
         testForR8(parameters.getBackend())
             .addProgramFiles(jarMap.getForConfiguration(kotlinc, targetVersion))
@@ -74,24 +81,60 @@
                   assertThat(direction, isPresentAndNotRenamed());
                   direction.allFields().forEach(field -> assertTrue(field.isRenamed()));
                 });
-    Path libJar = r8libResult.writeToZip();
+    ProcessResult kotlinCompileResult =
+        kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
+            .addClasspathFiles(r8libResult.writeToZip())
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/enum_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compileRaw();
+    assertEquals(1, kotlinCompileResult.exitCode);
+    assertThat(kotlinCompileResult.stderr, containsString("unresolved reference"));
+    assertThat(kotlinCompileResult.stderr, containsString("Direction.UP"));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8TestCompileResult r8libResult =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(jarMap.getForConfiguration(kotlinc, targetVersion))
+            .addClasspathFiles(kotlinc.getKotlinStdlibJar())
+            .addKeepKotlinMetadata()
+            .addKeepEnumsRule()
+            .addKeepClassRules(DIRECTION_TYPE_NAME)
+            .addKeepClassAndMembersRulesWithAllowObfuscation(DIRECTION_TYPE_NAME)
+            .compile()
+            .inspect(
+                inspector -> {
+                  ClassSubject direction = inspector.clazz(DIRECTION_TYPE_NAME);
+                  assertThat(direction, isPresentAndNotRenamed());
+                  direction.allFields().forEach(field -> assertTrue(field.isRenamed()));
+                  KmClassSubject kmClass = direction.getKmClass();
+                  List<String> expectedEnumNames = Arrays.asList("a", "b", "c", "d");
+                  Assert.assertEquals(expectedEnumNames, kmClass.getEnumEntries());
+                });
     Path output =
         kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
-            .addClasspathFiles(libJar)
+            .addClasspathFiles(jarMap.getForConfiguration(kotlinc, targetVersion))
             .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/enum_app", "main"))
             .compile();
     Path path =
         testForR8(parameters.getBackend())
-            .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinReflectJar(), libJar)
+            .addClasspathFiles(
+                kotlinc.getKotlinStdlibJar(),
+                kotlinc.getKotlinReflectJar(),
+                jarMap.getForConfiguration(kotlinc, targetVersion))
             .addProgramFiles(output)
             .addKeepAllClassesRule()
             .addApplyMapping(r8libResult.getProguardMap())
             .compile()
             .writeToZip();
-    // TODO(b/259389417): We should rename enum values in metadata.
     testForRuntime(parameters)
-        .addProgramFiles(libJar, kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinReflectJar(), path)
+        .addProgramFiles(
+            r8libResult.writeToZip(),
+            kotlinc.getKotlinStdlibJar(),
+            kotlinc.getKotlinReflectJar(),
+            path)
         .run(parameters.getRuntime(), PKG + ".enum_app.MainKt")
-        .assertFailureWithErrorThatThrows(NoSuchFieldError.class);
+        .assertSuccessWithOutputLines(EXPECTED);
   }
 }
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 d87e74e..1da8375 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
@@ -141,6 +141,11 @@
   }
 
   @Override
+  public List<String> getEnumEntries() {
+    return null;
+  }
+
+  @Override
   public String getCompanionObject() {
     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 cc9bfd0..34f7c4c 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
@@ -127,6 +127,11 @@
   }
 
   @Override
+  public List<String> getEnumEntries() {
+    return kmClass.getEnumEntries();
+  }
+
+  @Override
   public List<KmTypeParameter> getKmTypeParameters() {
     return kmClass.getTypeParameters();
   }
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 9d776d3..84b493a 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
@@ -26,5 +26,7 @@
 
   public abstract List<ClassSubject> getSealedSubclasses();
 
+  public abstract List<String> getEnumEntries();
+
   public abstract String getCompanionObject();
 }