Introduce KmFunction subject to inspect (extension) function.

Bug: 70169921
Change-Id: Ida7b85097c32eaf5bc245f83740091cfd37c0d2f
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java
index d6300f3..ab702b1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.kotlin.metadata;
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtension;
 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.not;
@@ -18,6 +19,8 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 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 java.nio.file.Path;
 import java.util.Collection;
 import java.util.List;
@@ -77,10 +80,21 @@
             .compile();
     String pkg = getClass().getPackage().getName();
     final String implClassName = pkg + ".classpath_lib_ext.Impl";
+    final String implKtClassName = pkg + ".classpath_lib_ext.ImplKt";
     final String extraClassName = pkg + ".classpath_lib_ext.Extra";
     compileResult.inspect(inspector -> {
       assertThat(inspector.clazz(implClassName), not(isPresent()));
 
+      ClassSubject implKt = inspector.clazz(implKtClassName);
+      assertThat(implKt, isPresent());
+      assertThat(implKt, not(isRenamed()));
+      // API entry is kept, hence the presence of Metadata.
+      KmPackageSubject kmPackage = implKt.getKmPackage();
+      assertThat(kmPackage, isPresent());
+
+      KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("fooExt");
+      assertThat(kmFunction, isPresent());
+
       ClassSubject extra = inspector.clazz(extraClassName);
       assertThat(extra, isPresent());
       assertThat(extra, not(isRenamed()));
@@ -132,11 +146,22 @@
             .compile();
     String pkg = getClass().getPackage().getName();
     final String implClassName = pkg + ".classpath_lib_ext.Impl";
+    final String implKtClassName = pkg + ".classpath_lib_ext.ImplKt";
     final String extraClassName = pkg + ".classpath_lib_ext.Extra";
     compileResult.inspect(inspector -> {
       ClassSubject impl = inspector.clazz(implClassName);
       assertThat(impl, isRenamed());
 
+      ClassSubject implKt = inspector.clazz(implKtClassName);
+      assertThat(implKt, isPresent());
+      assertThat(implKt, not(isRenamed()));
+      // API entry is kept, hence the presence of Metadata.
+      KmPackageSubject kmPackage = implKt.getKmPackage();
+      assertThat(kmPackage, isPresent());
+
+      KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("fooExt");
+      assertThat(kmFunction, isExtension());
+
       ClassSubject extra = inspector.clazz(extraClassName);
       assertThat(extra, isPresent());
       assertThat(extra, not(isRenamed()));
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
index 665414a..6f9dd7f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.kotlin.metadata;
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtension;
 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.not;
@@ -18,6 +19,8 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 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 java.nio.file.Path;
 import java.util.Collection;
 import java.util.List;
@@ -71,6 +74,7 @@
     String pkg = getClass().getPackage().getName();
     final String superClassName = pkg + ".extension_lib.Super";
     final String bClassName = pkg + ".extension_lib.B";
+    final String bKtClassName = pkg + ".extension_lib.BKt";
     compileResult.inspect(inspector -> {
       assertThat(inspector.clazz(superClassName), not(isPresent()));
 
@@ -83,7 +87,16 @@
       List<ClassSubject> superTypes = kmClass.getSuperTypes();
       assertTrue(superTypes.stream().noneMatch(
           supertype -> supertype.getFinalDescriptor().contains("Super")));
-      // TODO(b/70169921): introduce KmFunction subject and make sure extension exists.
+
+      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, isExtension());
     });
 
     Path libJar = compileResult.writeToZip();
@@ -122,6 +135,7 @@
     String pkg = getClass().getPackage().getName();
     final String superClassName = pkg + ".extension_lib.Super";
     final String bClassName = pkg + ".extension_lib.B";
+    final String bKtClassName = pkg + ".extension_lib.BKt";
     compileResult.inspect(inspector -> {
       ClassSubject sup = inspector.clazz(superClassName);
       assertThat(sup, isPresent());
@@ -138,7 +152,16 @@
           supertype -> supertype.getFinalDescriptor().contains("Super")));
       assertTrue(superTypes.stream().anyMatch(
           supertype -> supertype.getFinalDescriptor().equals(sup.getFinalDescriptor())));
-      // TODO(b/70169921): introduce KmFunction subject and make sure extension exists.
+
+      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, isExtension());
     });
 
     Path libJar = compileResult.writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInFunctionTest.java
