[KeepAnno] Maintain constraints until rule extraction

This is in preparation for determining the needed keepattribute clauses.

Bug: b/319473429
Change-Id: I5979bea74ceb64a7924528dd307f5f3efc4f69f6
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstraint.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstraint.java
index 7f10457..8cfa99d 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstraint.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstraint.java
@@ -80,6 +80,16 @@
   VISIBILITY_RESTRICT,
 
   /**
+   * Indicates that the visibility of the target must remain as declared.
+   *
+   * <p>Note that this constraint does not place any restrictions on any other accesses flags than
+   * visibility. In particular, flags such a static, final and abstract may change.
+   *
+   * <p>This is equivalent to using both {@link #VISIBILITY_RELAX} and {@link #VISIBILITY_RESTRICT}.
+   */
+  VISIBILITY_INVARIANT,
+
+  /**
    * Indicates that the class target is being instantiated reflectively.
    *
    * <p>This usage constraint is only valid on class targets.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/AnnotationVisitorBase.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/AnnotationVisitorBase.java
index 777e99b..87e9e42 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/AnnotationVisitorBase.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/AnnotationVisitorBase.java
@@ -21,6 +21,10 @@
     return Type.getType(descriptor).getClassName();
   }
 
+  public ParsingContext getParsingContext() {
+    return parsingContext;
+  }
+
   @Override
   public void visit(String name, Object value) {
     throw parsingContext.error("Unexpected value for property " + name + " with value " + value);
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ConstraintsParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ConstraintsParser.java
new file mode 100644
index 0000000..981c480
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ConstraintsParser.java
@@ -0,0 +1,51 @@
+// Copyright (c) 2024, 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.asm;
+
+import com.android.tools.r8.keepanno.asm.ConstraintsParser.ConstraintsProperty;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.Target;
+import com.android.tools.r8.keepanno.ast.KeepConstraints;
+import com.android.tools.r8.keepanno.ast.KeepOptions;
+import com.android.tools.r8.keepanno.ast.ParsingContext;
+import java.util.function.Consumer;
+import org.objectweb.asm.AnnotationVisitor;
+
+public class ConstraintsParser extends PropertyParserBase<KeepConstraints, ConstraintsProperty> {
+
+  public enum ConstraintsProperty {
+    CONSTRAINTS,
+    ALLOW,
+    DISALLOW
+  }
+
+  public ConstraintsParser(ParsingContext parsingContext) {
+    super(parsingContext.group(Target.constraintsGroup));
+  }
+
+  @Override
+  AnnotationVisitor tryPropertyArray(
+      ConstraintsProperty property, String name, Consumer<KeepConstraints> setValue) {
+    switch (property) {
+      case CONSTRAINTS:
+        return new KeepConstraintsVisitor(getParsingContext(), setValue::accept);
+      case ALLOW:
+        return new KeepOptionsVisitor(
+            getParsingContext(),
+            options ->
+                setValue.accept(
+                    KeepConstraints.fromLegacyOptions(
+                        KeepOptions.allowBuilder().addAll(options).build())));
+      case DISALLOW:
+        return new KeepOptionsVisitor(
+            getParsingContext(),
+            options ->
+                setValue.accept(
+                    KeepConstraints.fromLegacyOptions(
+                        KeepOptions.disallowBuilder().addAll(options).build())));
+      default:
+        return null;
+    }
+  }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepConstraintsVisitor.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepConstraintsVisitor.java
index 9012458..b0b58f8 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepConstraintsVisitor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepConstraintsVisitor.java
@@ -7,19 +7,16 @@
 import com.android.tools.r8.keepanno.asm.KeepEdgeReader.Parent;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Constraints;
-import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption;
+import com.android.tools.r8.keepanno.ast.KeepConstraint;
+import com.android.tools.r8.keepanno.ast.KeepConstraints;
 import com.android.tools.r8.keepanno.ast.ParsingContext;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
 
 public class KeepConstraintsVisitor extends AnnotationVisitorBase {
 
-  private final Parent<Collection<KeepOption>> parent;
-  private final Set<KeepOption> options = new HashSet<>();
+  private final Parent<KeepConstraints> parent;
+  private final KeepConstraints.Builder builder = KeepConstraints.builder();
 
-  public KeepConstraintsVisitor(
-      ParsingContext parsingContext, Parent<Collection<KeepOption>> parent) {
+  public KeepConstraintsVisitor(ParsingContext parsingContext, Parent<KeepConstraints> parent) {
     super(parsingContext);
     this.parent = parent;
   }
@@ -31,38 +28,47 @@
     }
     switch (value) {
       case Constraints.LOOKUP:
-        options.add(KeepOption.SHRINKING);
+        builder.add(KeepConstraint.lookup());
         break;
       case Constraints.NAME:
-        options.add(KeepOption.OBFUSCATING);
+        builder.add(KeepConstraint.name());
         break;
       case Constraints.VISIBILITY_RELAX:
-        // The compiler currently satisfies that access is never restricted.
+        builder.add(KeepConstraint.visibilityRelax());
         break;
       case Constraints.VISIBILITY_RESTRICT:
-        // We don't have directional rules so this prohibits any modification.
-        options.add(KeepOption.ACCESS_MODIFICATION);
+        builder.add(KeepConstraint.visibilityRestrict());
+        break;
+      case Constraints.VISIBILITY_INVARIANT:
+        builder.add(KeepConstraint.visibilityRelax());
+        builder.add(KeepConstraint.visibilityRestrict());
         break;
       case Constraints.CLASS_INSTANTIATE:
+        builder.add(KeepConstraint.classInstantiate());
+        break;
       case Constraints.METHOD_INVOKE:
+        builder.add(KeepConstraint.methodInvoke());
+        break;
       case Constraints.FIELD_GET:
+        builder.add(KeepConstraint.fieldGet());
+        break;
       case Constraints.FIELD_SET:
-        // These options are the item-specific actual uses of the items.
-        // Allocating, invoking and read/writing all imply that the item cannot be "optimized"
-        // at compile time. It would be natural to refine the field specific uses but that is
-        // not expressible as keep options.
-        options.add(KeepOption.OPTIMIZING);
+        builder.add(KeepConstraint.fieldSet());
         break;
       case Constraints.METHOD_REPLACE:
+        builder.add(KeepConstraint.methodReplace());
+        break;
       case Constraints.FIELD_REPLACE:
+        builder.add(KeepConstraint.fieldReplace());
+        break;
       case Constraints.NEVER_INLINE:
+        builder.add(KeepConstraint.neverInline());
+        break;
       case Constraints.CLASS_OPEN_HIERARCHY:
-        options.add(KeepOption.OPTIMIZING);
+        builder.add(KeepConstraint.classOpenHierarchy());
         break;
       case Constraints.ANNOTATIONS:
-        // The annotation constrain only implies that annotations should remain, no restrictions
-        // are on the item otherwise.
-        options.add(KeepOption.ANNOTATION_REMOVAL);
+        builder.add(KeepConstraint.annotationsAll());
         break;
       default:
         super.visitEnum(ignore, descriptor, value);
@@ -71,7 +77,7 @@
 
   @Override
   public void visitEnd() {
-    parent.accept(options);
+    parent.accept(builder.build());
     super.visitEnd();
   }
 }
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 b8b81ae..c0d6087 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
@@ -4,8 +4,8 @@
 package com.android.tools.r8.keepanno.asm;
 
 import com.android.tools.r8.keepanno.asm.ClassNameParser.ClassNameProperty;
+import com.android.tools.r8.keepanno.asm.ConstraintsParser.ConstraintsProperty;
 import com.android.tools.r8.keepanno.asm.InstanceOfParser.InstanceOfProperties;
-import com.android.tools.r8.keepanno.asm.OptionsParser.OptionsProperty;
 import com.android.tools.r8.keepanno.asm.StringPatternParser.StringProperty;
 import com.android.tools.r8.keepanno.asm.TypeParser.TypeProperty;
 import com.android.tools.r8.keepanno.ast.AccessVisibility;
@@ -30,6 +30,7 @@
 import com.android.tools.r8.keepanno.ast.KeepClassItemReference;
 import com.android.tools.r8.keepanno.ast.KeepCondition;
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
+import com.android.tools.r8.keepanno.ast.KeepConstraints;
 import com.android.tools.r8.keepanno.ast.KeepDeclaration;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
@@ -49,8 +50,6 @@
 import com.android.tools.r8.keepanno.ast.KeepMethodParametersPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodReturnTypePattern;
-import com.android.tools.r8.keepanno.ast.KeepOptions;
-import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption;
 import com.android.tools.r8.keepanno.ast.KeepPreconditions;
 import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepStringPattern;
@@ -96,22 +95,8 @@
         KeepQualifiedClassNamePattern.exact(className));
   }
 
-  private static KeepOptions getKeepOptionsOrDefault(KeepOptions options) {
-    // TODO(b/248408342): These should be constraints computed/filtered based on the item type but
-    //   currently the constraints default to the same set of options.
-    if (options != null) {
-      return options;
-    }
-    return KeepOptions.disallowBuilder()
-        // LOOKUP (same for any type of item).
-        .add(KeepOption.SHRINKING)
-        // NAME (same for any type of item).
-        .add(KeepOption.OBFUSCATING)
-        // CLASS_INSTANTIATE / METHOD_INVOKE / FIELD_GET & FIELD_SET:
-        .add(KeepOption.OPTIMIZING)
-        .add(KeepOption.ACCESS_MODIFICATION)
-        // ACCESS_ALLOW - currently no options needed.
-        .build();
+  private static KeepConstraints getClassConstraintsOrDefault(KeepConstraints constraints) {
+    return constraints != null ? constraints : KeepConstraints.defaultConstraints();
   }
 
   /** Internal copy of the user-facing KeepItemKind */
