Introduce KmProperty subject to inspect (extension) property.

Bug: 70169921
Change-Id: I9257e1b7b61a40f04b8886173703c713dc6627de
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 7e4f7ca..51b04d9 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -35,6 +35,10 @@
     return kmFunction.getReceiverParameterType() != null;
   }
 
+  static boolean isExtension(KmProperty kmProperty) {
+    return kmProperty.getReceiverParameterType() != null;
+  }
+
   static KmType toKmType(String descriptor) {
     KmType kmType = new KmType(flagsOf());
     kmType.visitClass(descriptorToInternalName(descriptor));
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 ab702b1..6bc4908 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,7 +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.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.not;
@@ -160,7 +160,7 @@
       assertThat(kmPackage, isPresent());
 
       KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("fooExt");
-      assertThat(kmFunction, isExtension());
+      assertThat(kmFunction, isExtensionFunction());
 
       ClassSubject extra = inspector.clazz(extraClassName);
       assertThat(extra, isPresent());
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionFunctionTest.java
index 8e1e6b7..c752186 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionFunctionTest.java
@@ -4,7 +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.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.not;
@@ -96,7 +96,7 @@
       assertThat(kmPackage, isPresent());
 
       KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("extension");
-      assertThat(kmFunction, isExtension());
+      assertThat(kmFunction, isExtensionFunction());
     });
 
     Path libJar = compileResult.writeToZip();
@@ -161,7 +161,7 @@
       assertThat(kmPackage, isPresent());
 
       KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("extension");
-      assertThat(kmFunction, isExtension());
+      assertThat(kmFunction, isExtensionFunction());
     });
 
     Path libJar = compileResult.writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionPropertyTest.java
index 0b327c9..8690b16 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionPropertyTest.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.isExtensionProperty;
 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;
@@ -21,6 +22,7 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import com.android.tools.r8.utils.codeinspector.KmPropertySubject;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.List;
@@ -95,7 +97,8 @@
       KmPackageSubject kmPackage = bKt.getKmPackage();
       assertThat(kmPackage, isPresent());
 
-      // TODO(b/70169921): test property details.
+      KmPropertySubject kmProperty = kmPackage.kmPropertyExtensionWithUniqueName("asI");
+      assertThat(kmProperty, isExtensionProperty());
     });
 
     Path libJar = compileResult.writeToZip();
@@ -156,7 +159,8 @@
       KmPackageSubject kmPackage = bKt.getKmPackage();
       assertThat(kmPackage, isPresent());
 
-      // TODO(b/70169921): test property details.
+      KmPropertySubject kmProperty = kmPackage.kmPropertyExtensionWithUniqueName("asI");
+      assertThat(kmProperty, isExtensionProperty());
     });
 
     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 c7e5db4..c14ecba 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,7 +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.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.not;
@@ -96,7 +96,7 @@
 
       KmFunctionSubject kmFunction = kmPackage.kmFunctionWithUniqueName("fun");
       assertThat(kmFunction, isPresent());
-      assertThat(kmFunction, not(isExtension()));
+      assertThat(kmFunction, not(isExtensionFunction()));
     });
 
     Path libJar = compileResult.writeToZip();
@@ -160,7 +160,7 @@
 
       KmFunctionSubject kmFunction = kmPackage.kmFunctionWithUniqueName("fun");
       assertThat(kmFunction, isPresent());
-      assertThat(kmFunction, not(isExtension()));
+      assertThat(kmFunction, not(isExtensionFunction()));
     });
 
     Path libJar = compileResult.writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java
index c671ea5..d400279 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java
@@ -4,10 +4,13 @@
 package com.android.tools.r8.kotlin.metadata;
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionProperty;
 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;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
@@ -17,6 +20,7 @@
 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.KmPropertySubject;
 import java.nio.file.Path;
 import java.util.Collection;
 import org.junit.BeforeClass;
@@ -69,10 +73,35 @@
       ClassSubject person = inspector.clazz(personClassName);
       assertThat(person, isPresent());
       assertThat(person, not(isRenamed()));
+
       // API entry is kept, hence the presence of Metadata.
       KmClassSubject kmClass = person.getKmClass();
       assertThat(kmClass, isPresent());
