[KeepAnno] Introduce a class pattern in the AST

This replaces/unifies the name and instance-of properties on class-items and
type-patterns. A follow-up should likely remove the instance-of from
type-pattern and only allow it via a @ClassPattern annotation.

Bug: b/350647134
Change-Id: I3313f2b6f75793fddb3195912a36f36ca4baa4fb
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
index e0ef6b5..81d3598 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.keepanno.asm.PackageNameParser.PackageNameProperty;
 import com.android.tools.r8.keepanno.asm.TypeParser.TypeProperty;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.ClassNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepClassPattern;
 import com.android.tools.r8.keepanno.ast.KeepPackagePattern;
 import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepTypePattern;
@@ -46,20 +47,22 @@
                 TypeProperty.TYPE_NAME,
                 name,
                 value,
-                type -> setValue.accept(typeToClassType(type, getParsingContext().property(name))));
+                type ->
+                    setValue.accept(typeToClassNameType(type, getParsingContext().property(name))));
       case CONSTANT:
         return new TypeParser(getParsingContext())
             .tryProperty(
                 TypeProperty.TYPE_CONSTANT,
                 name,
                 value,
-                type -> setValue.accept(typeToClassType(type, getParsingContext().property(name))));
+                type ->
+                    setValue.accept(typeToClassNameType(type, getParsingContext().property(name))));
       default:
         return false;
     }
   }
 
-  KeepQualifiedClassNamePattern typeToClassType(
+  KeepQualifiedClassNamePattern typeToClassNameType(
       KeepTypePattern typePattern, PropertyParsingContext parsingContext) {
     return typePattern.apply(
         KeepQualifiedClassNamePattern::any,
@@ -69,11 +72,7 @@
         arrayTypePattern -> {
           throw parsingContext.error("Invalid use of array type where class type was expected");
         },
-        classNamePattern -> classNamePattern,
-        instanceOfPattern -> {
-          throw parsingContext.error(
-              "Invalid use of instance of type where class type was expected");
-        });
+        KeepClassPattern::getClassNamePattern);
   }
 
   @Override
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
index f81610e..85962be 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.keepanno.asm.InstanceOfParser.InstanceOfProperties;
 import com.android.tools.r8.keepanno.asm.TypeParser.TypeProperty;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.TypePattern;
+import com.android.tools.r8.keepanno.ast.KeepClassPattern;
 import com.android.tools.r8.keepanno.ast.KeepTypePattern;
 import com.android.tools.r8.keepanno.ast.ParsingContext;
 import com.android.tools.r8.keepanno.ast.ParsingContext.AnnotationParsingContext;
@@ -71,7 +72,8 @@
               ClassNameProperty.PATTERN,
               name,
               descriptor,