@@ -746,7 +731,7 @@
     private final KeepConsequences.Builder consequences = KeepConsequences.builder();
     private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
     private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
-    private final OptionsParser optionsParser;
+    private final ConstraintsParser constraintsParser;
 
     UsedByReflectionClassVisitor(
         AnnotationParsingContext parsingContext,
@@ -760,10 +745,10 @@
       addContext.accept(metaInfoBuilder);
       // The class context/holder is the annotated class.
       visit(Item.className, className);
-      optionsParser = new OptionsParser(parsingContext);
-      optionsParser.setProperty(Target.constraints, OptionsProperty.CONSTRAINTS);
-      optionsParser.setProperty(Target.allow, OptionsProperty.ALLOW);
-      optionsParser.setProperty(Target.disallow, OptionsProperty.DISALLOW);
+      constraintsParser = new ConstraintsParser(parsingContext);
+      constraintsParser.setProperty(Target.constraints, ConstraintsProperty.CONSTRAINTS);
+      constraintsParser.setProperty(Target.allow, ConstraintsProperty.ALLOW);
+      constraintsParser.setProperty(Target.disallow, ConstraintsProperty.DISALLOW);
     }
 
     @Override
@@ -794,7 +779,7 @@
             },
             bindingsHelper);
       }
-      AnnotationVisitor visitor = optionsParser.tryParseArray(name, unused -> {});
+      AnnotationVisitor visitor = constraintsParser.tryParseArray(name, unused -> {});
       if (visitor != null) {
         return visitor;
       }