-      // TODO(b/70169921): test property details.
+
+      KmPropertySubject name = kmClass.kmPropertyWithUniqueName("name");
+      assertThat(name, isPresent());
+      assertThat(name, not(isExtensionProperty()));
+      assertNotNull(name.fieldSignature());
+      assertNotNull(name.getterSignature());
+      // TODO(b/70169921): Can remove setter.
+      assertNotNull(name.setterSignature());
+
+      KmPropertySubject familyName = kmClass.kmPropertyWithUniqueName("familyName");
+      assertThat(familyName, isPresent());
+      assertThat(familyName, not(isExtensionProperty()));
+      // No backing field for property `familyName`
+      assertNull(familyName.fieldSignature());
+      assertNotNull(familyName.getterSignature());
+      // No setter for property `familyName`
+      assertNull(familyName.setterSignature());
+
+      KmPropertySubject age = kmClass.kmPropertyWithUniqueName("age");
+      assertThat(age, isPresent());
+      assertThat(age, not(isExtensionProperty()));
+      assertNotNull(age.fieldSignature());
+      assertNotNull(age.getterSignature());
+      // TODO(b/70169921): Can remove setter.
+      assertNotNull(name.setterSignature());
     });
 
     Path libJar = compileResult.writeToZip();
@@ -116,7 +145,31 @@
       // API entry is kept, hence the presence of Metadata.
       KmClassSubject kmClass = person.getKmClass();
       assertThat(kmClass, isPresent());
-      // TODO(b/70169921): test property details.
+
+      KmPropertySubject name = kmClass.kmPropertyWithUniqueName("name");
+      assertThat(name, isPresent());
+      assertThat(name, not(isExtensionProperty()));
+      assertNotNull(name.fieldSignature());
+      // TODO(b/70169921): Either remove getter or rewrite renamed signature.
+      assertNotNull(name.getterSignature());
+      assertNotNull(name.setterSignature());
+
+      KmPropertySubject familyName = kmClass.kmPropertyWithUniqueName("familyName");
+      assertThat(familyName, isPresent());
+      assertThat(familyName, not(isExtensionProperty()));
+      // No backing field for property `familyName`
+      assertNull(familyName.fieldSignature());
+      assertNotNull(familyName.getterSignature());
+      // No setter for property `familyName`
+      assertNull(familyName.setterSignature());
+
+      KmPropertySubject age = kmClass.kmPropertyWithUniqueName("age");
+      assertThat(age, isPresent());
+      assertThat(age, not(isExtensionProperty()));
+      assertNotNull(age.fieldSignature());
+      // TODO(b/70169921): Either remove getter or rewrite renamed signature.
+      assertNotNull(age.getterSignature());
+      assertNotNull(name.setterSignature());
     });
 
     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 212dd9f..6a2f504 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
@@ -69,6 +69,21 @@
   }
 
   @Override
+  public KmPropertySubject kmPropertyWithUniqueName(String name) {
+    return null;
+  }
+
+  @Override
+  public KmPropertySubject kmPropertyExtensionWithUniqueName(String name) {
+    return null;
+  }
+
+  @Override
+  public List<KmPropertySubject> getProperties() {
+    return null;
+  }
+
+  @Override
   public List<ClassSubject> getReturnTypesInProperties() {
     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
index fb7466b..e869207 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
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.codeinspector;
 
+import kotlinx.metadata.jvm.JvmMethodSignature;
+
 public class AbsentKmFunctionSubject extends KmFunctionSubject {
 
   @Override
@@ -24,4 +26,9 @@
   public boolean isExtension() {
     return false;
   }
+
+  @Override
+  public JvmMethodSignature signature() {
+    return null;
+  }
 }
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 1fd6506..aef78fc 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
@@ -69,6 +69,21 @@
   }
 
   @Override
