Add initial support for printing kotlin metadata in AssemblyWriter

Bug: 148581822
Change-Id: I04612b531ee08a4af1e9c8dd2e80e3d807cef36e
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 74735f4..4eb9a85 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -7,6 +7,9 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
+import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.kotlin.KotlinClassMetadataReader;
+import com.android.tools.r8.kotlin.KotlinInfo;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.utils.CfgPrinter;
@@ -21,6 +24,7 @@
   private final boolean writeAnnotations;
   private final boolean writeIR;
   private final AppInfoWithSubtyping appInfo;
+  private final Kotlin kotlin;
   private final Timing timing = new Timing("AssemblyWriter");
 
   public AssemblyWriter(
@@ -41,6 +45,7 @@
     } else {
       this.appInfo = null;
     }
+    kotlin = new Kotlin(application.dexItemFactory);
   }
 
   @Override
@@ -59,7 +64,7 @@
     ps.println("# Bytecode for");
     ps.println("# Class: '" + clazzName + "'");
     if (writeAllClassInfo) {
-      writeAnnotations(clazz.annotations(), ps);
+      writeAnnotations(clazz, clazz.annotations(), ps);
       ps.println("# Flags: '" + clazz.accessFlags + "'");
       if (clazz.superType != application.dexItemFactory.objectType) {
         ps.println("# Extends: '" + clazz.superType.toSourceString() + "'");
@@ -87,7 +92,7 @@
       FieldSignature fieldSignature = naming != null
           ? naming.originalSignatureOf(field.field)
           : FieldSignature.fromDexField(field.field);
-      writeAnnotations(field.annotations(), ps);
+      writeAnnotations(null, field.annotations(), ps);
       ps.print(field.accessFlags + " ");
       ps.print(fieldSignature);
       if (field.isStatic() && field.hasExplicitStaticValue()) {
@@ -110,7 +115,7 @@
         : method.method.name.toString();
     ps.println("#");
     ps.println("# Method: '" + methodName + "':");
-    writeAnnotations(method.annotations(), ps);
+    writeAnnotations(null, method.annotations(), ps);
     ps.println("# " + method.accessFlags);
     ps.println("#");
     ps.println();
@@ -134,16 +139,17 @@
     ps.println(printer.toString());
   }
 
-  private void writeAnnotations(DexAnnotationSet annotations, PrintStream ps) {
+  private void writeAnnotations(
+      DexProgramClass clazz, DexAnnotationSet annotations, PrintStream ps) {
     if (writeAnnotations) {
       if (!annotations.isEmpty()) {
         ps.println("# Annotations:");
         for (DexAnnotation annotation : annotations.annotations) {
-          ps.print("#   ");
-          if (annotation.annotation.type
-              == application.dexItemFactory.createType("Lkotlin/Metadata;")) {
-            ps.println("<kotlin metadata>");
+          if (annotation.annotation.type == kotlin.metadata.kotlinMetadataType) {
+            assert clazz != null : "Kotlin metadata is a class annotation";
+            writeKotlinMetadata(clazz, annotation, ps);
           } else {
+            ps.print("#   ");
             ps.println(annotation);
           }
         }
@@ -151,6 +157,14 @@
     }
   }
 
+  private void writeKotlinMetadata(
+      DexProgramClass clazz, DexAnnotation annotation, PrintStream ps) {
+    assert annotation.annotation.type == kotlin.metadata.kotlinMetadataType;
+    KotlinInfo kotlinInfo =
+        KotlinClassMetadataReader.createKotlinInfo(kotlin, clazz, annotation.annotation);
+    ps.println(kotlinInfo.toString("#  "));
+  }
+
   @Override
   void writeClassFooter(DexProgramClass clazz, PrintStream ps) {
 
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 a37a42b..136947b 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
 import kotlinx.metadata.KmClass;
 import kotlinx.metadata.KmConstructor;
@@ -187,7 +188,13 @@
   }
 
   @Override
-  public String toString() {
-    return clazz.toString() + ": " + kmClass.toString();
+  public String toString(String indent) {
+    StringBuilder sb = new StringBuilder(indent);
+    sb.append("Metadata.Class {");
+    sb.append(StringUtils.LINE_SEPARATOR);
+    sb.append(kmDeclarationContainerToString(indent + INDENT));
+    sb.append(indent);
+    sb.append("}");
+    return sb.toString();
   }
 }
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 cc1cd14..627ac9d 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -81,8 +81,7 @@
   }
 
   @Override
-  public String toString() {
-    return clazz.toString()
-        + ": MultiFileClassFacade(" + StringUtils.join(partClassNames, ", ") + ")";
+  public String toString(String indent) {
+    return indent + "MultiFileClassFacade(" + StringUtils.join(partClassNames, ", ") + ")";
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 5a3e469..e49d99d 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -18,7 +18,7 @@
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
-final class KotlinClassMetadataReader {
+public final class KotlinClassMetadataReader {
 
   static KotlinInfo getKotlinInfo(
       Kotlin kotlin,
@@ -72,7 +72,7 @@
     return KotlinClassMetadata.read(header);
   }
 
-  private static KotlinInfo createKotlinInfo(
+  public static KotlinInfo createKotlinInfo(
       Kotlin kotlin, DexClass clazz, DexEncodedAnnotation metadataAnnotation) {
     KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, metadataAnnotation);
 
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 16223bb..e67cd05 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
 import kotlinx.metadata.KmPackage;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -82,8 +83,14 @@
   }
 
   @Override
-  public String toString() {
-    return clazz.toString() + ": " + kmPackage.toString()
-        + ": MultiFileClassPart(" + facadeClassName + ")";
+  public String toString(String indent) {
+    StringBuilder sb = new StringBuilder(indent);
+    sb.append("Metadata.MultiFileClassPart {");
+    sb.append(StringUtils.LINE_SEPARATOR);
+    sb.append(kmDeclarationContainerToString(indent + INDENT));
+    appendKeyValue(indent + INDENT, "facadeClassName", facadeClassName, sb);
+    sb.append(indent);
+    sb.append("}");
+    return sb.toString();
   }
 }
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 27c9230..08fc8ef 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -64,7 +64,13 @@
   }
 
   @Override
-  public String toString() {
-    return clazz.toString() + ": " + kmPackage.toString();
+  public String toString(String indent) {
+    StringBuilder sb = new StringBuilder(indent);
+    sb.append("Metadata.MultiFileClassPart {\n");
+    sb.append(kmDeclarationContainerToString(indent + INDENT));
+    appendKeyValue(indent + INDENT, "package", kmPackage.toString(), sb);
+    sb.append(indent);
+    sb.append("}");
+    return sb.toString();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index 7c53cba..3727146 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -15,13 +15,18 @@
 import com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.KmPropertyGroup;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.BiFunction;
 import java.util.function.Predicate;
 import kotlinx.metadata.KmDeclarationContainer;
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmProperty;
+import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeAlias;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
@@ -202,4 +207,100 @@
       }
     }
   }
+
+  public abstract String toString(String indent);
+
+  String kmDeclarationContainerToString(String indent) {
+    StringBuilder sb = new StringBuilder();
+    KmDeclarationContainer declarations = getDeclarations();
+    appendKmSection(indent, "functions", declarations.getFunctions(), this::kmFunctionToString, sb);
+    appendKmSection(
+        indent, "properties", declarations.getProperties(), this::kmPropertyToString, sb);
+    appendKmSection(
+        indent, "typeAliases", declarations.getTypeAliases(), this::kmTypeAliasToString, sb);
+    return sb.toString();
+  }
+
+  final String INDENT = "  ";
+
+  private <T> void appendKmSection(
+      String indent,
+      String header,
+      List<T> items,
+      BiFunction<String, T, String> stringify,
+      StringBuilder sb) {
+    if (items.size() > 0) {
+      sb.append(indent);
+      sb.append(header);
+      sb.append(": [");
+      sb.append(StringUtils.LINE_SEPARATOR);
+    }
+    for (T item : items) {
+      sb.append(stringify.apply(indent + INDENT, item));
+      sb.append(",");
+      sb.append(StringUtils.LINE_SEPARATOR);
+    }
+    if (items.size() > 0) {
+      sb.append(indent);
+      sb.append("]");
+      sb.append(StringUtils.LINE_SEPARATOR);
+    }
+  }
+
+  private String kmFunctionToString(String indent, KmFunction function) {
+    assert function != null;
+    StringBuilder sb = new StringBuilder();
+    sb.append(indent);
+    sb.append("KmFunction {");
+    sb.append(StringUtils.LINE_SEPARATOR);
+    String newIndent = indent + INDENT;
+    KmType receiverParameterType = function.getReceiverParameterType();
+    appendKeyValue(
+        newIndent,
+        "receiverParameterType",
+        receiverParameterType == null ? "null" : kmTypeToString(receiverParameterType),
+        sb);
+    appendKeyValue(newIndent, "returnType", kmTypeToString(function.returnType), sb);
+    appendKeyValue(newIndent, "name", function.getName(), sb);
+    // TODO(b/148581822): Print flags, generic signature etc.
+    sb.append(indent);
+    sb.append("}");
+    return sb.toString();
+  }
+
+  private String kmPropertyToString(String indent, KmProperty property) {
+    // TODO(b/148581822): Add information.
+    return indent + "KmProperty { " + property + "}";
+  }
+
+  private String kmTypeAliasToString(String indent, KmTypeAlias alias) {
+    assert alias != null;
+    StringBuilder sb = new StringBuilder(indent);
+    sb.append("KmAlias {");
+    sb.append(StringUtils.LINE_SEPARATOR);
+    String newIndent = indent + INDENT;
+    appendKeyValue(newIndent, "name", alias.getName(), sb);
+    appendKeyValue(newIndent, "underlyingType", kmTypeToString(alias.underlyingType), sb);
+    appendKeyValue(newIndent, "expandedType", kmTypeToString(alias.expandedType), sb);
+    sb.append(indent);
+    sb.append("}");
+    return sb.toString();
+  }
+
+  void appendKeyValue(String indent, String key, String value, StringBuilder sb) {
+    sb.append(indent);
+    sb.append(key);
+    sb.append(": ");
+    sb.append(value);
+    sb.append(StringUtils.LINE_SEPARATOR);
+  }
+
+  private String kmTypeToString(KmType type) {
+    return DescriptorUtils.getDescriptorFromKmType(type);
+  }
+
+  @Override
+  public String toString() {
+    return toString("");
+  }
 }
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 1bb17ea..8c4ccaf 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -119,7 +119,7 @@
   }
 
   @Override
-  public String toString() {
-    return clazz.toString() + ": " + metadata.toString();
+  public String toString(String indent) {
+    return indent + "Metadata.SyntheticClass { function: " + metadata.toString() + "}";
   }
 }