-              value -> setValue.accept(KeepTypePattern.fromClass(value)));
+              value ->
+                  setValue.accept(KeepTypePattern.fromClass(KeepClassPattern.fromName(value))));
         }
       case INSTANCE_OF_PATTERN:
         {
@@ -80,7 +82,10 @@
               InstanceOfProperties.PATTERN,
               name,
               descriptor,
-              value -> setValue.accept(KeepTypePattern.fromInstanceOf(value)));
+              value ->
+                  setValue.accept(
+                      KeepTypePattern.fromClass(
+                          KeepClassPattern.builder().setInstanceOfPattern(value).build())));
         }
       default:
         return null;
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepAnnotationPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepAnnotationPattern.java
index 13d4df3..830417d 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepAnnotationPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepAnnotationPattern.java
@@ -46,7 +46,7 @@
 
   public AnnotationPattern.Builder buildProto() {
     AnnotationPattern.Builder builder = AnnotationPattern.newBuilder();
-    builder.setName(namePattern.buildProto());
+    namePattern.buildProtoIfNotAny(builder::setName);
     if (retentionPolicies == RUNTIME_RETENTION_MASK) {
       builder.setRetention(AnnotationRetention.RETENTION_RUNTIME);
     } else if (retentionPolicies == CLASS_RETENTION_MASK) {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java
index 0c86c6c..23e2c2e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java
@@ -52,10 +52,7 @@
             array -> {
               throw new KeepEdgeException("Unexpected nested array");
             },
-            KeepQualifiedClassNamePattern::getExactDescriptor,
-            instanceOf -> {
-              throw new KeepEdgeException("No descriptor exists for instanceOf array");
-            });
+            clazz -> clazz.getClassNamePattern().getExactDescriptor());
   }
 
   @Override
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassItemPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassItemPattern.java
index 4ccfad3..d833da0 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassItemPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassItemPattern.java
@@ -25,49 +25,48 @@
 
   public ClassItemPattern.Builder buildClassProto() {
     ClassItemPattern.Builder builder = ClassItemPattern.newBuilder();
+    classPattern.buildProtoIfNotAny(builder::setClassPattern);
     KeepSpecUtils.buildAnnotatedByProto(annotatedByPattern, builder::setAnnotatedBy);
-    instanceOfPattern.buildProto(builder::setInstanceOf);
-    return builder.setClassName(classNamePattern.buildProto());
+    return builder;
   }
 
   public static class Builder {
 
-    private KeepQualifiedClassNamePattern classNamePattern = KeepQualifiedClassNamePattern.any();
-    private KeepInstanceOfPattern instanceOfPattern = KeepInstanceOfPattern.any();
+    private KeepClassPattern.Builder classPattern = KeepClassPattern.builder();
     private OptionalPattern<KeepQualifiedClassNamePattern> annotatedByPattern =
         OptionalPattern.absent();
 
     private Builder() {}
 
     public Builder applyProto(ClassItemPattern protoItem) {
-      assert classNamePattern.isAny();
-      if (protoItem.hasClassName()) {
-        setClassNamePattern(KeepQualifiedClassNamePattern.fromProto(protoItem.getClassName()));
+      assert classPattern.build().isAny();
+      if (protoItem.hasClassPattern()) {
+        setClassPattern(KeepClassPattern.fromProto(protoItem.getClassPattern()));
       }
       assert annotatedByPattern.isAbsent();
       if (protoItem.hasAnnotatedBy()) {
         setAnnotatedByPattern(KeepSpecUtils.annotatedByFromProto(protoItem.getAnnotatedBy()));
       }
-      assert instanceOfPattern.isAny();
-      if (protoItem.hasInstanceOf()) {
-        setInstanceOfPattern(KeepInstanceOfPattern.fromProto(protoItem.getInstanceOf()));
-      }
       return this;
     }
 
     public Builder copyFrom(KeepClassItemPattern pattern) {
-      return setClassNamePattern(pattern.getClassNamePattern())
-          .setInstanceOfPattern(pattern.getInstanceOfPattern())
+      return setClassPattern(pattern.getClassPattern())
           .setAnnotatedByPattern(pattern.getAnnotatedByPattern());
     }
 
+    public Builder setClassPattern(KeepClassPattern classPattern) {
+      this.classPattern = KeepClassPattern.builder().copyFrom(classPattern);
+      return this;
+    }
+
     public Builder setClassNamePattern(KeepQualifiedClassNamePattern classNamePattern) {
-      this.classNamePattern = classNamePattern;
+      classPattern.setClassNamePattern(classNamePattern);
       return this;
     }
 
     public Builder setInstanceOfPattern(KeepInstanceOfPattern instanceOfPattern) {
-      this.instanceOfPattern = instanceOfPattern;
+      classPattern.setInstanceOfPattern(instanceOfPattern);
       return this;
     }
 
@@ -79,23 +78,19 @@
     }
 
     public KeepClassItemPattern build() {
-      return new KeepClassItemPattern(classNamePattern, instanceOfPattern, annotatedByPattern);
+      return new KeepClassItemPattern(classPattern.build(), annotatedByPattern);
     }
   }
 
-  private final KeepQualifiedClassNamePattern classNamePattern;
-  private final KeepInstanceOfPattern instanceOfPattern;
+  private final KeepClassPattern classPattern;
   private final OptionalPattern<KeepQualifiedClassNamePattern> annotatedByPattern;
 
   public KeepClassItemPattern(
-      KeepQualifiedClassNamePattern classNamePattern,
-      KeepInstanceOfPattern instanceOfPattern,
+      KeepClassPattern classPattern,
       OptionalPattern<KeepQualifiedClassNamePattern> annotatedByPattern) {
-    assert classNamePattern != null;
-    assert instanceOfPattern != null;
+    assert classPattern != null;
     assert annotatedByPattern != null;
-    this.classNamePattern = classNamePattern;
-    this.instanceOfPattern = instanceOfPattern;
+    this.classPattern = classPattern;
     this.annotatedByPattern = annotatedByPattern;
   }
 
