[keepanno] Initial implementation of support for @UsesReflectiontoAccessField

Bug: b/392865072
Change-Id: I4e6341d60e7d1d6b17746bab321d30cce7e89095
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
index e409d74..8f7ad00 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.MethodAccess;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Target;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsedByReflection;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsesReflectionToAccessField;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsesReflectionToAccessMethod;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsesReflectionToConstruct;
 import com.android.tools.r8.keepanno.ast.KeepAnnotationPattern;
@@ -109,6 +110,7 @@
         || AnnotationConstants.UsesReflection.isDescriptor(descriptor)
         || AnnotationConstants.UsesReflectionToConstruct.isDescriptor(descriptor)
         || AnnotationConstants.UsesReflectionToAccessMethod.isDescriptor(descriptor)
+        || AnnotationConstants.UsesReflectionToAccessField.isDescriptor(descriptor)
         || AnnotationConstants.ForApi.isDescriptor(descriptor)
         || AnnotationConstants.UsedByReflection.isDescriptor(descriptor)
         || AnnotationConstants.UsedByNative.isDescriptor(descriptor)
@@ -117,6 +119,8 @@
         || AnnotationConstants.UsesReflectionToConstruct.isKotlinRepeatableContainerDescriptor(
             descriptor)
         || AnnotationConstants.UsesReflectionToAccessMethod.isKotlinRepeatableContainerDescriptor(
+            descriptor)
+        || AnnotationConstants.UsesReflectionToAccessField.isKotlinRepeatableContainerDescriptor(
             descriptor)) {
       return true;
     }
@@ -391,6 +395,16 @@
                     .setClassNamePattern(KeepQualifiedClassNamePattern.exact(className))
                     .build());
       }
+      if (AnnotationConstants.UsesReflectionToAccessField.isDescriptor(descriptor)) {
+        return new UsesReflectionToAccessFieldVisitor(
+            parsingContext,
+            parent::accept,
+            setContext,
+            bindingsHelper ->
+                KeepClassItemPattern.builder()
+                    .setClassNamePattern(KeepQualifiedClassNamePattern.exact(className))
+                    .build());
+      }
       if (ForApi.isDescriptor(descriptor)) {
         return new ForApiClassVisitor(parsingContext, parent::accept, setContext, className);
       }
@@ -567,6 +581,23 @@
             bindingsHelper ->
                 createMethodItemContext(className, methodName, methodDescriptor, bindingsHelper));
       }
+      if (AnnotationConstants.UsesReflectionToAccessField.isDescriptor(descriptor)) {
+        return new UsesReflectionToAccessFieldVisitor(
+            parsingContext,
+            parent::accept,
+            setContext,
+            bindingsHelper ->
+                createMethodItemContext(className, methodName, methodDescriptor, bindingsHelper));
+      }
+      if (AnnotationConstants.UsesReflectionToAccessField.isKotlinRepeatableContainerDescriptor(
+          descriptor)) {
+        return new UsesReflectionToAccessFieldContainerVisitor(
+            parsingContext,
+            parent::accept,
+            setContext,
+            bindingsHelper ->
+                createMethodItemContext(className, methodName, methodDescriptor, bindingsHelper));
+      }
       if (AnnotationConstants.ForApi.isDescriptor(descriptor)) {
         return new ForApiMemberVisitor(
             parsingContext,
@@ -1910,6 +1941,209 @@
     }
   }
 
