[KeepAnno] Encode property groups in the parsing contexts

This eliminates the previous encoding of the information as a "kind".
The generator is extended to emit "group" names for the cases where
multiple options are possible. This ensures consistency in the naming
between error messages and java doc.

Bug: b/248408342
Change-Id: Icfec26462dc2d16edd0ea19a1425381fdb47da5c
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
index 74b2add..642e829 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
@@ -254,6 +254,8 @@
    * <p>Mutually exclusive with all field and method properties as use restricts the match to both
    * types of members.
    *
+   * <p>Mutually exclusive with the property `memberFromBinding` also defining member-access.
+   *
    * @return The member access-flag constraints that must be met.
    */
   MemberAccessFlags[] memberAccess() default {};
@@ -266,6 +268,8 @@
    * <p>If none, and other properties define this item as a method, the default matches any
    * method-access flags.
    *
+   * <p>Mutually exclusive with the property `memberFromBinding` also defining method-access.
+   *
    * @return The method access-flag constraints that must be met.
    */
   MethodAccessFlags[] methodAccess() default {};
@@ -278,6 +282,8 @@
    * <p>If none, and other properties define this item as a method, the default matches any method
    * name.
    *
+   * <p>Mutually exclusive with the property `memberFromBinding` also defining method-name.
+   *
    * @return The exact method name of the method.
    */
   String methodName() default "";
@@ -295,6 +301,7 @@
    * <ul>
    *   <li>methodReturnTypeConstant
    *   <li>methodReturnTypePattern
+   *   <li>memberFromBinding
    * </ul>
    *
    * @return The qualified type name of the method return type.
@@ -314,6 +321,7 @@
    * <ul>
    *   <li>methodReturnType
    *   <li>methodReturnTypePattern
+   *   <li>memberFromBinding
    * </ul>
    *
    * @return A class constant denoting the type of the method return type.
@@ -333,6 +341,7 @@
    * <ul>
    *   <li>methodReturnType
    *   <li>methodReturnTypeConstant
+   *   <li>memberFromBinding
    * </ul>
    *
    * @return The pattern of the method return type.
@@ -347,7 +356,12 @@
    * <p>If none, and other properties define this item as a method, the default matches any
    * parameters.
    *
-   * <p>Mutually exclusive with the property `methodParameterTypePatterns` also defining parameters.
+   * <p>Mutually exclusive with the following other properties defining parameters:
+   *
+   * <ul>
+   *   <li>methodParameterTypePatterns
+   *   <li>memberFromBinding
+   * </ul>
    *
    * @return The list of qualified type names of the method parameters.
    */
@@ -361,7 +375,12 @@
    * <p>If none, and other properties define this item as a method, the default matches any
    * parameters.
    *
-   * <p>Mutually exclusive with the property `methodParameters` also defining parameters.
+   * <p>Mutually exclusive with the following other properties defining parameters:
+   *
+   * <ul>
+   *   <li>methodParameters
+   *   <li>memberFromBinding
+   * </ul>
    *
    * @return The list of type patterns for the method parameters.
    */
@@ -375,6 +394,8 @@
    * <p>If none, and other properties define this item as a field, the default matches any
    * field-access flags.
    *
+   * <p>Mutually exclusive with the property `memberFromBinding` also defining field-access.
+   *
    * @return The field access-flag constraints that must be met.
    */
   FieldAccessFlags[] fieldAccess() default {};
@@ -387,6 +408,8 @@
    * <p>If none, and other properties define this item as a field, the default matches any field
    * name.
    *
+   * <p>Mutually exclusive with the property `memberFromBinding` also defining field-name.
+   *
    * @return The exact field name of the field.
    */
   String fieldName() default "";
@@ -403,6 +426,7 @@
    * <ul>
    *   <li>fieldTypeConstant
    *   <li>fieldTypePattern
+   *   <li>memberFromBinding
    * </ul>
    *
    * @return The qualified type name for the field type.
@@ -421,6 +445,7 @@
    * <ul>
    *   <li>fieldType
    *   <li>fieldTypePattern
+   *   <li>memberFromBinding
    * </ul>
    *
    * @return The class constant for the field type.
@@ -439,6 +464,7 @@
    * <ul>
    *   <li>fieldType
    *   <li>fieldTypeConstant
+   *   <li>memberFromBinding
    * </ul>
    *
    * @return The type pattern for the field type.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
index d60525d..20705dd 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
@@ -351,6 +351,8 @@
    * <p>Mutually exclusive with all field and method properties as use restricts the match to both
    * types of members.
    *
+   * <p>Mutually exclusive with the property `memberFromBinding` also defining member-access.
+   *
    * @return The member access-flag constraints that must be met.
    */
   MemberAccessFlags[] memberAccess() default {};