@@ -830,7 +815,8 @@
         consequences.addTarget(
             KeepTarget.builder()
                 .setItemPattern(itemPattern)
-                .setOptions(getKeepOptionsOrDefault(optionsParser.getValue()))
+                .setConstraints(
+                    constraintsParser.getValueOrDefault(KeepConstraints.defaultConstraints()))
                 .build());
       }
       parent.accept(
@@ -857,7 +843,7 @@
     private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
     private final KeepConsequences.Builder consequences = KeepConsequences.builder();
     private ItemKind kind = KeepEdgeReader.ItemKind.ONLY_MEMBERS;
-    private final OptionsParser optionsParser;
+    private final ConstraintsParser constraintsParser;
 
     UsedByReflectionMemberVisitor(
         AnnotationParsingContext parsingContext,
@@ -869,10 +855,10 @@
       this.parent = parent;
       this.context = context;
       addContext.accept(metaInfoBuilder);
-      optionsParser = new OptionsParser(parsingContext);
-      optionsParser.setProperty(Target.constraints, OptionsProperty.CONSTRAINTS);
-      optionsParser.setProperty(Target.allow, OptionsProperty.ALLOW);
-      optionsParser.setProperty(Target.disallow, OptionsProperty.DISALLOW);
+      constraintsParser = new ConstraintsParser(parsingContext);
+      constraintsParser.setProperty(Target.constraints, ConstraintsProperty.CONSTRAINTS);
+      constraintsParser.setProperty(Target.allow, ConstraintsProperty.ALLOW);
+      constraintsParser.setProperty(Target.disallow, ConstraintsProperty.DISALLOW);
     }
 
     @Override
@@ -911,7 +897,7 @@
             },
             bindingsHelper);
       }
-      AnnotationVisitor visitor = optionsParser.tryParseArray(name, unused -> {});
+      AnnotationVisitor visitor = constraintsParser.tryParseArray(name, unused -> {});
       if (visitor != null) {
         return visitor;
       }
