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