@@ -109,12 +104,16 @@
     return Collections.emptyList();
   }
 
+  public KeepClassPattern getClassPattern() {
+    return classPattern;
+  }
+
   public KeepQualifiedClassNamePattern getClassNamePattern() {
-    return classNamePattern;
+    return classPattern.getClassNamePattern();
   }
 
   public KeepInstanceOfPattern getInstanceOfPattern() {
-    return instanceOfPattern;
+    return classPattern.getInstanceOfPattern();
   }
 
   public OptionalPattern<KeepQualifiedClassNamePattern> getAnnotatedByPattern() {
@@ -122,7 +121,7 @@
   }
 
   public boolean isAny() {
-    return classNamePattern.isAny() && instanceOfPattern.isAny() && annotatedByPattern.isAbsent();
+    return classPattern.isAny() && annotatedByPattern.isAbsent();
   }
 
   @Override
@@ -134,25 +133,22 @@
       return false;
     }
     KeepClassItemPattern that = (KeepClassItemPattern) obj;
-    return classNamePattern.equals(that.classNamePattern)
-        && instanceOfPattern.equals(that.instanceOfPattern)
+    return classPattern.equals(that.classPattern)
         && annotatedByPattern.equals(that.annotatedByPattern);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(classNamePattern, instanceOfPattern, annotatedByPattern);
+    return Objects.hash(classPattern, annotatedByPattern);
   }
 
   @Override
   public String toString() {
     return "KeepClassItemPattern"
         + "{ class="
-        + classNamePattern
+        + classPattern
         + ", annotated-by="
         + annotatedByPattern
-        + ", instance-of="
-        + instanceOfPattern
         + '}';
   }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassPattern.java
new file mode 100644
index 0000000..dee7f6b
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassPattern.java
@@ -0,0 +1,127 @@
+// 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.proto.KeepSpecProtos.ClassPattern;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+public class KeepClassPattern {
+
+  private static final KeepClassPattern ANY =
+      new KeepClassPattern(KeepQualifiedClassNamePattern.any(), KeepInstanceOfPattern.any());
+
+  public static KeepClassPattern any() {
+    return ANY;
+  }
+
+  public static KeepClassPattern fromName(KeepQualifiedClassNamePattern namePattern) {
+    return builder().setClassNamePattern(namePattern).build();
+  }
+
+  public static KeepClassPattern fromProto(ClassPattern proto) {
+    return builder().applyProto(proto).build();
+  }
+
+  public static KeepClassPattern exactFromDescriptor(String typeDescriptor) {
+    return fromName(KeepQualifiedClassNamePattern.exactFromDescriptor(typeDescriptor));
+  }
+
+  private final KeepQualifiedClassNamePattern classNamePattern;
+  private final KeepInstanceOfPattern instanceOfPattern;
+
+  public KeepClassPattern(
+      KeepQualifiedClassNamePattern classNamePattern, KeepInstanceOfPattern instanceOfPattern) {
+    this.classNamePattern = classNamePattern;
+    this.instanceOfPattern = instanceOfPattern;
+  }
+
+  public boolean isAny() {
+    return classNamePattern.isAny() && instanceOfPattern.isAny();
+  }
+
+  public KeepQualifiedClassNamePattern getClassNamePattern() {
+    return classNamePattern;
+  }
+
+  public KeepInstanceOfPattern getInstanceOfPattern() {
+    return instanceOfPattern;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public void buildProtoIfNotAny(Consumer<ClassPattern.Builder> setter) {
+    if (!isAny()) {
+      setter.accept(buildProto());
+    }
+  }
+
+  public ClassPattern.Builder buildProto() {
+    ClassPattern.Builder builder = ClassPattern.newBuilder();
+    classNamePattern.buildProtoIfNotAny(builder::setClassName);
+    instanceOfPattern.buildProtoIfNotAny(builder::setInstanceOf);
+    return builder;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    }
+    if (!(obj instanceof KeepClassPattern)) {
+      return false;
+    }
+    KeepClassPattern other = (KeepClassPattern) obj;
+    return classNamePattern.equals(other.classNamePattern)
+        && instanceOfPattern.equals(other.instanceOfPattern);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(classNamePattern, instanceOfPattern);
+  }
+
+  public static class Builder {
+
+    private KeepQualifiedClassNamePattern classNamePattern = KeepQualifiedClassNamePattern.any();
+    private KeepInstanceOfPattern instanceOfPattern = KeepInstanceOfPattern.any();
+
+    public Builder applyProto(ClassPattern protoItem) {
+      assert classNamePattern.isAny();
+      if (protoItem.hasClassName()) {
+        setClassNamePattern(KeepQualifiedClassNamePattern.fromProto(protoItem.getClassName()));
+      }
+      assert instanceOfPattern.isAny();
+      if (protoItem.hasInstanceOf()) {
+        setInstanceOfPattern(KeepInstanceOfPattern.fromProto(protoItem.getInstanceOf()));
+      }
+      return this;
+    }
+
+    public Builder copyFrom(KeepClassPattern pattern) {
+      return setClassNamePattern(pattern.getClassNamePattern())
+          .setInstanceOfPattern(pattern.getInstanceOfPattern());
+    }
+
+    public Builder setClassNamePattern(KeepQualifiedClassNamePattern classNamePattern) {
+      this.classNamePattern = classNamePattern;
+      return this;
+    }
+
+    public Builder setInstanceOfPattern(KeepInstanceOfPattern instanceOfPattern) {
+      this.instanceOfPattern = instanceOfPattern;
+      return this;
+    }
+
+    public KeepClassPattern build() {
+      if (classNamePattern.isAny() && instanceOfPattern.isAny()) {
+        return any();
+      }
+      return new KeepClassPattern(classNamePattern, instanceOfPattern);
+    }
+  }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldAccessPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldAccessPattern.java
index b9bf204..2db295e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldAccessPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldAccessPattern.java
@@ -76,8 +76,8 @@
     }
     MemberAccessField.Builder builder = MemberAccessField.newBuilder();
     buildGeneralProto(builder::setGeneralAccess);