@@ -932,7 +918,8 @@
       validateConsistentKind(memberContext.getMemberPattern());
       consequences.addTarget(
           KeepTarget.builder()
-              .setOptions(getKeepOptionsOrDefault(optionsParser.getValue()))
+              .setConstraints(
+                  constraintsParser.getValueOrDefault(KeepConstraints.defaultConstraints()))
               .setItemPattern(context)
               .build());
       parent.accept(
@@ -1803,7 +1790,7 @@
 
     private final Parent<KeepTarget> parent;
     private final UserBindingsHelper bindingsHelper;
-    private final OptionsParser optionsParser;
+    private final ConstraintsParser optionsParser;
     private final KeepTarget.Builder builder = KeepTarget.builder();
 
     static KeepTargetVisitor create(
@@ -1820,10 +1807,10 @@
       super(parsingContext);
       this.parent = parent;
       this.bindingsHelper = bindingsHelper;
-      optionsParser = new OptionsParser(parsingContext);
-      optionsParser.setProperty(Target.constraints, OptionsProperty.CONSTRAINTS);
-      optionsParser.setProperty(Target.allow, OptionsProperty.ALLOW);
-      optionsParser.setProperty(Target.disallow, OptionsProperty.DISALLOW);
+      optionsParser = new ConstraintsParser(parsingContext);
+      optionsParser.setProperty(Target.constraints, ConstraintsProperty.CONSTRAINTS);
+      optionsParser.setProperty(Target.allow, ConstraintsProperty.ALLOW);
+      optionsParser.setProperty(Target.disallow, ConstraintsProperty.DISALLOW);
     }
 
     @Override
@@ -1843,7 +1830,7 @@
     @Override
     public void visitEnd() {
       super.visitEnd();
-      builder.setOptions(getKeepOptionsOrDefault(optionsParser.getValue()));
+      builder.setConstraints(optionsParser.getValueOrDefault(KeepConstraints.defaultConstraints()));
       for (KeepItemReference item : getItemsWithBinding()) {
         parent.accept(builder.setItemReference(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 87e2624..565067d 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
@@ -124,10 +124,6 @@
         target -> {
           AnnotationVisitor targetVisitor =
               arrayVisitor.visitAnnotation(ignoredArrayValueName, Target.DESCRIPTOR);
-          // No options imply keep all.
-          if (!target.getOptions().isKeepAll()) {
-            throw new Unimplemented();
-          }
           if (target.getItem().isBindingReference()) {
             throw new Unimplemented();
           }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/OptionsParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/OptionsParser.java
deleted file mode 100644
index dd2beda..0000000
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/OptionsParser.java
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) 2024, 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.asm;
-
-import com.android.tools.r8.keepanno.asm.OptionsParser.OptionsProperty;
-import com.android.tools.r8.keepanno.ast.AnnotationConstants.Target;
-import com.android.tools.r8.keepanno.ast.KeepOptions;
-import com.android.tools.r8.keepanno.ast.ParsingContext;
-import java.util.function.Consumer;
-import org.objectweb.asm.AnnotationVisitor;
-
-public class OptionsParser extends PropertyParserBase<KeepOptions, OptionsProperty> {
-
-  public enum OptionsProperty {
-    CONSTRAINTS,
-    ALLOW,
-    DISALLOW
-  }
-
-  public OptionsParser(ParsingContext parsingContext) {
-    super(parsingContext.group(Target.constraintsGroup));
-  }
-
-  @Override
-  AnnotationVisitor tryPropertyArray(
-      OptionsProperty property, String name, Consumer<KeepOptions> setValue) {
-    switch (property) {
-      case CONSTRAINTS:
-        return new KeepConstraintsVisitor(
-            getParsingContext(),
-            options -> setValue.accept(KeepOptions.disallowBuilder().addAll(options).build()));
-      case ALLOW:
-        return new KeepOptionsVisitor(
-            getParsingContext(),
-            options -> setValue.accept(KeepOptions.allowBuilder().addAll(options).build()));
-      case DISALLOW:
-        return new KeepOptionsVisitor(
-            getParsingContext(),
-            options -> setValue.accept(KeepOptions.disallowBuilder().addAll(options).build()));
-      default:
-        return null;
-    }
-  }
-}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
index 2ac416c..85082de 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
@@ -163,6 +163,7 @@
     public static final String NAME = "NAME";
     public static final String VISIBILITY_RELAX = "VISIBILITY_RELAX";
     public static final String VISIBILITY_RESTRICT = "VISIBILITY_RESTRICT";
+    public static final String VISIBILITY_INVARIANT = "VISIBILITY_INVARIANT";
     public static final String CLASS_INSTANTIATE = "CLASS_INSTANTIATE";
     public static final String METHOD_INVOKE = "METHOD_INVOKE";
     public static final String FIELD_GET = "FIELD_GET";
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraint.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraint.java
new file mode 100644
index 0000000..6283ec6
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraint.java
@@ -0,0 +1,327 @@
+// Copyright (c) 2024, 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.ast;
+
+import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption;
+
+public abstract class KeepConstraint {
+
+  @Override
+  public String toString() {
+    String typeName = getClass().getTypeName();
+    return typeName.substring(typeName.lastIndexOf('$') + 1);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    return this == obj;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+
+  public abstract void convertToDisallowKeepOptions(KeepOptions.Builder builder);
+
+  public final boolean validForClass() {
+    return !isMethodOnly() && !isFieldOnly();
+  }
+
+  public final boolean validForMethod() {
+    return !isClassOnly() && !isFieldOnly();
+  }
+
+  public final boolean validForField() {
+    return !isClassOnly() && !isMethodOnly();
+  }
+
+  boolean isClassOnly() {
+    return false;
+  }
+
+  boolean isMethodOnly() {
+    return false;
+  }
+
+  boolean isFieldOnly() {
+    return false;
+  }
+
+  public static Lookup lookup() {
+    return Lookup.INSTANCE;
+  }
+
+  public static final class Lookup extends KeepConstraint {
+
+    private static final Lookup INSTANCE = new Lookup();
+
+    private Lookup() {}
+
+    @Override
+    public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
+      builder.add(KeepOption.SHRINKING);
+    }
+  }
+
+  public static Name name() {
+    return Name.INSTANCE;
+  }
+
+  public static final class Name extends KeepConstraint {
+
+    private static final Name INSTANCE = new Name();
+
+    private Name() {}
+
+    @Override
+    public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
+      builder.add(KeepOption.OBFUSCATING);
+    }
+  }
+
+  public static VisibilityRelax visibilityRelax() {
+    return VisibilityRelax.INSTANCE;
+  }
+
+  public static final class VisibilityRelax extends KeepConstraint {
+
+    private static final VisibilityRelax INSTANCE = new VisibilityRelax();
+
+    private VisibilityRelax() {}
+
+    @Override
+    public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
+      // The compiler currently satisfies that access is never restricted.
+    }
+  }
+
+  public static VisibilityRestrict visibilityRestrict() {
+    return VisibilityRestrict.INSTANCE;
+  }
+
+  public static final class VisibilityRestrict extends KeepConstraint {
+
+    private static final VisibilityRestrict INSTANCE = new VisibilityRestrict();
+
+    private VisibilityRestrict() {}
+
+    @Override
+    public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
+      // We don't have directional rules so this prohibits any modification.
+      builder.add(KeepOption.ACCESS_MODIFICATION);
+    }
+  }
+
+  public static NeverInline neverInline() {
+    return NeverInline.INSTANCE;
+  }
+
+  public static final class NeverInline extends KeepConstraint {
+
+    private static final NeverInline INSTANCE = new NeverInline();
+
+    private NeverInline() {}
+
+    @Override
+    public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
+      builder.add(KeepOption.OPTIMIZING);
+    }
+  }
+
+  public static ClassInstantiate classInstantiate() {
+    return ClassInstantiate.INSTANCE;
+  }
+
+  public static final class ClassInstantiate extends KeepConstraint {
+
+    private static final ClassInstantiate INSTANCE = new ClassInstantiate();
+
+    private ClassInstantiate() {}
+
+    @Override
+    boolean isClassOnly() {
+      return true;
+    }
+
+    @Override
+    public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
+      builder.add(KeepOption.OPTIMIZING);
+    }
+  }
+
+  public static ClassOpenHierarchy classOpenHierarchy() {
+    return ClassOpenHierarchy.INSTANCE;
+  }
+
+  public static final class ClassOpenHierarchy extends KeepConstraint {
+
+    private static final ClassOpenHierarchy INSTANCE = new ClassOpenHierarchy();
+
+    private ClassOpenHierarchy() {}
+
+    @Override
+    boolean isClassOnly() {
+      return true;
+    }
+
+    @Override
+    public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
+      builder.add(KeepOption.OPTIMIZING);
+    }
+  }
+
+  public static MethodInvoke methodInvoke() {
+    return MethodInvoke.INSTANCE;
+  }
+
+  public static final class MethodInvoke extends KeepConstraint {
+
+    private static final MethodInvoke INSTANCE = new MethodInvoke();
+
+    private MethodInvoke() {}
+
+    @Override
+    boolean isMethodOnly() {
+      return true;
+    }
+
+    @Override
+    public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
+      builder.add(KeepOption.OPTIMIZING);
+    }
+  }
+
+  public static MethodReplace methodReplace() {
+    return MethodReplace.INSTANCE;
+  }
+
+  public static final class MethodReplace extends KeepConstraint {
+
+    private static final MethodReplace INSTANCE = new MethodReplace();
+
+    private MethodReplace() {}
+
+    @Override
+    boolean isMethodOnly() {
+      return true;
+    }
+
+    @Override
+    public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
+      builder.add(KeepOption.OPTIMIZING);
+    }
+  }
+
+  public static FieldGet fieldGet() {
+    return FieldGet.INSTANCE;
+  }
+
+  public static final class FieldGet extends KeepConstraint {
+
+    private static final FieldGet INSTANCE = new FieldGet();
+
+    private FieldGet() {}
+
+    @Override
+    boolean isFieldOnly() {
+      return true;
+    }
+
+    @Override
+    public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
+      builder.add(KeepOption.OPTIMIZING);
+    }
+  }
+
+  public static FieldSet fieldSet() {
+    return FieldSet.INSTANCE;
+  }
+
+  public static final class FieldSet extends KeepConstraint {
+
+    private static final FieldSet INSTANCE = new FieldSet();
+
+    private FieldSet() {}
+
+    @Override
+    boolean isFieldOnly() {
+      return true;
+    }
+
+    @Override
+    public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
+      builder.add(KeepOption.OPTIMIZING);
+    }
+  }
+
+  public static FieldReplace fieldReplace() {
+    return FieldReplace.INSTANCE;
+  }
+
+  public static final class FieldReplace extends KeepConstraint {
+
+    private static final FieldReplace INSTANCE = new FieldReplace();
+
+    private FieldReplace() {}
+
+    @Override
+    boolean isFieldOnly() {
+      return true;
+    }
+
+    @Override
+    public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
+      builder.add(KeepOption.OPTIMIZING);
+    }
+  }
+
+  public static Annotation annotationsAll() {
+    return Annotation.ALL_INSTANCE;
+  }
+
+  public static Annotation annotation(KeepQualifiedClassNamePattern pattern) {
+    if (pattern.isAny()) {
+      return annotationsAll();
+    }
+    return new Annotation(pattern);
+  }
+
+  public static final class Annotation extends KeepConstraint {
+
+    private static final Annotation ALL_INSTANCE =
+        new Annotation(KeepQualifiedClassNamePattern.any());
+
+    private final KeepQualifiedClassNamePattern classNamePattern;
+
+    private Annotation(KeepQualifiedClassNamePattern classNamePattern) {
+      this.classNamePattern = classNamePattern;
+    }
+
+    @Override
+    public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
+      // The annotation constraint only implies that annotations should remain, no restrictions
+      // are on the item otherwise. Also, we can't restrict the rule to just the annotations being
+      // constrained in the legacy rules.
+      builder.add(KeepOption.ANNOTATION_REMOVAL);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (!(o instanceof Annotation)) {
+        return false;
+      }
+      Annotation that = (Annotation) o;
+      return classNamePattern.equals(that);
+    }
+
+    @Override
+    public int hashCode() {
+      return classNamePattern.hashCode();
+    }
+  }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraints.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraints.java
new file mode 100644
index 0000000..669a0b1
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraints.java
@@ -0,0 +1,103 @@
+// Copyright (c) 2024, 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.ast;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public abstract class KeepConstraints {
+
+  public static KeepConstraints fromLegacyOptions(KeepOptions options) {
+    return new LegacyOptions(options);
+  }
+
+  public static KeepConstraints defaultConstraints() {
+    return Defaults.INSTANCE;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+
+    private final Set<KeepConstraint> constraints = new HashSet<>();
+
+    private Builder() {}
+
+    public Builder add(KeepConstraint constraint) {
+      constraints.add(constraint);
+      return this;
+    }
+
+    public KeepConstraints build() {
+      return new Constraints(constraints);
+    }
+  }
+
+  private static class LegacyOptions extends KeepConstraints {
+
+    private final KeepOptions options;
+
+    private LegacyOptions(KeepOptions options) {
+      this.options = options;
+    }
+
+    @Override
+    public String toString() {
+      return options.toString();
+    }
+
+    @Override
+    public KeepOptions convertToKeepOptions(KeepOptions defaultOptions) {
+      return options;
+    }
+  }
+
+  private static class Defaults extends KeepConstraints {
+
+    private static final Defaults INSTANCE = new Defaults();
+
+    @Override
+    public KeepOptions convertToKeepOptions(KeepOptions defaultOptions) {
+      return defaultOptions;
+    }
+
+    @Override
+    public String toString() {
+      return "KeepConstraints.Defaults{}";
+    }
+  }
+
+  private static class Constraints extends KeepConstraints {
+
+    private final Set<KeepConstraint> constraints;
+
+    public Constraints(Set<KeepConstraint> constraints) {
+      this.constraints = ImmutableSet.copyOf(constraints);
+    }
+
+    @Override
+    public KeepOptions convertToKeepOptions(KeepOptions defaultOptions) {
+      KeepOptions.Builder builder = KeepOptions.disallowBuilder();
+      for (KeepConstraint constraint : constraints) {
+        constraint.convertToDisallowKeepOptions(builder);
+      }
+      return builder.build();
+    }
+
+    @Override
+    public String toString() {
+      return "KeepConstraints{"
+          + constraints.stream().map(Objects::toString).collect(Collectors.joining(", "))
+          + '}';
+    }
+  }
+
+  public abstract KeepOptions convertToKeepOptions(KeepOptions defaultOptions);
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
index 713686a..b894eae 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
@@ -35,6 +35,24 @@
  *
  *   CONSEQUENCES ::= TARGET+
  *   TARGET ::= OPTIONS ITEM_REFERENCE