+  public KmPropertySubject kmPropertyWithUniqueName(String name) {
+    return null;
+  }
+
+  @Override
+  public KmPropertySubject kmPropertyExtensionWithUniqueName(String name) {
+    return null;
+  }
+
+  @Override
+  public List<KmPropertySubject> getProperties() {
+    return null;
+  }
+
+  @Override
   public List<ClassSubject> getReturnTypesInProperties() {
     return null;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPropertySubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPropertySubject.java
new file mode 100644
index 0000000..70f6e52
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPropertySubject.java
@@ -0,0 +1,45 @@
+// 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.jvm.JvmFieldSignature;
+import kotlinx.metadata.jvm.JvmMethodSignature;
+
+public class AbsentKmPropertySubject extends KmPropertySubject {
+
+  @Override
+  public boolean isPresent() {
+    return false;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return false;
+  }
+
+  @Override
+  public boolean isSynthetic() {
+    return false;
+  }
+
+  @Override
+  public boolean isExtension() {
+    return false;
+  }
+
+  @Override
+  public JvmFieldSignature fieldSignature() {
+    return null;
+  }
+
+  @Override
+  public JvmMethodSignature getterSignature() {
+    return null;
+  }
+
+  @Override
+  public JvmMethodSignature setterSignature() {
+    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 f30a0e5..34bc771 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
@@ -15,10 +15,15 @@
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmFunctionExtensionVisitor;
 import kotlinx.metadata.KmFunctionVisitor;
+import kotlinx.metadata.KmProperty;
+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;
+import kotlinx.metadata.jvm.JvmPropertyExtensionVisitor;
 
 public interface FoundKmDeclarationContainerSubject extends KmDeclarationContainerSubject {
 
@@ -74,7 +79,7 @@
   // TODO(b/145824437): This is a dup of KotlinMetadataJvmExtensionUtils$KmFunctionProcessor
   class KmFunctionProcessor {
     // Custom name via @JvmName("..."). Otherwise, null.
-    private JvmMethodSignature signature = null;
+    JvmMethodSignature signature = null;
 
     KmFunctionProcessor(KmFunction kmFunction) {
       kmFunction.accept(new KmFunctionVisitor() {
@@ -93,26 +98,28 @@
         }
       });
     }
-
-    JvmMethodSignature signature() {
-      return signature;
-    }
   }
 
   default KmFunctionSubject kmFunctionOrExtensionWithUniqueName(String name, boolean isExtension) {
+    KmFunction foundFunction = null;
     for (KmFunction kmFunction : getKmDeclarationContainer().getFunctions()) {
       if (KmFunctionSubject.isExtension(kmFunction) != isExtension) {
         continue;
       }
       if (kmFunction.getName().equals(name)) {
-        return new FoundKmFunctionSubject(codeInspector(), kmFunction);
+        foundFunction = kmFunction;
+        break;
       }
       KmFunctionProcessor kmFunctionProcessor = new KmFunctionProcessor(kmFunction);
-      if (kmFunctionProcessor.signature() != null
-          && kmFunctionProcessor.signature().getName().equals(name)) {
-        return new FoundKmFunctionSubject(codeInspector(), kmFunction);
+      if (kmFunctionProcessor.signature != null
+          && kmFunctionProcessor.signature.getName().equals(name)) {
+        foundFunction = kmFunction;
+        break;
       }
     }
+    if (foundFunction != null) {
+      return new FoundKmFunctionSubject(codeInspector(), foundFunction);
+    }
     return new AbsentKmFunctionSubject();
   }
 
@@ -159,6 +166,91 @@
         .collect(Collectors.toList());
   }
 
+  // TODO(b/145824437): This is a dup of KotlinMetadataJvmExtensionUtils$KmPropertyProcessor
+  static class KmPropertyProcessor {
+    JvmFieldSignature fieldSignature = null;
+    // Custom getter via @get:JvmName("..."). Otherwise, null.
+    JvmMethodSignature getterSignature = null;
+    // Custom getter via @set:JvmName("..."). Otherwise, null.
+    JvmMethodSignature setterSignature = null;
+
+    KmPropertyProcessor(KmProperty kmProperty) {
+      kmProperty.accept(new KmPropertyVisitor() {
+        @Override
+        public KmPropertyExtensionVisitor visitExtensions(KmExtensionType type) {
+          if (type != JvmPropertyExtensionVisitor.TYPE) {
+            return null;
+          }
+          return new JvmPropertyExtensionVisitor() {
+            @Override
+            public void visit(
+                int flags,
+                JvmFieldSignature fieldDesc,
+                JvmMethodSignature getterDesc,
+                JvmMethodSignature setterDesc) {
+              assert fieldSignature == null : fieldSignature.asString();
+              fieldSignature = fieldDesc;
+              assert getterSignature == null : getterSignature.asString();
+              getterSignature = getterDesc;
+              assert setterSignature == null : setterSignature.asString();
+              setterSignature = setterDesc;
+            }
+          };
+        }
+      });
+    }
+  }
+
+  default KmPropertySubject kmPropertyOrExtensionWithUniqueName(String name, boolean isExtension) {
+    KmProperty foundProperty = null;
+    for (KmProperty kmProperty : getKmDeclarationContainer().getProperties()) {
+      if (KmPropertySubject.isExtension(kmProperty) != isExtension) {
+        continue;
+      }
+      if (kmProperty.getName().equals(name)) {
+        foundProperty = kmProperty;
+        break;
+      }
+      KmPropertyProcessor kmPropertyProcessor = new KmPropertyProcessor(kmProperty);
+      if (kmPropertyProcessor.fieldSignature != null
+          && kmPropertyProcessor.fieldSignature.getName().equals(name)) {
+        foundProperty = kmProperty;
+        break;
+      }
+      if (kmPropertyProcessor.getterSignature != null
+          && kmPropertyProcessor.getterSignature.getName().equals(name)) {
+        foundProperty = kmProperty;
+        break;
+      }
+      if (kmPropertyProcessor.setterSignature != null
+          && kmPropertyProcessor.setterSignature.getName().equals(name)) {
+        foundProperty = kmProperty;
+        break;
+      }
+    }
+    if (foundProperty != null) {
+      return new FoundKmPropertySubject(codeInspector(), foundProperty);
+    }
+    return new AbsentKmPropertySubject();
+  }
+
+  @Override
+  default KmPropertySubject kmPropertyWithUniqueName(String name) {
+    return kmPropertyOrExtensionWithUniqueName(name, false);
+  }
+
+  @Override
+  default KmPropertySubject kmPropertyExtensionWithUniqueName(String name) {
+    return kmPropertyOrExtensionWithUniqueName(name, true);
+  }
+
+  @Override
+  default List<KmPropertySubject> getProperties() {
+    return getKmDeclarationContainer().getProperties().stream()
+        .map(kmProperty -> new FoundKmPropertySubject(codeInspector(), kmProperty))
+        .collect(Collectors.toList());
+  }
+
   @Override
   default List<ClassSubject> getReturnTypesInProperties() {
     return getKmDeclarationContainer().getProperties().stream()
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 4ee0f82..7c970c2 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
@@ -3,15 +3,20 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.utils.codeinspector.FoundKmDeclarationContainerSubject.KmFunctionProcessor;
 import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.jvm.JvmMethodSignature;
 
 public class FoundKmFunctionSubject extends KmFunctionSubject {
   private final CodeInspector codeInspector;
   private final KmFunction kmFunction;
+  private final JvmMethodSignature signature;
 
   FoundKmFunctionSubject(CodeInspector codeInspector, KmFunction kmFunction) {
     this.codeInspector = codeInspector;
     this.kmFunction = kmFunction;
+    KmFunctionProcessor kmFunctionProcessor = new KmFunctionProcessor(kmFunction);
+    this.signature = kmFunctionProcessor.signature;
   }
 
   @Override
@@ -36,4 +41,9 @@
   public boolean isExtension() {
     return isExtension(kmFunction);
   }
+
+  @Override
+  public JvmMethodSignature signature() {
+    return signature;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java
new file mode 100644
index 0000000..4f9cc94
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java
@@ -0,0 +1,65 @@
+// 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 com.android.tools.r8.utils.codeinspector.FoundKmDeclarationContainerSubject.KmPropertyProcessor;
+import kotlinx.metadata.KmProperty;
+import kotlinx.metadata.jvm.JvmFieldSignature;
+import kotlinx.metadata.jvm.JvmMethodSignature;
+
+public class FoundKmPropertySubject extends KmPropertySubject {
+  private final CodeInspector codeInspector;
+  private final KmProperty kmProperty;
+  private final JvmFieldSignature fieldSignature;
+  private final JvmMethodSignature getterSignature;
+  private final JvmMethodSignature setterSignature;
+
+  FoundKmPropertySubject(CodeInspector codeInspector, KmProperty kmProperty) {
+    this.codeInspector = codeInspector;
+    this.kmProperty = kmProperty;
+    KmPropertyProcessor kmPropertyProcessor = new KmPropertyProcessor(kmProperty);
+    this.fieldSignature = kmPropertyProcessor.fieldSignature;
+    this.getterSignature = kmPropertyProcessor.getterSignature;
+    this.setterSignature = kmPropertyProcessor.setterSignature;
+  }
+
+  @Override
+  public boolean isPresent() {
+    return true;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    // TODO(b/70169921): How to determine it is renamed?
+    //   backing field renamed? If no backing field exists, then examine getter/setter?
+    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(kmProperty);
+  }
+
+  @Override
+  public JvmFieldSignature fieldSignature() {
+    return fieldSignature;
+  }
+
+  @Override
+  public JvmMethodSignature getterSignature() {
+    return getterSignature;
+  }
+
+  @Override
+  public JvmMethodSignature setterSignature() {
+    return setterSignature;
+  }
+}
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 677ad3d..0893077 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
@@ -22,5 +22,11 @@
 
   List<ClassSubject> getReturnTypesInFunctions();
 
+  KmPropertySubject kmPropertyWithUniqueName(String name);
+
+  KmPropertySubject kmPropertyExtensionWithUniqueName(String name);
+
+  List<KmPropertySubject> getProperties();
+
   List<ClassSubject> getReturnTypesInProperties();
 }
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 ca91912..2e9eeda 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
@@ -4,6 +4,7 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.jvm.JvmMethodSignature;
 
 public abstract class KmFunctionSubject extends Subject {
   // TODO(b/145824437): This is a dup of KotlinMetadataSynthesizer#isExtension
@@ -12,4 +13,6 @@
   }
 
   public abstract boolean isExtension();
+
+  public abstract JvmMethodSignature signature();
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java
new file mode 100644
index 0000000..9fb5a0a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java
@@ -0,0 +1,23 @@
+// 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.KmProperty;
+import kotlinx.metadata.jvm.JvmFieldSignature;
+import kotlinx.metadata.jvm.JvmMethodSignature;
+
+public abstract class KmPropertySubject extends Subject {
+  // TODO(b/145824437): This is a dup of KotlinMetadataSynthesizer#isExtension
+  static boolean isExtension(KmProperty kmProperty) {
+    return kmProperty.getReceiverParameterType() != null;
+  }
+
+  public abstract boolean isExtension();
+
+  public abstract JvmFieldSignature fieldSignature();
+
+  public abstract JvmMethodSignature getterSignature();
+
+  public abstract JvmMethodSignature setterSignature();
+}
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 b1c6958..d8d219e 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
@@ -65,6 +65,8 @@
       type = "@Metadata.KmPackage";
     } else if (subject instanceof KmFunctionSubject) {
       type = "@Metadata.KmFunction";
+    } else if (subject instanceof KmPropertySubject) {
+      type = "@Metadata.KmProperty";
     }
     return type;
   }
@@ -85,6 +87,8 @@
       name = ((KmPackageSubject) subject).getDexClass().toSourceString();
     } else if (subject instanceof KmFunctionSubject) {
       name = ((KmFunctionSubject) subject).toString();
+    } else if (subject instanceof KmPropertySubject) {
+      name = ((KmPropertySubject) subject).toString();
     }
     return name;
   }
@@ -365,7 +369,7 @@
     };
   }
 
-  public static Matcher<KmFunctionSubject> isExtension() {
+  public static Matcher<KmFunctionSubject> isExtensionFunction() {
     return new TypeSafeMatcher<KmFunctionSubject>() {
       @Override
       protected boolean matchesSafely(KmFunctionSubject kmFunction) {
@@ -388,6 +392,29 @@
     };
   }
 
+  public static Matcher<KmPropertySubject> isExtensionProperty() {
+    return new TypeSafeMatcher<KmPropertySubject>() {
+      @Override
+      protected boolean matchesSafely(KmPropertySubject kmProperty) {
+        return kmProperty.isPresent() && kmProperty.isExtension();
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("is extension property");
+      }
+
+      @Override
+      public void describeMismatchSafely(
+          final KmPropertySubject kmProperty, Description description) {
+        description
+            .appendText("kmProperty ")
+            .appendValue(kmProperty)
+            .appendText(" was not");
+      }
+    };
+  }
+
   public static Matcher<RetraceMethodResult> isInlineFrame() {
     return new TypeSafeMatcher<RetraceMethodResult>() {
       @Override