Add more conversions from Kotlin types to JVM types, and vice versa.

Bug: 143687784, 70169921
Change-Id: I163e51fdd89896b8c961db077df053f09f8cbac9
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ed70c22..78471d6 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -887,7 +887,7 @@
     for (DexProgramClass programClass : application.classes()) {
       KotlinInfo kotlinInfo = kotlin.getKotlinInfo(programClass, reporter);
       programClass.setKotlinInfo(kotlinInfo);
-      KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo);
+      KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo, reporter);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/errors/InvalidDescriptorException.java b/src/main/java/com/android/tools/r8/errors/InvalidDescriptorException.java
new file mode 100644
index 0000000..f6702b7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/InvalidDescriptorException.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2020, 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.errors;
+
+public class InvalidDescriptorException extends InternalCompilerError {
+  public InvalidDescriptorException(String message) {
+    super(message);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 7d568f6..04d2cc3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -122,6 +122,15 @@
   public final DexString voidDescriptor = createString("V");
   public final DexString descriptorSeparator = createString("/");
 
+  private final DexString booleanArrayDescriptor = createString("[Z");
+  private final DexString byteArrayDescriptor = createString("[B");
+  private final DexString charArrayDescriptor = createString("[C");
+  private final DexString doubleArrayDescriptor = createString("[D");
+  private final DexString floatArrayDescriptor = createString("[F");
+  private final DexString intArrayDescriptor = createString("[I");
+  private final DexString longArrayDescriptor = createString("[J");
+  private final DexString shortArrayDescriptor = createString("[S");
+
   public final DexString boxedBooleanDescriptor = createString("Ljava/lang/Boolean;");
   public final DexString boxedByteDescriptor = createString("Ljava/lang/Byte;");
   public final DexString boxedCharDescriptor = createString("Ljava/lang/Character;");
@@ -131,6 +140,7 @@
   public final DexString boxedLongDescriptor = createString("Ljava/lang/Long;");
   public final DexString boxedShortDescriptor = createString("Ljava/lang/Short;");
   public final DexString boxedNumberDescriptor = createString("Ljava/lang/Number;");
+  public final DexString boxedVoidDescriptor = createString("Ljava/lang/Void;");
 
   public final DexString unboxBooleanMethodName = createString("booleanValue");
   public final DexString unboxByteMethodName = createString("byteValue");
@@ -261,15 +271,14 @@
   public final DexString newUpdaterName = createString("newUpdater");
 
   public final DexString constructorMethodName = createString(Constants.INSTANCE_INITIALIZER_NAME);
-  public final DexString classConstructorMethodName = createString(Constants.CLASS_INITIALIZER_NAME);
+  public final DexString classConstructorMethodName =
+      createString(Constants.CLASS_INITIALIZER_NAME);
 
   public final DexString thisName = createString("this");
   public final DexString enumValuesFieldName = createString("$VALUES");
 
   public final DexString enabledFieldName = createString("ENABLED");
 
-  private final DexString charArrayDescriptor = createString("[C");
-  private final DexType charArrayType = createType(charArrayDescriptor);
   public final DexString throwableArrayDescriptor = createString("[Ljava/lang/Throwable;");
 
   public final DexType booleanType = createType(booleanDescriptor);
@@ -282,6 +291,15 @@
   public final DexType shortType = createType(shortDescriptor);
   public final DexType voidType = createType(voidDescriptor);
 
+  public final DexType booleanArrayType = createType(booleanArrayDescriptor);
+  public final DexType byteArrayType = createType(byteArrayDescriptor);
+  public final DexType charArrayType = createType(charArrayDescriptor);
+  public final DexType doubleArrayType = createType(doubleArrayDescriptor);
+  public final DexType floatArrayType = createType(floatArrayDescriptor);
+  public final DexType intArrayType = createType(intArrayDescriptor);
+  public final DexType longArrayType = createType(longArrayDescriptor);
+  public final DexType shortArrayType = createType(shortArrayDescriptor);
+
   public final DexType boxedBooleanType = createType(boxedBooleanDescriptor);
   public final DexType boxedByteType = createType(boxedByteDescriptor);
   public final DexType boxedCharType = createType(boxedCharDescriptor);
@@ -291,6 +309,7 @@
   public final DexType boxedLongType = createType(boxedLongDescriptor);
   public final DexType boxedShortType = createType(boxedShortDescriptor);
   public final DexType boxedNumberType = createType(boxedNumberDescriptor);
+  public final DexType boxedVoidType = createType(boxedVoidDescriptor);
 
   public final DexType charSequenceType = createType(charSequenceDescriptor);
   public final DexType charSequenceArrayType = createType(charSequenceArrayDescriptor);
@@ -425,6 +444,7 @@
   public final DexType enumerationType = createType("Ljava/util/Enumeration;");
   public final DexType serializableType = createType("Ljava/io/Serializable;");
   public final DexType externalizableType = createType("Ljava/io/Externalizable;");
+  public final DexType cloneableType = createType("Ljava/lang/Cloneable;");
   public final DexType comparableType = createType("Ljava/lang/Comparable;");
 
   public final ServiceLoaderMethods serviceLoaderMethods = new ServiceLoaderMethods();
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 34a98eb..941d7e2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -40,6 +40,7 @@
   public final Intrinsics intrinsics;
   public final Metadata metadata;
 
+  // Mappings from JVM types to Kotlin types (of type DexType)
   final Map<DexType, DexType> knownTypeConversion;
 
   public Kotlin(DexItemFactory factory) {
@@ -50,20 +51,44 @@
     this.intrinsics = new Intrinsics();
     this.metadata = new Metadata();
 
+    // See {@link org.jetbrains.kotlin.metadata.jvm.deserialization.ClassMapperLite}
     this.knownTypeConversion =
         ImmutableMap.<DexType, DexType>builder()
             // https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/index.html
+            // Boxed primitives and arrays
             .put(factory.booleanType, factory.createType(addKotlinPrefix("Boolean;")))
+            .put(factory.booleanArrayType, factory.createType(addKotlinPrefix("BooleanArray;")))
             .put(factory.byteType, factory.createType(addKotlinPrefix("Byte;")))
-            .put(factory.charType, factory.createType(addKotlinPrefix("Character;")))
+            .put(factory.byteArrayType, factory.createType(addKotlinPrefix("ByteArray;")))
+            .put(factory.charType, factory.createType(addKotlinPrefix("Char;")))
+            .put(factory.charArrayType, factory.createType(addKotlinPrefix("CharArray;")))
             .put(factory.shortType, factory.createType(addKotlinPrefix("Short;")))
+            .put(factory.shortArrayType, factory.createType(addKotlinPrefix("ShortArray;")))
             .put(factory.intType, factory.createType(addKotlinPrefix("Int;")))
+            .put(factory.intArrayType, factory.createType(addKotlinPrefix("IntArray;")))
             .put(factory.longType, factory.createType(addKotlinPrefix("Long;")))
+            .put(factory.longArrayType, factory.createType(addKotlinPrefix("LongArray;")))
             .put(factory.floatType, factory.createType(addKotlinPrefix("Float;")))
+            .put(factory.floatArrayType, factory.createType(addKotlinPrefix("FloatArray;")))
             .put(factory.doubleType, factory.createType(addKotlinPrefix("Double;")))
+            .put(factory.doubleArrayType, factory.createType(addKotlinPrefix("DoubleArray;")))
+            // Other intrinsics
             .put(factory.voidType, factory.createType(addKotlinPrefix("Unit;")))
+            .put(factory.objectType, factory.createType(addKotlinPrefix("Any;")))
+            .put(factory.boxedVoidType, factory.createType(addKotlinPrefix("Nothing;")))
             .put(factory.stringType, factory.createType(addKotlinPrefix("String;")))
-            // TODO(b/70169921): Collections?
+            .put(factory.charSequenceType, factory.createType(addKotlinPrefix("CharSequence;")))
+            .put(factory.throwableType, factory.createType(addKotlinPrefix("Throwable;")))
+            .put(factory.cloneableType, factory.createType(addKotlinPrefix("Cloneable;")))
+            .put(factory.boxedNumberType, factory.createType(addKotlinPrefix("Number;")))
+            .put(factory.comparableType, factory.createType(addKotlinPrefix("Comparable;")))
+            .put(factory.enumType, factory.createType(addKotlinPrefix("Enum;")))
+            // TODO(b/70169921): (Mutable) Collections?
+            // .../jvm/functions/FunctionN -> .../FunctionN
+            .putAll(
+                IntStream.rangeClosed(0, 22).boxed().collect(Collectors.toMap(
+                    i -> factory.createType(addKotlinPrefix("jvm/functions/Function" + i + ";")),
+                    i -> factory.createType(addKotlinPrefix("Function" + i + ";")))))
             .build();
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
index 5382e44..62b5355 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmFunctionProcessor;
 import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmPropertyProcessor;
+import com.android.tools.r8.utils.Reporter;
 import java.util.HashMap;
 import java.util.Map;
 import kotlinx.metadata.KmDeclarationContainer;
@@ -88,36 +89,37 @@
     }
   }
 
-  public static void markKotlinMemberInfo(DexClass clazz, KotlinInfo kotlinInfo) {
+  public static void markKotlinMemberInfo(
+      DexClass clazz, KotlinInfo kotlinInfo, Reporter reporter) {
     if (kotlinInfo == null || !kotlinInfo.hasDeclarations()) {
       return;
     }
     if (kotlinInfo.isClass()) {
-      markKotlinMemberInfo(clazz, kotlinInfo.asClass().kmClass);
+      markKotlinMemberInfo(clazz, kotlinInfo.asClass().kmClass, reporter);
     } else if (kotlinInfo.isFile()) {
-      markKotlinMemberInfo(clazz, kotlinInfo.asFile().kmPackage);
+      markKotlinMemberInfo(clazz, kotlinInfo.asFile().kmPackage, reporter);
     } else if (kotlinInfo.isClassPart()) {
-      markKotlinMemberInfo(clazz, kotlinInfo.asClassPart().kmPackage);
+      markKotlinMemberInfo(clazz, kotlinInfo.asClassPart().kmPackage, reporter);
     } else {
       throw new Unreachable("Unexpected KotlinInfo: " + kotlinInfo);
     }
   }
 
   private static void markKotlinMemberInfo(
-      DexClass clazz, KmDeclarationContainer kmDeclarationContainer) {
+      DexClass clazz, KmDeclarationContainer kmDeclarationContainer, Reporter reporter) {
     Map<String, KmFunction> kmFunctionMap = new HashMap<>();
     Map<String, KmProperty> kmPropertyFieldMap = new HashMap<>();
     Map<String, KmProperty> kmPropertyGetterMap = new HashMap<>();
     Map<String, KmProperty> kmPropertySetterMap = new HashMap<>();
 
     kmDeclarationContainer.getFunctions().forEach(kmFunction -> {
-      KmFunctionProcessor functionProcessor = new KmFunctionProcessor(kmFunction);
+      KmFunctionProcessor functionProcessor = new KmFunctionProcessor(kmFunction, reporter);
       if (functionProcessor.signature() != null) {
         kmFunctionMap.put(functionProcessor.signature().asString(), kmFunction);
       }
     });
     kmDeclarationContainer.getProperties().forEach(kmProperty -> {
-      KmPropertyProcessor propertyProcessor = new KmPropertyProcessor(kmProperty);
+      KmPropertyProcessor propertyProcessor = new KmPropertyProcessor(kmProperty, reporter);
       if (propertyProcessor.fieldSignature() != null) {
         kmPropertyFieldMap.put(propertyProcessor.fieldSignature().asString(), kmProperty);
       }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
index bdd7f60..fadf9cb 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
@@ -3,9 +3,22 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
+
+import com.android.tools.r8.errors.InvalidDescriptorException;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmConstructorExtensionVisitor;
 import kotlinx.metadata.KmConstructorVisitor;
@@ -24,6 +37,94 @@
 
 class KotlinMetadataJvmExtensionUtils {
 
+  // Mappings from Kotlin types to JVM types (of String)
+  private static final Map<String, String> knownTypeConversion =
+      // See {@link org.jetbrains.kotlin.metadata.jvm.deserialization.ClassMapperLite}
+      ImmutableMap.<String, String>builder()
+          // Boxed primitives and arrays
+          .put(addKotlinPrefix("Boolean;"), "Z")
+          .put(addKotlinPrefix("BooleanArray;"), "[Z")
+          .put(addKotlinPrefix("Byte;"), "B")
+          .put(addKotlinPrefix("ByteArray;"), "[B")
+          .put(addKotlinPrefix("Char;"), "C")
+          .put(addKotlinPrefix("CharArray;"), "[C")
+          .put(addKotlinPrefix("Short;"), "S")
+          .put(addKotlinPrefix("ShortArray;"), "[S")
+          .put(addKotlinPrefix("Int;"), "I")
+          .put(addKotlinPrefix("IntArray;"), "[I")
+          .put(addKotlinPrefix("Long;"), "J")
+          .put(addKotlinPrefix("LongArray;"), "[J")
+          .put(addKotlinPrefix("Float;"), "F")
+          .put(addKotlinPrefix("FloatArray;"), "[F")
+          .put(addKotlinPrefix("Double;"), "D")
+          .put(addKotlinPrefix("DoubleArray;"), "[D")
+          // Other intrinsics
+          .put(addKotlinPrefix("Unit;"), "V")
+          .put(addKotlinPrefix("Any;"), "Ljava/lang/Object;")
+          .put(addKotlinPrefix("Nothing;"), "Ljava/lang/Void;")
+          .putAll(ImmutableList.of(
+              "String", "CharSequence", "Throwable", "Cloneable", "Number", "Comparable", "Enum")
+                  .stream().collect(Collectors.toMap(
+                      t -> addKotlinPrefix(t + ";"),
+                      t -> "Ljava/lang/" + t + ";")))
+          // Collections
+          .putAll(ImmutableList.of("Iterator", "Collection", "List", "Set", "Map", "ListIterator")
+              .stream().collect(Collectors.toMap(
+                  t -> addKotlinPrefix("collections/" + t + ";"),
+                  t -> "Ljava/util/" + t + ";")))
+          .putAll(ImmutableList.of("Iterator", "Collection", "List", "Set", "Map", "ListIterator")
+              .stream().collect(Collectors.toMap(
+                  t -> addKotlinPrefix("collections/Mutable" + t + ";"),
+                  t -> "Ljava/util/" + t + ";")))
+          .put(addKotlinPrefix("collections/Iterable;"), "Ljava/lang/Iterable;")
+          .put(addKotlinPrefix("collections/MutableIterable;"), "Ljava/lang/Iterable;")
+          .put(addKotlinPrefix("collections/Map.Entry;"), "Ljava/util/Map$Entry;")
+          .put(addKotlinPrefix("collections/MutableMap.MutableEntry;"), "Ljava/util/Map$Entry;")
+          // .../FunctionN -> .../jvm/functions/FunctionN
+          .putAll(
+              IntStream.rangeClosed(0, 22).boxed().collect(Collectors.toMap(
+                  i -> addKotlinPrefix("Function" + i + ";"),
+                  i -> addKotlinPrefix("jvm/functions/Function" + i + ";"))))
+          .build();
+
+  private static String remapKotlinType(String type) {
+    if (knownTypeConversion.containsKey(type)) {
+      return knownTypeConversion.get(type);
+    }
+    return type;
+  }
+
+  // Kotlin @Metadata deserialization has plain "kotlin", which will be relocated in r8lib.
+  // See b/70169921#comment57 for more details.
+  // E.g., desc: (Labc/xyz/C;Lkotlin/Function1;)kotlin/Unit
+  // remapped desc would be: (Labc/xyz/C;Lkotlin/jvm/functions/Function1;)V
+  private static String remapKotlinTypeInDesc(String desc, Reporter reporter) {
+    if (desc == null) {
+      return null;
+    }
+    if (desc.isEmpty()) {
+      return desc;
+    }
+    String[] parameterTypes;
+    try {
+      parameterTypes = DescriptorUtils.getArgumentTypeDescriptors(desc);
+      for (int i = 0; i < parameterTypes.length; i++) {
+        parameterTypes[i] = remapKotlinType(parameterTypes[i]);
+      }
+    } catch (InvalidDescriptorException e) {
+      // JvmMethodSignature from @Metadata is not 100% reliable (due to its own optimization using
+      // map, relocation in r8lib, etc.)
+      reporter.info(
+          new StringDiagnostic(
+              "Invalid descriptor (deserialized from Kotlin @Metadata): " + desc));
+      return desc;
+    }
+    int index = desc.indexOf(')');
+    assert 0 < index && index < desc.length() : desc;
+    String returnType = remapKotlinType(desc.substring(index + 1));
+    return "(" + StringUtils.join(Arrays.asList(parameterTypes), "") + ")" + returnType;
+  }
+
   static JvmFieldSignature toJvmFieldSignature(DexField field) {
     return new JvmFieldSignature(field.name.toString(), field.type.toDescriptorString());
   }
@@ -42,7 +143,7 @@
   static class KmConstructorProcessor {
     private JvmMethodSignature signature = null;
 
-    KmConstructorProcessor(KmConstructor kmConstructor) {
+    KmConstructorProcessor(KmConstructor kmConstructor, Reporter reporter) {
       kmConstructor.accept(new KmConstructorVisitor() {
         @Override
         public KmConstructorExtensionVisitor visitExtensions(KmExtensionType type) {
@@ -58,6 +159,12 @@
           };
         }
       });
+      if (signature != null) {
+        String remappedDesc = remapKotlinTypeInDesc(signature.getDesc(), reporter);
+        if (remappedDesc != null && !remappedDesc.equals(signature.getDesc())) {
+          signature = new JvmMethodSignature(signature.getName(), remappedDesc);
+        }
+      }
     }
 
     JvmMethodSignature signature() {
@@ -69,7 +176,7 @@
     // Custom name via @JvmName("..."). Otherwise, null.
     private JvmMethodSignature signature = null;
 
-    KmFunctionProcessor(KmFunction kmFunction) {
+    KmFunctionProcessor(KmFunction kmFunction, Reporter reporter) {
       kmFunction.accept(new KmFunctionVisitor() {
         @Override
         public KmFunctionExtensionVisitor visitExtensions(KmExtensionType type) {
@@ -85,6 +192,12 @@
           };
         }
       });
+      if (signature != null) {
+        String remappedDesc = remapKotlinTypeInDesc(signature.getDesc(), reporter);
+        if (remappedDesc != null && !remappedDesc.equals(signature.getDesc())) {
+          signature = new JvmMethodSignature(signature.getName(), remappedDesc);
+        }
+      }
     }
 
     JvmMethodSignature signature() {
@@ -99,7 +212,7 @@
     // Custom getter via @set:JvmName("..."). Otherwise, null.
     private JvmMethodSignature setterSignature = null;
 
-    KmPropertyProcessor(KmProperty kmProperty) {
+    KmPropertyProcessor(KmProperty kmProperty, Reporter reporter) {
       kmProperty.accept(new KmPropertyVisitor() {
         @Override
         public KmPropertyExtensionVisitor visitExtensions(KmExtensionType type) {
@@ -123,6 +236,24 @@
           };
         }
       });
+      if (fieldSignature != null) {
+        String remappedDesc = remapKotlinType(fieldSignature.getDesc());
+        if (remappedDesc != null && !remappedDesc.equals(fieldSignature.getDesc())) {
+          fieldSignature = new JvmFieldSignature(fieldSignature.getName(), remappedDesc);
+        }
+      }
+      if (getterSignature != null) {
+        String remappedDesc = remapKotlinTypeInDesc(getterSignature.getDesc(), reporter);
+        if (remappedDesc != null && !remappedDesc.equals(getterSignature.getDesc())) {
+          getterSignature = new JvmMethodSignature(getterSignature.getName(), remappedDesc);
+        }
+      }
+      if (setterSignature != null) {
+        String remappedDesc = remapKotlinTypeInDesc(setterSignature.getDesc(), reporter);
+        if (remappedDesc != null && !remappedDesc.equals(setterSignature.getDesc())) {
+          setterSignature = new JvmMethodSignature(setterSignature.getName(), remappedDesc);
+        }
+      }
     }
 
     JvmFieldSignature fieldSignature() {
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 c46b94a..94c65e3 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -46,16 +46,16 @@
 
   static String toRenamedClassifier(
       DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    // E.g., [Ljava/lang/String; -> kotlin/Array
-    if (type.isArrayType()) {
-      return NAME + "/Array";
-    }
-    // E.g., void -> kotlin/Unit
+    // E.g., V -> kotlin/Unit, J -> kotlin/Long, [J -> kotlin/LongArray
     if (appView.dexItemFactory().kotlin.knownTypeConversion.containsKey(type)) {
       DexType convertedType = appView.dexItemFactory().kotlin.knownTypeConversion.get(type);
       assert convertedType != null;
       return descriptorToKotlinClassifier(convertedType.toDescriptorString());
     }
+    // E.g., [Ljava/lang/String; -> kotlin/Array
+    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)) {
@@ -79,6 +79,9 @@
     //   and/or why wiping out flags works for KmType but not KmFunction?!
     KmType kmType = new KmType(flagsOf());
     kmType.visitClass(classifier);
+    // TODO(b/70169921): Need to set arguments as type parameter.
+    //  E.g., for kotlin/Function1<P1, R>, P1 and R are recorded inside KmType.arguments, which
+    //  enables kotlinc to resolve `this` type and return type of the lambda.
     return kmType;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index 70d6df0..b532e50 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -8,6 +8,7 @@
 import static com.android.tools.r8.utils.FileUtils.MODULES_PREFIX;
 
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.InvalidDescriptorException;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -370,6 +371,12 @@
     return 'L' + className.replace(JAVA_PACKAGE_SEPARATOR, INNER_CLASS_SEPARATOR) + ';';
   }
 
+  // Kotlin @Metadata deserialization has plain "kotlin", which will be relocated in r8lib.
+  // See b/70169921#comment25 for more details.
+  private static String backwardRelocatedName(String name) {
+    return name.replace("com/android/tools/r8/jetbrains/", "");
+  }
+
   /**
    * Get a fully qualified name from a classifier in Kotlin metadata.
    * @param kmType where classifier contains Kotlin internal name, like "org/foo/bar/Baz.Nested"
@@ -385,16 +392,16 @@
       public void visitClass(String name) {
         // TODO(b/70169921): Remove this if metadata lib is resilient to namespace relocation.
         //  See b/70169921#comment25 for more details.
-        String backwardRelocatedName = name.replace("com/android/tools/r8/jetbrains/", "");
-        descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName));
+        assert descriptor.get() == null : descriptor.get();
+        descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName(name)));
       }
 
       @Override
       public void visitTypeAlias(String name) {
         // TODO(b/70169921): Remove this if metadata lib is resilient to namespace relocation.
         //  See b/70169921#comment25 for more details.
-        String backwardRelocatedName = name.replace("com/android/tools/r8/jetbrains/", "");
-        descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName));
+        assert descriptor.get() == null : descriptor.get();
+        descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName(name)));
       }
     });
     return descriptor.get();
@@ -619,7 +626,7 @@
     while ((c = methodDescriptor.charAt(charIdx)) != ')') {
       switch (c) {
         case 'V':
-          throw new Unreachable();
+          throw new InvalidDescriptorException(methodDescriptor);
         case 'Z':
         case 'C':
         case 'B':
@@ -646,7 +653,7 @@
           argDescriptors[argIdx++] = methodDescriptor.substring(startType, charIdx + 1);
           break;
         default:
-          throw new Unreachable();
+          throw new InvalidDescriptorException(methodDescriptor);
       }
       charIdx++;
     }
@@ -657,7 +664,7 @@
     int charIdx = 1;
     char c;
     int argCount = 0;
-    while ((c = methodDescriptor.charAt(charIdx++)) != ')') {
+    while (charIdx < methodDescriptor.length() && (c = methodDescriptor.charAt(charIdx++)) != ')') {
       if (c == 'L') {
         while (methodDescriptor.charAt(charIdx++) != ';')
           ;
@@ -666,6 +673,9 @@
         argCount++;
       }
     }
+    if (charIdx >= methodDescriptor.length() || methodDescriptor.charAt(charIdx - 1) != ')') {
+      throw new InvalidDescriptorException(methodDescriptor);
+    }
     return argCount;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
index 917ede7..1f8fee7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
@@ -7,8 +7,10 @@
 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.assertTrue;
 
 import com.android.tools.r8.TestParameters;
@@ -20,6 +22,7 @@
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
 import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeSubject;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.HashMap;
@@ -94,7 +97,6 @@
   private void inspectMerged(CodeInspector inspector) {
     String superClassName = PKG + ".extension_function_lib.Super";
     String bClassName = PKG + ".extension_function_lib.B";
-    String bKtClassName = PKG + ".extension_function_lib.BKt";
 
     assertThat(inspector.clazz(superClassName), not(isPresent()));
 
@@ -108,15 +110,7 @@
     assertTrue(superTypes.stream().noneMatch(
         supertype -> supertype.getFinalDescriptor().contains("Super")));
 
-    ClassSubject bKt = inspector.clazz(bKtClassName);
-    assertThat(bKt, isPresent());
-    assertThat(bKt, not(isRenamed()));
-    // API entry is kept, hence the presence of Metadata.
-    KmPackageSubject kmPackage = bKt.getKmPackage();
-    assertThat(kmPackage, isPresent());
-
-    KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("extension");
-    assertThat(kmFunction, isExtensionFunction());
+    inspectExtensions(inspector);
   }
 
   @Test
@@ -154,7 +148,6 @@
   private void inspectRenamed(CodeInspector inspector) {
     String superClassName = PKG + ".extension_function_lib.Super";
     String bClassName = PKG + ".extension_function_lib.B";
-    String bKtClassName = PKG + ".extension_function_lib.BKt";
 
     ClassSubject sup = inspector.clazz(superClassName);
     assertThat(sup, isPresent());
@@ -172,6 +165,16 @@
     assertTrue(superTypes.stream().anyMatch(
         supertype -> supertype.getFinalDescriptor().equals(sup.getFinalDescriptor())));
 
+    inspectExtensions(inspector);
+  }
+
+  private void inspectExtensions(CodeInspector inspector) {
+    String bClassName = PKG + ".extension_function_lib.B";
+    String bKtClassName = PKG + ".extension_function_lib.BKt";
+
+    ClassSubject impl = inspector.clazz(bClassName);
+    assertThat(impl, isPresent());
+
     ClassSubject bKt = inspector.clazz(bKtClassName);
     assertThat(bKt, isPresent());
     assertThat(bKt, not(isRenamed()));
@@ -181,5 +184,29 @@
 
     KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("extension");
     assertThat(kmFunction, isExtensionFunction());
+    KmTypeSubject kmTypeSubject = kmFunction.receiverParameterType();
+    assertEquals(impl.getFinalDescriptor(), kmTypeSubject.descriptor());
+
+    kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("csHash");
+    assertThat(kmFunction, isExtensionFunction());
+    kmTypeSubject = kmFunction.receiverParameterType();
+    assertEquals("Lkotlin/CharSequence;", kmTypeSubject.descriptor());
+    kmTypeSubject = kmFunction.returnType();
+    assertEquals("Lkotlin/Long;", kmTypeSubject.descriptor());
+
+    kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("longArrayHash");
+    assertThat(kmFunction, isExtensionFunction());
+    kmTypeSubject = kmFunction.receiverParameterType();
+    assertEquals("Lkotlin/LongArray;", kmTypeSubject.descriptor());
+    kmTypeSubject = kmFunction.returnType();
+    assertEquals("Lkotlin/Long;", kmTypeSubject.descriptor());
+
+    kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("myApply");
+    assertThat(kmFunction, isExtensionFunction());
+    kmTypeSubject = kmFunction.receiverParameterType();
+    assertEquals(impl.getFinalDescriptor(), kmTypeSubject.descriptor());
+    // TODO(b/70169921): Check param[0] has type kotlin/Function1<(renamed) B, Unit>
+    String desc = kmFunction.signature().getDesc();
+    assertThat(desc, containsString("kotlin/jvm/functions/Function1"));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
index e0c17a8..f4e79f3 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
@@ -88,12 +88,6 @@
     assertThat(
         kotlinTestCompileResult.stderr,
         containsString("type mismatch: inferred type is String but Array<T> was expected"));
-    assertThat(
-        kotlinTestCompileResult.stderr,
-        containsString("(???, ???) -> [ERROR : <ERROR FUNCTION RETURN TYPE>]"));
-    assertThat(
-        kotlinTestCompileResult.stderr,
-        containsString("but kotlin.jvm.functions.Function2<P1, P2, R> was expected"));
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt
index 2196586..d85c992 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt
@@ -5,8 +5,16 @@
 
 import com.android.tools.r8.kotlin.metadata.extension_function_lib.B
 import com.android.tools.r8.kotlin.metadata.extension_function_lib.extension
+import com.android.tools.r8.kotlin.metadata.extension_function_lib.csHash
+import com.android.tools.r8.kotlin.metadata.extension_function_lib.longArrayHash
+import com.android.tools.r8.kotlin.metadata.extension_function_lib.myApply
 
 fun main() {
   B().doStuff()
   B().extension()
+
+  "R8".csHash()
+  longArrayOf(42L).longArrayHash()
+  // TODO(b/70169921): Need to set arguments as type parameter.
+  //  B().myApply { this.doStuff() }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_lib/B.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_lib/B.kt
index 84c759a..4321ac1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_lib/B.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_lib/B.kt
@@ -17,4 +17,20 @@
 
 fun B.extension() {
   doStuff()
-}
\ No newline at end of file
+}
+
+fun CharSequence.csHash(): Long {
+  var result = 0L
+  this.forEach { result = result * 8L + it.toLong() }
+  return result
+}
+
+fun LongArray.longArrayHash(): Long {
+  var result = 0L
+  this.forEach { result = result * 8L + it }
+  return result
+}
+
+fun B.myApply(apply: B.() -> Unit) {
+  apply.invoke(this)
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
index e869207..c72d305 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
@@ -31,4 +31,14 @@
   public JvmMethodSignature signature() {
     return null;
   }
+
+  @Override
+  public KmTypeSubject receiverParameterType() {
+    return null;
+  }
+
+  @Override
+  public KmTypeSubject returnType() {
+    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 f99ac6c..7b57397 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
@@ -63,7 +63,7 @@
   @Override
   public List<String> getSuperTypeDescriptors() {
     return kmClass.getSupertypes().stream()
-        .map(this::getDescriptorFromKmType)
+        .map(KmTypeSubject::getDescriptorFromKmType)
         .filter(Objects::nonNull)
         .collect(Collectors.toList());
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
index 9fece50..b9fa99a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
@@ -3,10 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.codeinspector;
 
-import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKotlinClassifier;
+import static com.android.tools.r8.utils.codeinspector.KmTypeSubject.getDescriptorFromKmType;
 
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.utils.Box;
 import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
@@ -19,7 +18,6 @@
 import kotlinx.metadata.KmPropertyExtensionVisitor;
 import kotlinx.metadata.KmPropertyVisitor;
 import kotlinx.metadata.KmType;
-import kotlinx.metadata.KmTypeVisitor;
 import kotlinx.metadata.jvm.JvmFieldSignature;
 import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor;
 import kotlinx.metadata.jvm.JvmMethodSignature;
@@ -30,26 +28,6 @@
   CodeInspector codeInspector();
   KmDeclarationContainer getKmDeclarationContainer();
 
-  // TODO(b/145824437): This is a dup of DescriptorUtils#getDescriptorFromKmType
-  default String getDescriptorFromKmType(KmType kmType) {
-    if (kmType == null) {
-      return null;
-    }
-    Box<String> descriptor = new Box<>(null);
-    kmType.accept(new KmTypeVisitor() {
-      @Override
-      public void visitClass(String name) {
-        descriptor.set(getDescriptorFromKotlinClassifier(name));
-      }
-
-      @Override
-      public void visitTypeAlias(String name) {
-        descriptor.set(getDescriptorFromKotlinClassifier(name));
-      }
-    });
-    return descriptor.get();
-  }
-
   @Override
   default List<String> getParameterTypeDescriptorsInFunctions() {
     return getKmDeclarationContainer().getFunctions().stream()
@@ -97,6 +75,8 @@
           };
         }
       });
+      // We don't check Kotlin types in tests, but be aware of the relocation issue.
+      // See b/70169921#comment57 for more details.
     }
   }
 
@@ -202,6 +182,8 @@
           };
         }
       });
+      // We don't check Kotlin types in tests, but be aware of the relocation issue.
+      // See b/70169921#comment57 for more details.
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
index 7c970c2..3bbf235 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.utils.codeinspector.FoundKmDeclarationContainerSubject.KmFunctionProcessor;
 import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.KmType;
 import kotlinx.metadata.jvm.JvmMethodSignature;
 
 public class FoundKmFunctionSubject extends KmFunctionSubject {
@@ -46,4 +47,16 @@
   public JvmMethodSignature signature() {
     return signature;
   }
+
+  @Override
+  public KmTypeSubject receiverParameterType() {
+    KmType kmType = kmFunction.getReceiverParameterType();
+    assert !isExtension() || kmType != null;
+    return kmType == null ? null : new KmTypeSubject(kmType);
+  }
+
+  @Override
+  public KmTypeSubject returnType() {
+    return new KmTypeSubject(kmFunction.getReturnType());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
index 2e9eeda..d0e0a37 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
@@ -15,4 +15,8 @@
   public abstract boolean isExtension();
 
   public abstract JvmMethodSignature signature();
+
+  public abstract KmTypeSubject receiverParameterType();
+
+  public abstract KmTypeSubject returnType();
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
new file mode 100644
index 0000000..aa52c67
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2020, 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.utils.codeinspector;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKotlinClassifier;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.Box;
+import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeVisitor;
+
+public class KmTypeSubject extends Subject {
+  private final KmType kmType;
+
+  KmTypeSubject(KmType kmType) {
+    assert kmType != null;
+    this.kmType = kmType;
+  }
+
+  // TODO(b/145824437): This is a dup of DescriptorUtils#getDescriptorFromKmType
+  static String getDescriptorFromKmType(KmType kmType) {
+    if (kmType == null) {
+      return null;
+    }
+    Box<String> descriptor = new Box<>(null);
+    kmType.accept(new KmTypeVisitor() {
+      @Override
+      public void visitClass(String name) {
+        // We don't check Kotlin types in tests, but be aware of the relocation issue.
+        // See b/70169921#comment25 for more details.
+        assert descriptor.get() == null;
+        descriptor.set(getDescriptorFromKotlinClassifier(name));
+      }
+
+      @Override
+      public void visitTypeAlias(String name) {
+        assert descriptor.get() == null;
+        descriptor.set(getDescriptorFromKotlinClassifier(name));
+      }
+    });
+    return descriptor.get();
+  }
+
+  public String descriptor() {
+    return getDescriptorFromKmType(kmType);
+  }
+
+  @Override
+  public boolean isPresent() {
+    return true;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    throw new Unreachable("Cannot determine if a type is renamed");
+  }
+
+  @Override
+  public boolean isSynthetic() {
+    throw new Unreachable("Cannot determine if a type is synthetic");
+  }
+}