-    volatilePattern.buildProto(builder::setVolatilePattern);
-    transientPattern.buildProto(builder::setTransientPattern);
+    volatilePattern.buildProtoIfNotAny(builder::setVolatilePattern);
+    transientPattern.buildProtoIfNotAny(builder::setTransientPattern);
     callback.accept(builder);
   }
 
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepInstanceOfPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepInstanceOfPattern.java
index 114ada8..72da0d0 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepInstanceOfPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepInstanceOfPattern.java
@@ -17,14 +17,12 @@
     return builder().applyProto(proto).build();
   }
 
-  public void buildProto(Consumer<InstanceOfPattern.Builder> setter) {
-    if (isAny()) {
-      return;
+  public void buildProtoIfNotAny(Consumer<InstanceOfPattern.Builder> setter) {
+    if (!isAny()) {
+      InstanceOfPattern.Builder builder = InstanceOfPattern.newBuilder();
+      getClassNamePattern().buildProtoIfNotAny(builder::setClassName);
+      setter.accept(builder.setInclusive(isInclusive()));
     }
-    setter.accept(
-        InstanceOfPattern.newBuilder()
-            .setInclusive(isInclusive())
-            .setClassName(getClassNamePattern().buildProto()));
   }
 
   public static class Builder {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberAccessPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberAccessPattern.java
index 487ddc8..0446b39 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberAccessPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberAccessPattern.java
@@ -103,9 +103,9 @@
       }
       builder.setAccessVisibility(visibilityBuilder.build());
     }
-    staticPattern.buildProto(builder::setStaticPattern);
-    finalPattern.buildProto(builder::setFinalPattern);
-    syntheticPattern.buildProto(builder::setSyntheticPattern);
+    staticPattern.buildProtoIfNotAny(builder::setStaticPattern);
+    finalPattern.buildProtoIfNotAny(builder::setFinalPattern);
+    syntheticPattern.buildProtoIfNotAny(builder::setSyntheticPattern);
     callback.accept(builder);
   }
 
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodAccessPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodAccessPattern.java
index 5b3a1bd..2c14d18 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodAccessPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodAccessPattern.java
@@ -65,11 +65,11 @@
     }
     MemberAccessMethod.Builder builder = MemberAccessMethod.newBuilder();
     buildGeneralProto(builder::setGeneralAccess);