+ *
+ *   CONSTRAINTS ::= CONSTRAINT+ | OPTIONS (legacy)
+ *   CONSTRAINT
+ *       ::= lookup
+ *         | name
+ *         | visibility-relax
+ *         | visibility-restrict
+ *         | annotations(QUALIFIED_CLASS_NAME_PATTERN*)
+ *         | never-inline
+ *         | class-instantiate
+ *         | class-open-hierarchy
+ *         | method-invoke
+ *         | method-replace
+ *         | field-get
+ *         | field-set
+ *         | field-replace
+ *
+ *   (legacy options - deprecated)
  *   OPTIONS ::= keep-all | OPTION+
  *   OPTION ::= shrinking | optimizing | obfuscating | access-modification | annotation-removal
  *
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTarget.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTarget.java
index 1d8b32b..e6bcf49 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTarget.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTarget.java
@@ -10,7 +10,7 @@
   public static class Builder {
 
     private KeepItemReference item;
-    private KeepOptions options = KeepOptions.keepAll();
+    private KeepConstraints constraints = KeepConstraints.defaultConstraints();
 
     private Builder() {}
 
@@ -24,7 +24,11 @@
     }
 
     public Builder setOptions(KeepOptions options) {
-      this.options = options;
+      return setConstraints(KeepConstraints.fromLegacyOptions(options));
+    }
+
+    public Builder setConstraints(KeepConstraints constraints) {
+      this.constraints = constraints;
       return this;
     }
 
