[KeepAnno] Extract class and any members items as a disjoint rule

Bug: b/322104143
Bug: b/323136645
Bug: b/321674067
Change-Id: Ia9617c8fd1e0a469a8e7426f0de864ed02e3cb00
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 c5d43c5..dba2375 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
@@ -386,16 +386,26 @@
       OnTargetCallback callback) {
     TargetKeepKind keepKind = TargetKeepKind.JUST_MEMBERS;
     List<KeepBindingSymbol> targetMembers = new ArrayList<>();
+    boolean isAllMembersTarget = false;
     for (KeepBindingSymbol targetReference : targets) {
       KeepItemPattern item = bindings.get(targetReference).getItem();
       if (item.isClassItemPattern()) {
         keepKind = TargetKeepKind.CLASS_AND_MEMBERS;
-      } else {
+      } else if (!isAllMembersTarget) {
         KeepMemberItemPattern memberItemPattern = item.asMemberItemPattern();
+        if (memberItemPattern.getMemberPattern().isAllMembers()) {
+          targetMembers.clear();
+          isAllMembersTarget = true;
+        }
         memberPatterns.putIfAbsent(targetReference, memberItemPattern.getMemberPattern());
         targetMembers.add(targetReference);
       }
     }
+    if (isAllMembersTarget && keepKind == TargetKeepKind.CLASS_AND_MEMBERS) {
+      // If the rule is keeping the class and all of its members, the member pattern should
+      // match even the empty set of members, e.g., be disjoint from keeping the class.
+      keepKind = TargetKeepKind.CLASS_OR_MEMBERS;
+    }
     if (targetMembers.isEmpty()) {
       keepKind = TargetKeepKind.CLASS_OR_MEMBERS;
     }
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepEmptyClassTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepEmptyClassTest.java
index f4f769b..c1e5683 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepEmptyClassTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepEmptyClassTest.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.keepanno.annotations.KeepItemKind;
@@ -25,7 +25,6 @@
 public class KeepEmptyClassTest extends KeepAnnoTestBase {
 
   static final String EXPECTED = StringUtils.lines("B, #members: 0");
-  static final String UNEXPECTED = StringUtils.lines("b, #members: 0");
 
   @Parameter public KeepAnnoParameters parameters;
 
@@ -43,8 +42,7 @@
             transformer(B.class).removeMethods(MethodPredicate.all()).transform())
         .setExcludedOuterClass(getClass())
         .run(TestClass.class)
-        // TODO(b/322104143): The any-member pattern does not match due to classeswithmembers.
-        .assertSuccessWithOutput(parameters.isReference() ? EXPECTED : UNEXPECTED)
+        .assertSuccessWithOutput(EXPECTED)
         .applyIf(parameters.isShrinker(), r -> r.inspect(this::checkOutput));
   }
 
@@ -53,8 +51,7 @@
   }
 
   private void checkOutput(CodeInspector inspector) {
-    // TODO(b/322104143): The class should not be renamed.
-    assertThat(inspector.clazz(B.class), isPresentAndRenamed());
+    assertThat(inspector.clazz(B.class), isPresentAndNotRenamed());
   }
 
   static class A {
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepFieldValueApiTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepFieldValueApiTest.java
index dfca281..9ac84d1 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepFieldValueApiTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepFieldValueApiTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -68,11 +67,7 @@
         .addProgramFiles(lib.get())
         .setMinApi(parameters.parameters())
         .run(parameters.getRuntime(), TestClass.class)
-        // TODO(b/322104143): The -keepclasseswithmembers rule does not keep B and its members.
-        .applyIf(
-            parameters.isPG(),
-            r -> r.assertFailureWithErrorThatThrows(ClassNotFoundException.class),
-            r -> r.assertSuccessWithOutput(EXPECTED));
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   public List<Class<?>> getLibraryClasses() {
@@ -88,11 +83,6 @@
     assertThat(aClass, isPresent());
     assertThat(aClass.uniqueFieldWithFinalName("CLASS"), isPresent());
     ClassSubject bClass = inspector.clazz(B.class);
-    if (parameters.isPG()) {
-      // TODO(b/322104143): The -keepclasseswithmembers rule does not keep B and its members.
-      assertThat(bClass, isAbsent());
-      return;
-    }
     assertThat(bClass, isPresent());
     assertThat(bClass.uniqueMethodWithOriginalName("foo"), isPresent());
     assertThat(bClass.uniqueMethodWithOriginalName("bar"), isPresent());