index b11d4ee..c7e5db4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInFunctionTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.kotlin.metadata;
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtension;
 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.not;
@@ -18,6 +19,8 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 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 java.nio.file.Path;
 import java.util.Collection;
 import java.util.List;
@@ -70,6 +73,7 @@
     String pkg = getClass().getPackage().getName();
     final String superClassName = pkg + ".function_lib.Super";
     final String bClassName = pkg + ".function_lib.B";
+    final String bKtClassName = pkg + ".function_lib.BKt";
     compileResult.inspect(inspector -> {
       assertThat(inspector.clazz(superClassName), not(isPresent()));
 
@@ -82,7 +86,17 @@
       List<ClassSubject> superTypes = kmClass.getSuperTypes();
       assertTrue(superTypes.stream().noneMatch(
           supertype -> supertype.getFinalDescriptor().contains("Super")));
-      // TODO(b/70169921): introduce KmFunction subject and make sure function exists.
+
+      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.kmFunctionWithUniqueName("fun");
+      assertThat(kmFunction, isPresent());
+      assertThat(kmFunction, not(isExtension()));
     });
 
     Path libJar = compileResult.writeToZip();
@@ -120,6 +134,7 @@
     String pkg = getClass().getPackage().getName();
     final String superClassName = pkg + ".function_lib.Super";
     final String bClassName = pkg + ".function_lib.B";
+    final String bKtClassName = pkg + ".function_lib.BKt";
     compileResult.inspect(inspector -> {
       ClassSubject sup = inspector.clazz(superClassName);
       assertThat(sup, isRenamed());
@@ -135,7 +150,17 @@
           supertype -> supertype.getFinalDescriptor().contains("Super")));
       assertTrue(superTypes.stream().anyMatch(
           supertype -> supertype.getFinalDescriptor().equals(sup.getFinalDescriptor())));
-      // TODO(b/70169921): introduce KmFunction subject and make sure function exists.
+
+      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.kmFunctionWithUniqueName("fun");
+      assertThat(kmFunction, isPresent());
+      assertThat(kmFunction, not(isExtension()));
     });
 
     Path libJar = compileResult.writeToZip();
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 2704774..212dd9f 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
@@ -44,6 +44,21 @@
   }
 
   @Override