@@ -363,6 +365,8 @@
    * <p>If none, and other properties define this item as a method, the default matches any
    * method-access flags.
    *
+   * <p>Mutually exclusive with the property `memberFromBinding` also defining method-access.
+   *
    * @return The method access-flag constraints that must be met.
    */
   MethodAccessFlags[] methodAccess() default {};
@@ -375,6 +379,8 @@
    * <p>If none, and other properties define this item as a method, the default matches any method
    * name.
    *
+   * <p>Mutually exclusive with the property `memberFromBinding` also defining method-name.
+   *
    * @return The exact method name of the method.
    */
   String methodName() default "";
@@ -392,6 +398,7 @@
    * <ul>
    *   <li>methodReturnTypeConstant
    *   <li>methodReturnTypePattern
+   *   <li>memberFromBinding
    * </ul>
    *
    * @return The qualified type name of the method return type.
@@ -411,6 +418,7 @@
    * <ul>
    *   <li>methodReturnType
    *   <li>methodReturnTypePattern
+   *   <li>memberFromBinding
    * </ul>
    *
    * @return A class constant denoting the type of the method return type.
@@ -430,6 +438,7 @@
    * <ul>
    *   <li>methodReturnType
    *   <li>methodReturnTypeConstant
+   *   <li>memberFromBinding
    * </ul>
    *
    * @return The pattern of the method return type.
@@ -444,7 +453,12 @@
    * <p>If none, and other properties define this item as a method, the default matches any
    * parameters.
    *
-   * <p>Mutually exclusive with the property `methodParameterTypePatterns` also defining parameters.
+   * <p>Mutually exclusive with the following other properties defining parameters:
+   *
+   * <ul>
+   *   <li>methodParameterTypePatterns
+   *   <li>memberFromBinding
+   * </ul>
    *
    * @return The list of qualified type names of the method parameters.
    */
@@ -458,7 +472,12 @@
    * <p>If none, and other properties define this item as a method, the default matches any
    * parameters.
    *
-   * <p>Mutually exclusive with the property `methodParameters` also defining parameters.
+   * <p>Mutually exclusive with the following other properties defining parameters:
+   *
+   * <ul>
+   *   <li>methodParameters
+   *   <li>memberFromBinding
+   * </ul>
    *
    * @return The list of type patterns for the method parameters.
    */
@@ -472,6 +491,8 @@
    * <p>If none, and other properties define this item as a field, the default matches any
    * field-access flags.
    *
+   * <p>Mutually exclusive with the property `memberFromBinding` also defining field-access.
+   *
    * @return The field access-flag constraints that must be met.
    */
   FieldAccessFlags[] fieldAccess() default {};
@@ -484,6 +505,8 @@
    * <p>If none, and other properties define this item as a field, the default matches any field
    * name.
    *
+   * <p>Mutually exclusive with the property `memberFromBinding` also defining field-name.
+   *
    * @return The exact field name of the field.
    */
   String fieldName() default "";
@@ -500,6 +523,7 @@
    * <ul>
    *   <li>fieldTypeConstant
    *   <li>fieldTypePattern
+   *   <li>memberFromBinding
    * </ul>
    *
    * @return The qualified type name for the field type.
@@ -518,6 +542,7 @@
    * <ul>
    *   <li>fieldType
    *   <li>fieldTypePattern
+   *   <li>memberFromBinding
    * </ul>
    *
    * @return The class constant for the field type.
@@ -536,6 +561,7 @@
    * <ul>
    *   <li>fieldType
    *   <li>fieldTypeConstant
+   *   <li>memberFromBinding
    * </ul>
    *
    * @return The type pattern for the field type.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ConvertingPropertyParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ConvertingPropertyParser.java
index ddbfb58..bbc07f3 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ConvertingPropertyParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ConvertingPropertyParser.java
@@ -30,11 +30,6 @@
   }
 
   @Override
-  public String kind() {
-    return parser.kind();
-  }
-
-  @Override
   public void setProperty(String name, P property) {
     parser.setProperty(name, property);
   }
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 1706b3a..a0461e6 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
@@ -20,7 +20,6 @@
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Option;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Target;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsedByReflection;
-import com.android.tools.r8.keepanno.ast.KeepAnnotationParserException;
 import com.android.tools.r8.keepanno.ast.KeepBindingReference;
 import com.android.tools.r8.keepanno.ast.KeepBindings;
 import com.android.tools.r8.keepanno.ast.KeepBindings.KeepBindingSymbol;