+  private static class UsesReflectionToAccessFieldContainerVisitor extends AnnotationVisitorBase {
+
+    private final AnnotationParsingContext parsingContext;
+    private final Parent<KeepEdge> parent;
+    private final Consumer<KeepEdgeMetaInfo.Builder> addContext;
+    private final Function<UserBindingsHelper, KeepItemPattern> contextBuilder;
+
+    UsesReflectionToAccessFieldContainerVisitor(
+        AnnotationParsingContext parsingContext,
+        Parent<KeepEdge> parent,
+        Consumer<KeepEdgeMetaInfo.Builder> addContext,
+        Function<UserBindingsHelper, KeepItemPattern> contextBuilder) {
+      super(parsingContext);
+      this.parsingContext = parsingContext;
+      this.parent = parent;
+      this.addContext = addContext;
+      this.contextBuilder = contextBuilder;
+    }
+
+    @Override
+    public AnnotationVisitor visitArray(String name) {
+      if (name.equals("value")) {
+        return new UsesReflectionToAccessFieldContainerElementVisitor(
+            parsingContext, parent, addContext, contextBuilder);
+      }
+      return super.visitArray(name);
+    }
+
+    @Override
+    public void visit(String name, Object value) {
+      super.visit(name, value);
+    }
+
+    @Override
+    public void visitEnd() {
+      super.visitEnd();
+    }
+  }
+
+  private static class UsesReflectionToAccessFieldContainerElementVisitor
+      extends AnnotationVisitorBase {
+    private final AnnotationParsingContext parsingContext;
+    private final Parent<KeepEdge> parent;
+    Consumer<KeepEdgeMetaInfo.Builder> addContext;
+    Function<UserBindingsHelper, KeepItemPattern> contextBuilder;
+
+    public UsesReflectionToAccessFieldContainerElementVisitor(
+        AnnotationParsingContext parsingContext,
+        Parent<KeepEdge> parent,
+        Consumer<KeepEdgeMetaInfo.Builder> addContext,
+        Function<UserBindingsHelper, KeepItemPattern> contextBuilder) {
+      super(parsingContext);
+      this.parsingContext = parsingContext;
+      this.parent = parent;
+      this.addContext = addContext;
+      this.contextBuilder = contextBuilder;
+    }
+
+    @Override
+    public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+      assert name == null;
+      if (AnnotationConstants.UsesReflectionToAccessField.isDescriptor(descriptor)) {
+        return new UsesReflectionToAccessFieldVisitor(
+            parsingContext, parent, addContext, contextBuilder);
+      }
+      return super.visitAnnotation(name, descriptor);
+    }
+
+    @Override
+    public void visit(String name, Object value) {
+      super.visit(name, value);
+    }
+  }
+
+  private static class UsesReflectionToAccessFieldVisitor extends AnnotationVisitorBase {
+
+    private final ParsingContext parsingContext;
+    private final Parent<KeepEdge> parent;
+    private final KeepEdge.Builder builder = KeepEdge.builder();
+    private final KeepPreconditions.Builder preconditions = KeepPreconditions.builder();
+    private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
+    private KeepMethodParametersPattern parameters = KeepMethodParametersPattern.any();
+    private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
+
+    private KeepQualifiedClassNamePattern qualifiedName;
+    private KeepFieldNamePattern fieldName;
+    private KeepFieldTypePattern fieldType = KeepFieldTypePattern.any();
+
+    UsesReflectionToAccessFieldVisitor(
+        AnnotationParsingContext parsingContext,
+        Parent<KeepEdge> parent,
+        Consumer<KeepEdgeMetaInfo.Builder> addContext,
+        Function<UserBindingsHelper, KeepItemPattern> contextBuilder) {
+      super(parsingContext);
+      this.parsingContext = parsingContext;
+      this.parent = parent;
+      KeepItemPattern context = contextBuilder.apply(bindingsHelper);
+      KeepBindingReference contextBinding =
+          bindingsHelper.defineFreshItemBinding("CONTEXT", context);
+      preconditions.addCondition(KeepCondition.builder().setItemReference(contextBinding).build());
+      addContext.accept(metaInfoBuilder);
+    }
+
+    @Override
+    public void visit(String name, Object value) {
+      if (name.equals(UsesReflectionToAccessField.classConstant) && value instanceof Type) {
+        qualifiedName =
+            KeepQualifiedClassNamePattern.exactFromDescriptor(((Type) value).getDescriptor());
+        return;
+      }
+      if (name.equals(UsesReflectionToAccessField.className) && value instanceof String) {
+        qualifiedName = KeepQualifiedClassNamePattern.exact((String) value);
+        return;
+      }
+      if (name.equals(UsesReflectionToAccessField.fieldName) && value instanceof String) {
+        fieldName = KeepFieldNamePattern.exact((String) value);
+        return;
+      }
+      if (name.equals(UsesReflectionToAccessField.fieldType) && value instanceof Type) {
+        fieldType =
+            KeepFieldTypePattern.fromType(
+                KeepTypePattern.fromDescriptor(((Type) value).getDescriptor()));
+        return;
+      }
+      if (name.equals(UsesReflectionToAccessField.fieldTypeName) && value instanceof String) {
+        fieldType =
+            KeepFieldTypePattern.fromType(
+                KeepTypePattern.fromClass(
+                    KeepClassPattern.fromName(
+                        KeepQualifiedClassNamePattern.exact((String) value))));
+        return;
+      }
+      super.visit(name, value);
+    }
+
+    @Override
+    public void visitEnd() {
+      String kotlinMetadataDescriptor = "Lkotlin/Metadata;";
+
+      KeepClassBindingReference classBinding =
+          bindingsHelper.defineFreshClassBinding(
+              KeepClassItemPattern.builder()
+                  .setClassPattern(
+                      KeepClassPattern.builder().setClassNamePattern(qualifiedName).build())
+                  .build());
+      KeepMemberBindingReference memberBinding =
+          bindingsHelper.defineFreshMemberBinding(
+              KeepMemberItemPattern.builder()
+                  .setClassReference(classBinding)
+                  .setMemberPattern(
+                      KeepFieldPattern.builder()
+                          .setNamePattern(fieldName)
+                          .setTypePattern(fieldType)
+                          .build())
+                  .build());
+
+      KeepClassBindingReference kotlinMetadataBinding =
+          bindingsHelper.defineFreshClassBinding(
+              KeepClassItemPattern.builder()
+                  .setClassPattern(KeepClassPattern.exactFromDescriptor(kotlinMetadataDescriptor))
+                  .build());
+      KeepMemberBindingReference kotlinMetadataMembersBinding =
+          bindingsHelper.defineFreshMemberBinding(
+              KeepMemberItemPattern.builder()
+                  .setClassReference(kotlinMetadataBinding)
+                  .setMemberPattern(KeepMemberPattern.allMembers())
+                  .build());
+
+      Annotation keepConstraintKotlinMetadataAnnotation =
+          KeepConstraint.annotation(
+              KeepAnnotationPattern.builder()
+                  .setNamePattern(
+                      KeepQualifiedClassNamePattern.exactFromDescriptor(kotlinMetadataDescriptor))
+                  .addRetentionPolicy(RetentionPolicy.RUNTIME)
+                  .build());
+
+      KeepConsequences.Builder consequencesBuilder =
+          KeepConsequences.builder()
+              .addTarget(
+                  KeepTarget.builder()
+                      .setItemReference(classBinding)
+                      .setConstraints(
+                          KeepConstraints.defaultAdditions(
+                              KeepConstraints.builder()
+                                  .add(keepConstraintKotlinMetadataAnnotation)
+                                  .build()))
+                      .build())
+              .addTarget(KeepTarget.builder().setItemReference(memberBinding).build())
+              .addTarget(KeepTarget.builder().setItemReference(memberBinding).build())
+              .addTarget(KeepTarget.builder().setItemReference(kotlinMetadataBinding).build())
+              .addTarget(
+                  KeepTarget.builder().setItemReference(kotlinMetadataMembersBinding).build());
+
+      parent.accept(
+          builder
+              .setMetaInfo(metaInfoBuilder.build())
+              .setBindings(bindingsHelper.build())
+              .setPreconditions(preconditions.build())
+              .setConsequences(consequencesBuilder.build())
+              .build());
+    }
+  }
+
   private static class KeepBindingsVisitor extends AnnotationVisitorBase {
     private final ParsingContext parsingContext;
     private final UserBindingsHelper helper;
diff --git a/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionToAccessFieldTest.java b/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionToAccessFieldTest.java
new file mode 100644
index 0000000..d5ce9e4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionToAccessFieldTest.java
@@ -0,0 +1,292 @@
+// Copyright (c) 2025, 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.keepanno.androidx;
+
+import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
+
+import androidx.annotation.keep.UsesReflectionToAccessField;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassFileTransformer.AnnotationBuilder;
+import com.android.tools.r8.transformers.ClassFileTransformer.AnnotationContentBuilder;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.function.Consumer;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Type;
+
+@RunWith(Parameterized.class)
+public class KeepUsesReflectionToAccessFieldTest extends KeepAnnoTestExtractedRulesBase {
+
+  @Parameterized.Parameters(name = "{0}, {1}")
+  public static Collection<Object[]> data() {
+    // Test with Android 14, which has `java.lang.ClassValue` to avoid having to deal with R8
+    // missing class warnings for tests using the kotlin-reflect library.
+    return buildParameters(
+        createParameters(
+            getTestParameters()
+                .withDexRuntime(Version.V14_0_0)
+                .withDefaultCfRuntime()
+                .withMaximumApiLevel()
+                .build()),
+        getKotlinTestParameters().withLatestCompiler().build());
+  }
+
+  private static Collection<Path> getKotlinSources() {
+    try {
+      return getFilesInTestFolderRelativeToClass(
+          KeepUsesReflectionToAccessFieldTest.class, "kt", "Fields.kt", "FieldsKeptClass.kt");
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    compilationResults = getCompileMemoizerWithKeepAnnoLib(getKotlinSources());
+  }
+
+  private static ExpectedRules getExpectedRulesJava(
+      Class<?> conditionClass, String... consequentMembers) {
+    java.util.function.Consumer<ExpectedKeepRule.Builder> setCondition =
+        b ->
+            b.setConditionClass(conditionClass)
+                .setConditionMembers("{ void foo(java.lang.Class); }");
+    ExpectedRules.Builder builder = ExpectedRules.builder();
+    for (int i = 0; i < consequentMembers.length; i++) {
+      builder.add(
+          ExpectedKeepRule.builder()
+              .apply(setCondition)
+              .setConsequentClass(KeptClass.class)
+              .setConsequentMembers(consequentMembers[i])
+              .build());
+    }
+    addConsequentKotlinMetadata(builder, b -> b.apply(setCondition));
+    addDefaultInitWorkaround(
+        builder, b -> b.apply(setCondition).setConsequentClass(KeptClass.class));
+    return builder.build();
+  }
+
+  private static ExpectedRules getExpectedRulesKotlin(
+      String conditionClass,
+      String conditionMembers,
+      String consequentClass,
+      String... consequentMembers) {
+    Consumer<ExpectedKeepRule.Builder> setCondition =
+        b -> b.setConditionClass(conditionClass).setConditionMembers(conditionMembers);
+    ExpectedRules.Builder builder = ExpectedRules.builder();
+    for (int i = 0; i < consequentMembers.length; i++) {
+      builder.add(
+          ExpectedKeepRule.builder()
+              .apply(setCondition)
+              .setConsequentClass(consequentClass)
+              .setConsequentMembers(consequentMembers[i])
+              .build());
+    }
+    addConsequentKotlinMetadata(builder, b -> b.apply(setCondition));
+    addDefaultInitWorkaround(
+        builder, b -> b.apply(setCondition).setConsequentClass(consequentClass));
+    return builder.build();
+  }
+
+  private static void buildUsesReflectionToAccessField(
+      AnnotationBuilder builder, Object clazz, String fieldName, Class<?> fieldType) {
+    AnnotationContentBuilder ab =
+        builder.setAnnotationClass(Reference.classFromClass(UsesReflectionToAccessField.class));
+    if (clazz instanceof String) {
+      ab.setField("className", clazz);
+    } else {
+      assert clazz instanceof Class<?> || clazz instanceof Type;
+      ab.setField("classConstant", clazz);
+    }
+    ab.setField("fieldName", fieldName);
+    // No fieldType means any field type.
+    if (fieldType != null) {
+      ab.setField("fieldType", fieldType);
+    }
+  }
+
+  private static void buildUsesReflectionToAccessField(
+      AnnotationBuilder builder, Object clazz, String fieldName) {
+    buildUsesReflectionToAccessField(builder, clazz, fieldName, null);
+  }
+
+  private static void buildUsesReflectionToAccessFieldMultiple(
+      AnnotationBuilder builder, Object clazz) {
+    builder
+        .setAnnotationClass(
+            Reference.classFromBinaryName(
+                Reference.classFromClass(UsesReflectionToAccessField.class).getBinaryName()
+                    + "$Container"))
+        .buildArray(
+            "value",
+            builder1 ->
+                builder1
+                    .setAnnotationField(
+                        null,
+                        builder2 ->
+                            buildUsesReflectionToAccessField(builder2, clazz, "x", int.class))
+                    .setAnnotationField(
+                        null,
+                        builder3 ->
+                            buildUsesReflectionToAccessField(builder3, clazz, "y", long.class))
+                    .setAnnotationField(
+                        null,
+                        builder4 ->
+                            buildUsesReflectionToAccessField(builder4, clazz, "s", String.class)));
+  }
+
+  @Test
+  public void testAnyFieldType() throws Exception {
+    testExtractedRulesAndRunJava(
+        ClassWithAnnotation.class,
+        ImmutableList.of(KeptClass.class),
+        ImmutableList.of(
+            setAnnotationOnMethod(
+                ClassWithAnnotation.class,
+                MethodPredicate.onName("foo"),
+                builder -> buildUsesReflectionToAccessField(builder, KeptClass.class, "x"))),
+        getExpectedRulesJava(ClassWithAnnotation.class, "{ *** x; }"),
+        parameters.isReference() ? StringUtils.lines("3") : StringUtils.lines("1"));
+  }
+
+  @Test
+  public void testIntFieldType() throws Exception {
+    testExtractedRulesAndRunJava(
+        ClassWithAnnotation.class,
+        ImmutableList.of(KeptClass.class),
+        ImmutableList.of(
+            setAnnotationOnMethod(
+                ClassWithAnnotation.class,
+                MethodPredicate.onName("foo"),
+                builder ->
+                    buildUsesReflectionToAccessField(builder, KeptClass.class, "x", int.class))),
+        getExpectedRulesJava(ClassWithAnnotation.class, "{ int x; }"),
+        parameters.isReference() ? StringUtils.lines("3") : StringUtils.lines("1"));
+  }
+
+  @Test
+  public void testMultipleFieldTypes() throws Exception {
+    testExtractedRulesAndRunJava(
+        ClassWithAnnotation.class,
+        ImmutableList.of(KeptClass.class),
+        ImmutableList.of(
+            setAnnotationOnMethod(
+                ClassWithAnnotation.class,
+                MethodPredicate.onName("foo"),
+                builder -> buildUsesReflectionToAccessFieldMultiple(builder, KeptClass.class))),
+        getExpectedRulesJava(
+            ClassWithAnnotation.class, "{ int x; }", "{ long y; }", "{ java.lang.String s; }"),
+        StringUtils.lines("3"));
+  }
+
+  @Test
+  public void testAnyFieldTypeKotlin() throws Exception {
+    testExtractedRulesAndRunKotlin(
+        compilationResults,
+        (classReference, classFileBytes) ->
+            setAnnotationOnMethod(
+                classReference,
+                classFileBytes,
+                Reference.classFromTypeName("com.android.tools.r8.keepanno.androidx.kt.Fields"),
+                MethodPredicate.onName("foo"),
+                builder ->
+                    buildUsesReflectionToAccessField(
+                        builder,
+                        Type.getType(
+                            DescriptorUtils.javaTypeToDescriptor(
+                                "com.android.tools.r8.keepanno.androidx.kt.FieldsKeptClass")),
+                        "x")),
+        "com.android.tools.r8.keepanno.androidx.kt.FieldsKt",
+        getExpectedRulesKotlin(
+            "com.android.tools.r8.keepanno.androidx.kt.Fields",
+            "{ void foo(kotlin.reflect.KClass); }",
+            "com.android.tools.r8.keepanno.androidx.kt.FieldsKeptClass",
+            "{ *** x; }"),
+        StringUtils.lines("1"));
+  }
+
+  @Test
+  public void testIntFieldTypeKotlin() throws Exception {
+    testExtractedRulesAndRunKotlin(
+        compilationResults,
+        (classReference, classFileBytes) ->
+            setAnnotationOnMethod(
+                classReference,
+                classFileBytes,
+                Reference.classFromTypeName("com.android.tools.r8.keepanno.androidx.kt.Fields"),
+                MethodPredicate.onName("foo"),
+                builder ->
+                    buildUsesReflectionToAccessField(
+                        builder,
+                        Type.getType(
+                            DescriptorUtils.javaTypeToDescriptor(
+                                "com.android.tools.r8.keepanno.androidx.kt.FieldsKeptClass")),
+                        "x",
+                        int.class)),
+        "com.android.tools.r8.keepanno.androidx.kt.FieldsKt",
+        getExpectedRulesKotlin(
+            "com.android.tools.r8.keepanno.androidx.kt.Fields",
+            "{ void foo(kotlin.reflect.KClass); }",
+            "com.android.tools.r8.keepanno.androidx.kt.FieldsKeptClass",
+            "{ int x; }"),
+        StringUtils.lines("1"));
+  }
+
+  @Test
+  public void testMultipleFieldsKotlin() throws Exception {
+    testExtractedRulesAndRunKotlin(
+        compilationResults,
+        (classReference, classFileBytes) ->
+            setAnnotationOnMethod(
+                classReference,
+                classFileBytes,
+                Reference.classFromTypeName("com.android.tools.r8.keepanno.androidx.kt.Fields"),
+                MethodPredicate.onName("foo"),
+                builder ->
+                    buildUsesReflectionToAccessFieldMultiple(
+                        builder,
+                        Type.getType(
+                            DescriptorUtils.javaTypeToDescriptor(
+                                "com.android.tools.r8.keepanno.androidx.kt.FieldsKeptClass")))),
+        "com.android.tools.r8.keepanno.androidx.kt.FieldsKt",
+        getExpectedRulesKotlin(
+            "com.android.tools.r8.keepanno.androidx.kt.Fields",
+            "{ void foo(kotlin.reflect.KClass); }",
+            "com.android.tools.r8.keepanno.androidx.kt.FieldsKeptClass",
+            "{ int x; }",
+            "{ long y; }",
+            "{ java.lang.String s; }"),
+        StringUtils.lines("3"));
+  }
+
+  // Test class without annotation to be used by multiple tests inserting annotations using a
+  // transformer.
+  static class ClassWithAnnotation {
+
+    public void foo(Class<KeptClass> clazz) throws Exception {
+      if (clazz != null) {
+        System.out.println(clazz.getDeclaredFields().length);
+      }
+    }
+
+    public static void main(String[] args) throws Exception {
+      new ClassWithAnnotation().foo(System.nanoTime() > 0 ? KeptClass.class : null);
+    }
+  }
+
+  static class KeptClass {
+    public int x;
+    public long y;
+    public String s;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/androidx/kt/Fields.kt b/src/test/java/com/android/tools/r8/keepanno/androidx/kt/Fields.kt
new file mode 100644
index 0000000..13dabea
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/androidx/kt/Fields.kt
@@ -0,0 +1,25 @@
+// Copyright (c) 2025, 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.keepanno.androidx.kt
+
+import kotlin.reflect.KClass
+import kotlin.reflect.full.declaredMemberProperties
+
+class Fields {
+  fun foo(clazz: KClass<FieldsKeptClass>?) {
+    println(clazz?.declaredMemberProperties?.size)
+  }
+}
+
+class FieldsKeptClass {
+  var x: Int = 0
+
+  var y: Long = 0
+
+  var s: String = ""
+}
+
+fun main() {
+  Fields().foo(if (System.nanoTime() > 0) FieldsKeptClass::class else null)
+}