[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();
}