@@ -32,18 +36,18 @@
       if (item == null) {
         throw new KeepEdgeException("Target must define an item pattern");
       }
-      return new KeepTarget(item, options);
+      return new KeepTarget(item, constraints);
     }
   }
 
   private final KeepItemReference item;
-  private final KeepOptions options;
+  private final KeepConstraints constraints;
 
-  private KeepTarget(KeepItemReference item, KeepOptions options) {
+  private KeepTarget(KeepItemReference item, KeepConstraints constraints) {
     assert item != null;
-    assert options != null;
+    assert constraints != null;
     this.item = item;
-    this.options = options;
+    this.constraints = constraints;
   }
 
   public static Builder builder() {
@@ -54,30 +58,29 @@
     return item;
   }
 
-  public KeepOptions getOptions() {
-    return options;
+  public KeepConstraints getConstraints() {
+    return constraints;
   }
 
   @Override
-  @SuppressWarnings("EqualsGetClass")
   public boolean equals(Object o) {
     if (this == o) {
       return true;
     }
-    if (o == null || getClass() != o.getClass()) {
+    if (!(o instanceof KeepTarget)) {
       return false;
     }
     KeepTarget that = (KeepTarget) o;
-    return item.equals(that.item) && options.equals(that.options);
+    return item.equals(that.item) && constraints.equals(that.constraints);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(item, options);
+    return Objects.hash(item, constraints);
   }
 
   @Override
   public String toString() {
-    return "KeepTarget{" + "item=" + item + ", options=" + options + '}';
+    return "KeepTarget{" + "item=" + item + ", constraints=" + constraints + '}';
   }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeNormalizer.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeNormalizer.java
index baf9f6c..9806026 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeNormalizer.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeNormalizer.java
@@ -65,7 +65,7 @@
             target -> {
               consequencesBuilder.addTarget(
                   KeepTarget.builder()
-                      .setOptions(target.getOptions())
+                      .setConstraints(target.getConstraints())
                       .setItemReference(normalizeItemReference(target.getItem()))
                       .build());
             });
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
index 41f593e..a84754d 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.keepanno.ast.KeepCheck.KeepCheckKind;
 import com.android.tools.r8.keepanno.ast.KeepClassItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepCondition;
+import com.android.tools.r8.keepanno.ast.KeepConstraints;
 import com.android.tools.r8.keepanno.ast.KeepDeclaration;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.ast.KeepEdgeException;
@@ -208,6 +209,11 @@
     }
   }
 