-    synchronizedPattern.buildProto(builder::setSynchronizedPattern);
-    bridgePattern.buildProto(builder::setBridgePattern);
-    nativePattern.buildProto(builder::setNativePattern);
-    abstractPattern.buildProto(builder::setAbstractPattern);
-    strictFpPattern.buildProto(builder::setStrictFpPattern);
+    synchronizedPattern.buildProtoIfNotAny(builder::setSynchronizedPattern);
+    bridgePattern.buildProtoIfNotAny(builder::setBridgePattern);
+    nativePattern.buildProtoIfNotAny(builder::setNativePattern);
+    abstractPattern.buildProtoIfNotAny(builder::setAbstractPattern);
+    strictFpPattern.buildProtoIfNotAny(builder::setStrictFpPattern);
     callback.accept(builder);
   }
 
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepQualifiedClassNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepQualifiedClassNamePattern.java
index 94445bb..fc1a44a 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepQualifiedClassNamePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepQualifiedClassNamePattern.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.keepanno.proto.KeepSpecProtos.ClassNamePattern;
 import java.util.Objects;
+import java.util.function.Consumer;
 
 public final class KeepQualifiedClassNamePattern {
 
@@ -49,11 +50,12 @@
     return KeepQualifiedClassNamePattern.builder().applyProto(clazz).build();
   }
 
-  public ClassNamePattern.Builder buildProto() {
-    ClassNamePattern.Builder builder = ClassNamePattern.newBuilder();
-    return builder
-        .setPackage(packagePattern.buildProto())
-        .setUnqualifiedName(namePattern.buildProto());
+  public void buildProtoIfNotAny(Consumer<ClassNamePattern.Builder> setter) {
+    if (!isAny()) {
+      ClassNamePattern.Builder builder = ClassNamePattern.newBuilder();
+      builder.setPackage(packagePattern.buildProto()).setUnqualifiedName(namePattern.buildProto());
+      setter.accept(builder);
+    }
   }
 
   public static class Builder {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepSpecUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepSpecUtils.java
index 5a76764..51da9a9 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepSpecUtils.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepSpecUtils.java
@@ -49,7 +49,11 @@
       Consumer<AnnotatedByPattern.Builder> callback) {
     // If the annotated-by pattern is absent then no restrictions are present, and we don't set it.
     if (pattern.isPresent()) {
-      callback.accept(AnnotatedByPattern.newBuilder().setName(pattern.get().buildProto()));
+      // We must set a pattern here even if the annotation is "any" as that implies that the item
+      // must be annotated by some annotation.
+      AnnotatedByPattern.Builder builder = AnnotatedByPattern.newBuilder();
+      pattern.get().buildProtoIfNotAny(builder::setName);
+      callback.accept(builder);
     }
   }
 
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java
index 15cfef7..a911ca6 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java
@@ -25,14 +25,10 @@
     return new ArrayType(type);
   }
 
-  public static KeepTypePattern fromClass(KeepQualifiedClassNamePattern type) {
+  public static KeepTypePattern fromClass(KeepClassPattern type) {
     return new ClassType(type);
   }
 
-  public static KeepTypePattern fromInstanceOf(KeepInstanceOfPattern pattern) {
-    return new KeepInstanceOf(pattern);
-  }
-
   public static KeepTypePattern fromDescriptor(String typeDescriptor) {
     char c = typeDescriptor.charAt(0);
     if (c == 'L') {
@@ -40,7 +36,7 @@
       if (typeDescriptor.charAt(end) != ';') {
         throw new KeepEdgeException("Invalid type descriptor: " + typeDescriptor);
       }
-      return fromClass(KeepQualifiedClassNamePattern.exactFromDescriptor(typeDescriptor));
+      return fromClass(KeepClassPattern.exactFromDescriptor(typeDescriptor));
     }
     if (c == '[') {
       int dim = 1;
@@ -61,21 +57,18 @@
       Supplier<T> onAny,
       Function<KeepPrimitiveTypePattern, T> onPrimitive,
       Function<KeepArrayTypePattern, T> onArray,
-      Function<KeepQualifiedClassNamePattern, T> onClass,
-      Function<KeepInstanceOfPattern, T> onInstanceOf);
+      Function<KeepClassPattern, T> onClass);
 
   public final void match(
       Runnable onAny,
       Consumer<KeepPrimitiveTypePattern> onPrimitive,
       Consumer<KeepArrayTypePattern> onArray,
-      Consumer<KeepQualifiedClassNamePattern> onClass,
-      Consumer<KeepInstanceOfPattern> onInstanceOf) {
+      Consumer<KeepClassPattern> onClass) {
     apply(
         AstUtils.toVoidSupplier(onAny),
         AstUtils.toVoidFunction(onPrimitive),
         AstUtils.toVoidFunction(onArray),
-        AstUtils.toVoidFunction(onClass),
-        AstUtils.toVoidFunction(onInstanceOf));
+        AstUtils.toVoidFunction(onClass));
   }
 
   public boolean isAny() {
@@ -95,8 +88,7 @@
         Supplier<T> onAny,
         Function<KeepPrimitiveTypePattern, T> onPrimitive,
         Function<KeepArrayTypePattern, T> onArray,
-        Function<KeepQualifiedClassNamePattern, T> onClass,
-        Function<KeepInstanceOfPattern, T> onInstanceOf) {
+        Function<KeepClassPattern, T> onClass) {
       return onAny.get();
     }
 