+  public KmFunctionSubject kmFunctionWithUniqueName(String name) {
+    return null;
+  }
+
+  @Override
+  public KmFunctionSubject kmFunctionExtensionWithUniqueName(String name) {
+    return null;
+  }
+
+  @Override
+  public List<KmFunctionSubject> getFunctions() {
+    return null;
+  }
+
+  @Override
   public List<ClassSubject> getParameterTypesInFunctions() {
     return null;
   }
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
new file mode 100644
index 0000000..fb7466b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
@@ -0,0 +1,27 @@
+// 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;
+
+public class AbsentKmFunctionSubject extends KmFunctionSubject {
+
+  @Override
+  public boolean isPresent() {
+    return false;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return false;
+  }
+
+  @Override
+  public boolean isSynthetic() {
+    return false;
+  }
+
+  @Override
+  public boolean isExtension() {
+    return false;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPackageSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPackageSubject.java
index 6ea407a..1fd6506 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPackageSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPackageSubject.java
@@ -3,11 +3,17 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.graph.DexClass;
 import java.util.List;
 
 public class AbsentKmPackageSubject extends KmPackageSubject {
 
   @Override
+  public DexClass getDexClass() {
+    return null;
+  }
+
+  @Override
   public boolean isPresent() {
     return false;
   }
@@ -38,6 +44,21 @@
   }
 
   @Override
+  public KmFunctionSubject kmFunctionWithUniqueName(String name) {
+    return null;
+  }
+
+  @Override
+  public KmFunctionSubject kmFunctionExtensionWithUniqueName(String name) {
+    return null;
+  }
+
+  @Override
+  public List<KmFunctionSubject> getFunctions() {
+    return null;
+  }
+
+  @Override
   public List<ClassSubject> getParameterTypesInFunctions() {
     return null;
   }
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 45607ed..f30a0e5 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
@@ -11,8 +11,14 @@
 import java.util.Objects;
 import java.util.stream.Collectors;
 import kotlinx.metadata.KmDeclarationContainer;
+import kotlinx.metadata.KmExtensionType;
+import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.KmFunctionExtensionVisitor;
+import kotlinx.metadata.KmFunctionVisitor;
 import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmTypeVisitor;
+import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor;
+import kotlinx.metadata.jvm.JvmMethodSignature;
 
 public interface FoundKmDeclarationContainerSubject extends KmDeclarationContainerSubject {
 
@@ -65,6 +71,68 @@
         .collect(Collectors.toList());
   }
 
+  // TODO(b/145824437): This is a dup of KotlinMetadataJvmExtensionUtils$KmFunctionProcessor
+  class KmFunctionProcessor {
+    // Custom name via @JvmName("..."). Otherwise, null.
+    private JvmMethodSignature signature = null;
+
+    KmFunctionProcessor(KmFunction kmFunction) {
+      kmFunction.accept(new KmFunctionVisitor() {
+        @Override
+        public KmFunctionExtensionVisitor visitExtensions(KmExtensionType type) {
+          if (type != JvmFunctionExtensionVisitor.TYPE) {
+            return null;
+          }
+          return new JvmFunctionExtensionVisitor() {
+            @Override
+            public void visit(JvmMethodSignature desc) {
+              assert signature == null : signature.asString();
+              signature = desc;
+            }
+          };
+        }
+      });
+    }
+
+    JvmMethodSignature signature() {
+      return signature;
+    }
+  }
+
+  default KmFunctionSubject kmFunctionOrExtensionWithUniqueName(String name, boolean isExtension) {
+    for (KmFunction kmFunction : getKmDeclarationContainer().getFunctions()) {
+      if (KmFunctionSubject.isExtension(kmFunction) != isExtension) {
+        continue;
+      }
+      if (kmFunction.getName().equals(name)) {
+        return new FoundKmFunctionSubject(codeInspector(), kmFunction);
+      }
+      KmFunctionProcessor kmFunctionProcessor = new KmFunctionProcessor(kmFunction);
+      if (kmFunctionProcessor.signature() != null
+          && kmFunctionProcessor.signature().getName().equals(name)) {
+        return new FoundKmFunctionSubject(codeInspector(), kmFunction);
+      }
+    }
+    return new AbsentKmFunctionSubject();
+  }
+
+  @Override
+  default KmFunctionSubject kmFunctionWithUniqueName(String name) {
+    return kmFunctionOrExtensionWithUniqueName(name, false);
+  }
+
+  @Override
+  default KmFunctionSubject kmFunctionExtensionWithUniqueName(String name) {
+    return kmFunctionOrExtensionWithUniqueName(name, true);
+  }
+
+  @Override
+  default List<KmFunctionSubject> getFunctions() {
+    return getKmDeclarationContainer().getFunctions().stream()
+        .map(kmFunction -> new FoundKmFunctionSubject(codeInspector(), kmFunction))
+        .collect(Collectors.toList());
+  }
+
   default ClassSubject getClassSubjectFromKmType(KmType kmType) {
     String descriptor = getDescriptorFromKmType(kmType);
     if (descriptor == null) {
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
new file mode 100644
index 0000000..4ee0f82
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
@@ -0,0 +1,39 @@
+// 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 kotlinx.metadata.KmFunction;
+
+public class FoundKmFunctionSubject extends KmFunctionSubject {
+  private final CodeInspector codeInspector;
+  private final KmFunction kmFunction;
+
+  FoundKmFunctionSubject(CodeInspector codeInspector, KmFunction kmFunction) {
+    this.codeInspector = codeInspector;
+    this.kmFunction = kmFunction;
+  }
+
+  @Override
+  public boolean isPresent() {
+    return true;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    // TODO(b/70169921): need to know the corresponding DexEncodedMethod.
+    return false;
+  }
+
+  @Override
+  public boolean isSynthetic() {
+    // TODO(b/70169921): This should return `true` conditionally if we start synthesizing @Metadata
+    //   from scratch.
+    return false;
+  }
+
+  @Override
+  public boolean isExtension() {
+    return isExtension(kmFunction);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPackageSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPackageSubject.java
index 8bc9459..b4e796c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPackageSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPackageSubject.java
@@ -20,6 +20,11 @@
   }
 
   @Override
+  public DexClass getDexClass() {
+    return clazz;
+  }
+
+  @Override
   public CodeInspector codeInspector() {
     return codeInspector;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmDeclarationContainerSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmDeclarationContainerSubject.java
index d553228..677ad3d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmDeclarationContainerSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmDeclarationContainerSubject.java
@@ -12,6 +12,12 @@
 
   List<String> getReturnTypeDescriptorsInProperties();
 
+  KmFunctionSubject kmFunctionWithUniqueName(String name);
+
+  KmFunctionSubject kmFunctionExtensionWithUniqueName(String name);
+
+  List<KmFunctionSubject> getFunctions();
+
   List<ClassSubject> getParameterTypesInFunctions();
 
   List<ClassSubject> getReturnTypesInFunctions();
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
new file mode 100644
index 0000000..ca91912
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
@@ -0,0 +1,15 @@
+// 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 kotlinx.metadata.KmFunction;
+
+public abstract class KmFunctionSubject extends Subject {
+  // TODO(b/145824437): This is a dup of KotlinMetadataSynthesizer#isExtension
+  static boolean isExtension(KmFunction kmFunction) {
+    return kmFunction.getReceiverParameterType() != null;
+  }
+
+  public abstract boolean isExtension();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmPackageSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmPackageSubject.java
index 8e00ce3..af44e1f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmPackageSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmPackageSubject.java
@@ -3,5 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.graph.DexClass;
+
 public abstract class KmPackageSubject extends Subject implements KmDeclarationContainerSubject {
+  public abstract DexClass getDexClass();
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index d9f11d6..b1c6958 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -61,6 +61,10 @@
       type = "annotation";
     } else if (subject instanceof KmClassSubject) {
       type = "@Metadata.KmClass";
+    } else if (subject instanceof KmPackageSubject) {
+      type = "@Metadata.KmPackage";
+    } else if (subject instanceof KmFunctionSubject) {
+      type = "@Metadata.KmFunction";
     }
     return type;
   }
@@ -77,6 +81,10 @@
       name = ((AnnotationSubject) subject).getAnnotation().type.toSourceString();
     } else if (subject instanceof KmClassSubject) {
       name = ((KmClassSubject) subject).getDexClass().toSourceString();
+    } else if (subject instanceof KmPackageSubject) {
+      name = ((KmPackageSubject) subject).getDexClass().toSourceString();
+    } else if (subject instanceof KmFunctionSubject) {
+      name = ((KmFunctionSubject) subject).toString();
     }
     return name;
   }
@@ -357,6 +365,29 @@
     };
   }
 
+  public static Matcher<KmFunctionSubject> isExtension() {
+    return new TypeSafeMatcher<KmFunctionSubject>() {
+      @Override
+      protected boolean matchesSafely(KmFunctionSubject kmFunction) {
+        return kmFunction.isPresent() && kmFunction.isExtension();
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("is extension function");
+      }
+
+      @Override
+      public void describeMismatchSafely(
+          final KmFunctionSubject kmFunction, Description description) {
+        description
+            .appendText("kmFunction ")
+            .appendValue(kmFunction)
+            .appendText(" was not");
+      }
+    };
+  }
+
   public static Matcher<RetraceMethodResult> isInlineFrame() {
     return new TypeSafeMatcher<RetraceMethodResult>() {
       @Override