+  private static KeepOptions defaultOptions =
+      KeepOptions.disallowBuilder()
+          .addAll(KeepOption.SHRINKING, KeepOption.OBFUSCATING, KeepOption.OPTIMIZING)
+          .build();
+
   private static class BindingUsers {
 
     final Holder holder;
@@ -229,8 +235,10 @@
 
     public void addTarget(KeepTarget target) {
       assert target.getItem().isBindingReference();
+      KeepConstraints constraints = target.getConstraints();
+      KeepOptions options = constraints.convertToKeepOptions(defaultOptions);
       targetRefs
-          .computeIfAbsent(target.getOptions(), k -> new HashSet<>())
+          .computeIfAbsent(options, k -> new HashSet<>())
           .add(target.getItem().asBindingReference().getName());
     }
   }
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAccessVisibilityFlagsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepAccessVisibilityFlagsTest.java
index 4505562..2454356 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAccessVisibilityFlagsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAccessVisibilityFlagsTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
+import com.android.tools.r8.keepanno.annotations.KeepConstraint;
 import com.android.tools.r8.keepanno.annotations.KeepItemKind;
 import com.android.tools.r8.keepanno.annotations.KeepTarget;
 import com.android.tools.r8.keepanno.annotations.MemberAccessFlags;
@@ -186,14 +187,29 @@
       @KeepTarget(
           kind = KeepItemKind.CLASS_AND_MEMBERS,
           classConstant = FieldRuleTarget.class,
+          constraints = {
+            KeepConstraint.LOOKUP,
+            KeepConstraint.NAME,
+            KeepConstraint.VISIBILITY_INVARIANT
+          },
           fieldAccess = {FieldAccessFlags.NON_PRIVATE}),
       @KeepTarget(
           kind = KeepItemKind.CLASS_AND_MEMBERS,
           classConstant = MethodRuleTarget.class,
+          constraints = {
+            KeepConstraint.LOOKUP,
+            KeepConstraint.NAME,
+            KeepConstraint.VISIBILITY_INVARIANT
+          },
           methodAccess = {MethodAccessFlags.NON_PACKAGE_PRIVATE}),
       @KeepTarget(
           kind = KeepItemKind.CLASS_AND_MEMBERS,
           classConstant = MemberRuleTarget.class,
+          constraints = {
+            KeepConstraint.LOOKUP,
+            KeepConstraint.NAME,
+            KeepConstraint.VISIBILITY_INVARIANT
+          },
           memberAccess = {MemberAccessFlags.PACKAGE_PRIVATE, MemberAccessFlags.PRIVATE}),
     })
     void foo() {
diff --git a/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
index dae2c6b..c504c4a 100644
--- a/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
@@ -53,7 +53,8 @@
             .build();
     assertEquals(
         StringUtils.unixLines(
-            "-keep class ** { void finalize(); }", "-keepclassmembers class ** { *; }"),
+            "-keep,allowaccessmodification class ** { void finalize(); }",
+            "-keepclassmembers,allowaccessmodification class ** { *; }"),
         extract(edge));
   }
 