@@ -161,16 +153,15 @@
         Supplier<T> onAny,
         Function<KeepPrimitiveTypePattern, T> onPrimitive,
         Function<KeepArrayTypePattern, T> onArray,
-        Function<KeepQualifiedClassNamePattern, T> onClass,
-        Function<KeepInstanceOfPattern, T> onInstanceOf) {
+        Function<KeepClassPattern, T> onClass) {
       return onPrimitive.apply(type);
     }
   }
 
   private static class ClassType extends KeepTypePattern {
-    private final KeepQualifiedClassNamePattern type;
+    private final KeepClassPattern type;
 
-    public ClassType(KeepQualifiedClassNamePattern type) {
+    public ClassType(KeepClassPattern type) {
       this.type = type;
     }
 
@@ -179,8 +170,7 @@
         Supplier<T> onAny,
         Function<KeepPrimitiveTypePattern, T> onPrimitive,
         Function<KeepArrayTypePattern, T> onArray,
-        Function<KeepQualifiedClassNamePattern, T> onClass,
-        Function<KeepInstanceOfPattern, T> onInstanceOf) {
+        Function<KeepClassPattern, T> onClass) {
       return onClass.apply(type);
     }
 
@@ -219,8 +209,7 @@
         Supplier<T> onAny,
         Function<KeepPrimitiveTypePattern, T> onPrimitive,
         Function<KeepArrayTypePattern, T> onArray,
-        Function<KeepQualifiedClassNamePattern, T> onClass,
-        Function<KeepInstanceOfPattern, T> onInstanceOf) {
+        Function<KeepClassPattern, T> onClass) {
       return onArray.apply(type);
     }
 
@@ -247,24 +236,6 @@
     }
   }
 
-  private static class KeepInstanceOf extends KeepTypePattern {
-    private final KeepInstanceOfPattern instanceOf;
-
-    private KeepInstanceOf(KeepInstanceOfPattern instanceOf) {
-      this.instanceOf = instanceOf;
-    }
-
-    @Override
-    public <T> T apply(
-        Supplier<T> onAny,
-        Function<KeepPrimitiveTypePattern, T> onPrimitive,
-        Function<KeepArrayTypePattern, T> onArray,
-        Function<KeepQualifiedClassNamePattern, T> onClass,
-        Function<KeepInstanceOfPattern, T> onInstanceOf) {
-      return onInstanceOf.apply(instanceOf);
-    }
-  }
-
   public static KeepTypePattern fromProto(TypePattern typeProto) {
     if (typeProto.hasPrimitive()) {
       return KeepTypePattern.fromPrimitive(
@@ -273,13 +244,8 @@
     if (typeProto.hasArray()) {
       return KeepTypePattern.fromArray(KeepArrayTypePattern.fromProto(typeProto.getArray()));
     }
-    if (typeProto.hasClazz()) {
-      return KeepTypePattern.fromClass(
-          KeepQualifiedClassNamePattern.fromProto(typeProto.getClazz()));
-    }
-    if (typeProto.hasInstanceOf()) {
-      return KeepTypePattern.fromInstanceOf(
-          KeepInstanceOfPattern.fromProto(typeProto.getInstanceOf()));
+    if (typeProto.hasClassPattern()) {
+      return KeepTypePattern.fromClass(KeepClassPattern.fromProto(typeProto.getClassPattern()));
     }
     return KeepTypePattern.any();
   }
@@ -292,17 +258,7 @@
         },
         primitive -> builder.setPrimitive(primitive.buildProto()),
         array -> builder.setArray(array.buildProto()),
-        clazz -> builder.setClazz(clazz.buildProto()),
-        instanceOf -> {
-          if (instanceOf.isAny()) {
-            // Note that an "any" instance-of pattern should match any class-type
-            // TODO(b/350647134): This should become evident when introducing a class-pattern.
-            //  When doing so, consider also if/how to match a general reference type.
-            builder.setClazz(KeepQualifiedClassNamePattern.any().buildProto());
-          } else {
-            instanceOf.buildProto(builder::setInstanceOf);
-          }
-        });
+        clazz -> builder.setClassPattern(clazz.buildProto()));
     return builder;
   }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/ModifierPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/ModifierPattern.java
