Merge commit '8f575aa213d7d4325677f365848e4f1e57c34e05' into dev-release
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 3ba3b8b..37c4087 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
@@ -10,7 +10,6 @@
 import com.android.tools.r8.keepanno.ast.KeepEdgeException;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern.Builder;
-import com.android.tools.r8.keepanno.ast.KeepMembersPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
 import com.android.tools.r8.keepanno.ast.KeepPreconditions;
@@ -170,9 +169,7 @@
       }
       if (methodName != null) {
         itemBuilder.setMembersPattern(
-            KeepMembersPattern.builder()
-                .addMethodPattern(KeepMethodPattern.builder().setNamePattern(methodName).build())
-                .build());
+            KeepMethodPattern.builder().setNamePattern(methodName).build());
       }
       KeepTarget target = KeepTarget.builder().setItem(itemBuilder.build()).build();
       parent.accept(target);
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
index 1641d8d..0e96364 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
@@ -3,12 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.asm;
 
-import com.android.tools.r8.keepanno.annotations.KeepConstants;
 import com.android.tools.r8.keepanno.annotations.KeepConstants.Edge;
 import com.android.tools.r8.keepanno.annotations.KeepConstants.Target;
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
-import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepItemPattern;
+import com.android.tools.r8.keepanno.ast.KeepMembersPattern;
+import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern.KeepMethodNameExactPattern;
+import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
 import com.android.tools.r8.keepanno.ast.KeepPreconditions;
 import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
 import com.android.tools.r8.keepanno.utils.Unimplemented;
@@ -45,68 +47,58 @@
   private void writeConsequences(AnnotationVisitor visitor, KeepConsequences consequences) {
     assert !consequences.isEmpty();
     String ignoredArrayValueName = null;
-    AnnotationVisitor arrayVisitor = visitor.visitArray(KeepConstants.Edge.consequences);
+    AnnotationVisitor arrayVisitor = visitor.visitArray(Edge.consequences);
     consequences.forEachTarget(
         target -> {
           AnnotationVisitor targetVisitor =
-              arrayVisitor.visitAnnotation(ignoredArrayValueName, KeepConstants.Target.DESCRIPTOR);
+              arrayVisitor.visitAnnotation(ignoredArrayValueName, Target.DESCRIPTOR);
           // No options imply keep all.
           if (!target.getOptions().isKeepAll()) {
             throw new Unimplemented();
           }
-          target
-              .getItem()
-              .match(
-                  () -> {
-                    throw new Unimplemented();
-                  },
-                  clazz -> {
-                    KeepQualifiedClassNamePattern namePattern = clazz.getClassNamePattern();
-                    if (namePattern.isExact()) {
-                      Type typeConstant = Type.getType(namePattern.getExactDescriptor());
-                      targetVisitor.visit(KeepConstants.Target.classConstant, typeConstant);
-                    } else {
-                      throw new Unimplemented();
-                    }
-                    if (!clazz.getExtendsPattern().isAny()) {
-                      throw new Unimplemented();
-                    }
-                    if (clazz.getMembersPattern().isNone()) {
-                      // Default is "no methods".
-                    } else if (clazz.getMembersPattern().isAll()) {
-                      throw new Unimplemented();
-                    } else {
-                      clazz
-                          .getMembersPattern()
-                          .forEach(
-                              field -> {
-                                throw new Unimplemented();
-                              },
-                              method -> {
-                                KeepMethodNamePattern methodNamePattern = method.getNamePattern();
-                                methodNamePattern.match(
-                                    () -> {
-                                      throw new Unimplemented();
-                                    },
-                                    exactMethodName -> {
-                                      targetVisitor.visit(Target.methodName, exactMethodName);
-                                      return null;
-                                    });
-                                if (!method.getAccessPattern().isAny()) {
-                                  throw new Unimplemented();
-                                }
-                                if (!method.getReturnTypePattern().isAny()) {
-                                  throw new Unimplemented();
-                                }
-                                if (!method.getParametersPattern().isAny()) {
-                                  throw new Unimplemented();
-                                }
-                              });
-                    }
-                    return null;
-                  });
+          KeepItemPattern item = target.getItem();
+          if (item.isAny()) {
+            throw new Unimplemented();
+          }
+          KeepQualifiedClassNamePattern namePattern = item.getClassNamePattern();
+          if (namePattern.isExact()) {
+            Type typeConstant = Type.getType(namePattern.getExactDescriptor());
+            targetVisitor.visit(Target.classConstant, typeConstant);
+          } else {
+            throw new Unimplemented();
+          }
+          if (!item.getExtendsPattern().isAny()) {
+            throw new Unimplemented();
+          }
+          writeMembers(item.getMembersPattern(), targetVisitor);
           targetVisitor.visitEnd();
         });
     arrayVisitor.visitEnd();
   }
+
+  private void writeMembers(KeepMembersPattern membersPattern, AnnotationVisitor targetVisitor) {
+    if (membersPattern.isNone()) {
+      // Default is "no methods".
+      return;
+    }
+    if (membersPattern.isAll()) {
+      throw new Unimplemented();
+    }
+    KeepMethodPattern method = membersPattern.asMethod();
+    KeepMethodNameExactPattern exactMethodName = method.getNamePattern().asExact();
+    if (exactMethodName != null) {
+      targetVisitor.visit(Target.methodName, exactMethodName.getName());
+    } else {
+      throw new Unimplemented();
+    }
+    if (!method.getAccessPattern().isAny()) {
+      throw new Unimplemented();
+    }
+    if (!method.getReturnTypePattern().isAny()) {
+      throw new Unimplemented();
+    }
+    if (!method.getParametersPattern().isAny()) {
+      throw new Unimplemented();
+    }
+  }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
index 4da44aa..5271837 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
@@ -25,12 +25,12 @@
  *   CONDITION ::= ITEM_PATTERN
  *
  *   CONSEQUENCES ::= TARGET+
- *   TARGET ::= any | OPTIONS ITEM_PATTERN
+ *   TARGET ::= any | OPTIONS ITEM_PATTERN // TODO(b/248408342): What options are on target 'any'?
  *   OPTIONS ::= keep-all | OPTION+
  *   OPTION ::= shrinking | optimizing | obfuscating | access-modifying
  *
- *   ITEM_PATTERN ::= any | CLASS_PATTERN
- *   CLASS_PATTERN ::= QUALIFIED_CLASS_NAME_PATTERN extends EXTENDS_PATTERN { MEMBERS_PATTERN }
+ *   ITEM_PATTERN ::=
+ *     class QUALIFIED_CLASS_NAME_PATTERN extends EXTENDS_PATTERN { MEMBERS_PATTERN }
  *
  *   TYPE_PATTERN ::= any
  *   PACKAGE_PATTERN ::= any | exact package-name
@@ -38,7 +38,7 @@
  *   UNQUALIFIED_CLASS_NAME_PATTERN ::= any | exact simple-class-name
  *   EXTENDS_PATTERN ::= any | QUALIFIED_CLASS_NAME_PATTERN
  *
- *   MEMBERS_PATTERN ::= none | all | METHOD_PATTERN*
+ *   MEMBERS_PATTERN ::= none | all | METHOD_PATTERN
  *
  *   METHOD_PATTERN
  *     ::= METHOD_ACCESS_PATTERN
@@ -49,7 +49,7 @@
  *   METHOD_ACCESS_PATTERN ::= any
  *   METHOD_NAME_PATTERN ::= any | exact method-name
  *   METHOD_RETURN_TYPE_PATTERN ::= void | TYPE_PATTERN
