[KeepAnno] Relax annotation contexts and support preconditions.

Bug: b/248408342
Change-Id: I47291a9cb764508c3a835299849e892e793910b3
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
index 9d0265e..6d66d0a 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
@@ -8,6 +8,28 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * A condition for a keep edge.
+ *
+ * <p>The condition denotes a keep item:
+ *
+ * <ul>
+ *   <li>a class, or pattern on classes;
+ *   <li>a method, or pattern on methods; or
+ *   <li>a field, or pattern on fields.
+ * </ul>
+ *
+ * <p>The structure of a condition item is the same as for a target item but without a notion of
+ * "keep options".
+ */
 @Target(ElementType.ANNOTATION_TYPE)
 @Retention(RetentionPolicy.CLASS)
-public @interface KeepCondition {}
+public @interface KeepCondition {
+  Class<?> classConstant() default Object.class;
+
+  String classTypeName() default "";
+
+  String methodName() default "";
+
+  String fieldName() default "";
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
index 5b6de6e..ca219b2 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
@@ -26,11 +26,20 @@
     public static final String consequences = "consequences";
   }
 
-  public static final class Target {
-    public static final Class<KeepTarget> CLASS = KeepTarget.class;
-    public static final String DESCRIPTOR = getDescriptor(CLASS);
+  // Implicit hidden item which is "super type" of Condition and Target.
+  public static final class Item {
     public static final String classConstant = "classConstant";
     public static final String methodName = "methodName";
     public static final String fieldName = "fieldName";
   }
+
+  public static final class Condition {
+    public static final Class<KeepCondition> CLASS = KeepCondition.class;
+    public static final String DESCRIPTOR = getDescriptor(CLASS);
+  }
+
+  public static final class Target {
+    public static final Class<KeepTarget> CLASS = KeepTarget.class;
+    public static final String DESCRIPTOR = getDescriptor(CLASS);
+  }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepEdge.java
index 19b1a4a..fb9a772 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepEdge.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepEdge.java
@@ -8,7 +8,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
-@Target(ElementType.TYPE)
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
 @Retention(RetentionPolicy.CLASS)
 public @interface KeepEdge {
   KeepCondition[] preconditions() default {};
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 cc6e4ee..dd5be82 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
@@ -3,8 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.asm;
 
+import com.android.tools.r8.keepanno.annotations.KeepConstants.Condition;
 import com.android.tools.r8.keepanno.annotations.KeepConstants.Edge;
+import com.android.tools.r8.keepanno.annotations.KeepConstants.Item;
 import com.android.tools.r8.keepanno.annotations.KeepConstants.Target;
+import com.android.tools.r8.keepanno.ast.KeepCondition;
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.ast.KeepEdgeException;
@@ -22,6 +25,8 @@
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 
@@ -52,6 +57,54 @@
       }
       return null;
     }
+
+    @Override
+    public MethodVisitor visitMethod(
+        int access, String name, String descriptor, String signature, String[] exceptions) {
+      return new KeepEdgeMethodVisitor(parent);
+    }
+
+    @Override
+    public FieldVisitor visitField(
+        int access, String name, String descriptor, String signature, Object value) {
+      return new KeepEdgeFieldVisitor(parent);
+    }
+  }
+
+  private static class KeepEdgeMethodVisitor extends MethodVisitor {
+    private final Parent<KeepEdge> parent;
+
+    KeepEdgeMethodVisitor(Parent<KeepEdge> parent) {
+      super(ASM_VERSION);
+      this.parent = parent;
+    }
+
+    @Override
+    public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+      // Skip any visible annotations as @KeepEdge is not runtime visible.
+      if (!visible && descriptor.equals(Edge.DESCRIPTOR)) {
+        return new KeepEdgeVisitor(parent);
+      }
+      return null;
+    }
+  }
+
+  private static class KeepEdgeFieldVisitor extends FieldVisitor {
+    private final Parent<KeepEdge> parent;
+
+    KeepEdgeFieldVisitor(Parent<KeepEdge> parent) {
+      super(ASM_VERSION);
+      this.parent = parent;
+    }
+
+    @Override
+    public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+      // Skip any visible annotations as @KeepEdge is not runtime visible.
+      if (!visible && descriptor.equals(Edge.DESCRIPTOR)) {
+        return new KeepEdgeVisitor(parent);
+      }
+      return null;
+    }
   }
 
   // Interface for providing AST result(s) for a sub-tree back up to its parent.