@@ -121,7 +122,9 @@
     KeepConsequences consequences = KeepConsequences.builder().addTarget(target).build();
     KeepEdge edge = KeepEdge.builder().setConsequences(consequences).build();
     assertEquals(
-        StringUtils.unixLines("-keep class " + CLASS + " { void finalize(); }"), extract(edge));
+        StringUtils.unixLines(
+            "-keep,allowaccessmodification class " + CLASS + " { void finalize(); }"),
+        extract(edge));
   }
 
   @Test
@@ -142,7 +145,8 @@
                     .build())
             .build();
     assertEquals(
-        StringUtils.unixLines("-keepclassmembers class " + CLASS + " { void <init>(); }"),
+        StringUtils.unixLines(
+            "-keepclassmembers,allowaccessmodification class " + CLASS + " { void <init>(); }"),
         extract(edge));
   }
 
@@ -158,7 +162,11 @@
             .build();
     assertEquals(
         StringUtils.unixLines(
-            "-if class " + CLASS + " -keep class " + CLASS + " { void finalize(); }"),
+            "-if class "
+                + CLASS
+                + " -keep,allowaccessmodification class "
+                + CLASS
+                + " { void finalize(); }"),
         extract(edge));
   }
 
@@ -182,8 +190,12 @@
             .build();
     assertEquals(
         StringUtils.unixLines(
-            "-keepclassmembers class " + CLASS + " { void <init>(); }",
-            "-if class " + CLASS + " -keep class " + CLASS + " { void finalize(); }"),
+            "-keepclassmembers,allowaccessmodification class " + CLASS + " { void <init>(); }",
+            "-if class "
+                + CLASS
+                + " -keep,allowaccessmodification class "
+                + CLASS
+                + " { void finalize(); }"),
         extract(edge));
   }
 
@@ -216,7 +228,7 @@
         StringUtils.unixLines(
             "-if class "
                 + CLASS
-                + " -keepclasseswithmembers class "
+                + " -keepclasseswithmembers,allowaccessmodification class "
                 + CLASS
                 + " { void <init>(); }"),
         extract(edge));