- *   METHOD_PARAMETERS_PATTERN ::= any | none | TYPE_PATTERN+
+ *   METHOD_PARAMETERS_PATTERN ::= any | none | (TYPE_PATTERN+)
  * </pre>
  */
 public final class KeepEdge {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepExtendsPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepExtendsPattern.java
index b6b0fd7..02c8768 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepExtendsPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepExtendsPattern.java
@@ -7,7 +7,7 @@
 public abstract class KeepExtendsPattern {
 
   public static KeepExtendsPattern any() {
-    return KeepExtendsAnyPattern.getInstance();
+    return Any.getInstance();
   }
 
   public static class Builder {
@@ -17,24 +17,21 @@
     private Builder() {}
 
     public Builder any() {
-      pattern = KeepExtendsAnyPattern.getInstance();
+      pattern = Any.getInstance();
       return this;
     }
 
     public Builder classPattern(KeepQualifiedClassNamePattern pattern) {
-      this.pattern = new KeepExtendsClassPattern(pattern);
+      this.pattern = new Some(pattern);
       return this;
     }
   }
 
-  private static class KeepExtendsAnyPattern extends KeepExtendsPattern {
+  private static class Any extends KeepExtendsPattern {
 
-    private static KeepExtendsAnyPattern INSTANCE = null;
+    private static final Any INSTANCE = new Any();
 
-    public static KeepExtendsAnyPattern getInstance() {
-      if (INSTANCE == null) {
-        INSTANCE = new KeepExtendsAnyPattern();
-      }
+    public static Any getInstance() {
       return INSTANCE;
     }
 
@@ -59,11 +56,11 @@
     }
   }
 
-  private static class KeepExtendsClassPattern extends KeepExtendsPattern {
+  private static class Some extends KeepExtendsPattern {
 
     private final KeepQualifiedClassNamePattern pattern;
 
-    public KeepExtendsClassPattern(KeepQualifiedClassNamePattern pattern) {
+    public Some(KeepQualifiedClassNamePattern pattern) {
       assert pattern != null;
       this.pattern = pattern;
     }
@@ -81,7 +78,7 @@
       if (o == null || getClass() != o.getClass()) {
         return false;
       }
-      KeepExtendsClassPattern that = (KeepExtendsClassPattern) o;
+      Some that = (Some) o;
       return pattern.equals(that.pattern);
     }
 
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java
deleted file mode 100644
index 8de46e1..0000000
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 2022, 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;
-
-public class KeepFieldPattern extends KeepMemberPattern {
-
-  private KeepFieldPattern() {}
-
-  public boolean isAnyField() {
-    return false;
-  }
-}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
index e5b0987..68c5c17 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
@@ -4,8 +4,6 @@
 package com.android.tools.r8.keepanno.ast;
 
 import java.util.Objects;
-import java.util.function.Function;
-import java.util.function.Supplier;
 
 /**
  * A pattern for matching items in the program.
@@ -17,16 +15,18 @@
  * classes or it is a pattern on members. The distinction is defined by having a "none" member
  * pattern.
  */
-public abstract class KeepItemPattern {
+public class KeepItemPattern {
+
+  public static KeepItemPattern any() {
+    KeepItemPattern any = builder().any().build();
+    assert any.isAny();
+    return any;
+  }
 
   public static Builder builder() {
     return new Builder();
   }
 
-  public static KeepItemPattern any() {
-    return KeepItemAnyPattern.getInstance();
-  }
-
   public static class Builder {
 
     private KeepQualifiedClassNamePattern classNamePattern;
@@ -61,128 +61,71 @@
       if (classNamePattern == null) {
         throw new KeepEdgeException("Class pattern must define a class name pattern.");
       }
-      if (classNamePattern.isAny() && extendsPattern.isAny() && membersPattern.isAll()) {
-        return KeepItemPattern.any();
-      }
-      return new KeepClassPattern(classNamePattern, extendsPattern, membersPattern);
+      return new KeepItemPattern(classNamePattern, extendsPattern, membersPattern);
     }
   }
 
-  private static class KeepItemAnyPattern extends KeepItemPattern {
+  private final KeepQualifiedClassNamePattern qualifiedClassPattern;
+  private final KeepExtendsPattern extendsPattern;
+  private final KeepMembersPattern membersPattern;
+  // TODO: class annotations
 
-    private static KeepItemAnyPattern INSTANCE = null;
+  private KeepItemPattern(
+      KeepQualifiedClassNamePattern qualifiedClassPattern,
+      KeepExtendsPattern extendsPattern,
+      KeepMembersPattern membersPattern) {
+    assert qualifiedClassPattern != null;
+    assert extendsPattern != null;
+    assert membersPattern != null;
+    this.qualifiedClassPattern = qualifiedClassPattern;
+    this.extendsPattern = extendsPattern;
+    this.membersPattern = membersPattern;
+  }
 
-    public static KeepItemAnyPattern getInstance() {
-      if (INSTANCE == null) {
-        INSTANCE = new KeepItemAnyPattern();
-      }
-      return INSTANCE;
-    }
+  public boolean isAny() {
+    return qualifiedClassPattern.isAny() && extendsPattern.isAny() && membersPattern.isAll();
+  }
 
-    @Override
-    public boolean isAny() {
+  public KeepQualifiedClassNamePattern getClassNamePattern() {
+    return qualifiedClassPattern;
+  }
+
+  public KeepExtendsPattern getExtendsPattern() {
+    return extendsPattern;
+  }
+
+  public KeepMembersPattern getMembersPattern() {
+    return membersPattern;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
       return true;
     }
-
-    @Override
-    public <T> T match(Supplier<T> onAny, Function<KeepClassPattern, T> onItem) {
-      return onAny.get();
+    if (obj == null || getClass() != obj.getClass()) {
+      return false;
     }
-
-    @Override
-    public boolean equals(Object obj) {
-      return this == obj;
-    }
-
-    @Override
-    public int hashCode() {
-      return System.identityHashCode(this);
-    }
-
-    @Override
-    public String toString() {
-      return "*";
-    }
+    KeepItemPattern that = (KeepItemPattern) obj;
+    return qualifiedClassPattern.equals(that.qualifiedClassPattern)
+        && extendsPattern.equals(that.extendsPattern)
+        && membersPattern.equals(that.membersPattern);
   }
 
-  public static class KeepClassPattern extends KeepItemPattern {
-
-    private final KeepQualifiedClassNamePattern qualifiedClassPattern;
-    private final KeepExtendsPattern extendsPattern;
-    private final KeepMembersPattern membersPattern;
-    // TODO: class annotations
-
-    private KeepClassPattern(
-        KeepQualifiedClassNamePattern qualifiedClassPattern,
-        KeepExtendsPattern extendsPattern,
-        KeepMembersPattern membersPattern) {
-      assert qualifiedClassPattern != null;
-      assert extendsPattern != null;
-      assert membersPattern != null;
-      this.qualifiedClassPattern = qualifiedClassPattern;
-      this.extendsPattern = extendsPattern;
-      this.membersPattern = membersPattern;
-    }
-
-    @Override
-    public boolean isAny() {
-      return qualifiedClassPattern.isAny() && extendsPattern.isAny() && membersPattern.isAll();
-    }
-
-    @Override
-    public <T> T match(Supplier<T> onAny, Function<KeepClassPattern, T> onItem) {
-      if (isAny()) {
-        return onAny.get();
-      } else {
-        return onItem.apply(this);
-      }
-    }
-
-    public KeepQualifiedClassNamePattern getClassNamePattern() {
-      return qualifiedClassPattern;
-    }
-
-    public KeepExtendsPattern getExtendsPattern() {
-      return extendsPattern;
-    }
-
-    public KeepMembersPattern getMembersPattern() {
-      return membersPattern;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      if (this == obj) {
-        return true;
-      }
-      if (obj == null || getClass() != obj.getClass()) {
-        return false;
-      }
-      KeepClassPattern that = (KeepClassPattern) obj;
-      return qualifiedClassPattern.equals(that.qualifiedClassPattern)
-          && extendsPattern.equals(that.extendsPattern)
-          && membersPattern.equals(that.membersPattern);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hash(qualifiedClassPattern, extendsPattern, membersPattern);
-    }
-
-    @Override
-    public String toString() {
-      return "KeepClassPattern{"
-          + "qualifiedClassPattern="
-          + qualifiedClassPattern
-          + ", extendsPattern="
-          + extendsPattern
-          + ", membersPattern="
-          + membersPattern
-          + '}';
-    }
+  @Override
+  public int hashCode() {
+    return Objects.hash(qualifiedClassPattern, extendsPattern, membersPattern);
   }
 
-  public abstract boolean isAny();
-
-  public abstract <T> T match(Supplier<T> onAny, Function<KeepClassPattern, T> onItem);
+  @Override
+  public String toString() {
+    return "KeepClassPattern{"
+        + "qualifiedClassPattern="
+        + qualifiedClassPattern
+        + ", extendsPattern="
+        + extendsPattern
+        + ", membersPattern="
+        + membersPattern
+        + '}';
+  }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
deleted file mode 100644
index bd716e9..0000000
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) 2022, 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;
-
-public abstract class KeepMemberPattern {
-
-  public static KeepMemberPattern anyMember() {
-    return KeepMemberAnyPattern.getInstance();
-  }
-
-  private static class KeepMemberAnyPattern extends KeepMemberPattern {
-    private static KeepMemberAnyPattern INSTANCE = null;
-
-    public static KeepMemberAnyPattern getInstance() {
-      if (INSTANCE == null) {
-        INSTANCE = new KeepMemberAnyPattern();
-      }
-      return INSTANCE;
-    }
-
-    @Override
-    public boolean isAnyMember() {
-      return true;
-    }
-  }
-
-  public boolean isAnyMember() {
-    return false;
-  }
-
-  abstract static class Builder<T extends Builder<T>> {
-
-    public abstract T self();
-
-    Builder() {}
-  }
-}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMembersPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMembersPattern.java
index be5af32..64e1b25 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMembersPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMembersPattern.java
@@ -3,77 +3,22 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.ast;
 
-import com.android.tools.r8.keepanno.utils.Unimplemented;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
 
 public abstract class KeepMembersPattern {
 
-  public static Builder builder() {
-    return new Builder();
-  }
-
   public static KeepMembersPattern none() {
-    return KeepMembersNonePattern.getInstance();
+    return None.getInstance();
   }
 
   public static KeepMembersPattern all() {
-    return KeepMembersAllPattern.getInstance();
+    return All.getInstance();
   }
 
-  public static class Builder {
+  private static class All extends KeepMembersPattern {
 
-    private boolean anyMethod = false;
-    private boolean anyField = false;
-    private List<KeepMethodPattern> methods = new ArrayList<>();
-    private List<KeepFieldPattern> fields = new ArrayList<>();
+    private static final All INSTANCE = new All();
 
-    public Builder addMethodPattern(KeepMethodPattern methodPattern) {
-      if (anyMethod) {
-        return this;
-      }
-      if (methodPattern.isAnyMethod()) {
-        methods.clear();
-        anyMethod = true;
-      }
-      methods.add(methodPattern);
-      return this;
-    }
-
-    public Builder addFieldPattern(KeepFieldPattern fieldPattern) {
-      if (anyField) {
-        return this;
-      }
-      if (fieldPattern.isAnyField()) {
-        fields.clear();
-        anyField = true;
-      }
-      fields.add(fieldPattern);
-      return this;
-    }
-
-    public KeepMembersPattern build() {
-      if (methods.isEmpty() && fields.isEmpty()) {
-        return KeepMembersPattern.none();
-      }
-      if (anyMethod && anyField) {
-        return KeepMembersPattern.all();
-      }
-      return new KeepMembersSomePattern(methods, fields);
-    }
-  }
-
-  private static class KeepMembersAllPattern extends KeepMembersPattern {
-
-    private static KeepMembersAllPattern INSTANCE = null;
-
-    public static KeepMembersAllPattern getInstance() {
-      if (INSTANCE == null) {
-        INSTANCE = new KeepMembersAllPattern();
-      }
+    public static All getInstance() {
       return INSTANCE;
     }
 
@@ -83,16 +28,6 @@
     }
 
     @Override
-    public boolean isNone() {
-      return true;
-    }
-
-    @Override
-    public void forEach(Consumer<KeepFieldPattern> onField, Consumer<KeepMethodPattern> onMethod) {
-      throw new Unimplemented("Should this include all and none?");
-    }
-
-    @Override
     public boolean equals(Object obj) {
       return this == obj;
     }
@@ -108,33 +43,20 @@
     }
   }
 
-  private static class KeepMembersNonePattern extends KeepMembersPattern {
+  private static class None extends KeepMembersPattern {
 
-    private static KeepMembersNonePattern INSTANCE = null;
+    private static final None INSTANCE = new None();
 
-    public static KeepMembersNonePattern getInstance() {
-      if (INSTANCE == null) {
-        INSTANCE = new KeepMembersNonePattern();
-      }
+    public static None getInstance() {
       return INSTANCE;
     }
 
     @Override
-    public boolean isAll() {
-      return false;
-    }
-
-    @Override
     public boolean isNone() {
       return true;
     }
 
     @Override
-    public void forEach(Consumer<KeepFieldPattern> onField, Consumer<KeepMethodPattern> onMethod) {
-      throw new Unimplemented("Should this include all and none?");
-    }
-
-    @Override
     public boolean equals(Object obj) {
       return this == obj;
     }
@@ -150,69 +72,21 @@
     }
   }
 
-  private static class KeepMembersSomePattern extends KeepMembersPattern {
+  KeepMembersPattern() {}
 
-    private final List<KeepMethodPattern> methods;
-    private final List<KeepFieldPattern> fields;
-
-    private KeepMembersSomePattern(List<KeepMethodPattern> methods, List<KeepFieldPattern> fields) {
-      assert !methods.isEmpty() || !fields.isEmpty();
-      this.methods = methods;
-      this.fields = fields;
-    }
-
-    @Override
-    public boolean isAll() {
-      // Since there is at least one none-all field or method this is not a match all.
-      return false;
-    }
-
-    @Override
-    public boolean isNone() {
-      // Since there is at least one field or method this is not a match none.
-      return false;
-    }
-
-    @Override
-    public void forEach(Consumer<KeepFieldPattern> onField, Consumer<KeepMethodPattern> onMethod) {
-      fields.forEach(onField);
-      methods.forEach(onMethod);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      if (this == obj) {
-        return true;
-      }
-      if (obj == null || getClass() != obj.getClass()) {
-        return false;
-      }
-      KeepMembersSomePattern that = (KeepMembersSomePattern) obj;
-      return methods.equals(that.methods) && fields.equals(that.fields);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hash(methods, fields);
-    }
-
-    @Override
-    public String toString() {
-      return "KeepMembersSomePattern{"
-          + "methods={"
-          + methods.stream().map(Object::toString).collect(Collectors.joining(", "))
-          + "}, fields={"
-          + fields.stream().map(Object::toString).collect(Collectors.joining(", "))
-          + "}}";
-    }
+  public boolean isAll() {
+    return false;
   }
 
-  private KeepMembersPattern() {}
+  public boolean isNone() {
+    return false;
+  }
 
-  public abstract boolean isAll();
+  public final boolean isMethod() {
+    return asMethod() != null;
+  }
 
-  public abstract boolean isNone();
-
-  public abstract void forEach(
-      Consumer<KeepFieldPattern> onField, Consumer<KeepMethodPattern> onMethod);
+  public KeepMethodPattern asMethod() {
+    return null;
+  }
 }
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 fd0fd3e..4417b2e 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
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.ast;
 
+
 // TODO: finish this.
 public abstract class KeepMethodAccessPattern {
 
@@ -14,12 +15,9 @@
 
   private static class Any extends KeepMethodAccessPattern {
 
-    private static Any INSTANCE = null;
+    private static final Any INSTANCE = new Any();
 
     private static Any getInstance() {
-      if (INSTANCE == null) {
-        INSTANCE = new Any();
-      }
       return INSTANCE;
     }
 
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodNamePattern.java
index 6dc373d..8203d56 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodNamePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodNamePattern.java
@@ -3,13 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.ast;
 
-import java.util.function.Function;
-import java.util.function.Supplier;
 
 public abstract class KeepMethodNamePattern {
 
   public static KeepMethodNamePattern any() {
-    return KeepMethodNameAnyPattern.getInstance();
+    return Any.getInstance();
   }
 
   public static KeepMethodNamePattern initializer() {
@@ -23,29 +21,27 @@
   private KeepMethodNamePattern() {}
 
   public boolean isAny() {
-    return match(() -> true, ignore -> false);
+    return false;
   }
 
-  public boolean isExact() {
-    return match(() -> false, ignore -> true);
+  public final boolean isExact() {
+    return asExact() != null;
   }
-  ;
 
-  public abstract <T> T match(Supplier<T> onAny, Function<String, T> onExact);
+  public KeepMethodNameExactPattern asExact() {
+    return null;
+  }
 
-  private static class KeepMethodNameAnyPattern extends KeepMethodNamePattern {
-    private static KeepMethodNameAnyPattern INSTANCE = null;
+  private static class Any extends KeepMethodNamePattern {
+    private static final Any INSTANCE = new Any();
 
-    public static KeepMethodNameAnyPattern getInstance() {
-      if (INSTANCE == null) {
-        INSTANCE = new KeepMethodNameAnyPattern();
-      }
+    public static Any getInstance() {
       return INSTANCE;
     }
 
     @Override
-    public <T> T match(Supplier<T> onAny, Function<String, T> onExact) {
-      return onAny.get();
+    public boolean isAny() {
+      return true;
     }
 
     @Override
@@ -64,7 +60,7 @@
     }
   }
 
-  private static class KeepMethodNameExactPattern extends KeepMethodNamePattern {
+  public static class KeepMethodNameExactPattern extends KeepMethodNamePattern {
     private final String name;
 
     public KeepMethodNameExactPattern(String name) {
@@ -73,8 +69,12 @@
     }
 
     @Override
-    public <T> T match(Supplier<T> onAny, Function<String, T> onExact) {
-      return onExact.apply(name);
+    public KeepMethodNameExactPattern asExact() {
+      return this;
+    }
+
+    public String getName() {
+      return name;
     }
 
     @Override
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java
index 83b4606..29442ff 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java
@@ -5,8 +5,6 @@
 
 import java.util.Collections;
 import java.util.List;
-import java.util.function.Function;
-import java.util.function.Supplier;
 
 public abstract class KeepMethodParametersPattern {
 
@@ -15,61 +13,68 @@
   }
 
   public static KeepMethodParametersPattern none() {
-    return None.getInstance();
+    return Some.EMPTY_INSTANCE;
   }
 
   private KeepMethodParametersPattern() {}
 
-  public abstract <T> T match(Supplier<T> onAny, Function<List<KeepTypePattern>, T> onList);
-
   public boolean isAny() {
-    return match(() -> true, params -> false);
+    return false;
   }
 
-  private static class None extends KeepMethodParametersPattern {
-    private static None INSTANCE = null;
+  public boolean isList() {
+    return asList() != null;
+  }
 
-    public static None getInstance() {
-      if (INSTANCE == null) {
-        INSTANCE = new None();
+  public List<KeepTypePattern> asList() {
+    return null;
+  }
+
+  private static class Some extends KeepMethodParametersPattern {
+
+    private static final Some EMPTY_INSTANCE = new Some(Collections.emptyList());
+
+    private final List<KeepTypePattern> parameterPatterns;
+
+    private Some(List<KeepTypePattern> parameterPatterns) {
+      assert parameterPatterns != null;
+      this.parameterPatterns = parameterPatterns;
+    }
+
+    @Override
+    public List<KeepTypePattern> asList() {
+      return parameterPatterns;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
       }
-      return INSTANCE;
-    }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
 
-    @Override
-    public <T> T match(Supplier<T> onAny, Function<List<KeepTypePattern>, T> onList) {
-      return onList.apply(Collections.emptyList());
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      return this == obj;
+      Some that = (Some) o;
+      return parameterPatterns.equals(that.parameterPatterns);
     }
 
     @Override
     public int hashCode() {
-      return System.identityHashCode(this);
-    }
-
-    @Override
-    public String toString() {
-      return "()";
+      return parameterPatterns.hashCode();
     }
   }
 
   private static class Any extends KeepMethodParametersPattern {
-    private static Any INSTANCE = null;
+    private static final Any INSTANCE = new Any();
 
     public static Any getInstance() {
-      if (INSTANCE == null) {
-        INSTANCE = new Any();
-      }
       return INSTANCE;
     }
 
     @Override
-    public <T> T match(Supplier<T> onAny, Function<List<KeepTypePattern>, T> onList) {
-      return onAny.get();
+    public boolean isAny() {
+      return true;
     }
 
     @Override
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
index b3ec418..bec8102 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
@@ -5,13 +5,13 @@
 
 import java.util.Objects;
 
-public final class KeepMethodPattern extends KeepMemberPattern {
+public final class KeepMethodPattern extends KeepMembersPattern {
 
   public static Builder builder() {
     return new Builder();
   }
 
-  public static class Builder extends KeepMemberPattern.Builder<Builder> {
+  public static class Builder {
 
     private KeepMethodAccessPattern accessPattern = KeepMethodAccessPattern.any();
     private KeepMethodNamePattern namePattern = null;
@@ -20,7 +20,6 @@
 
     private Builder() {}
 
-    @Override
     public Builder self() {
       return this;
     }
@@ -74,8 +73,16 @@
     this.parametersPattern = parametersPattern;
   }
 
+  @Override
+  public KeepMethodPattern asMethod() {
+    return this;
+  }
+
   public boolean isAnyMethod() {
-    return false;
+    return accessPattern.isAny()
+        && namePattern.isAny()
+        && returnTypePattern.isAny()
+        && parametersPattern.isAny();
   }
 
   public KeepMethodAccessPattern getAccessPattern() {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java
index f711bc0..ccfc183 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java
@@ -3,43 +3,43 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.ast;
 
-import java.util.function.Function;
-import java.util.function.Supplier;
 
 public abstract class KeepMethodReturnTypePattern {
 
-  private static SomeType ANY_TYPE_INSTANCE = null;
-
   public static KeepMethodReturnTypePattern any() {
-    if (ANY_TYPE_INSTANCE == null) {
-      ANY_TYPE_INSTANCE = new SomeType(KeepTypePattern.any());
-    }
-    return ANY_TYPE_INSTANCE;
+    return SomeType.ANY_TYPE_INSTANCE;
   }
 
   public static KeepMethodReturnTypePattern voidType() {
     return VoidType.getInstance();
   }
 
-  public abstract <T> T match(Supplier<T> onVoid, Function<KeepTypePattern, T> onType);
-
   public boolean isAny() {
-    return match(() -> false, KeepTypePattern::isAny);
+    return isType() && asType().isAny();
+  }
+
+  public boolean isVoid() {
+    return false;
+  }
+
+  public boolean isType() {
+    return asType() != null;
+  }
+
+  public KeepTypePattern asType() {
+    return null;
   }
 
   private static class VoidType extends KeepMethodReturnTypePattern {
-    private static VoidType INSTANCE = null;
+    private static final VoidType INSTANCE = new VoidType();
 
     public static VoidType getInstance() {
-      if (INSTANCE == null) {
-        INSTANCE = new VoidType();
-      }
       return INSTANCE;
     }
 
     @Override
-    public <T> T match(Supplier<T> onVoid, Function<KeepTypePattern, T> onType) {
-      return onVoid.get();
+    public boolean isVoid() {
+      return true;
     }
 
     @Override
@@ -60,6 +60,8 @@
 
   private static class SomeType extends KeepMethodReturnTypePattern {
 
+    private static final SomeType ANY_TYPE_INSTANCE = new SomeType(KeepTypePattern.any());
+
     private final KeepTypePattern typePattern;
 
     private SomeType(KeepTypePattern typePattern) {
@@ -68,8 +70,8 @@
     }
 
     @Override
-    public <T> T match(Supplier<T> onVoid, Function<KeepTypePattern, T> onType) {
-      return onType.apply(typePattern);
+    public KeepTypePattern asType() {
+      return typePattern;
     }
 
     @Override
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepOptions.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepOptions.java
index 21b7487..8e1b2e9 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepOptions.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepOptions.java
@@ -12,6 +12,7 @@
 import java.util.stream.Collectors;
 
 public final class KeepOptions {
+  private static final KeepOptions ALLOW_NONE_INSTANCE = new KeepOptions(ImmutableSet.of());
 
   public boolean isKeepAll() {
     return allowedOptions.isEmpty();
@@ -25,9 +26,6 @@
   }
 
   public static KeepOptions keepAll() {
-    if (ALLOW_NONE_INSTANCE == null) {
-      ALLOW_NONE_INSTANCE = new KeepOptions(ImmutableSet.of());
-    }
     return ALLOW_NONE_INSTANCE;
   }
 
@@ -97,8 +95,6 @@
     }
   }
 
-  private static KeepOptions ALLOW_NONE_INSTANCE = null;
-
   private final ImmutableSet<KeepOption> allowedOptions;
 
   private KeepOptions(ImmutableSet<KeepOption> options) {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPackagePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPackagePattern.java
index 7d23490..6042930 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPackagePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPackagePattern.java
@@ -10,11 +10,11 @@
   }
 
   public static KeepPackagePattern any() {
-    return KeepPackageAnyPattern.getInstance();
+    return Any.getInstance();
   }
 
   public static KeepPackagePattern top() {
-    return KeepPackageTopPattern.getInstance();
+    return Top.getInstance();
   }
 
   public static KeepPackagePattern exact(String fullPackage) {
@@ -26,20 +26,17 @@
     private KeepPackagePattern pattern;
 
     public Builder any() {
-      pattern = KeepPackageAnyPattern.getInstance();
+      pattern = Any.getInstance();
       return this;
     }
 
     public Builder top() {
-      pattern = KeepPackageTopPattern.getInstance();
+      pattern = Top.getInstance();
       return this;
     }
 
     public Builder exact(String fullPackage) {
-      pattern =
-          fullPackage.isEmpty()
-              ? KeepPackagePattern.top()
-              : new KeepPackageExactPattern(fullPackage);
+      pattern = fullPackage.isEmpty() ? KeepPackagePattern.top() : new Exact(fullPackage);
       return this;
     }
 
@@ -51,18 +48,15 @@
     }
   }
 
-  private static final class KeepPackageAnyPattern extends KeepPackagePattern {
+  private static final class Any extends KeepPackagePattern {
 
-    private static KeepPackageAnyPattern INSTANCE = null;
+    private static final Any INSTANCE = new Any();
 
-    public static KeepPackageAnyPattern getInstance() {
-      if (INSTANCE == null) {
-        INSTANCE = new KeepPackageAnyPattern();
-      }
+    public static Any getInstance() {
       return INSTANCE;
     }
 
-    private KeepPackageAnyPattern() {}
+    private Any() {}
 
     @Override
     public boolean isAny() {
@@ -95,18 +89,15 @@
     }
   }
 
-  private static final class KeepPackageTopPattern extends KeepPackageExactPattern {
+  private static final class Top extends Exact {
 
-    private static KeepPackageTopPattern INSTANCE = null;
+    private static final Top INSTANCE = new Top();
 
-    public static KeepPackageTopPattern getInstance() {
-      if (INSTANCE == null) {
-        INSTANCE = new KeepPackageTopPattern();
-      }
+    public static Top getInstance() {
       return INSTANCE;
     }
 
-    private KeepPackageTopPattern() {
+    private Top() {
       super("");
     }
 
@@ -126,11 +117,11 @@
     }
   }
 
-  public static class KeepPackageExactPattern extends KeepPackagePattern {
+  private static class Exact extends KeepPackagePattern {
 
     private final String fullPackage;
 
-    private KeepPackageExactPattern(String fullPackage) {
+    private Exact(String fullPackage) {
       assert fullPackage != null;
       this.fullPackage = fullPackage;
       // TODO: Verify valid package identifiers.
@@ -152,10 +143,6 @@
     }
 
     @Override
-    public KeepPackageExactPattern asExact() {
-      return this;
-    }
-
     public String getExactPackageAsString() {
       return fullPackage;
     }
@@ -168,7 +155,7 @@
       if (o == null || getClass() != o.getClass()) {
         return false;
       }
-      KeepPackageExactPattern that = (KeepPackageExactPattern) o;
+      Exact that = (Exact) o;
       return fullPackage.equals(that.fullPackage);
     }
 
@@ -189,7 +176,7 @@
 
   public abstract boolean isExact();
 
-  public KeepPackageExactPattern asExact() {
-    return null;
+  public String getExactPackageAsString() {
+    throw new IllegalStateException();
   }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPreconditions.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPreconditions.java
index f672013..dfba9f6 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPreconditions.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPreconditions.java
@@ -34,19 +34,16 @@
   }
 
   public static KeepPreconditions always() {
-    return KeepPreconditionsAlways.getInstance();
+    return Always.getInstance();
   }
 
   public abstract boolean isAlways();
 
-  private static class KeepPreconditionsAlways extends KeepPreconditions {
+  private static class Always extends KeepPreconditions {
 
-    private static KeepPreconditionsAlways INSTANCE = null;
+    private static final Always INSTANCE = new Always();
 
-    public static KeepPreconditionsAlways getInstance() {
-      if (INSTANCE == null) {
-        INSTANCE = new KeepPreconditionsAlways();
-      }
+    public static Always getInstance() {
       return INSTANCE;
     }
 
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 97d01b9..ee7f3d9 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
@@ -83,7 +83,7 @@
       throw new KeepEdgeException("Attempt to obtain exact qualified type for inexact pattern");
     }
     return 'L'
-        + packagePattern.asExact().getExactPackageAsString().replace('.', '/')
+        + packagePattern.getExactPackageAsString().replace('.', '/')
         + (packagePattern.isTop() ? "" : "/")
         + namePattern.asExact().getExactNameAsString()
         + ';';
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 a790094..c7c24b5 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
@@ -10,12 +10,9 @@
   }
 
   private static class Any extends KeepTypePattern {
-    private static Any INSTANCE = null;
+    private static final Any INSTANCE = new Any();
 
     public static Any getInstance() {
-      if (INSTANCE == null) {
-        INSTANCE = new Any();
-      }
       return INSTANCE;
     }
 
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java
index c9ac380..f1fbd28 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java
@@ -10,7 +10,7 @@
   }
 
   public static KeepUnqualfiedClassNamePattern any() {
-    return KeepClassNameAnyPattern.getInstance();
+    return Any.getInstance();
   }
 
   public static KeepUnqualfiedClassNamePattern exact(String className) {
@@ -22,7 +22,7 @@
     private KeepUnqualfiedClassNamePattern pattern;
 
     public Builder any() {
-      pattern = KeepClassNameAnyPattern.getInstance();
+      pattern = Any.getInstance();
       return this;
     }
 
@@ -39,18 +39,15 @@
     }
   }
 
-  private static class KeepClassNameAnyPattern extends KeepUnqualfiedClassNamePattern {
+  private static class Any extends KeepUnqualfiedClassNamePattern {
 
-    private static KeepClassNameAnyPattern INSTANCE = null;
+    private static final Any INSTANCE = new Any();
 
-    public static KeepClassNameAnyPattern getInstance() {
-      if (INSTANCE == null) {
-        INSTANCE = new KeepClassNameAnyPattern();
-      }
+    public static Any getInstance() {
       return INSTANCE;
     }
 
-    private KeepClassNameAnyPattern() {}
+    private Any() {}
 
     @Override
     public boolean isAny() {
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 d9b75ae..3ab882f 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
@@ -5,8 +5,7 @@
 
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
-import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
-import com.android.tools.r8.keepanno.ast.KeepItemPattern.KeepClassPattern;
+import com.android.tools.r8.keepanno.ast.KeepItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepMembersPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodAccessPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
@@ -51,37 +50,29 @@
     boolean[] hasAtLeastOneConditionalClause = new boolean[1];
     preconditions.forEach(
         condition -> {
-          // The usage kind for a predicate is not expressible in keep rules, so it is
-          // ignored.
-          condition
-              .getItemPattern()
-              .match(
-                  () -> {
-                    // If the conditions is "any" then we ignore it for now (identity of
-                    // conjunction).
-                    return null;
-                  },
-                  conditionItem -> {
-                    hasAtLeastOneConditionalClause[0] = true;
-                    consequentRules.forEach(
-                        consequentItem -> {
-                          // Since conjunctions are not supported in keep rules, we expand them into
-                          // disjunctions so conservatively we keep the consequences if any one of
-                          // the preconditions hold.
-                          StringBuilder builder = new StringBuilder();
-                          if (!consequentItem.isMemberConsequent()
-                              || !conditionItem
-                                  .getClassNamePattern()
-                                  .equals(consequentItem.getHolderPattern())) {
-                            builder.append("-if ");
-                            printClassItem(builder, conditionItem);
-                            builder.append(' ');
-                          }
-                          printConsequentRule(builder, consequentItem);
-                          ruleConsumer.accept(builder.toString());
-                        });
-                    return null;
-                  });
+          KeepItemPattern conditionItem = condition.getItemPattern();
+          // If the conditions is "any" then we ignore it for now (identity of conjunction).
+          if (conditionItem.isAny()) {
+            return;
+          }
+          hasAtLeastOneConditionalClause[0] = true;
+          consequentRules.forEach(
+              consequentItem -> {
+                // Since conjunctions are not supported in keep rules, we expand them into
+                // disjunctions so conservatively we keep the consequences if any one of
+                // the preconditions hold.
+                StringBuilder builder = new StringBuilder();
+                if (!consequentItem.isMemberOnlyConsequent()
+                    || !conditionItem
+                        .getClassNamePattern()
+                        .equals(consequentItem.getHolderPattern())) {
+                  builder.append("-if ");
+                  printItem(builder, conditionItem);
+                  builder.append(' ');
+                }
+                printConsequentRule(builder, consequentItem);
+                ruleConsumer.accept(builder.toString());
+              });
         });
     assert !(preconditions.isAlways() && hasAtLeastOneConditionalClause[0]);
     if (!hasAtLeastOneConditionalClause[0]) {
@@ -92,7 +83,7 @@
   }
 
   private static StringBuilder printConsequentRule(StringBuilder builder, ItemRule rule) {
-    if (rule.isMemberConsequent()) {
+    if (rule.isMemberOnlyConsequent()) {
       builder.append("-keepclassmembers");
     } else {
       builder.append("-keep");
@@ -105,8 +96,7 @@
     return builder.append(" ").append(rule.getKeepRuleForItem());
   }
 
-  private static StringBuilder printClassItem(
-      StringBuilder builder, KeepClassPattern clazzPattern) {
+  private static StringBuilder printItem(StringBuilder builder, KeepItemPattern clazzPattern) {
     builder.append("class ");
     printClassName(builder, clazzPattern.getClassNamePattern());
     if (!clazzPattern.getExtendsPattern().isAny()) {
@@ -119,19 +109,12 @@
     if (members.isAll()) {
       return builder.append(" { *; }");
     }
-    builder.append(" {");
-    members.forEach(
-        field -> printField(builder.append(' '), field),
-        method -> printMethod(builder.append(' '), method));
-    return builder.append(" }");
-  }
-
-  private static StringBuilder printField(StringBuilder builder, KeepFieldPattern field) {
-    if (field.isAnyField()) {
-      return builder.append("<fields>;");
-    } else {
-      throw new Unimplemented();
+    if (members.isMethod()) {
+      builder.append(" {");
+      printMethod(builder.append(' '), members.asMethod());
+      return builder.append(" }");
     }
+    throw new Unimplemented();
   }
 
   private static StringBuilder printMethod(StringBuilder builder, KeepMethodPattern methodPattern) {
@@ -148,24 +131,31 @@
 
   private static StringBuilder printParameters(
       StringBuilder builder, KeepMethodParametersPattern parametersPattern) {
-    return parametersPattern.match(
-        () -> builder.append("(***)"),
-        list ->
-            builder
-                .append('(')
-                .append(list.stream().map(Object::toString).collect(Collectors.joining(", ")))
-                .append(')'));
+    if (parametersPattern.isAny()) {
+      return builder.append("(***)");
+    }
+    return builder
+        .append('(')
+        .append(
+            parametersPattern.asList().stream()
+                .map(Object::toString)
+                .collect(Collectors.joining(", ")))
+        .append(')');
   }
 
   private static StringBuilder printMethodName(
       StringBuilder builder, KeepMethodNamePattern namePattern) {
-    return namePattern.match(() -> builder.append("*"), builder::append);
+    return namePattern.isAny()
+        ? builder.append("*")
+        : builder.append(namePattern.asExact().getName());
   }
 
   private static StringBuilder printReturnType(
       StringBuilder builder, KeepMethodReturnTypePattern returnTypePattern) {
-    return returnTypePattern.match(
-        () -> builder.append("void"), typePattern -> printType(builder, typePattern));
+    if (returnTypePattern.isVoid()) {
+      return builder.append("void");
+    }
+    return printType(builder, returnTypePattern.asType());
   }
 
   private static StringBuilder printType(StringBuilder builder, KeepTypePattern typePattern) {
@@ -203,7 +193,7 @@
       return builder;
     }
     assert packagePattern.isExact();
-    return builder.append(packagePattern.asExact().getExactPackageAsString()).append('.');
+    return builder.append(packagePattern.getExactPackageAsString()).append('.');
   }
 
   private static StringBuilder printSimpleClassName(
@@ -240,24 +230,20 @@
       this.options = target.getOptions();
     }
 
-    public boolean isMemberConsequent() {
-      return target.getItem().match(() -> false, clazz -> !clazz.getMembersPattern().isNone());
+    public boolean isMemberOnlyConsequent() {
+      KeepItemPattern item = target.getItem();
+      return !item.isAny() && !item.getMembersPattern().isNone();
     }
 
     public KeepQualifiedClassNamePattern getHolderPattern() {
-      return target
-          .getItem()
-          .match(KeepQualifiedClassNamePattern::any, KeepClassPattern::getClassNamePattern);
+      return target.getItem().getClassNamePattern();
     }
 
     public String getKeepRuleForItem() {
       if (ruleLine == null) {
+        KeepItemPattern item = target.getItem();
         ruleLine =
-            target
-                .getItem()
-                .match(
-                    () -> "class * { *; }",
-                    clazz -> printClassItem(new StringBuilder(), clazz).toString());
+            item.isAny() ? "class * { *; }" : printItem(new StringBuilder(), item).toString();
       }
       return ruleLine;
     }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java b/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
index 36bbaeb..58601a6 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.ast.KeepEdge.Builder;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
-import com.android.tools.r8.keepanno.ast.KeepMembersPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
 import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
@@ -137,11 +136,8 @@
     if (methodNameValue != null) {
       String methodName = AnnotationStringValueVisitor.getString(methodNameValue);
       itemBuilder.setMembersPattern(
-          KeepMembersPattern.builder()
-              .addMethodPattern(
-                  KeepMethodPattern.builder()
-                      .setNamePattern(KeepMethodNamePattern.exact(methodName))
-                      .build())
+          KeepMethodPattern.builder()
+              .setNamePattern(KeepMethodNamePattern.exact(methodName))
               .build());
     }
 
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
index 65351f8..1ba0b22 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
@@ -215,11 +215,14 @@
     if (arg.equals("info")) {
       return DiagnosticsLevel.INFO;
     }
+    if (arg.equals("none")) {
+      return DiagnosticsLevel.NONE;
+    }
     errorHandler.accept(
         new StringDiagnostic(
             "Invalid diagnostics level '"
                 + arg
-                + "'. Valid levels are 'error', 'warning' and 'info'.",
+                + "'. Valid levels are 'error', 'warning', 'info' and 'none'.",
             origin));
 
     return null;
diff --git a/src/main/java/com/android/tools/r8/DiagnosticsLevel.java b/src/main/java/com/android/tools/r8/DiagnosticsLevel.java
index 9f9d26a..a022b0d 100644
--- a/src/main/java/com/android/tools/r8/DiagnosticsLevel.java
+++ b/src/main/java/com/android/tools/r8/DiagnosticsLevel.java
@@ -8,5 +8,6 @@
 public enum DiagnosticsLevel {
   ERROR,
   WARNING,
-  INFO
+  INFO,
+  NONE
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 751b635..f85f8d5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -184,7 +184,9 @@
     this.printer = printer;
     this.codeRewriter = new CodeRewriter(appView);
     this.assertionErrorTwoArgsConstructorRewriter =
-        new AssertionErrorTwoArgsConstructorRewriter(appView);
+        appView.options().desugarState.isOn()
+            ? new AssertionErrorTwoArgsConstructorRewriter(appView)
+            : null;
     this.classInitializerDefaultsOptimization =
         new ClassInitializerDefaultsOptimization(appView, this);
     this.stringOptimizer = new StringOptimizer(appView);
@@ -1341,9 +1343,11 @@
     timing.begin("Natural Int Loop Remover");
     naturalIntLoopRemover.run(appView, code);
     timing.end();
-    timing.begin("Rewrite AssertionError");
-    assertionErrorTwoArgsConstructorRewriter.rewrite(code, methodProcessingContext);
-    timing.end();
+    if (assertionErrorTwoArgsConstructorRewriter != null) {
+      timing.begin("Rewrite AssertionError");
+      assertionErrorTwoArgsConstructorRewriter.rewrite(code, methodProcessingContext);
+      timing.end();
+    }
     timing.begin("Run CSE");
     codeRewriter.commonSubexpressionElimination(code);
     timing.end();
@@ -1522,6 +1526,11 @@
       timing.end();
     }
 
+    timing.begin("Redundant catch/rethrow elimination");
+    codeRewriter.optimizeRedundantCatchRethrowInstructions(code);
+    timing.end();
+    previous = printMethod(code, "IR after redundant catch/rethrow elimination (SSA)", previous);
+
     if (assumeInserter != null) {
       timing.begin("Remove assume instructions");
       CodeRewriter.removeAssumeInstructions(appView, code);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 3d9b8d2..6314197 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -3070,6 +3070,123 @@
     return changed;
   }
 
+  private boolean isPotentialTrivialRethrowValue(Value exceptionValue) {
+    if (exceptionValue.hasDebugUsers()) {
+      return false;
+    }
+    if (exceptionValue.hasUsers()) {
+      if (exceptionValue.hasPhiUsers()
+          || !exceptionValue.hasSingleUniqueUser()
+          || !exceptionValue.singleUniqueUser().isThrow()) {
+        return false;
+      }
+    } else if (exceptionValue.numberOfPhiUsers() != 1) {
+      return false;
+    }
+    return true;
+  }
+
+  private boolean isSingleHandlerTrivial(BasicBlock firstBlock, IRCode code) {
+    InstructionListIterator instructionIterator = firstBlock.listIterator(code);
+    Instruction instruction = instructionIterator.next();
+    if (!instruction.isMoveException()) {
+      // A catch handler which doesn't use its exception is not going to be a trivial rethrow.
+      return false;
+    }
+    Value exceptionValue = instruction.outValue();
+    if (!isPotentialTrivialRethrowValue(exceptionValue)) {
+      return false;
+    }
+    while (instructionIterator.hasNext()) {
+      instruction = instructionIterator.next();
+      BasicBlock currentBlock = instruction.getBlock();
+      if (instruction.isGoto()) {
+        BasicBlock nextBlock = instruction.asGoto().getTarget();
+        int predecessorIndex = nextBlock.getPredecessors().indexOf(currentBlock);
+        Value phiAliasOfExceptionValue = null;
+        for (Phi phi : nextBlock.getPhis()) {
+          Value operand = phi.getOperand(predecessorIndex);
+          if (exceptionValue == operand) {
+            phiAliasOfExceptionValue = phi;
+            break;
+          }
+        }
+        if (phiAliasOfExceptionValue != null) {
+          if (!isPotentialTrivialRethrowValue(phiAliasOfExceptionValue)) {
+            return false;
+          }
+          exceptionValue = phiAliasOfExceptionValue;
+        }
+        instructionIterator = nextBlock.listIterator(code);
+      } else if (instruction.isThrow()) {
+        List<Value> throwValues = instruction.inValues();
+        assert throwValues.size() == 1;
+        if (throwValues.get(0) != exceptionValue) {
+          return false;
+        }
+        CatchHandlers<BasicBlock> currentHandlers = currentBlock.getCatchHandlers();
+        if (!currentHandlers.isEmpty()) {
+          // This is the case where our trivial catch handler has catch handler(s). For now, we
+          // will only treat our block as trivial if all its catch handlers are also trivial.
+          // Note: it is possible that we could "bridge" a trivial handler, where we take the
+          // handlers of the handler and bring them up to replace the trivial handler. Example:
+          //   catch (Throwable t) {
+          //     try { throw t; } catch(Throwable abc) { foo(abc); }
+          //   }
+          // could turn into:
+          //   catch (Throwable abc) {
+          //     foo(abc);
+          //   }
+          // However this gets significantly harder when you have to consider non-matching guard
+          // types.
+          for (CatchHandler<BasicBlock> handler : currentHandlers) {
+            if (!isSingleHandlerTrivial(handler.getTarget(), code)) {
+              return false;
+            }
+          }
+        }
+        return true;
+      } else {
+        // Any other instructions in the catch handler means it's not trivial, and thus we can't
+        // elide.
+        return false;
+      }
+    }
+    throw new Unreachable("Triviality check should always return before the loop terminates");
+  }
+
+  // Find any case where we have a catch followed immediately and only by a rethrow. This is extra
+  // code doing what the JVM does automatically and can be safely elided.
+  public void optimizeRedundantCatchRethrowInstructions(IRCode code) {
+    ListIterator<BasicBlock> blockIterator = code.listIterator();
+    boolean hasUnlinkedCatchHandlers = false;
+    while (blockIterator.hasNext()) {
+      BasicBlock blockWithHandlers = blockIterator.next();
+      if (blockWithHandlers.hasCatchHandlers()) {
+        boolean allHandlersAreTrivial = true;
+        for (CatchHandler<BasicBlock> handler : blockWithHandlers.getCatchHandlers()) {
+          if (!isSingleHandlerTrivial(handler.target, code)) {
+            allHandlersAreTrivial = false;
+            break;
+          }
+        }
+        // We need to ensure all handlers are trivial to unlink, since if one is non-trivial, and
+        // its guard is a parent type to a trivial one, removing the trivial catch will result in
+        // us hitting the non-trivial catch. This could be avoided by more sophisticated type
+        // analysis.
+        if (allHandlersAreTrivial) {
+          hasUnlinkedCatchHandlers = true;
+          for (CatchHandler<BasicBlock> handler : blockWithHandlers.getCatchHandlers()) {
+            handler.getTarget().unlinkCatchHandler();
+          }
+        }
+      }
+    }
+    if (hasUnlinkedCatchHandlers) {
+      code.removeUnreachableBlocks();
+    }
+  }
+
   // Find all instructions that always throw, split the block after each such instruction and follow
   // it with a block throwing a null value (which should result in NPE). Note that this throw is not
   // expected to be ever reached, but is intended to satisfy verifier.
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index 65103ac..d2aa075 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -81,6 +81,8 @@
         abort = new AbortException(diagnostic);
         clientHandler.error(diagnostic);
         break;
+      case NONE:
+        break;
       default:
         throw new Unreachable();
     }
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java
index 568c66f..faa47f1 100644
--- a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java
@@ -101,7 +101,10 @@
                       .assertErrorsMatch(errorMatchers())
                       .assertWarningsMatch(warningMatchers())
                       .assertInfosMatch(infoMatchers()));
-      assertTrue(mappedLevel == DiagnosticsLevel.INFO || mappedLevel == DiagnosticsLevel.WARNING);
+      assertTrue(
+          mappedLevel == DiagnosticsLevel.INFO
+              || mappedLevel == DiagnosticsLevel.WARNING
+              || mappedLevel == DiagnosticsLevel.NONE);
     } catch (CompilationFailedException e) {
       assertEquals(mappedLevel, DiagnosticsLevel.ERROR);
     }
diff --git a/src/test/java/com/android/tools/r8/diagnostics/ModifyDiagnosticsLevelTest.java b/src/test/java/com/android/tools/r8/diagnostics/ModifyDiagnosticsLevelTest.java
index 9f20b7d..239a0f6 100644
--- a/src/test/java/com/android/tools/r8/diagnostics/ModifyDiagnosticsLevelTest.java
+++ b/src/test/java/com/android/tools/r8/diagnostics/ModifyDiagnosticsLevelTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.DiagnosticsLevel;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.diagnostic.internal.MissingDefinitionsDiagnosticImpl;
@@ -84,6 +85,24 @@
     }
   }
 
+  @Test
+  public void testWarningToNone() throws CompilationFailedException {
+    testForR8(Backend.DEX)
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-ignorewarnings")
+        .setDiagnosticsLevelModifier(
+            (level, diagnostic) -> {
+              if (level == DiagnosticsLevel.WARNING
+                  && diagnostic instanceof MissingDefinitionsDiagnosticImpl
+                  && diagnostic.getDiagnosticMessage().startsWith(MISSING_CLASS_MESSAGE_PREFIX)) {
+                return DiagnosticsLevel.NONE;
+              }
+              return level;
+            })
+        .compileWithExpectedDiagnostics(TestDiagnosticMessages::assertNoMessages);
+  }
+
   static class TestClass implements I {
 
     public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantcatchrethrowelimination/RedundantCatchRethrowEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantcatchrethrowelimination/RedundantCatchRethrowEliminationTest.java
new file mode 100644
index 0000000..7f7ef6d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantcatchrethrowelimination/RedundantCatchRethrowEliminationTest.java
@@ -0,0 +1,210 @@
+// Copyright (c) 2022, 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.ir.optimize.redundantcatchrethrowelimination;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.containsThrow;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RedundantCatchRethrowEliminationTest extends TestBase {
+
+  static final String EXPECTED =
+      StringUtils.lines(
+          "removableRethrow",
+          "trivialContext",
+          "complexRemovableRethrow",
+          "keptRethrow",
+          "nonTrivialContext",
+          "CloseableContext::close",
+          "complexKeptRethrow");
+
+  @Parameterized.Parameter() public TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(Main.class, TrivialClosableContext.class, ClosableContext.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClasses(Main.class, TrivialClosableContext.class, ClosableContext.class)
+        .release()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::inspectD8);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, TrivialClosableContext.class, ClosableContext.class)
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::inspectR8);
+  }
+
+  private void inspectD8(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(Main.class);
+    MethodSubject removableRethrowSubject =
+        classSubject.uniqueMethodWithOriginalName("removableRethrow");
+    assertThat(removableRethrowSubject, not(containsThrow()));
+    // Without whole-program optimizations, we can't get rid of the trivial closable context.close()
+    // call, which means that we can't remove the trivialContext throw.
+    MethodSubject complexRemovableRethrowSubject =
+        classSubject.uniqueMethodWithOriginalName("complexRemovableRethrow");
+    assertThat(complexRemovableRethrowSubject, not(containsThrow()));
+
+    MethodSubject keptRethrowSubject = classSubject.uniqueMethodWithOriginalName("keptRethrow");
+    assertThat(keptRethrowSubject, containsThrow());
+    MethodSubject nonTrivialContextSubject =
+        classSubject.uniqueMethodWithOriginalName("nonTrivialContext");
+    assertThat(nonTrivialContextSubject, containsThrow());
+    MethodSubject complexKeptRethrowSubject =
+        classSubject.uniqueMethodWithOriginalName("complexKeptRethrow");
+    assertThat(complexKeptRethrowSubject, containsThrow());
+  }
+
+  private void inspectR8(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(Main.class);
+    MethodSubject removableRethrowSubject =
+        classSubject.uniqueMethodWithOriginalName("removableRethrow");
+    assertThat(removableRethrowSubject, not(containsThrow()));
+    MethodSubject trivialContextSubject =
+        classSubject.uniqueMethodWithOriginalName("trivialContext");
+    assertThat(trivialContextSubject, not(containsThrow()));
+    MethodSubject complexRemovableRethrowSubject =
+        classSubject.uniqueMethodWithOriginalName("complexRemovableRethrow");
+    assertThat(complexRemovableRethrowSubject, not(containsThrow()));
+
+    MethodSubject keptRethrowSubject = classSubject.uniqueMethodWithOriginalName("keptRethrow");
+    assertThat(keptRethrowSubject, containsThrow());
+    MethodSubject nonTrivialContextSubject =
+        classSubject.uniqueMethodWithOriginalName("nonTrivialContext");
+    assertThat(nonTrivialContextSubject, containsThrow());
+    MethodSubject complexKeptRethrowSubject =
+        classSubject.uniqueMethodWithOriginalName("complexKeptRethrow");
+    assertThat(complexKeptRethrowSubject, containsThrow());
+  }
+
+  public static class TrivialClosableContext implements java.io.Closeable {
+    @Override
+    public void close() {}
+  }
+
+  public static class ClosableContext implements java.io.Closeable {
+    @Override
+    public void close() {
+      System.out.println("CloseableContext::close");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      removableRethrow();
+      trivialContext();
+      complexRemovableRethrow();
+      keptRethrow();
+      nonTrivialContext();
+      complexKeptRethrow();
+    }
+
+    @NeverInline
+    static void removableRethrow() {
+      try {
+        System.out.println("removableRethrow");
+      } catch (Throwable t) {
+        throw t;
+      }
+    }
+
+    @NeverInline
+    static void trivialContext() {
+      try (TrivialClosableContext unused = new TrivialClosableContext()) {
+        System.out.println("trivialContext");
+      }
+    }
+
+    @NeverInline
+    static void complexRemovableRethrow() {
+      try {
+        System.out.println("complexRemovableRethrow");
+      } catch (RuntimeException e) {
+        try {
+          throw e;
+        } catch (RuntimeException e2) {
+          throw e2;
+        } catch (Throwable t) {
+          throw t;
+        }
+      } catch (Throwable t) {
+        throw t;
+      }
+    }
+
+    @NeverInline
+    static void keptRethrow() {
+      try {
+        System.out.println("keptRethrow");
+      } catch (Throwable t) {
+        throw new RuntimeException("cause", t);
+      }
+    }
+
+    @NeverInline
+    static void nonTrivialContext() {
+      try (ClosableContext unused = new ClosableContext()) {
+        System.out.println("nonTrivialContext");
+      }
+    }
+
+    @NeverInline
+    static void complexKeptRethrow() {
+      try {
+        System.out.println("complexKeptRethrow");
+      } catch (RuntimeException e) {
+        try {
+          throw e;
+        } catch (RuntimeException e2) {
+          throw e2;
+        } catch (Throwable t) {
+          System.out.println("So that this can't be elided");
+          throw t;
+        }
+      } catch (Throwable t) {
+        throw t;
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
index bcdb8ed..0f07d76 100644
--- a/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
@@ -170,13 +170,10 @@
   }
 
   private KeepMembersPattern defaultInitializerPattern() {
-    return KeepMembersPattern.builder()
-        .addMethodPattern(
-            KeepMethodPattern.builder()
-                .setNamePattern(KeepMethodNamePattern.initializer())
-                .setParametersPattern(KeepMethodParametersPattern.none())
-                .setReturnTypeVoid()
-                .build())
+    return KeepMethodPattern.builder()
+        .setNamePattern(KeepMethodNamePattern.initializer())
+        .setParametersPattern(KeepMethodParametersPattern.none())
+        .setReturnTypeVoid()
         .build();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
index 187a019..b6369de 100644
--- a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
+++ b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
@@ -6,7 +6,6 @@
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
-import com.android.tools.r8.keepanno.ast.KeepMembersPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
 import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
@@ -37,12 +36,10 @@
     // Build the constructor target.
     KeepMethodPattern constructorMethod =
         KeepMethodPattern.builder().setNamePattern(KeepMethodNamePattern.exact("<init>")).build();
-    KeepMembersPattern constructorMembers =
-        KeepMembersPattern.builder().addMethodPattern(constructorMethod).build();
     KeepItemPattern constructorItem =
         KeepItemPattern.builder()
             .setClassPattern(name)
-            .setMembersPattern(constructorMembers)
+            .setMembersPattern(constructorMethod)
             .build();
     KeepTarget constructorTarget = KeepTarget.builder().setItem(constructorItem).build();
     // The consequet set is the class an its constructor.
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertionerror/AssertionErrorRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/assertionerror/AssertionErrorRewriteTest.java
index f22208c..2fa9d11 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertionerror/AssertionErrorRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertionerror/AssertionErrorRewriteTest.java
@@ -3,11 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.rewrite.assertionerror;
 
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -34,7 +36,8 @@
             || parameters.getApiLevel().getLevel() >= AndroidApiLevel.J.getLevel();
   }
 
-  @Test public void d8() throws Exception {
+  @Test
+  public void d8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
     testForD8()
         .addProgramClasses(Main.class)
@@ -44,7 +47,30 @@
         .assertSuccessWithOutputLines("message", "java.lang.RuntimeException: cause message");
   }
 
-  @Test public void r8() throws Exception {
+  @Test
+  public void d8NoDesugar() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addProgramClasses(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .disableDesugaring()
+        .compile()
+        // TODO(b/247596495): There should be no synthetics.
+        .inspect(
+            inspector ->
+                assertTrue(
+                    inspector.allClasses().stream()
+                        .noneMatch(
+                            clazz ->
+                                SyntheticItemsTestUtils.isExternalSynthetic(
+                                    clazz.getFinalReference()))))
+        .run(parameters.getRuntime(), Main.class)
+        // None of the VMs we have for testing is missing the two args constructor.
+        .assertSuccessWithOutputLines("message", "java.lang.RuntimeException: cause message");
+  }
+
+  @Test
+  public void r8() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramClasses(Main.class)
         .addKeepMainRule(Main.class)