@@ -113,10 +166,24 @@
 
   private static class KeepPreconditionsVisitor extends AnnotationVisitorBase {
     private final Parent<KeepPreconditions> parent;
+    private final KeepPreconditions.Builder builder = KeepPreconditions.builder();
 
     public KeepPreconditionsVisitor(Parent<KeepPreconditions> parent) {
       this.parent = parent;
     }
+
+    @Override
+    public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+      if (descriptor.equals(Condition.DESCRIPTOR)) {
+        return new KeepConditionVisitor(builder::addCondition);
+      }
+      return super.visitAnnotation(name, descriptor);
+    }
+
+    @Override
+    public void visitEnd() {
+      parent.accept(builder.build());
+    }
   }
 
   private static class KeepConsequencesVisitor extends AnnotationVisitorBase {
@@ -141,27 +208,28 @@
     }
   }
 
-  private static class KeepTargetVisitor extends AnnotationVisitorBase {
-    private final Parent<KeepTarget> parent;
+  private abstract static class KeepItemVisitorBase extends AnnotationVisitorBase {
+    private final Parent<KeepItemPattern> parent;
+
     private KeepQualifiedClassNamePattern classNamePattern = null;
     private KeepMethodNamePattern methodName = null;
     private KeepFieldNamePattern fieldName = null;
 
-    public KeepTargetVisitor(Parent<KeepTarget> parent) {
+    public KeepItemVisitorBase(Parent<KeepItemPattern> parent) {
       this.parent = parent;
     }
 
     @Override
     public void visit(String name, Object value) {
-      if (name.equals(Target.classConstant) && value instanceof Type) {
+      if (name.equals(Item.classConstant) && value instanceof Type) {
         classNamePattern = KeepQualifiedClassNamePattern.exact(((Type) value).getClassName());
         return;
       }
-      if (name.equals(Target.methodName) && value instanceof String) {
+      if (name.equals(Item.methodName) && value instanceof String) {
         methodName = KeepMethodNamePattern.exact((String) value);
         return;
       }
-      if (name.equals(Target.fieldName) && value instanceof String) {
+      if (name.equals(Item.fieldName) && value instanceof String) {
         fieldName = KeepFieldNamePattern.exact((String) value);
         return;
       }
@@ -184,8 +252,21 @@
       if (fieldName != null) {
         itemBuilder.setMemberPattern(KeepFieldPattern.builder().setNamePattern(fieldName).build());
       }
-      KeepTarget target = KeepTarget.builder().setItem(itemBuilder.build()).build();
-      parent.accept(target);
+      parent.accept(itemBuilder.build());
+    }
+  }
+
+  private static class KeepTargetVisitor extends KeepItemVisitorBase {
+
+    public KeepTargetVisitor(Parent<KeepTarget> parent) {
+      super(item -> parent.accept(KeepTarget.builder().setItem(item).build()));
+    }
+  }
+
+  private static class KeepConditionVisitor extends KeepItemVisitorBase {
+
+    public KeepConditionVisitor(Parent<KeepCondition> parent) {
+      super(item -> parent.accept(KeepCondition.builder().setItem(item).build()));
     }
   }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
index 0a0ccc8..a8f631a 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
@@ -3,7 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.asm;
 
+import com.android.tools.r8.keepanno.annotations.KeepConstants;
+import com.android.tools.r8.keepanno.annotations.KeepConstants.Condition;
 import com.android.tools.r8.keepanno.annotations.KeepConstants.Edge;
+import com.android.tools.r8.keepanno.annotations.KeepConstants.Item;
 import com.android.tools.r8.keepanno.annotations.KeepConstants.Target;
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
@@ -44,7 +47,15 @@
     if (preconditions.isAlways()) {
       return;
     }
-    throw new Unimplemented();
+    String ignoredArrayValueName = null;
+    AnnotationVisitor arrayVisitor = visitor.visitArray(Edge.preconditions);
+    preconditions.forEach(
+        condition -> {
+          AnnotationVisitor conditionVisitor =
+              arrayVisitor.visitAnnotation(ignoredArrayValueName, Condition.DESCRIPTOR);
+          writeItem(conditionVisitor, condition.getItemPattern());
+        });
+    arrayVisitor.visitEnd();
   }
 
   private void writeConsequences(AnnotationVisitor visitor, KeepConsequences consequences) {
@@ -59,26 +70,29 @@
           if (!target.getOptions().isKeepAll()) {
             throw new Unimplemented();
           }
-          KeepItemPattern item = target.getItem();
-          if (item.isAny()) {
-            throw new Unimplemented();
-          }
-          KeepQualifiedClassNamePattern namePattern = item.getClassNamePattern();
-          if (namePattern.isExact()) {
-            Type typeConstant = Type.getType(namePattern.getExactDescriptor());
-            targetVisitor.visit(Target.classConstant, typeConstant);
-          } else {
-            throw new Unimplemented();
-          }
-          if (!item.getExtendsPattern().isAny()) {
-            throw new Unimplemented();
-          }
-          writeMember(item.getMemberPattern(), targetVisitor);
-          targetVisitor.visitEnd();
+          writeItem(targetVisitor, target.getItem());
         });
     arrayVisitor.visitEnd();
   }
 
+  private void writeItem(AnnotationVisitor itemVisitor, KeepItemPattern item) {
+    if (item.isAny()) {
+      throw new Unimplemented();
+    }
+    KeepQualifiedClassNamePattern namePattern = item.getClassNamePattern();
+    if (namePattern.isExact()) {
+      Type typeConstant = Type.getType(namePattern.getExactDescriptor());
+      itemVisitor.visit(KeepConstants.Item.classConstant, typeConstant);
+    } else {
+      throw new Unimplemented();
+    }
+    if (!item.getExtendsPattern().isAny()) {
+      throw new Unimplemented();
+    }
+    writeMember(item.getMemberPattern(), itemVisitor);
+    itemVisitor.visitEnd();
+  }
+
   private void writeMember(KeepMemberPattern memberPattern, AnnotationVisitor targetVisitor) {
     if (memberPattern.isNone()) {
       // Default is "no methods".
@@ -99,7 +113,7 @@
   private void writeField(KeepFieldPattern field, AnnotationVisitor targetVisitor) {
     KeepFieldNameExactPattern exactFieldName = field.getNamePattern().asExact();
     if (exactFieldName != null) {
-      targetVisitor.visit(Target.fieldName, exactFieldName.getName());
+      targetVisitor.visit(Item.fieldName, exactFieldName.getName());
     } else {
       throw new Unimplemented();
     }
@@ -114,7 +128,7 @@
   private void writeMethod(KeepMethodPattern method, AnnotationVisitor targetVisitor) {
     KeepMethodNameExactPattern exactMethodName = method.getNamePattern().asExact();
     if (exactMethodName != null) {
-      targetVisitor.visit(Target.methodName, exactMethodName.getName());
+      targetVisitor.visit(Item.methodName, exactMethodName.getName());
     } else {
       throw new Unimplemented();
     }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCondition.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCondition.java
index 43b4176..9909097 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCondition.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCondition.java
@@ -36,10 +36,33 @@
   private final KeepItemPattern itemPattern;
 
   private KeepCondition(KeepItemPattern itemPattern) {
+    assert itemPattern != null;
     this.itemPattern = itemPattern;
   }
 
   public KeepItemPattern getItemPattern() {
     return itemPattern;
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    KeepCondition that = (KeepCondition) o;
+    return itemPattern.equals(that.itemPattern);
+  }
+
+  @Override
+  public int hashCode() {
+    return itemPattern.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return itemPattern.toString();
+  }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java b/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
index 18d3932..fe49466 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
@@ -9,9 +9,10 @@
 
 import com.android.tools.r8.keepanno.annotations.KeepConstants;
 import com.android.tools.r8.keepanno.annotations.KeepConstants.Edge;
-import com.android.tools.r8.keepanno.annotations.KeepConstants.Target;
+import com.android.tools.r8.keepanno.annotations.KeepConstants.Item;
 import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
 import com.android.tools.r8.keepanno.asm.KeepEdgeWriter;
+import com.android.tools.r8.keepanno.ast.KeepCondition;
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.ast.KeepEdge.Builder;
@@ -21,12 +22,15 @@
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
+import com.android.tools.r8.keepanno.ast.KeepPreconditions;
 import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepTarget;
-import com.android.tools.r8.keepanno.utils.Unimplemented;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.function.Consumer;
 import javax.annotation.processing.AbstractProcessor;
@@ -58,26 +62,33 @@
 
   @Override
   public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
-    for (Element rootElement : roundEnv.getRootElements()) {
-      TypeElement typeElement = getEnclosingTypeElement(rootElement);
-      KeepEdge edge = processKeepEdge(typeElement, roundEnv);
-      if (edge != null) {
-        String edgeTargetClass =
-            getClassTypeNameForSynthesizedEdges(typeElement.getQualifiedName().toString());
-        byte[] writtenEdge = writeEdge(edge, edgeTargetClass);
-        Filer filer = processingEnv.getFiler();
-        try {
-          JavaFileObject classFile = filer.createClassFile(edgeTargetClass);
-          classFile.openOutputStream().write(writtenEdge);
-        } catch (IOException e) {
-          error(e.getMessage());
+    Map<String, List<KeepEdge>> collectedEdges = new HashMap<>();
+    for (TypeElement annotation : annotations) {
+      for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
+        KeepEdge edge = processKeepEdge(element, roundEnv);
+        if (edge != null) {
+          TypeElement enclosingType = getEnclosingTypeElement(element);
+          String enclosingTypeName = enclosingType.getQualifiedName().toString();
+          collectedEdges.computeIfAbsent(enclosingTypeName, k -> new ArrayList<>()).add(edge);
         }
       }
     }
+    for (Entry<String, List<KeepEdge>> entry : collectedEdges.entrySet()) {
+      String enclosingTypeName = entry.getKey();
+      String edgeTargetClass = getClassTypeNameForSynthesizedEdges(enclosingTypeName);
+      byte[] writtenEdge = writeEdges(entry.getValue(), edgeTargetClass);
+      Filer filer = processingEnv.getFiler();
+      try {
+        JavaFileObject classFile = filer.createClassFile(edgeTargetClass);
+        classFile.openOutputStream().write(writtenEdge);
+      } catch (IOException e) {
+        error(e.getMessage());
+      }
+    }
     return true;
   }
 
-  private static byte[] writeEdge(KeepEdge edge, String classTypeName) {
+  private static byte[] writeEdges(List<KeepEdge> edges, String classTypeName) {
     String classBinaryName = KeepConstants.getBinaryNameFromClassTypeName(classTypeName);
     ClassWriter classWriter = new ClassWriter(0);
     classWriter.visit(
@@ -88,13 +99,15 @@
         "java/lang/Object",
         null);
     classWriter.visitSource("SynthesizedKeepEdge", null);
-    KeepEdgeWriter.writeEdge(edge, classWriter);
+    for (KeepEdge edge : edges) {
+      KeepEdgeWriter.writeEdge(edge, classWriter);
+    }
     classWriter.visitEnd();
     return classWriter.toByteArray();
   }
 
-  private KeepEdge processKeepEdge(TypeElement keepEdge, RoundEnvironment roundEnv) {
-    AnnotationMirror mirror = getAnnotationMirror(keepEdge, KeepConstants.Edge.CLASS);
+  private KeepEdge processKeepEdge(Element element, RoundEnvironment roundEnv) {
+    AnnotationMirror mirror = getAnnotationMirror(element, KeepConstants.Edge.CLASS);
     if (mirror == null) {
       return null;
     }
@@ -109,7 +122,15 @@
     if (preconditions == null) {
       return;
     }
-    throw new Unimplemented();
+    KeepPreconditions.Builder preconditionsBuilder = KeepPreconditions.builder();
+    new AnnotationListValueVisitor(
+            value -> {
+              KeepCondition.Builder conditionBuilder = KeepCondition.builder();
+              processCondition(conditionBuilder, AnnotationMirrorValueVisitor.getMirror(value));
+              preconditionsBuilder.addCondition(conditionBuilder.build());
+            })
+        .onValue(preconditions);
+    edgeBuilder.setPreconditions(preconditionsBuilder.build());
   }
 
   private void processConsequences(Builder edgeBuilder, AnnotationMirror mirror) {
@@ -141,31 +162,41 @@
     }
   }
 
+  private void processCondition(KeepCondition.Builder builder, AnnotationMirror mirror) {
+    KeepItemPattern.Builder itemBuilder = KeepItemPattern.builder();
+    processItem(itemBuilder, mirror);
+    builder.setItem(itemBuilder.build());
+  }
+
   private void processTarget(KeepTarget.Builder builder, AnnotationMirror mirror) {
     KeepItemPattern.Builder itemBuilder = KeepItemPattern.builder();
-    AnnotationValue classConstantValue = getAnnotationValue(mirror, Target.classConstant);
+    processItem(itemBuilder, mirror);
+    builder.setItem(itemBuilder.build());
+  }
+
+  private void processItem(KeepItemPattern.Builder builder, AnnotationMirror mirror) {
+    AnnotationValue classConstantValue = getAnnotationValue(mirror, Item.classConstant);
     if (classConstantValue != null) {
       DeclaredType type = AnnotationClassValueVisitor.getType(classConstantValue);
       String typeName = getTypeNameForClassConstantElement(type);
-      itemBuilder.setClassPattern(KeepQualifiedClassNamePattern.exact(typeName));
+      builder.setClassPattern(KeepQualifiedClassNamePattern.exact(typeName));
     }
-    AnnotationValue methodNameValue = getAnnotationValue(mirror, Target.methodName);
-    AnnotationValue fieldNameValue = getAnnotationValue(mirror, Target.fieldName);
+    AnnotationValue methodNameValue = getAnnotationValue(mirror, Item.methodName);
+    AnnotationValue fieldNameValue = getAnnotationValue(mirror, Item.fieldName);
     if (methodNameValue != null && fieldNameValue != null) {
       throw new KeepEdgeException("Cannot define both a method and a field name pattern");
     }
     if (methodNameValue != null) {
       String methodName = AnnotationStringValueVisitor.getString(methodNameValue);
-      itemBuilder.setMemberPattern(
+      builder.setMemberPattern(
           KeepMethodPattern.builder()
               .setNamePattern(KeepMethodNamePattern.exact(methodName))
               .build());
     } else if (fieldNameValue != null) {
       String fieldName = AnnotationStringValueVisitor.getString(fieldNameValue);
-      itemBuilder.setMemberPattern(
+      builder.setMemberPattern(
           KeepFieldPattern.builder().setNamePattern(KeepFieldNamePattern.exact(fieldName)).build());
     }
-    builder.setItem(itemBuilder.build());
   }
 
   private void error(String message) {
@@ -181,9 +212,9 @@
     }
   }
 
-  private static AnnotationMirror getAnnotationMirror(TypeElement typeElement, Class<?> clazz) {
+  private static AnnotationMirror getAnnotationMirror(Element element, Class<?> clazz) {
     String clazzName = clazz.getName();
-    for (AnnotationMirror m : typeElement.getAnnotationMirrors()) {
+    for (AnnotationMirror m : element.getAnnotationMirrors()) {
       if (m.getAnnotationType().toString().equals(clazzName)) {
         return m;
       }
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
index fcdab96..01ec5ac 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor;
 import com.android.tools.r8.keepanno.processor.KeepEdgeProcessor;
 import com.android.tools.r8.keepanno.testsource.KeepClassAndDefaultConstructorSource;
+import com.android.tools.r8.keepanno.testsource.KeepDependentFieldSource;
 import com.android.tools.r8.keepanno.testsource.KeepFieldSource;
 import com.android.tools.r8.keepanno.testsource.KeepSourceEdges;
 import com.android.tools.r8.references.ClassReference;
@@ -42,7 +43,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.objectweb.asm.AnnotationVisitor;
 
 @RunWith(Parameterized.class)
 public class KeepEdgeAnnotationsTest extends TestBase {
@@ -66,7 +66,10 @@
       Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "keepanno");
 
   private static List<Class<?>> getTestClasses() {
-    return ImmutableList.of(KeepClassAndDefaultConstructorSource.class, KeepFieldSource.class);
+    return ImmutableList.of(
+        KeepClassAndDefaultConstructorSource.class,
+        KeepFieldSource.class,
+        KeepDependentFieldSource.class);
   }
 
   private final TestParameters parameters;
@@ -145,14 +148,9 @@
     // Strip out all the annotations to ensure they are actually added again.
     byte[] stripped =
         transformer(source)
-            .addClassTransformer(
-                new ClassTransformer() {
-                  @Override
-                  public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
-                    // Ignore all input annotations.
-                    return null;
-                  }
-                })
+            .removeClassAnnotations()
+            .removeMethodAnnotations()
+            .removeFieldAnnotations()
             .transform();
     // Manually add in the expected edges again.
     byte[] readded =
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepDependentFieldSource.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepDependentFieldSource.java
new file mode 100644
index 0000000..4a0df32
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepDependentFieldSource.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2022, 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.testsource;
+
+import com.android.tools.r8.keepanno.annotations.KeepCondition;
+import com.android.tools.r8.keepanno.annotations.KeepEdge;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import java.lang.reflect.Field;
+
+public class KeepDependentFieldSource {
+
+  public static class A {
+
+    public int f;
+
+    public A(int x) {
+      f = x;
+    }
+  }
+
+  // The keep edge is context independent, but natural to place close to the reflection usage.
+  @KeepEdge(
+      preconditions = {
+        // The edge is only needed if the main method that uses reflection is actually present.
+        @KeepCondition(classConstant = KeepDependentFieldSource.class, methodName = "main")
+      },
+      consequences = {
+        // Keep the reflectively accessed field.
+        @KeepTarget(classConstant = KeepDependentFieldSource.A.class, fieldName = "f")
+      })
+  public static void main(String[] args) throws Exception {
+    int x = 42 + args.length;
+    Object o = System.nanoTime() > 0 ? new A(x) : null;
+    Field f = o.getClass().getDeclaredField("f");
+    int y = f.getInt(o);
+    if (x == y) {
+      System.out.println("The values match!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
index 7af9aee..d4c7121 100644
--- a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
+++ b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
@@ -3,17 +3,23 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.testsource;
 
+import com.android.tools.r8.keepanno.ast.KeepCondition;
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
+import com.android.tools.r8.keepanno.ast.KeepConsequences.Builder;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
+import com.android.tools.r8.keepanno.ast.KeepPreconditions;
 import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepTarget;
 import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -23,22 +29,50 @@
  */
 public class KeepSourceEdges {
 
-  public static Set<KeepEdge> getExpectedEdges(Class<?> clazz) {
-    if (clazz.equals(KeepClassAndDefaultConstructorSource.class)) {
-      return getKeepClassAndDefaultConstructorSourceEdges();
+  private static class SourceData {
+    final Class<?> clazz;
+    final String expected;
+    final Set<KeepEdge> edges;
+
+    public SourceData(Class<?> clazz, String expected, Set<KeepEdge> edges) {
+      this.clazz = clazz;
+      this.expected = expected;
+      this.edges = edges;
     }
-    if (clazz.equals(KeepFieldSource.class)) {
-      return getKeepFieldSourceEdges();
+  }
+
+  private static final List<SourceData> SOURCES = new ArrayList<>();
+
+  static {
+    SOURCES.add(
+        new SourceData(
+            KeepClassAndDefaultConstructorSource.class,
+            getKeepClassAndDefaultConstructorSourceExpected(),
+            getKeepClassAndDefaultConstructorSourceEdges()));
+    SOURCES.add(
+        new SourceData(
+            KeepFieldSource.class, getKeepFieldSourceExpected(), getKeepFieldSourceEdges()));
+    SOURCES.add(
+        new SourceData(
+            KeepDependentFieldSource.class,
+            getKeepDependentFieldSourceExpected(),
+            getKeepDependentFieldSourceEdges()));
+  }
+
+  public static Set<KeepEdge> getExpectedEdges(Class<?> clazz) {
+    for (SourceData source : SOURCES) {
+      if (source.clazz == clazz) {
+        return source.edges;
+      }
     }
     throw new RuntimeException();
   }
 
   public static String getExpected(Class<?> clazz) {
-    if (clazz.equals(KeepClassAndDefaultConstructorSource.class)) {
-      return getKeepClassAndDefaultConstructorSourceExpected();
-    }
-    if (clazz.equals(KeepFieldSource.class)) {
-      return getKeepFieldSourceExpected();
+    for (SourceData source : SOURCES) {
+      if (source.clazz == clazz) {
+        return source.expected;
+      }
     }
     throw new RuntimeException();
   }
@@ -49,21 +83,8 @@
 
   public static Set<KeepEdge> getKeepClassAndDefaultConstructorSourceEdges() {
     Class<?> clazz = KeepClassAndDefaultConstructorSource.A.class;
-    // Build the class target.
-    KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
-    KeepItemPattern classItem = KeepItemPattern.builder().setClassPattern(name).build();
-    KeepTarget classTarget = KeepTarget.builder().setItem(classItem).build();
-    // Build the constructor target.
-    KeepMethodPattern constructorMethod =
-        KeepMethodPattern.builder().setNamePattern(KeepMethodNamePattern.exact("<init>")).build();
-    KeepItemPattern constructorItem =
-        KeepItemPattern.builder().setClassPattern(name).setMemberPattern(constructorMethod).build();
-    KeepTarget constructorTarget = KeepTarget.builder().setItem(constructorItem).build();
-    // The consequet set is the class an its constructor.
-    KeepConsequences consequences =
-        KeepConsequences.builder().addTarget(classTarget).addTarget(constructorTarget).build();
-    KeepEdge edge = KeepEdge.builder().setConsequences(consequences).build();
-    return Collections.singleton(edge);
+    return Collections.singleton(
+        mkEdge(mkConsequences(mkTarget(mkClass(clazz)), mkTarget(mkMethod(clazz, "<init>")))));
   }
 
   public static String getKeepFieldSourceExpected() {
@@ -71,15 +92,71 @@
   }
 
   public static Set<KeepEdge> getKeepFieldSourceEdges() {
-    Class<?> clazz = KeepFieldSource.A.class;
+    return Collections.singleton(
+        mkEdge(mkConsequences(mkTarget(mkField(KeepFieldSource.A.class, "f")))));
+  }
+
+  public static String getKeepDependentFieldSourceExpected() {
+    return getKeepFieldSourceExpected();
+  }
+
+  public static Set<KeepEdge> getKeepDependentFieldSourceEdges() {
+    return Collections.singleton(
+        mkDepEdge(
+            mkPreconditions(mkCondition(mkMethod(KeepDependentFieldSource.class, "main"))),
+            mkConsequences(mkTarget(mkField(KeepDependentFieldSource.A.class, "f")))));
+  }
+
+  // Ast helpers.
+
+  static KeepItemPattern mkClass(Class<?> clazz) {
+    KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
+    return KeepItemPattern.builder().setClassPattern(name).build();
+  }
+
+  static KeepItemPattern mkMethod(Class<?> clazz, String methodName) {
+    KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
+    KeepMethodPattern methodPattern =
+        KeepMethodPattern.builder().setNamePattern(KeepMethodNamePattern.exact(methodName)).build();
+    KeepItemPattern methodItem =
+        KeepItemPattern.builder().setClassPattern(name).setMemberPattern(methodPattern).build();
+    return methodItem;
+  }
+
+  static KeepItemPattern mkField(Class<?> clazz, String fieldName) {
     KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
     KeepFieldPattern fieldPattern =
-        KeepFieldPattern.builder().setNamePattern(KeepFieldNamePattern.exact("f")).build();
+        KeepFieldPattern.builder().setNamePattern(KeepFieldNamePattern.exact(fieldName)).build();
     KeepItemPattern fieldItem =
         KeepItemPattern.builder().setClassPattern(name).setMemberPattern(fieldPattern).build();
-    KeepTarget fieldTarget = KeepTarget.builder().setItem(fieldItem).build();
-    KeepConsequences consequences = KeepConsequences.builder().addTarget(fieldTarget).build();
-    KeepEdge edge = KeepEdge.builder().setConsequences(consequences).build();
-    return Collections.singleton(edge);
+    return fieldItem;
+  }
+
+  static KeepTarget mkTarget(KeepItemPattern item) {
+    return KeepTarget.builder().setItem(item).build();
+  }
+
+  static KeepCondition mkCondition(KeepItemPattern item) {
+    return KeepCondition.builder().setItem(item).build();
+  }
+
+  static KeepConsequences mkConsequences(KeepTarget... targets) {
+    Builder builder = KeepConsequences.builder();
+    Arrays.asList(targets).forEach(builder::addTarget);
+    return builder.build();
+  }
+
+  static KeepPreconditions mkPreconditions(KeepCondition... conditions) {
+    KeepPreconditions.Builder builder = KeepPreconditions.builder();
+    Arrays.asList(conditions).forEach(builder::addCondition);
+    return builder.build();
+  }
+
+  static KeepEdge mkEdge(KeepConsequences consequences) {
+    return KeepEdge.builder().setConsequences(consequences).build();
+  }
+
+  static KeepEdge mkDepEdge(KeepPreconditions preconditions, KeepConsequences consequences) {
+    return KeepEdge.builder().setPreconditions(preconditions).setConsequences(consequences).build();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 8108730..7aa8301 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -1463,4 +1463,42 @@
           }
         });
   }
+
+  public ClassFileTransformer removeClassAnnotations() {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+            // Ignore all input annotations.
+            return null;
+          }
+        });
+  }
+
+  public ClassFileTransformer removeMethodAnnotations() {
+    return addMethodTransformer(
+        new MethodTransformer() {
+          @Override
+          public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+            return null;
+          }
+        });
+  }
+
+  public ClassFileTransformer removeFieldAnnotations() {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public FieldVisitor visitField(
+              int access, String name, String descriptor, String signature, Object value) {
+            FieldVisitor fv = visitField(access, name, descriptor, signature, value);
+            return new FieldVisitor(ASM_VERSION, fv) {
+              @Override
+              public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+                return null;
+              }
+            };
+          }
+        });
+  }
 }