[KeepAnno] Introduce lattice for annotation keep info

This is in preparation for allowing keep annotations to selectively
keep subsets of annotations on items. The lattice can only assume
the top and bottom values, thus it is functionally equivalent to the
boolean value it replaces. Follow-up CLs will refactor the
determination what of annotations to keep and extend the lattice.

Bug: b/319474935
Change-Id: I4d7b8910ac4e222fa1942fd4dba733e73da0314b
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepAnnotationCollectionInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepAnnotationCollectionInfo.java
new file mode 100644
index 0000000..7fb168e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepAnnotationCollectionInfo.java
@@ -0,0 +1,188 @@
+// 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.shaking;
+
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.DexType;
+import java.util.HashSet;
+import java.util.Set;
+
+public abstract class KeepAnnotationCollectionInfo {
+
+  public static class KeepAnnotationInfo {
+    public static final int RETAIN_NONE = 0;
+    public static final int RETAIN_VISIBLE = 1;
+    public static final int RETAIN_INVISIBLE = 2;
+    public static final int RETAIN_ALL = RETAIN_VISIBLE | RETAIN_INVISIBLE;
+
+    private static KeepAnnotationInfo BOTTOM = new KeepAnnotationInfo(null, RETAIN_NONE);
+    private static KeepAnnotationInfo TOP = new KeepAnnotationInfo(null, RETAIN_ALL);
+
+    public static KeepAnnotationInfo getTop() {
+      return TOP;
+    }
+
+    public static KeepAnnotationInfo getBottom() {
+      return BOTTOM;
+    }
+
+    // A concrete annotation type or null if applicable to any type.
+    private final DexType type;
+
+    // The retention set for which this info is applicable. A RETAIN_NONE value implies bottom.
+    private final int retention;
+
+    private KeepAnnotationInfo(DexType type, int retention) {
+      this.type = type;
+      this.retention = retention;
+    }
+
+    @Override
+    public String toString() {
+      return "KeepAnnotationInfo{" + "type=" + type + ", retention=" + retention + '}';
+    }
+  }
+
+  // Singleton representing all possible collections of annotations.
+  private static final class TopKeepAnnotationCollectionInfo extends KeepAnnotationCollectionInfo {
+
+    private static final KeepAnnotationCollectionInfo INSTANCE =
+        new TopKeepAnnotationCollectionInfo();
+
+    public static KeepAnnotationCollectionInfo getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    public boolean isTop() {
+      return true;
+    }
+
+    @Override
+    public String toString() {
+      return "top";
+    }
+  }
+
+  // Singleton class representing no collections of annotations.
+  private static final class BottomKeepAnnotationCollectionInfo
+      extends KeepAnnotationCollectionInfo {
+
+    private static final KeepAnnotationCollectionInfo INSTANCE =
+        new BottomKeepAnnotationCollectionInfo();
+
+    public static KeepAnnotationCollectionInfo getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    public boolean isBottom() {
+      return true;
+    }
+
+    @Override
+    public String toString() {
+      return "bottom";
+    }
+  }
+
+  public Builder toBuilder() {
+    return new Builder(this);
+  }
+
+  public boolean isTop() {
+    return false;
+  }
+
+  public boolean isBottom() {
+    return false;
+  }
+
+  public boolean isLessThanOrEquals(KeepAnnotationCollectionInfo other) {
+    if (this == other) {
+      return true;
+    }
+    if (isBottom() || other.isTop()) {
+      return true;
+    }
+    if (isTop() || other.isBottom()) {
+      return false;
+    }
+    throw new Unimplemented();
+  }
+
+  public static class Builder {
+
+    public static Builder makeTop() {
+      return new Builder();
+    }
+
+    public static Builder makeBottom() {
+      Builder builder = new Builder();
+      builder.items = new HashSet<>(1);
+      return builder;
+    }
+
+    // Set of retained items.
+    // The null value implies any item.
+    // The empty set implies no items.
+    private Set<KeepAnnotationInfo> items = null;
+
+    private Builder() {
+      assert isTop();
+    }
+
+    private Builder(KeepAnnotationCollectionInfo original) {
+      if (original.isTop()) {
+        assert isTop();
+        return;
+      }
+      if (original.isBottom()) {
+        items = new HashSet<>(1);
+        assert isBottom();
+        return;
+      }
+      throw new Unimplemented();
+    }
+
+    public boolean isTop() {
+      return items == null;
+    }
+
+    public boolean isBottom() {
+      return items != null && items.isEmpty();
+    }
+
+    public boolean isEqualTo(KeepAnnotationCollectionInfo other) {
+      // TODO(b/319474935): Consider checking directly on the builder and avoid the build.
+      KeepAnnotationCollectionInfo self = build();
+      return self.isLessThanOrEquals(other) && other.isLessThanOrEquals(self);
+    }
+
+    public void join(Builder other) {
+      // Joining mutates 'this' with the join of settings from 'other'.
+      if (other.isBottom()) {
+        // The empty collection is bottom which joins as identity.
+        return;
+      }
+      if (other.isTop()) {
+        // The null item set represents top and joins to top.
+        items = null;
+        return;
+      }
+      throw new Unimplemented();
+    }
+
+    public KeepAnnotationCollectionInfo build() {
+      if (isTop()) {
+        return TopKeepAnnotationCollectionInfo.getInstance();
+      }
+      if (isBottom()) {
+        return BottomKeepAnnotationCollectionInfo.getInstance();
+      }
+      throw new Unimplemented();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index 6d88923..d2e5f1f 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -19,42 +19,42 @@
 
   private final boolean allowAccessModification;
   private final boolean allowAccessModificationForTesting;
-  private final boolean allowAnnotationRemoval;
   private final boolean allowMinification;
   private final boolean allowOptimization;
   private final boolean allowShrinking;
   private final boolean allowSignatureRemoval;
   private final boolean checkDiscarded;
+  private final KeepAnnotationCollectionInfo annotationsInfo;
 
   private KeepInfo(
       boolean allowAccessModification,
       boolean allowAccessModificationForTesting,
-      boolean allowAnnotationRemoval,
       boolean allowMinification,
       boolean allowOptimization,
       boolean allowShrinking,
       boolean allowSignatureRemoval,
-      boolean checkDiscarded) {
+      boolean checkDiscarded,
+      KeepAnnotationCollectionInfo annotationsInfo) {
     this.allowAccessModification = allowAccessModification;
     this.allowAccessModificationForTesting = allowAccessModificationForTesting;
-    this.allowAnnotationRemoval = allowAnnotationRemoval;
     this.allowMinification = allowMinification;
     this.allowOptimization = allowOptimization;
     this.allowShrinking = allowShrinking;
     this.allowSignatureRemoval = allowSignatureRemoval;
     this.checkDiscarded = checkDiscarded;
+    this.annotationsInfo = annotationsInfo;
   }
 
   KeepInfo(B builder) {
     this(
         builder.isAccessModificationAllowed(),
         builder.isAccessModificationAllowedForTesting(),
-        builder.isAnnotationRemovalAllowed(),
         builder.isMinificationAllowed(),
         builder.isOptimizationAllowed(),
         builder.isShrinkingAllowed(),
         builder.isSignatureRemovalAllowed(),
-        builder.isCheckDiscardedEnabled());
+        builder.isCheckDiscardedEnabled(),
+        builder.getAnnotationsInfo().build());
   }
 
   public static Joiner<?, ?, ?> newEmptyJoinerFor(DexReference reference) {
@@ -73,11 +73,11 @@
    * keep all annotation attributes.
    */
   public boolean isAnnotationRemovalAllowed(GlobalKeepInfoConfiguration configuration) {
-    return configuration.isAnnotationRemovalEnabled() && internalIsAnnotationRemovalAllowed();
+    return configuration.isAnnotationRemovalEnabled() && internalAnnotationsInfo().isBottom();
   }
 
-  boolean internalIsAnnotationRemovalAllowed() {
-    return allowAnnotationRemoval;
+  KeepAnnotationCollectionInfo internalAnnotationsInfo() {
+    return annotationsInfo;
   }
 
   public boolean isCheckDiscardedEnabled(GlobalKeepInfoConfiguration configuration) {
@@ -225,12 +225,12 @@
     return (allowAccessModification || !other.internalIsAccessModificationAllowed())
         && (allowAccessModificationForTesting
             || !other.internalIsAccessModificationAllowedForTesting())
-        && (allowAnnotationRemoval || !other.internalIsAnnotationRemovalAllowed())
         && (allowMinification || !other.internalIsMinificationAllowed())
         && (allowOptimization || !other.internalIsOptimizationAllowed())
         && (allowShrinking || !other.internalIsShrinkingAllowed())
         && (allowSignatureRemoval || !other.internalIsSignatureRemovalAllowed())
-        && (!checkDiscarded || other.internalIsCheckDiscardedEnabled());
+        && (!checkDiscarded || other.internalIsCheckDiscardedEnabled())
+        && annotationsInfo.isLessThanOrEquals(other.internalAnnotationsInfo());
   }
 
   /** Builder to construct an arbitrary keep info object. */
@@ -249,12 +249,12 @@
     protected K original;
     private boolean allowAccessModification;
     private boolean allowAccessModificationForTesting;
-    private boolean allowAnnotationRemoval;
     private boolean allowMinification;
     private boolean allowOptimization;
     private boolean allowShrinking;
     private boolean allowSignatureRemoval;
     private boolean checkDiscarded;
+    private KeepAnnotationCollectionInfo.Builder annotationsInfo;
 
     Builder() {
       // Default initialized. Use should be followed by makeTop/makeBottom.
@@ -264,12 +264,12 @@
       this.original = original;
       allowAccessModification = original.internalIsAccessModificationAllowed();
       allowAccessModificationForTesting = original.internalIsAccessModificationAllowedForTesting();
-      allowAnnotationRemoval = original.internalIsAnnotationRemovalAllowed();
       allowMinification = original.internalIsMinificationAllowed();
       allowOptimization = original.internalIsOptimizationAllowed();
       allowShrinking = original.internalIsShrinkingAllowed();
       allowSignatureRemoval = original.internalIsSignatureRemovalAllowed();
       checkDiscarded = original.internalIsCheckDiscardedEnabled();
+      annotationsInfo = original.internalAnnotationsInfo().toBuilder();
     }
 
     B makeTop() {
@@ -315,12 +315,12 @@
       return isAccessModificationAllowed() == other.internalIsAccessModificationAllowed()
           && isAccessModificationAllowedForTesting()
               == other.internalIsAccessModificationAllowedForTesting()
-          && isAnnotationRemovalAllowed() == other.internalIsAnnotationRemovalAllowed()
           && isMinificationAllowed() == other.internalIsMinificationAllowed()
           && isOptimizationAllowed() == other.internalIsOptimizationAllowed()
           && isShrinkingAllowed() == other.internalIsShrinkingAllowed()
           && isSignatureRemovalAllowed() == other.internalIsSignatureRemovalAllowed()
-          && isCheckDiscardedEnabled() == other.internalIsCheckDiscardedEnabled();
+          && isCheckDiscardedEnabled() == other.internalIsCheckDiscardedEnabled()
+          && annotationsInfo.isEqualTo(other.internalAnnotationsInfo());
     }
 
     public boolean isAccessModificationAllowed() {
@@ -331,10 +331,6 @@
       return allowAccessModificationForTesting;
     }
 
-    public boolean isAnnotationRemovalAllowed() {
-      return allowAnnotationRemoval;
-    }
-
     public boolean isCheckDiscardedEnabled() {
       return checkDiscarded;
     }
@@ -433,17 +429,21 @@
       return setAllowAccessModificationForTesting(false);
     }
 
-    public B setAllowAnnotationRemoval(boolean allowAnnotationRemoval) {
-      this.allowAnnotationRemoval = allowAnnotationRemoval;
+    private B setAnnotationsInfo(KeepAnnotationCollectionInfo.Builder annotationsInfo) {
+      this.annotationsInfo = annotationsInfo;
       return self();
     }
 
+    KeepAnnotationCollectionInfo.Builder getAnnotationsInfo() {
+      return annotationsInfo;
+    }
+
     public B allowAnnotationRemoval() {
-      return setAllowAnnotationRemoval(true);
+      return setAnnotationsInfo(KeepAnnotationCollectionInfo.Builder.makeBottom());
     }
 
     public B disallowAnnotationRemoval() {
-      return setAllowAnnotationRemoval(false);
+      return setAnnotationsInfo(KeepAnnotationCollectionInfo.Builder.makeTop());
     }
 
     private B setAllowSignatureRemoval(boolean allowSignatureRemoval) {
@@ -603,17 +603,17 @@
     }
 
     public J merge(J joiner) {
-      Builder<B, K> builder = joiner.builder;
-      applyIf(!builder.isAccessModificationAllowed(), Joiner::disallowAccessModification);
+      Builder<B, K> otherBuilder = joiner.builder;
+      applyIf(!otherBuilder.isAccessModificationAllowed(), Joiner::disallowAccessModification);
       applyIf(
-          !builder.isAccessModificationAllowedForTesting(),
+          !otherBuilder.isAccessModificationAllowedForTesting(),
           Joiner::disallowAccessModificationForTesting);
-      applyIf(!builder.isAnnotationRemovalAllowed(), Joiner::disallowAnnotationRemoval);
-      applyIf(!builder.isMinificationAllowed(), Joiner::disallowMinification);
-      applyIf(!builder.isOptimizationAllowed(), Joiner::disallowOptimization);
-      applyIf(!builder.isShrinkingAllowed(), Joiner::disallowShrinking);
-      applyIf(!builder.isSignatureRemovalAllowed(), Joiner::disallowSignatureRemoval);
-      applyIf(builder.isCheckDiscardedEnabled(), Joiner::setCheckDiscarded);
+      applyIf(!otherBuilder.isMinificationAllowed(), Joiner::disallowMinification);
+      applyIf(!otherBuilder.isOptimizationAllowed(), Joiner::disallowOptimization);
+      applyIf(!otherBuilder.isShrinkingAllowed(), Joiner::disallowShrinking);
+      applyIf(!otherBuilder.isSignatureRemovalAllowed(), Joiner::disallowSignatureRemoval);
+      applyIf(otherBuilder.isCheckDiscardedEnabled(), Joiner::setCheckDiscarded);
+      builder.getAnnotationsInfo().join(otherBuilder.getAnnotationsInfo());
       reasons.addAll(joiner.reasons);
       rules.addAll(joiner.rules);
       return self();