index 1afe19e..0d8ef29 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/ModifierPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/ModifierPattern.java
@@ -73,7 +73,7 @@
     return System.identityHashCode(this);
   }
 
-  public void buildProto(Consumer<KeepSpecProtos.ModifierPattern.Builder> callback) {
+  public void buildProtoIfNotAny(Consumer<KeepSpecProtos.ModifierPattern.Builder> callback) {
     if (isOnlyPositive()) {
       callback.accept(KeepSpecProtos.ModifierPattern.newBuilder().setValue(true));
     } else if (isOnlyNegative()) {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
index 3d3e034..9289894 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
@@ -222,13 +222,13 @@
         printer::appendTripleStar,
         primitivePattern -> printPrimitiveType(printer, primitivePattern),
         arrayTypePattern -> printArrayType(printer, arrayTypePattern),
-        classTypePattern -> printClassName(classTypePattern, printer),
-        instanceOfPattern -> printInstanceOf(instanceOfPattern, printer));
-  }
-
-  private static RulePrinter printInstanceOf(
-      KeepInstanceOfPattern instanceOfPattern, RulePrinter printer) {
-    throw new Unimplemented();
+        classTypePattern -> {
+          if (!classTypePattern.getInstanceOfPattern().isAny()) {
+            throw new KeepEdgeException(
+                "Type patterns with instance-of are not supported in rule extraction");
+          }
+          return printClassName(classTypePattern.getClassNamePattern(), printer);
+        });
   }
 
   private static RulePrinter printPrimitiveType(
diff --git a/src/keepanno/proto/keepspec.proto b/src/keepanno/proto/keepspec.proto
index 4dae6b1..9760fe4 100644
--- a/src/keepanno/proto/keepspec.proto
+++ b/src/keepanno/proto/keepspec.proto
@@ -158,9 +158,13 @@
 }
 
 message ClassItemPattern {
+  optional ClassPattern class_pattern = 1;
+  optional AnnotatedByPattern annotated_by = 2;
+}
+
+message ClassPattern {
   optional ClassNamePattern class_name = 1;
   optional InstanceOfPattern instance_of = 2;
-  optional AnnotatedByPattern annotated_by = 3;
 }
 
 message InstanceOfPattern {
@@ -300,8 +304,7 @@
     // Unset type denotes any type.
     TypePatternPrimitive primitive = 1;
     TypePatternArray array = 2;
-    ClassNamePattern clazz = 3;
-    InstanceOfPattern instance_of = 4;
+    ClassPattern class_pattern = 3;
   }
 }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcherPredicates.java b/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcherPredicates.java
index dfe3f51..9587d94 100644
--- a/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcherPredicates.java
+++ b/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcherPredicates.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.keepanno.ast.KeepAnnotationPattern;
 import com.android.tools.r8.keepanno.ast.KeepArrayTypePattern;
 import com.android.tools.r8.keepanno.ast.KeepClassItemPattern;
+import com.android.tools.r8.keepanno.ast.KeepClassPattern;
 import com.android.tools.r8.keepanno.ast.KeepFieldAccessPattern;
 import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
 import com.android.tools.r8.keepanno.ast.KeepInstanceOfPattern;
@@ -296,8 +297,13 @@
         () -> true,
         p -> matchesPrimitiveType(type, p),
         p -> matchesArrayType(type, p, appInfo),
-        p -> matchesClassType(type, p),
-        p -> matchesInstanceOfPattern(type, p, appInfo));
+        p -> matchesClassPattern(type, p, appInfo));
+  }
+
+  public boolean matchesClassPattern(
+      DexType type, KeepClassPattern pattern, AppInfoWithClassHierarchy appInfo) {
+    return matchesClassType(type, pattern.getClassNamePattern())
+        && matchesInstanceOfPattern(type, pattern.getInstanceOfPattern(), appInfo);
   }
 
   public boolean matchesClassType(DexType type, KeepQualifiedClassNamePattern pattern) {