@@ -59,6 +58,7 @@
 import com.android.tools.r8.keepanno.ast.ParsingContext.AnnotationParsingContext;
 import com.android.tools.r8.keepanno.ast.ParsingContext.ClassParsingContext;
 import com.android.tools.r8.keepanno.ast.ParsingContext.FieldParsingContext;
+import com.android.tools.r8.keepanno.ast.ParsingContext.GroupParsingContext;
 import com.android.tools.r8.keepanno.ast.ParsingContext.MethodParsingContext;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
@@ -257,7 +257,6 @@
       if (descriptor.equals(AnnotationConstants.CheckRemoved.DESCRIPTOR)) {
         return new CheckRemovedClassVisitor(
             annotationParsingContext(descriptor),
-            descriptor,
             parent::accept,
             this::setContext,
             className,
@@ -266,7 +265,6 @@
       if (descriptor.equals(AnnotationConstants.CheckOptimizedOut.DESCRIPTOR)) {
         return new CheckRemovedClassVisitor(
             annotationParsingContext(descriptor),
-            descriptor,
             parent::accept,
             this::setContext,
             className,
@@ -378,7 +376,6 @@
       if (descriptor.equals(AnnotationConstants.CheckRemoved.DESCRIPTOR)) {
         return new CheckRemovedMemberVisitor(
             annotationParsingContext(descriptor),
-            descriptor,
             parent::accept,
             this::setContext,
             createMethodItemContext(),
@@ -387,7 +384,6 @@
       if (descriptor.equals(AnnotationConstants.CheckOptimizedOut.DESCRIPTOR)) {
         return new CheckRemovedMemberVisitor(
             annotationParsingContext(descriptor),
-            descriptor,
             parent::accept,
             this::setContext,
             createMethodItemContext(),
@@ -860,7 +856,6 @@
   private static class UsedByReflectionMemberVisitor extends AnnotationVisitorBase {
 
     private final AnnotationParsingContext parsingContext;
-    private final String annotationDescriptor;
     private final Parent<KeepEdge> parent;
     private final KeepItemPattern context;
     private final KeepEdge.Builder builder = KeepEdge.builder();
@@ -878,7 +873,6 @@
         KeepItemPattern context) {
       super(parsingContext);
       this.parsingContext = parsingContext;
-      this.annotationDescriptor = annotationDescriptor;
       this.parent = parent;
       this.context = context;
       addContext.accept(metaInfoBuilder);
@@ -1107,7 +1101,6 @@
   private static class CheckRemovedClassVisitor extends AnnotationVisitorBase {
 
     private final AnnotationParsingContext parsingContext;
-    private final String annotationDescriptor;
     private final Parent<KeepCheck> parent;
     private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
     private final String className;
@@ -1115,14 +1108,12 @@
 
     public CheckRemovedClassVisitor(
         AnnotationParsingContext parsingContext,
-        String annotationDescriptor,
         Parent<KeepCheck> parent,
         Consumer<KeepEdgeMetaInfo.Builder> addContext,
         String className,
         KeepCheckKind kind) {
       super(parsingContext);
       this.parsingContext = parsingContext;
-      this.annotationDescriptor = annotationDescriptor;
       this.parent = parent;
       this.className = className;
       this.kind = kind;
@@ -1140,7 +1131,6 @@
 
     @Override
     public void visitEnd() {
-      CheckRemovedClassVisitor superVisitor = this;
       KeepItemVisitorBase itemVisitor =
           new KeepItemVisitorBase(parsingContext) {
             @Override
@@ -1162,7 +1152,6 @@
   /** Parsing of @CheckRemoved and @CheckOptimizedOut on a class context. */
   private static class CheckRemovedMemberVisitor extends AnnotationVisitorBase {
 
-    private final String annotationDescriptor;
     private final Parent<KeepDeclaration> parent;
     private final KeepItemPattern context;
     private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
@@ -1170,13 +1159,11 @@
 
     CheckRemovedMemberVisitor(
         AnnotationParsingContext parsingContext,
-        String annotationDescriptor,
         Parent<KeepDeclaration> parent,
         Consumer<KeepEdgeMetaInfo.Builder> addContext,
         KeepItemPattern context,
         KeepCheckKind kind) {
       super(parsingContext);
-      this.annotationDescriptor = annotationDescriptor;
       this.parent = parent;
       this.context = context;
       this.kind = kind;
@@ -1205,7 +1192,6 @@
   }
 
   abstract static class Declaration<T> {
-    abstract String kind();
 
     boolean isDefault() {
       for (Declaration<?> declaration : declarations()) {
@@ -1216,8 +1202,6 @@
       return true;
     }
 
-    abstract T getValue();
-
     List<Declaration<?>> declarations() {
       return Collections.emptyList();
     }
@@ -1281,7 +1265,7 @@
     private T declarationValue = null;
     private AnnotationVisitor declarationVisitor = null;
 
-    private SingleDeclaration(ParsingContext parsingContext) {
+    private SingleDeclaration(GroupParsingContext parsingContext) {
       this.parsingContext = parsingContext;
     }
 
@@ -1311,19 +1295,11 @@
     }
 
     private void error(String name) {
-      throw new KeepAnnotationParserException(
-          parsingContext,
-          "Multiple declarations defining "
-              + kind()
-              + ": '"
-              + declarationName
-              + "' and '"
-              + name
-              + "'");
+      throw parsingContext.error(
+          "Multiple properties: '" + declarationName + "' and '" + name + "'");
     }
 
-    @Override
-    public final T getValue() {
+    final T getValue() {
       return declarationValue == null ? getDefaultValue() : declarationValue;
     }
 
@@ -1373,12 +1349,7 @@
   private static class InstanceOfDeclaration extends SingleDeclaration<KeepInstanceOfPattern> {
 
     private InstanceOfDeclaration(ParsingContext parsingContext) {
-      super(parsingContext);
-    }
-
-    @Override
-    String kind() {
-      return "instance-of";
+      super(parsingContext.group(Item.instanceOfGroup));
     }
 
     @Override
@@ -1439,9 +1410,9 @@
 
     public ClassDeclaration(
         ParsingContext parsingContext, Supplier<UserBindingsHelper> getBindingsHelper) {
-      this.parsingContext = parsingContext;
+      this.parsingContext = parsingContext.group(Item.classGroup);
       this.getBindingsHelper = getBindingsHelper;
-      classNameParser = new ClassNameParser(parsingContext);
+      classNameParser = new ClassNameParser(parsingContext.group(Item.classNameGroup));
       classNameParser.setProperty(Item.className, ClassNameProperty.NAME);
       classNameParser.setProperty(Item.classConstant, ClassNameProperty.CONSTANT);
       classNameParser.setProperty(Item.classNamePattern, ClassNameProperty.PATTERN);
@@ -1477,17 +1448,11 @@
     }
 
     @Override
-    String kind() {
-      return "class";
-    }
-
-    @Override
     boolean isDefault() {
       return !isBindingReferenceDefined() && super.isDefault();
     }
 
-    @Override
-    KeepClassItemReference getValue() {
+    private KeepClassItemReference getValue() {
       checkAllowedDefinitions();
       if (isBindingReferenceDefined()) {
         return boundClassItemReference;
@@ -1536,12 +1501,12 @@
     private MethodDeclaration(ParsingContext parsingContext) {
       this.parsingContext = parsingContext;
 
-      returnTypeParser = new MethodReturnTypeParser(parsingContext);
+      returnTypeParser = new MethodReturnTypeParser(parsingContext.group(Item.returnTypeGroup));
       returnTypeParser.setProperty(Item.methodReturnType, TypeProperty.TYPE_NAME);
       returnTypeParser.setProperty(Item.methodReturnTypeConstant, TypeProperty.TYPE_CONSTANT);
       returnTypeParser.setProperty(Item.methodReturnTypePattern, TypeProperty.TYPE_PATTERN);
 
-      parametersParser = new MethodParametersParser(parsingContext);
+      parametersParser = new MethodParametersParser(parsingContext.group(Item.parametersGroup));
       parametersParser.setProperty(Item.methodParameters, TypeProperty.TYPE_NAME);
       parametersParser.setProperty(Item.methodParameterTypePatterns, TypeProperty.TYPE_PATTERN);
 
@@ -1561,17 +1526,11 @@
     }
 
     @Override
-    String kind() {
-      return "method";
-    }
-
-    @Override
     boolean isDefault() {
       return accessBuilder == null && builder == null && super.isDefault();
     }
 
-    @Override
-    KeepMethodPattern getValue() {
+    private KeepMethodPattern getValue() {
       if (accessBuilder != null) {
         getBuilder().setAccessPattern(accessBuilder.build());
       }
@@ -1613,7 +1572,7 @@
 
     public FieldDeclaration(ParsingContext parsingContext) {
       this.parsingContext = parsingContext;
-      typeParser = new FieldTypeParser(parsingContext);
+      typeParser = new FieldTypeParser(parsingContext.group(Item.fieldTypeGroup));
       typeParser.setProperty(Item.fieldTypePattern, TypeProperty.TYPE_PATTERN);
       typeParser.setProperty(Item.fieldType, TypeProperty.TYPE_NAME);
       typeParser.setProperty(Item.fieldTypeConstant, TypeProperty.TYPE_CONSTANT);
@@ -1634,17 +1593,11 @@
     }
 
     @Override
-    String kind() {
-      return "field";
-    }
-
-    @Override
     boolean isDefault() {
       return accessBuilder == null && builder == null;
     }
 
-    @Override
-    KeepFieldPattern getValue() {
+    private KeepFieldPattern getValue() {
       if (accessBuilder != null) {
         getBuilder().setAccessPattern(accessBuilder.build());
       }
@@ -1682,7 +1635,7 @@
     private final List<Declaration<?>> declarations;
 
     MemberDeclaration(ParsingContext parsingContext) {
-      this.parsingContext = parsingContext;
+      this.parsingContext = parsingContext.group(Item.memberGroup);
       methodDeclaration = new MethodDeclaration(parsingContext);
       fieldDeclaration = new FieldDeclaration(parsingContext);
       declarations = ImmutableList.of(methodDeclaration, fieldDeclaration);
@@ -1694,17 +1647,11 @@
     }
 
     @Override
-    String kind() {
-      return "member";
-    }
-
-    @Override
     public boolean isDefault() {
       return accessBuilder == null && methodDeclaration.isDefault() && fieldDeclaration.isDefault();
     }
 
-    @Override
-    public KeepMemberPattern getValue() {
+    private KeepMemberPattern getValue() {
       KeepMethodPattern method = methodDeclaration.getValue();
       KeepFieldPattern field = fieldDeclaration.getValue();
       if (accessBuilder != null) {
@@ -2026,40 +1973,10 @@
     }
   }
 
-  private static class StringArrayVisitor extends AnnotationVisitorBase {
-    private final Consumer<List<String>> fn;
-    private final List<String> strings = new ArrayList<>();
-
-    public StringArrayVisitor(ParsingContext parsingContext, Consumer<List<String>> fn) {
-      super(parsingContext);
-      this.fn = fn;
-    }
-
-    @Override
-    public void visit(String name, Object value) {
-      if (value instanceof String) {
-        strings.add((String) value);
-      } else {
-        super.visit(name, value);
-      }
-    }
-
-    @Override
-    public void visitEnd() {
-      super.visitEnd();
-      fn.accept(strings);
-    }
-  }
-
   private static class OptionsDeclaration extends SingleDeclaration<KeepOptions> {
 
     public OptionsDeclaration(ParsingContext parsingContext) {
-      super(parsingContext);
-    }
-
-    @Override
-    String kind() {
-      return "options";
+      super(parsingContext.group(Target.constraintsGroup));
     }
 
     @Override
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParser.java
index 4dc7b38..0fd8a75 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParser.java
@@ -9,8 +9,6 @@
 
 public interface PropertyParser<T, P> {
 
-  String kind();
-
   void setProperty(String name, P property);
 
   boolean isDeclared();
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParserBase.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParserBase.java
index 6578a7f..1ab1fea 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParserBase.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParserBase.java
@@ -15,7 +15,6 @@
 
   private final ParsingContext parsingContext;
 
-  private String kind;
   private final Map<String, P> mapping = new HashMap<>();
   private String resultPropertyName = null;
   private T resultValue = null;
@@ -66,13 +65,7 @@
 
   private void error(String name) {
     throw parsingContext.error(
-        "Multiple properties defining "
-            + kind()
-            + ": '"
-            + resultPropertyName
-            + "' and '"
-            + name
-            + "'");
+        "Multiple properties: '" + resultPropertyName + "' and '" + name + "'");
   }
 
   public final boolean isDeclared() {
@@ -90,14 +83,6 @@
     return isDeclared() ? resultValue : defaultValue;
   }
 
-  public String kind() {
-    return kind != null ? kind : "";
-  }
-
-  public void setKind(String kind) {
-    this.kind = kind;
-  }
-
   /** Add property parsing for the given property-name. */
   public void setProperty(String name, P property) {
     P old = mapping.put(name, property);
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 e3922c6..c1682c9 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
@@ -51,8 +51,8 @@
         {
           AnnotationParsingContext parsingContext =
               new AnnotationParsingContext(getParsingContext(), descriptor);
-          TypeParser typeParser = new TypeParser(parsingContext);
-          typeParser.setKind(kind());
+          TypeParser typeParser =
+              new TypeParser(parsingContext.group(TypePattern.typePatternGroup));
           typeParser.setProperty(TypePattern.name, TypeProperty.TYPE_NAME);
           typeParser.setProperty(TypePattern.constant, TypeProperty.TYPE_CONSTANT);
           typeParser.setProperty(TypePattern.classNamePattern, TypeProperty.CLASS_NAME_PATTERN);
@@ -65,7 +65,6 @@
       case CLASS_NAME_PATTERN:
         {
           ClassNameParser parser = new ClassNameParser(getParsingContext());
-          parser.setKind(kind());
           return parser.tryPropertyAnnotation(
               ClassNameProperty.PATTERN,
               name,
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 13a228b..65dc77b 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
@@ -65,11 +65,15 @@
 
   /** Item properties common to binding items, conditions and targets. */
   public static final class Item {
+    public static final String classGroup = "class";
     public static final String classFromBinding = "classFromBinding";
+    public static final String memberGroup = "member";
     public static final String memberFromBinding = "memberFromBinding";
+    public static final String classNameGroup = "class-name";
     public static final String className = "className";
     public static final String classConstant = "classConstant";
     public static final String classNamePattern = "classNamePattern";
+    public static final String instanceOfGroup = "instance-of";
     public static final String instanceOfClassName = "instanceOfClassName";
     public static final String instanceOfClassNameExclusive = "instanceOfClassNameExclusive";
     public static final String instanceOfClassConstant = "instanceOfClassConstant";
@@ -80,13 +84,16 @@
     public static final String memberAccess = "memberAccess";
     public static final String methodAccess = "methodAccess";
     public static final String methodName = "methodName";
+    public static final String returnTypeGroup = "return-type";
     public static final String methodReturnType = "methodReturnType";
     public static final String methodReturnTypeConstant = "methodReturnTypeConstant";
     public static final String methodReturnTypePattern = "methodReturnTypePattern";
+    public static final String parametersGroup = "parameters";
     public static final String methodParameters = "methodParameters";
     public static final String methodParameterTypePatterns = "methodParameterTypePatterns";
     public static final String fieldAccess = "fieldAccess";
     public static final String fieldName = "fieldName";
+    public static final String fieldTypeGroup = "field-type";
     public static final String fieldType = "fieldType";
     public static final String fieldTypeConstant = "fieldTypeConstant";
     public static final String fieldTypePattern = "fieldTypePattern";
@@ -107,6 +114,7 @@
     public static final String DESCRIPTOR =
         "Lcom/android/tools/r8/keepanno/annotations/KeepTarget;";
     public static final String kind = "kind";
+    public static final String constraintsGroup = "constraints";
     public static final String constraints = "constraints";
     public static final String allow = "allow";
     public static final String disallow = "disallow";
@@ -185,6 +193,7 @@
   public static final class TypePattern {
     public static final String DESCRIPTOR =
         "Lcom/android/tools/r8/keepanno/annotations/TypePattern;";
+    public static final String typePatternGroup = "type-pattern";
     public static final String name = "name";
     public static final String constant = "constant";
     public static final String classNamePattern = "classNamePattern";
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepAnnotationParserException.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepAnnotationParserException.java
index 910b95a..7a08a6a 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepAnnotationParserException.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepAnnotationParserException.java
@@ -27,7 +27,11 @@
     StringBuilder builder = new StringBuilder();
     ParsingContext current = context;
     while (current != null) {
-      builder.append("\n  in ").append(current.getContextFrameAsString());
+      builder
+          .append("\n  at ")
+          .append(current.getContextType())
+          .append(": ")
+          .append(current.getContextFrameAsString());
       current = current.getParentContext();
     }
     return builder.toString();
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java
index 9998b6a..d732c65 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java
@@ -27,8 +27,14 @@
     return null;
   }
 
+  public abstract String getContextType();
+
   public abstract String getContextFrameAsString();
 
+  public GroupParsingContext group(String propertyGroupDescription) {
+    return new GroupParsingContext(this, propertyGroupDescription);
+  }
+
   public static class ClassParsingContext extends ParsingContext {
     private final String className;
 
@@ -42,6 +48,11 @@
     }
 
     @Override
+    public String getContextType() {
+      return "class";
+    }
+
+    @Override
     public String getContextFrameAsString() {
       return className;
     }
@@ -78,11 +89,15 @@
     }
 
     @Override
+    public String getContextType() {
+      return "method";
+    }
+
+    @Override
     public String getContextFrameAsString() {
       Type methodType = Type.getMethodType(methodDescriptor);
       StringBuilder builder = new StringBuilder();
       builder
-          .append("method ")
           .append(getJavaTypeFromDescriptor(methodType.getReturnType().getDescriptor()))
           .append(' ')
           .append(methodName)
@@ -113,8 +128,13 @@
     }
 
     @Override
+    public String getContextType() {
+      return "field";
+    }
+
+    @Override
     public String getContextFrameAsString() {
-      return "field " + getJavaTypeFromDescriptor(fieldDescriptor) + " " + fieldName;
+      return getJavaTypeFromDescriptor(fieldDescriptor) + " " + fieldName;
     }
   }
 
@@ -147,8 +167,53 @@
     }
 
     @Override
+    public String getContextType() {
+      return "annotation";
+    }
+
+    @Override
     public String getContextFrameAsString() {
       return "@" + getSimpleAnnotationName();
     }
   }
+
+  public static class GroupParsingContext extends ParsingContext {
+    private final ParsingContext parentContext;
+    private final String propertyGroupDescription;
+
+    public GroupParsingContext(ParsingContext parentContext, String propertyGroupDescription) {
+      // We don't want to maintain nested property groups as they are "synthetic" and only the
+      // inner-most group useful for uses in diagnosing an error.
+      if (parentContext instanceof GroupParsingContext) {
+        parentContext = parentContext.getParentContext();
+      }
+      assert !(parentContext instanceof GroupParsingContext);
+      this.parentContext = parentContext;
+      this.propertyGroupDescription = propertyGroupDescription;
+    }
+
+    public String getPropertyGroupDescription() {
+      return propertyGroupDescription;
+    }
+
+    @Override
+    public String getHolderName() {
+      return parentContext.getHolderName();
+    }
+
+    @Override
+    public ParsingContext getParentContext() {
+      return parentContext;
+    }
+
+    @Override
+    public String getContextType() {
+      return "property-group";
+    }
+
+    @Override
+    public String getContextFrameAsString() {
+      return getPropertyGroupDescription();
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepInvalidForApiTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepInvalidForApiTest.java
index 3d70d4c..af2087b 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepInvalidForApiTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepInvalidForApiTest.java
@@ -66,8 +66,9 @@
         () -> extractRuleForClass(RefineMemberAccess.class),
         allOf(
             containsString("Unexpected array"),
-            containsString("@KeepForApi"),
-            containsString("memberAccess")));
+            containsString("memberAccess"),
+            containsString("at annotation: @KeepForApi"),
+            containsString("at method: void main")));
   }
 
   static class RefineMemberAccess {
@@ -84,8 +85,9 @@
         () -> extractRuleForClass(RefineMethodName.class),
         allOf(
             containsString("Unexpected value"),
-            containsString("@KeepForApi"),
-            containsString("methodName")));
+            containsString("methodName"),
+            containsString("at annotation: @KeepForApi"),
+            containsString("at method: void main")));
   }
 
   static class RefineMethodName {
@@ -102,8 +104,9 @@
         () -> extractRuleForClass(RefineFieldName.class),
         allOf(
             containsString("Unexpected value"),
-            containsString("@KeepForApi"),
-            containsString("fieldName")));
+            containsString("fieldName"),
+            containsString("at annotation: @KeepForApi"),
+            containsString("at method: void main")));
   }
 
   static class RefineFieldName {
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java
index 9165d59..fbba2a1 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java
@@ -68,7 +68,8 @@
         allOf(
             containsString("Multiple properties"),
             containsString("className"),
-            containsString("classConstant")));
+            containsString("classConstant"),
+            containsString("at property-group: class-name")));
   }
 
   static class MultipleClassDeclarations {
@@ -83,7 +84,10 @@
   public void testInvalidClassDeclWithBinding() {
     assertThrowsWith(
         () -> extractRuleForClass(BindingAndClassDeclarations.class),
-        allOf(containsString("class binding"), containsString("class patterns")));
+        allOf(
+            containsString("class binding"),
+            containsString("class patterns"),
+            containsString("at property-group: class")));
   }
 
   static class BindingAndClassDeclarations {
@@ -99,9 +103,12 @@
     assertThrowsWith(
         () -> extractRuleForClass(MultipleExtendsDeclarations.class),
         allOf(
-            containsString("Multiple declarations"),
+            containsString("Multiple properties"),
             containsString("extendsClassName"),
-            containsString("extendsClassConstant")));
+            containsString("extendsClassConstant"),
+            containsString("at property-group: instance-of"),
+            containsString("at annotation: @UsesReflection"),
+            containsString("at method: void main")));
   }
 
   static class MultipleExtendsDeclarations {
@@ -119,7 +126,10 @@
   public void testInvalidMemberDecl() {
     assertThrowsWith(
         () -> extractRuleForClass(MultipleMemberDeclarations.class),
-        allOf(containsString("field"), containsString("method")));
+        allOf(
+            containsString("field"),
+            containsString("method"),
+            containsString("at property-group: member")));
   }
 
   static class MultipleMemberDeclarations {
@@ -134,7 +144,11 @@
   public void testInvalidOptionsDecl() {
     assertThrowsWith(
         () -> extractRuleForClass(MultipleOptionDeclarations.class),
-        allOf(containsString("options"), containsString("allow"), containsString("disallow")));
+        allOf(
+            containsString("Multiple properties"),
+            containsString("allow"),
+            containsString("disallow"),
+            containsString("at property-group: constraints")));
   }
 
   static class MultipleOptionDeclarations {
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
index 312e8bb..3aebdc0 100644
--- a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
@@ -151,10 +151,17 @@
     final List<String> footers = new ArrayList<>();
     final LinkedHashMap<String, Group> mutuallyExclusiveGroups = new LinkedHashMap<>();
 
+    boolean mutuallyExclusiveWithOtherGroups = false;
+
     private Group(String name) {
       this.name = name;
     }
 
+    Group allowMutuallyExclusiveWithOtherGroups() {
+      mutuallyExclusiveWithOtherGroups = true;
+      return this;
+    }
+
     Group addMember(GroupMember member) {
       members.add(member);
       return this;
@@ -199,6 +206,18 @@
     }
 
     void generateConstants(Generator generator) {
+      if (mutuallyExclusiveWithOtherGroups || members.size() > 1) {
+        StringBuilder camelCaseName = new StringBuilder();
+        for (int i = 0; i < name.length(); i++) {
+          char c = name.charAt(i);
+          if (c == '-') {
+            c = Character.toUpperCase(name.charAt(++i));
+          }
+          camelCaseName.append(c);
+        }
+        generator.println(
+            "public static final String " + camelCaseName + "Group = " + quote(name) + ";");
+      }
       for (GroupMember member : members) {
         member.generateConstants(generator);
       }
@@ -206,6 +225,7 @@
 
     public void addMutuallyExclusiveGroups(Group... groups) {
       for (Group group : groups) {
+        assert mutuallyExclusiveWithOtherGroups || group.mutuallyExclusiveWithOtherGroups;
         mutuallyExclusiveGroups.computeIfAbsent(
             group.name,
             k -> {
@@ -502,6 +522,7 @@
 
     private Group createClassBindingGroup() {
       return new Group(CLASS_GROUP)
+          .allowMutuallyExclusiveWithOtherGroups()
           .addMember(classFromBinding())
           .addDocFooterParagraph("If none are specified the default is to match any class.");
     }
@@ -627,6 +648,7 @@
 
     private Group createMemberBindingGroup() {
       return new Group("member")
+          .allowMutuallyExclusiveWithOtherGroups()
           .addMember(
               new GroupMember("memberFromBinding")
                   .setDocTitle("Define the member pattern in full by a reference to a binding.")
@@ -810,36 +832,49 @@
       }
 
       // Member binding properties.
+      Group memberBindingGroup = null;
       if (includeMemberBinding) {
-        createMemberBindingGroup().generate(this);
+        memberBindingGroup = createMemberBindingGroup();
+        memberBindingGroup.generate(this);
         println();
       }
 
       // The remaining member properties.
-      generateMemberPropertiesNoBinding();
+      internalGenerateMemberPropertiesNoBinding(memberBindingGroup);
+    }
+
+    private Group maybeLink(Group group, Group maybeExclusiveGroup) {
+      if (maybeExclusiveGroup != null) {
+        maybeExclusiveGroup.addMutuallyExclusiveGroups(group);
+      }
+      return group;
     }
 
     private void generateMemberPropertiesNoBinding() {
+      internalGenerateMemberPropertiesNoBinding(null);
+    }
+
+    private void internalGenerateMemberPropertiesNoBinding(Group memberBindingGroup) {
       // General member properties.
-      createMemberAccessGroup().generate(this);
+      maybeLink(createMemberAccessGroup(), memberBindingGroup).generate(this);
       println();
 
       // Method properties.
-      createMethodAccessGroup().generate(this);
+      maybeLink(createMethodAccessGroup(), memberBindingGroup).generate(this);
       println();
-      createMethodNameGroup().generate(this);
+      maybeLink(createMethodNameGroup(), memberBindingGroup).generate(this);
       println();
-      createMethodReturnTypeGroup().generate(this);
+      maybeLink(createMethodReturnTypeGroup(), memberBindingGroup).generate(this);
       println();
-      createMethodParametersGroup().generate(this);
+      maybeLink(createMethodParametersGroup(), memberBindingGroup).generate(this);
       println();
 
       // Field properties.
-      createFieldAccessGroup().generate(this);
+      maybeLink(createFieldAccessGroup(), memberBindingGroup).generate(this);
       println();
-      createFieldNameGroup().generate(this);
+      maybeLink(createFieldNameGroup(), memberBindingGroup).generate(this);
       println();
-      createFieldTypeGroup().generate(this);
+      maybeLink(createFieldTypeGroup(), memberBindingGroup).generate(this);
     }
 
     private void generateTypePattern() {