Merge commit 'f2c3df4e1cecb6215c901479c1ada04b95dfa336' into dev-release
diff --git a/compatibility-faq.md b/compatibility-faq.md
index 3ba23c8..8ca09d5 100644
--- a/compatibility-faq.md
+++ b/compatibility-faq.md
@@ -129,7 +129,7 @@
 the fields to be renamed by R8 to the same name, but GSON serialization will
 work as expected.
 
-### GSON with full mode
+### GSON
 
 GSON uses type tokens to serialize and deserialize generic types.
 
@@ -138,7 +138,7 @@
 The anonymous class will have a generic signature argument of `List<String>` to
 the super type `TypeToken` that is reflective read for serialization. It
 is therefore necessary to keep both the `Signature` attribute, the
-`com.google.gson.reflect.TypeToken` class and all sub-types:
+`com.google.gson.reflect.TypeToken` class and all sub-types.
 
 ```
 -keepattributes Signature
@@ -146,6 +146,9 @@
 -keep class * extends com.google.gson.reflect.TypeToken
 ```
 
+This is also needed for R8 in compat mode since multiple optimizations will
+remove the generic signature such as class merging and argument removal.
+
 ## Retrofit
 
 ### Objects instantiated with Retrofit's `create()` method are always replaced with `null`
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
index 6f1fde5..6fd3574 100644
--- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -153,6 +153,11 @@
   }
 
   @Override
+  public AppView<?> getAppView() {
+    return appView;
+  }
+
+  @Override
   public void allocateRegisters() {
     computeNeedsRegister();
     ImmutableList<BasicBlock> blocks = computeLivenessInformation();
diff --git a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
index 5a0ec34..a5d6098 100644
--- a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
+++ b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.dex;
 
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.isVivifiedType;
+
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
@@ -149,6 +151,9 @@
 
     private void keepClass(DexType type) {
       DexType baseType = type.lookupBaseType(appView.dexItemFactory());
+      if (isVivifiedType(baseType)) {
+        return;
+      }
       toKeep.putIfAbsent(baseType, new KeepStruct());
     }
 
diff --git a/src/main/java/com/android/tools/r8/errors/ProguardKeepRuleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/ProguardKeepRuleDiagnostic.java
index dcb6bb9..10d8c1f 100644
--- a/src/main/java/com/android/tools/r8/errors/ProguardKeepRuleDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/ProguardKeepRuleDiagnostic.java
@@ -4,6 +4,8 @@
 package com.android.tools.r8.errors;
 
 import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
 
-/** Base class for diagnostics related to proguard keep rules. */
-public abstract class ProguardKeepRuleDiagnostic implements Diagnostic {}
+/** Base interface for diagnostics related to proguard keep rules. */
+@Keep
+public interface ProguardKeepRuleDiagnostic extends Diagnostic {}
diff --git a/src/main/java/com/android/tools/r8/errors/UnusedProguardKeepRuleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnusedProguardKeepRuleDiagnostic.java
index 396f88b..2d2b326 100644
--- a/src/main/java/com/android/tools/r8/errors/UnusedProguardKeepRuleDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/UnusedProguardKeepRuleDiagnostic.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 
 @Keep
-public class UnusedProguardKeepRuleDiagnostic extends ProguardKeepRuleDiagnostic {
+public class UnusedProguardKeepRuleDiagnostic implements ProguardKeepRuleDiagnostic {
 
   private final ProguardConfigurationRule rule;
 
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java
new file mode 100644
index 0000000..acd327d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java
@@ -0,0 +1,45 @@
+// 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.experimental.keepanno.ast;
+
+/**
+ * A keep condition is the content of an item in the set of preconditions.
+ *
+ * <p>It can be trivially true, or represent some program item pattern that must be present in the
+ * program residual. When an condition is denoted by a program item, the condition also specifies
+ * the extent of the item for which it is predicated on. The extent is given by a "usage kind" that
+ * can be either its "symbolic reference" or its "actual use".
+ */
+public final class KeepCondition {
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+
+    private KeepItemPattern itemPattern;
+
+    private Builder() {}
+
+    public Builder setItem(KeepItemPattern itemPattern) {
+      this.itemPattern = itemPattern;
+      return this;
+    }
+
+    public KeepCondition build() {
+      return new KeepCondition(itemPattern);
+    }
+  }
+
+  private final KeepItemPattern itemPattern;
+
+  private KeepCondition(KeepItemPattern itemPattern) {
+    this.itemPattern = itemPattern;
+  }
+
+  public KeepItemPattern getItemPattern() {
+    return itemPattern;
+    }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java
new file mode 100644
index 0000000..6a227fd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java
@@ -0,0 +1,53 @@
+// 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.experimental.keepanno.ast;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Set of consequences of a keep edge.
+ *
+ * <p>The consequences are "targets" described by item patterns along with "keep options" which
+ * detail what aspects of the items must be retained.
+ *
+ * <p>The consequences come into effect if the preconditions of an edge are met.
+ */
+public final class KeepConsequences {
+
+  public static class Builder {
+
+    private List<KeepTarget> targets = new ArrayList<>();
+
+    private Builder() {}
+
+    public Builder addTarget(KeepTarget target) {
+      targets.add(target);
+      return this;
+    }
+
+    public KeepConsequences build() {
+      return new KeepConsequences(targets);
+    }
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  private final List<KeepTarget> targets;
+
+  private KeepConsequences(List<KeepTarget> targets) {
+    this.targets = targets;
+  }
+
+  public boolean isEmpty() {
+    return targets.isEmpty();
+  }
+
+  public void forEachTarget(Consumer<KeepTarget> fn) {
+    targets.forEach(fn);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java
new file mode 100644
index 0000000..f61483a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java
@@ -0,0 +1,57 @@
+// 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.experimental.keepanno.ast;
+
+/**
+ * An edge in the keep graph.
+ *
+ * <p>An edge describes a set of preconditions and a set of consequences. If the preconditions are
+ * met, then the consequences are put into effect.
+ */
+public final class KeepEdge {
+
+  public static class Builder {
+    private KeepPreconditions preconditions = KeepPreconditions.always();
+    private KeepConsequences consequences;
+
+    private Builder() {}
+
+    public Builder setPreconditions(KeepPreconditions preconditions) {
+      this.preconditions = preconditions;
+      return this;
+    }
+
+    public Builder setConsequences(KeepConsequences consequences) {
+      this.consequences = consequences;
+      return this;
+    }
+
+    public KeepEdge build() {
+      if (consequences.isEmpty()) {
+        throw new KeepEdgeException("KeepEdge must have non-empty set of consequences.");
+      }
+      return new KeepEdge(preconditions, consequences);
+    }
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  private final KeepPreconditions preconditions;
+  private final KeepConsequences consequences;
+
+  private KeepEdge(KeepPreconditions preconditions, KeepConsequences consequences) {
+    this.preconditions = preconditions;
+    this.consequences = consequences;
+  }
+
+  public KeepPreconditions getPreconditions() {
+    return preconditions;
+  }
+
+  public KeepConsequences getConsequences() {
+    return consequences;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java
new file mode 100644
index 0000000..3345c8f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java
@@ -0,0 +1,11 @@
+// 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.experimental.keepanno.ast;
+
+public class KeepEdgeException extends RuntimeException {
+
+  public KeepEdgeException(String message) {
+    super(message);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java
new file mode 100644
index 0000000..17e543f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java
@@ -0,0 +1,68 @@
+// 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.experimental.keepanno.ast;
+
+/** Pattern for matching the "extends" or "implements" clause of a class. */
+public abstract class KeepExtendsPattern {
+
+  public static KeepExtendsPattern any() {
+    return KeepExtendsAnyPattern.getInstance();
+  }
+
+  public static class Builder {
+
+    private KeepExtendsPattern pattern;
+
+    private Builder() {}
+
+    public Builder any() {
+      pattern = KeepExtendsAnyPattern.getInstance();
+      return this;
+    }
+
+    public Builder classPattern(KeepQualifiedClassNamePattern pattern) {
+      this.pattern = new KeepExtendsClassPattern(pattern);
+      return this;
+    }
+  }
+
+  private static class KeepExtendsAnyPattern extends KeepExtendsPattern {
+
+    private static KeepExtendsAnyPattern INSTANCE = null;
+
+    public static KeepExtendsAnyPattern getInstance() {
+      if (INSTANCE == null) {
+        INSTANCE = new KeepExtendsAnyPattern();
+      }
+      return INSTANCE;
+    }
+
+    @Override
+    public boolean isAny() {
+      return true;
+    }
+  }
+
+  private static class KeepExtendsClassPattern extends KeepExtendsPattern {
+
+    private final KeepQualifiedClassNamePattern pattern;
+
+    public KeepExtendsClassPattern(KeepQualifiedClassNamePattern pattern) {
+      this.pattern = pattern;
+    }
+
+    @Override
+    public boolean isAny() {
+      return pattern.isAny();
+    }
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  private KeepExtendsPattern() {}
+
+  public abstract boolean isAny();
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java
new file mode 100644
index 0000000..04e2697
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java
@@ -0,0 +1,13 @@
+// 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.experimental.keepanno.ast;
+
+public class KeepFieldPattern extends KeepMemberPattern {
+
+  private KeepFieldPattern() {}
+
+  public boolean isAnyField() {
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java
new file mode 100644
index 0000000..3c6f947
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java
@@ -0,0 +1,138 @@
+// 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.experimental.keepanno.ast;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * A pattern for matching items in the program.
+ *
+ * <p>An item pattern can be any item, or it can describe a family of classes or a family of members
+ * on a classes.
+ *
+ * <p>A pattern cannot describe both a class *and* a member of a class. Either it is a pattern on
+ * classes or it is a pattern on members. The distinction is defined by having a "none" member
+ * pattern.
+ */
+public abstract class KeepItemPattern {
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static KeepItemPattern any() {
+    return KeepItemAnyPattern.getInstance();
+  }
+
+  public static class Builder {
+
+    private KeepQualifiedClassNamePattern classNamePattern;
+    private KeepExtendsPattern extendsPattern = KeepExtendsPattern.any();
+    private KeepMembersPattern membersPattern = KeepMembersPattern.none();
+
+    private Builder() {}
+
+    public Builder any() {
+      classNamePattern = KeepQualifiedClassNamePattern.any();
+      extendsPattern = KeepExtendsPattern.any();
+      membersPattern = KeepMembersPattern.all();
+      return this;
+    }
+
+    public Builder setClassPattern(KeepQualifiedClassNamePattern qualifiedClassNamePattern) {
+      this.classNamePattern = qualifiedClassNamePattern;
+      return this;
+    }
+
+    public Builder setExtendsPattern(KeepExtendsPattern extendsPattern) {
+      this.extendsPattern = extendsPattern;
+      return this;
+    }
+
+    public Builder setMembersPattern(KeepMembersPattern membersPattern) {
+      this.membersPattern = membersPattern;
+      return this;
+    }
+
+    public KeepItemPattern build() {
+      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);
+    }
+  }
+
+  private static class KeepItemAnyPattern extends KeepItemPattern {
+
+    private static KeepItemAnyPattern INSTANCE = null;
+
+    public static KeepItemAnyPattern getInstance() {
+      if (INSTANCE == null) {
+        INSTANCE = new KeepItemAnyPattern();
+      }
+      return INSTANCE;
+    }
+
+    @Override
+    public boolean isAny() {
+      return true;
+    }
+
+    @Override
+    public <T> T match(Supplier<T> onAny, Function<KeepClassPattern, T> onItem) {
+      return onAny.get();
+    }
+  }
+
+  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) {
+      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;
+    }
+  }
+
+  public abstract boolean isAny();
+
+  public abstract <T> T match(Supplier<T> onAny, Function<KeepClassPattern, T> onItem);
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java
new file mode 100644
index 0000000..73bae7e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java
@@ -0,0 +1,38 @@
+// 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.experimental.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/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java
new file mode 100644
index 0000000..6d3f93e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java
@@ -0,0 +1,159 @@
+// 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.experimental.keepanno.ast;
+
+import com.android.tools.r8.errors.Unimplemented;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+public abstract class KeepMembersPattern {
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static KeepMembersPattern none() {
+    return KeepMembersNonePattern.getInstance();
+  }
+
+  public static KeepMembersPattern all() {
+    return KeepMembersAllPattern.getInstance();
+  }
+
+  public static class Builder {
+
+    private boolean anyMethod = false;
+    private boolean anyField = false;
+    private List<KeepMethodPattern> methods = new ArrayList<>();
+    private List<KeepFieldPattern> fields = new ArrayList<>();
+
+    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();
+      }
+      return INSTANCE;
+    }
+
+    @Override
+    public boolean isAll() {
+      return true;
+    }
+
+    @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?");
+    }
+  }
+
+  private static class KeepMembersNonePattern extends KeepMembersPattern {
+
+    private static KeepMembersNonePattern INSTANCE = null;
+
+    public static KeepMembersNonePattern getInstance() {
+      if (INSTANCE == null) {
+        INSTANCE = new KeepMembersNonePattern();
+      }
+      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?");
+    }
+  }
+
+  private static class KeepMembersSomePattern extends 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);
+    }
+  }
+
+  private KeepMembersPattern() {}
+
+  public abstract boolean isAll();
+
+  public abstract boolean isNone();
+
+  public abstract void forEach(
+      Consumer<KeepFieldPattern> onField, Consumer<KeepMethodPattern> onMethod);
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodAccessPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodAccessPattern.java
new file mode 100644
index 0000000..7f83937
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodAccessPattern.java
@@ -0,0 +1,16 @@
+// 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.experimental.keepanno.ast;
+
+// TODO: finish this.
+public class KeepMethodAccessPattern {
+
+  public static KeepMethodAccessPattern any() {
+    return new KeepMethodAccessPattern();
+  }
+
+  public boolean isAny() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java
new file mode 100644
index 0000000..ada6701
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java
@@ -0,0 +1,64 @@
+// 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.experimental.keepanno.ast;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public abstract class KeepMethodNamePattern {
+
+  public static KeepMethodNamePattern any() {
+    return KeepMethodNameAnyPattern.getInstance();
+  }
+
+  public static KeepMethodNamePattern initializer() {
+    return new KeepMethodNameExactPattern("<init>");
+  }
+
+  public static KeepMethodNamePattern exact(String methodName) {
+    return new KeepMethodNameExactPattern(methodName);
+  }
+
+  private KeepMethodNamePattern() {}
+
+  public boolean isAny() {
+    return match(() -> true, ignore -> false);
+  }
+
+  public boolean isExact() {
+    return match(() -> false, ignore -> true);
+  }
+  ;
+
+  public abstract <T> T match(Supplier<T> onAny, Function<String, T> onExact);
+
+  private static class KeepMethodNameAnyPattern extends KeepMethodNamePattern {
+    private static KeepMethodNameAnyPattern INSTANCE = null;
+
+    public static KeepMethodNameAnyPattern getInstance() {
+      if (INSTANCE == null) {
+        INSTANCE = new KeepMethodNameAnyPattern();
+      }
+      return INSTANCE;
+    }
+
+    @Override
+    public <T> T match(Supplier<T> onAny, Function<String, T> onExact) {
+      return onAny.get();
+    }
+  }
+
+  private static class KeepMethodNameExactPattern extends KeepMethodNamePattern {
+    private final String name;
+
+    public KeepMethodNameExactPattern(String name) {
+      this.name = name;
+    }
+
+    @Override
+    public <T> T match(Supplier<T> onAny, Function<String, T> onExact) {
+      return onExact.apply(name);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java
new file mode 100644
index 0000000..4f42202
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java
@@ -0,0 +1,56 @@
+// 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.experimental.keepanno.ast;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public abstract class KeepMethodParametersPattern {
+
+  public static KeepMethodParametersPattern any() {
+    return Any.getInstance();
+  }
+
+  public static KeepMethodParametersPattern none() {
+    return None.getInstance();
+  }
+
+  private KeepMethodParametersPattern() {}
+
+  public abstract <T> T match(Supplier<T> onAny, Function<List<KeepTypePattern>, T> onList);
+
+  private static class None extends KeepMethodParametersPattern {
+    private static None INSTANCE = null;
+
+    public static None getInstance() {
+      if (INSTANCE == null) {
+        INSTANCE = new None();
+      }
+      return INSTANCE;
+    }
+
+    @Override
+    public <T> T match(Supplier<T> onAny, Function<List<KeepTypePattern>, T> onList) {
+      return onList.apply(Collections.emptyList());
+    }
+  }
+
+  private static class Any extends KeepMethodParametersPattern {
+    private static Any INSTANCE = null;
+
+    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();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java
new file mode 100644
index 0000000..5db7d26
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java
@@ -0,0 +1,90 @@
+// 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.experimental.keepanno.ast;
+
+public final class KeepMethodPattern extends KeepMemberPattern {
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder extends KeepMemberPattern.Builder<Builder> {
+
+    private KeepMethodAccessPattern accessPattern = KeepMethodAccessPattern.any();
+    private KeepMethodNamePattern namePattern = null;
+    private KeepMethodReturnTypePattern returnTypePattern = KeepMethodReturnTypePattern.any();
+    private KeepMethodParametersPattern parametersPattern = KeepMethodParametersPattern.any();
+
+    private Builder() {}
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    public Builder setAccessPattern(KeepMethodAccessPattern accessPattern) {
+      this.accessPattern = accessPattern;
+      return self();
+    }
+
+    public Builder setNamePattern(KeepMethodNamePattern namePattern) {
+      this.namePattern = namePattern;
+      return self();
+    }
+
+    public Builder setReturnTypeVoid() {
+      returnTypePattern = KeepMethodReturnTypePattern.voidType();
+      return self();
+    }
+
+    public Builder setParametersPattern(KeepMethodParametersPattern parametersPattern) {
+      this.parametersPattern = parametersPattern;
+      return self();
+    }
+
+    public KeepMethodPattern build() {
+      if (namePattern == null) {
+        throw new KeepEdgeException("Method pattern must declar a name pattern");
+      }
+      return new KeepMethodPattern(
+          accessPattern, namePattern, returnTypePattern, parametersPattern);
+    }
+  }
+
+  private final KeepMethodAccessPattern accessPattern;
+  private final KeepMethodNamePattern namePattern;
+  private final KeepMethodReturnTypePattern returnTypePattern;
+  private final KeepMethodParametersPattern parametersPattern;
+
+  private KeepMethodPattern(
+      KeepMethodAccessPattern accessPattern,
+      KeepMethodNamePattern namePattern,
+      KeepMethodReturnTypePattern returnTypePattern,
+      KeepMethodParametersPattern parametersPattern) {
+    this.accessPattern = accessPattern;
+    this.namePattern = namePattern;
+    this.returnTypePattern = returnTypePattern;
+    this.parametersPattern = parametersPattern;
+  }
+
+  public boolean isAnyMethod() {
+    return false;
+  }
+
+  public KeepMethodAccessPattern getAccessPattern() {
+    return accessPattern;
+  }
+
+  public KeepMethodNamePattern getNamePattern() {
+    return namePattern;
+  }
+
+  public KeepMethodReturnTypePattern getReturnTypePattern() {
+    return returnTypePattern;
+  }
+
+  public KeepMethodParametersPattern getParametersPattern() {
+    return parametersPattern;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java
new file mode 100644
index 0000000..4e7b5b9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java
@@ -0,0 +1,55 @@
+// 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.experimental.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;
+  }
+
+  public static KeepMethodReturnTypePattern voidType() {
+    return VoidType.getInstance();
+  }
+
+  public abstract <T> T match(Supplier<T> onVoid, Function<KeepTypePattern, T> onType);
+
+  private static class VoidType extends KeepMethodReturnTypePattern {
+    private static VoidType INSTANCE = null;
+
+    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();
+    }
+  }
+
+  private static class SomeType extends KeepMethodReturnTypePattern {
+
+    private final KeepTypePattern typePattern;
+
+    private SomeType(KeepTypePattern typePattern) {
+      this.typePattern = typePattern;
+    }
+
+    @Override
+    public <T> T match(Supplier<T> onVoid, Function<KeepTypePattern, T> onType) {
+      return onType.apply(typePattern);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java
new file mode 100644
index 0000000..9fb521c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java
@@ -0,0 +1,97 @@
+// 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.experimental.keepanno.ast;
+
+import com.android.tools.r8.utils.SetUtils;
+import com.google.common.collect.ImmutableSet;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+public final class KeepOptions {
+
+  public enum KeepOption {
+    SHRINKING,
+    OPTIMIZING,
+    OBFUSCATING,
+    ACCESS_MODIFING,
+  }
+
+  public static KeepOptions keepAll() {
+    if (ALLOW_NONE_INSTANCE == null) {
+      ALLOW_NONE_INSTANCE = new KeepOptions(true, Collections.emptySet());
+    }
+    return ALLOW_NONE_INSTANCE;
+  }
+
+  public static Builder allowBuilder() {
+    return new Builder(true);
+  }
+
+  public static Builder disallowBuilder() {
+    return new Builder(false);
+  }
+
+  public static KeepOptions allow(KeepOption... options) {
+    return allowBuilder().addAll(options).build();
+  }
+
+  public static KeepOptions disallow(KeepOption... options) {
+    return disallowBuilder().addAll(options).build();
+  }
+
+  public static class Builder {
+    public final boolean allowIfSet;
+    public Set<KeepOption> options = SetUtils.newIdentityHashSet();
+
+    private Builder(boolean allowIfSet) {
+      this.allowIfSet = allowIfSet;
+    }
+
+    public Builder add(KeepOption option) {
+      options.add(option);
+      return this;
+    }
+
+    public Builder addAll(KeepOption... options) {
+      return addAll(Arrays.asList(options));
+    }
+
+    public Builder addAll(Collection<KeepOption> options) {
+      this.options.addAll(options);
+      return this;
+    }
+
+    public KeepOptions build() {
+      if (options.isEmpty()) {
+        if (allowIfSet) {
+          return keepAll();
+        }
+        throw new KeepEdgeException("Invalid keep options that disallow nothing.");
+      }
+      if (options.size() == KeepOption.values().length) {
+        if (!allowIfSet) {
+          return keepAll();
+        }
+        throw new KeepEdgeException("Invalid keep options that allow everything.");
+      }
+      return new KeepOptions(allowIfSet, ImmutableSet.copyOf(options));
+    }
+  }
+
+  private static KeepOptions ALLOW_NONE_INSTANCE = null;
+
+  private final boolean allowIfSet;
+  private final Set<KeepOption> options;
+
+  private KeepOptions(boolean allowIfSet, Set<KeepOption> options) {
+    this.allowIfSet = allowIfSet;
+    this.options = options;
+  }
+
+  public boolean isAllowed(KeepOption option) {
+    return options.contains(option) == allowIfSet;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java
new file mode 100644
index 0000000..10db385
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java
@@ -0,0 +1,180 @@
+// 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.experimental.keepanno.ast;
+
+public abstract class KeepPackagePattern {
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static KeepPackagePattern any() {
+    return KeepPackageAnyPattern.getInstance();
+  }
+
+  public static KeepPackagePattern top() {
+    return KeepPackageTopPattern.getInstance();
+  }
+
+  public static KeepPackagePattern exact(String fullPackage) {
+    return KeepPackagePattern.builder().exact(fullPackage).build();
+  }
+
+  public static class Builder {
+
+    private KeepPackagePattern pattern;
+
+    public Builder any() {
+      pattern = KeepPackageAnyPattern.getInstance();
+      return this;
+    }
+
+    public Builder top() {
+      pattern = KeepPackageTopPattern.getInstance();
+      return this;
+    }
+
+    public Builder exact(String fullPackage) {
+      pattern =
+          fullPackage.isEmpty()
+              ? KeepPackagePattern.top()
+              : new KeepPackageExactPattern(fullPackage);
+      return this;
+    }
+
+    public KeepPackagePattern build() {
+      if (pattern == null) {
+        throw new KeepEdgeException("Invalid package pattern: null");
+      }
+      return pattern;
+    }
+  }
+
+  private static final class KeepPackageAnyPattern extends KeepPackagePattern {
+
+    private static KeepPackageAnyPattern INSTANCE = null;
+
+    public static KeepPackageAnyPattern getInstance() {
+      if (INSTANCE == null) {
+        INSTANCE = new KeepPackageAnyPattern();
+      }
+      return INSTANCE;
+    }
+
+    private KeepPackageAnyPattern() {}
+
+    @Override
+    public boolean isAny() {
+      return true;
+    }
+
+    @Override
+    public boolean isTop() {
+      return false;
+    }
+
+    @Override
+    public boolean isExact() {
+      return false;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      return obj == this;
+    }
+
+    @Override
+    public int hashCode() {
+      return System.identityHashCode(this);
+    }
+  }
+
+  private static final class KeepPackageTopPattern extends KeepPackageExactPattern {
+
+    private static KeepPackageTopPattern INSTANCE = null;
+
+    public static KeepPackageTopPattern getInstance() {
+      if (INSTANCE == null) {
+        INSTANCE = new KeepPackageTopPattern();
+      }
+      return INSTANCE;
+    }
+
+    private KeepPackageTopPattern() {
+      super("");
+    }
+
+    @Override
+    public boolean isAny() {
+      return false;
+    }
+
+    @Override
+    public boolean isTop() {
+      return true;
+    }
+  }
+
+  public static class KeepPackageExactPattern extends KeepPackagePattern {
+
+    private final String fullPackage;
+
+    private KeepPackageExactPattern(String fullPackage) {
+      assert fullPackage != null;
+      this.fullPackage = fullPackage;
+      // TODO: Verify valid package identifiers.
+    }
+
+    @Override
+    public boolean isAny() {
+      return false;
+    }
+
+    @Override
+    public boolean isTop() {
+      return fullPackage.equals("");
+    }
+
+    @Override
+    public boolean isExact() {
+      return true;
+    }
+
+    @Override
+    public KeepPackageExactPattern asExact() {
+      return this;
+    }
+
+    public String getExactPackageAsString() {
+      return fullPackage;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      KeepPackageExactPattern that = (KeepPackageExactPattern) o;
+      return fullPackage.equals(that.fullPackage);
+    }
+
+    @Override
+    public int hashCode() {
+      return fullPackage.hashCode();
+    }
+  }
+
+  public abstract boolean isAny();
+
+  public abstract boolean isTop();
+
+  public abstract boolean isExact();
+
+  public KeepPackageExactPattern asExact() {
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java
new file mode 100644
index 0000000..f3083df
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java
@@ -0,0 +1,82 @@
+// 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.experimental.keepanno.ast;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+public abstract class KeepPreconditions {
+
+  public abstract void forEach(Consumer<KeepCondition> fn);
+
+  public static class Builder {
+
+    private List<KeepCondition> preconditions = new ArrayList<>();
+
+    private Builder() {}
+
+    public Builder addCondition(KeepCondition condition) {
+      preconditions.add(condition);
+      return this;
+    }
+
+    public KeepPreconditions build() {
+      return preconditions.isEmpty()
+          ? KeepPreconditions.always()
+          : new KeepPreconditionsSome(preconditions);
+    }
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static KeepPreconditions always() {
+    return KeepPreconditionsAlways.getInstance();
+  }
+
+  public abstract boolean isAlways();
+
+  private static class KeepPreconditionsAlways extends KeepPreconditions {
+
+    private static KeepPreconditionsAlways INSTANCE = null;
+
+    public static KeepPreconditionsAlways getInstance() {
+      if (INSTANCE == null) {
+        INSTANCE = new KeepPreconditionsAlways();
+      }
+      return INSTANCE;
+    }
+
+    @Override
+    public boolean isAlways() {
+      return true;
+    }
+
+    @Override
+    public void forEach(Consumer<KeepCondition> fn) {
+      // Empty.
+    }
+  }
+
+  private static class KeepPreconditionsSome extends KeepPreconditions {
+
+    private final List<KeepCondition> preconditions;
+
+    private KeepPreconditionsSome(List<KeepCondition> preconditions) {
+      this.preconditions = preconditions;
+    }
+
+    @Override
+    public boolean isAlways() {
+      return false;
+    }
+
+    @Override
+    public void forEach(Consumer<KeepCondition> fn) {
+      preconditions.forEach(fn);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java
new file mode 100644
index 0000000..9485580
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java
@@ -0,0 +1,101 @@
+// 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.experimental.keepanno.ast;
+
+import java.util.Objects;
+
+public final class KeepQualifiedClassNamePattern {
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static KeepQualifiedClassNamePattern any() {
+    return KeepQualifiedClassNamePattern.builder()
+        .setPackagePattern(KeepPackagePattern.any())
+        .setNamePattern(KeepUnqualfiedClassNamePattern.any())
+        .build();
+  }
+
+  public static KeepQualifiedClassNamePattern exact(String qualifiedClassName) {
+    int pkgSeparator = qualifiedClassName.lastIndexOf('.');
+    if (pkgSeparator == 0) {
+      throw new KeepEdgeException("Unexpected '.' at index 0 in '" + qualifiedClassName + "'");
+    }
+    if (pkgSeparator > 0) {
+      return KeepQualifiedClassNamePattern.builder()
+          .setPackagePattern(
+              KeepPackagePattern.exact(qualifiedClassName.substring(0, pkgSeparator)))
+          .setNamePattern(
+              KeepUnqualfiedClassNamePattern.exact(qualifiedClassName.substring(pkgSeparator + 1)))
+          .build();
+    }
+    return KeepQualifiedClassNamePattern.builder()
+        .setPackagePattern(KeepPackagePattern.top())
+        .setNamePattern(KeepUnqualfiedClassNamePattern.exact(qualifiedClassName))
+        .build();
+  }
+
+  public static class Builder {
+
+    private KeepPackagePattern packagePattern;
+    private KeepUnqualfiedClassNamePattern namePattern;
+
+    private Builder() {}
+
+    public Builder setPackagePattern(KeepPackagePattern packagePattern) {
+      this.packagePattern = packagePattern;
+      return this;
+    }
+
+    public Builder setNamePattern(KeepUnqualfiedClassNamePattern namePattern) {
+      this.namePattern = namePattern;
+      return this;
+    }
+
+    public KeepQualifiedClassNamePattern build() {
+      return new KeepQualifiedClassNamePattern(packagePattern, namePattern);
+    }
+  }
+
+  private final KeepPackagePattern packagePattern;
+  private final KeepUnqualfiedClassNamePattern namePattern;
+
+  public KeepQualifiedClassNamePattern(
+      KeepPackagePattern packagePattern, KeepUnqualfiedClassNamePattern namePattern) {
+    assert packagePattern != null;
+    assert namePattern != null;
+    this.packagePattern = packagePattern;
+    this.namePattern = namePattern;
+  }
+
+  public boolean isAny() {
+    return packagePattern.isAny() && namePattern.isAny();
+  }
+
+  public KeepPackagePattern getPackagePattern() {
+    return packagePattern;
+  }
+
+  public KeepUnqualfiedClassNamePattern getNamePattern() {
+    return namePattern;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    KeepQualifiedClassNamePattern that = (KeepQualifiedClassNamePattern) o;
+    return packagePattern.equals(that.packagePattern) && namePattern.equals(that.namePattern);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(packagePattern.hashCode(), namePattern.hashCode());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java
new file mode 100644
index 0000000..a63a34a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java
@@ -0,0 +1,56 @@
+// 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.experimental.keepanno.ast;
+
+public class KeepTarget {
+
+  public static KeepTarget any() {
+    return KeepTarget.builder().setItem(KeepItemPattern.any()).build();
+  }
+
+  public static class Builder {
+
+    private KeepItemPattern item;
+    private KeepOptions options = KeepOptions.keepAll();
+
+    private Builder() {}
+
+    public Builder setItem(KeepItemPattern item) {
+      this.item = item;
+      return this;
+    }
+
+    public Builder setOptions(KeepOptions options) {
+      this.options = options;
+      return this;
+    }
+
+    public KeepTarget build() {
+      if (item == null) {
+        throw new KeepEdgeException("Target must define an item pattern");
+      }
+      return new KeepTarget(item, options);
+    }
+  }
+
+  private final KeepItemPattern item;
+  private final KeepOptions options;
+
+  private KeepTarget(KeepItemPattern item, KeepOptions options) {
+    this.item = item;
+    this.options = options;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public KeepItemPattern getItem() {
+    return item;
+  }
+
+  public KeepOptions getOptions() {
+    return options;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTypePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTypePattern.java
new file mode 100644
index 0000000..eff9e9c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTypePattern.java
@@ -0,0 +1,29 @@
+// 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.experimental.keepanno.ast;
+
+public abstract class KeepTypePattern {
+
+  public static KeepTypePattern any() {
+    return Any.getInstance();
+  }
+
+  private static class Any extends KeepTypePattern {
+    private static Any INSTANCE = null;
+
+    public static Any getInstance() {
+      if (INSTANCE == null) {
+        INSTANCE = new Any();
+      }
+      return INSTANCE;
+    }
+
+    @Override
+    public boolean isAny() {
+      return true;
+    }
+  }
+
+  public abstract boolean isAny();
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java
new file mode 100644
index 0000000..ab38ed2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java
@@ -0,0 +1,129 @@
+// 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.experimental.keepanno.ast;
+
+public abstract class KeepUnqualfiedClassNamePattern {
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static KeepUnqualfiedClassNamePattern any() {
+    return KeepClassNameAnyPattern.getInstance();
+  }
+
+  public static KeepUnqualfiedClassNamePattern exact(String className) {
+    return builder().exact(className).build();
+  }
+
+  public static class Builder {
+
+    private KeepUnqualfiedClassNamePattern pattern;
+
+    public Builder any() {
+      pattern = KeepClassNameAnyPattern.getInstance();
+      return this;
+    }
+
+    public Builder exact(String className) {
+      pattern = new KeepClassNameExactPattern(className);
+      return this;
+    }
+
+    public KeepUnqualfiedClassNamePattern build() {
+      if (pattern == null) {
+        throw new KeepEdgeException("Invalid class name pattern: null");
+      }
+      return pattern;
+    }
+  }
+
+  private static class KeepClassNameAnyPattern extends KeepUnqualfiedClassNamePattern {
+
+    private static KeepClassNameAnyPattern INSTANCE = null;
+
+    public static KeepClassNameAnyPattern getInstance() {
+      if (INSTANCE == null) {
+        INSTANCE = new KeepClassNameAnyPattern();
+      }
+      return INSTANCE;
+    }
+
+    private KeepClassNameAnyPattern() {}
+
+    @Override
+    public boolean isAny() {
+      return true;
+    }
+
+    @Override
+    public boolean isExact() {
+      return false;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      return this == obj;
+    }
+
+    @Override
+    public int hashCode() {
+      return System.identityHashCode(this);
+    }
+  }
+
+  public static class KeepClassNameExactPattern extends KeepUnqualfiedClassNamePattern {
+
+    private final String className;
+
+    private KeepClassNameExactPattern(String className) {
+      assert className != null;
+      this.className = className;
+    }
+
+    @Override
+    public boolean isAny() {
+      return false;
+    }
+
+    @Override
+    public boolean isExact() {
+      return true;
+    }
+
+    @Override
+    public KeepClassNameExactPattern asExact() {
+      return this;
+    }
+
+    public String getExactNameAsString() {
+      return className;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      KeepClassNameExactPattern that = (KeepClassNameExactPattern) o;
+      return className.equals(that.className);
+    }
+
+    @Override
+    public int hashCode() {
+      return className.hashCode();
+    }
+  }
+
+  public abstract boolean isAny();
+
+  public abstract boolean isExact();
+
+  public KeepClassNameExactPattern asExact() {
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java b/src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java
new file mode 100644
index 0000000..6c24ec1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java
@@ -0,0 +1,262 @@
+// 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.experimental.keepanno.keeprules;
+
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.experimental.keepanno.ast.KeepConsequences;
+import com.android.tools.r8.experimental.keepanno.ast.KeepEdge;
+import com.android.tools.r8.experimental.keepanno.ast.KeepFieldPattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepItemPattern.KeepClassPattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepMembersPattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepMethodAccessPattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepMethodNamePattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepMethodParametersPattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepMethodPattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepMethodReturnTypePattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepOptions;
+import com.android.tools.r8.experimental.keepanno.ast.KeepOptions.KeepOption;
+import com.android.tools.r8.experimental.keepanno.ast.KeepPackagePattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepPreconditions;
+import com.android.tools.r8.experimental.keepanno.ast.KeepQualifiedClassNamePattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepTarget;
+import com.android.tools.r8.experimental.keepanno.ast.KeepTypePattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepUnqualfiedClassNamePattern;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.StringUtils.BraceType;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class KeepRuleExtractor {
+
+  private final Consumer<String> ruleConsumer;
+
+  public KeepRuleExtractor(Consumer<String> ruleConsumer) {
+    this.ruleConsumer = ruleConsumer;
+  }
+
+  public void extract(KeepEdge edge) {
+    List<ItemRule> consequentRules = getConsequentRules(edge.getConsequences());
+    printConditionalRules(consequentRules, edge.getPreconditions());
+  }
+
+  private List<ItemRule> getConsequentRules(KeepConsequences consequences) {
+    List<ItemRule> consequentItems = new ArrayList<>();
+    consequences.forEachTarget(target -> consequentItems.add(new ItemRule(target)));
+    return consequentItems;
+  }
+
+  private void printConditionalRules(
+      List<ItemRule> consequentRules, KeepPreconditions preconditions) {
+    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;
+                  });
+        });
+    assert !(preconditions.isAlways() && hasAtLeastOneConditionalClause[0]);
+    if (!hasAtLeastOneConditionalClause[0]) {
+      // If there are no preconditions, print each consequent as is.
+      consequentRules.forEach(
+          r -> ruleConsumer.accept(printConsequentRule(new StringBuilder(), r).toString()));
+    }
+  }
+
+  private static StringBuilder printConsequentRule(StringBuilder builder, ItemRule rule) {
+    if (rule.isMemberConsequent()) {
+      builder.append("-keepclassmembers");
+    } else {
+      builder.append("-keep");
+    }
+    for (KeepOption option : KeepOption.values()) {
+      if (rule.options.isAllowed(option)) {
+        builder.append(",allow").append(getOptionString(option));
+      }
+    }
+    return builder.append(" ").append(rule.getKeepRuleForItem());
+  }
+
+  private static StringBuilder printClassItem(
+      StringBuilder builder, KeepClassPattern clazzPattern) {
+    builder.append("class ");
+    printClassName(builder, clazzPattern.getClassNamePattern());
+    if (!clazzPattern.getExtendsPattern().isAny()) {
+      throw new Unimplemented();
+    }
+    KeepMembersPattern members = clazzPattern.getMembersPattern();
+    if (members.isNone()) {
+      return builder;
+    }
+    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();
+    }
+  }
+
+  private static StringBuilder printMethod(StringBuilder builder, KeepMethodPattern methodPattern) {
+    if (methodPattern.isAnyMethod()) {
+      return builder.append("<methods>;");
+    }
+    printAccess(builder, " ", methodPattern.getAccessPattern());
+    printReturnType(builder, methodPattern.getReturnTypePattern());
+    builder.append(' ');
+    printMethodName(builder, methodPattern.getNamePattern());
+    printParameters(builder, methodPattern.getParametersPattern());
+    return builder.append(';');
+  }
+
+  private static StringBuilder printParameters(
+      StringBuilder builder, KeepMethodParametersPattern parametersPattern) {
+    return parametersPattern.match(
+        () -> builder.append("(***)"),
+        list -> StringUtils.append(builder, list, ", ", BraceType.PARENS));
+  }
+
+  private static StringBuilder printMethodName(
+      StringBuilder builder, KeepMethodNamePattern namePattern) {
+    return namePattern.match(() -> builder.append("*"), builder::append);
+  }
+
+  private static StringBuilder printReturnType(
+      StringBuilder builder, KeepMethodReturnTypePattern returnTypePattern) {
+    return returnTypePattern.match(
+        () -> builder.append("void"), typePattern -> printType(builder, typePattern));
+  }
+
+  private static StringBuilder printType(StringBuilder builder, KeepTypePattern typePattern) {
+    if (typePattern.isAny()) {
+      return builder.append("*");
+    }
+    throw new Unimplemented();
+  }
+
+  private static StringBuilder printAccess(
+      StringBuilder builder, String indent, KeepMethodAccessPattern accessPattern) {
+    if (accessPattern.isAny()) {
+      // No text will match any access pattern.
+      // Don't print the indent in this case.
+      return builder;
+    }
+    throw new Unimplemented();
+  }
+
+  private static StringBuilder printClassName(
+      StringBuilder builder, KeepQualifiedClassNamePattern classNamePattern) {
+    if (classNamePattern.isAny()) {
+      return builder.append('*');
+    }
+    printPackagePrefix(builder, classNamePattern.getPackagePattern());
+    return printSimpleClassName(builder, classNamePattern.getNamePattern());
+  }
+
+  private static StringBuilder printPackagePrefix(
+      StringBuilder builder, KeepPackagePattern packagePattern) {
+    if (packagePattern.isAny()) {
+      return builder.append("**.");
+    }
+    if (packagePattern.isTop()) {
+      return builder;
+    }
+    assert packagePattern.isExact();
+    return builder.append(packagePattern.asExact().getExactPackageAsString()).append('.');
+  }
+
+  private static StringBuilder printSimpleClassName(
+      StringBuilder builder, KeepUnqualfiedClassNamePattern namePattern) {
+    if (namePattern.isAny()) {
+      return builder.append('*');
+    }
+    assert namePattern.isExact();
+    return builder.append(namePattern.asExact().getExactNameAsString());
+  }
+
+  private static String getOptionString(KeepOption option) {
+    switch (option) {
+      case SHRINKING:
+        return "shrinking";
+      case OPTIMIZING:
+        return "optimization";
+      case OBFUSCATING:
+        return "obfuscation";
+      case ACCESS_MODIFING:
+        return "accessmodification";
+      default:
+        throw new Unimplemented();
+    }
+  }
+
+  private static class ItemRule {
+    private final KeepTarget target;
+    private final KeepOptions options;
+    private String ruleLine = null;
+
+    public ItemRule(KeepTarget target) {
+      this.target = target;
+      this.options = target.getOptions();
+    }
+
+    public boolean isMemberConsequent() {
+      return target.getItem().match(() -> false, clazz -> !clazz.getMembersPattern().isNone());
+    }
+
+    public KeepQualifiedClassNamePattern getHolderPattern() {
+      return target
+          .getItem()
+          .match(KeepQualifiedClassNamePattern::any, KeepClassPattern::getClassNamePattern);
+    }
+
+    public String getKeepRuleForItem() {
+      if (ruleLine == null) {
+        ruleLine =
+            target
+                .getItem()
+                .match(
+                    () -> "class * { *; }",
+                    clazz -> printClassItem(new StringBuilder(), clazz).toString());
+      }
+      return ruleLine;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 85bc79c..cd7b5b9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -132,13 +132,11 @@
   BasicBlock nextBlock;
 
   public DexBuilder(
-      AppView<?> appView,
       IRCode ir,
       BytecodeMetadataProvider bytecodeMetadataProvider,
       RegisterAllocator registerAllocator,
       InternalOptions options) {
     this(
-        appView,
         ir,
         bytecodeMetadataProvider,
         registerAllocator,
@@ -147,14 +145,13 @@
   }
 
   public DexBuilder(
-      AppView<?> appView,
       IRCode ir,
       BytecodeMetadataProvider bytecodeMetadataProvider,
       RegisterAllocator registerAllocator,
       InternalOptions options,
       MethodConversionOptions conversionOptions) {
     assert ir == null || conversionOptions == ir.getConversionOptions();
-    this.appView = appView;
+    this.appView = registerAllocator.getAppView();
     this.ir = ir;
     this.bytecodeMetadataBuilder = BytecodeMetadata.builder(bytecodeMetadataProvider);
     this.registerAllocator = registerAllocator;
@@ -173,7 +170,6 @@
     DexBuilder builder =
         new DexBuilder(
             null,
-            null,
             BytecodeMetadataProvider.empty(),
             allocator,
             allocator.options(),
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
index cbd0d06..cf59a7e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
@@ -51,8 +51,7 @@
     RuntimeWorkaroundCodeRewriter.workaroundExceptionTargetingLoopHeaderBug(code, options);
     // Perform register allocation.
     RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing);
-    return new DexBuilder(appView, code, bytecodeMetadataProvider, registerAllocator, options)
-        .build();
+    return new DexBuilder(code, bytecodeMetadataProvider, registerAllocator, options).build();
   }
 
   private RegisterAllocator performRegisterAllocation(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index b1ea459..7f0fd6f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -1155,12 +1155,17 @@
     if (field == null) {
       return Reason.INVALID_FIELD_PUT;
     }
-    DexProgramClass dexClass = appView.programDefinitionFor(field.getHolderType(), code.context());
-    if (dexClass == null) {
+    DexProgramClass holderClass =
+        appView.programDefinitionFor(field.getHolderType(), code.context());
+    if (holderClass == null) {
       return Reason.INVALID_FIELD_PUT;
     }
     if (fieldPut.isInstancePut() && fieldPut.asInstancePut().object() == enumValue) {
-      return Reason.ELIGIBLE;
+      // TODO(b/249752942): The requirement to be inside an initializer of the enum can be relaxed
+      //  if we support puts.
+      return context.getHolder() == enumClass && context.getDefinition().isInstanceInitializer()
+          ? Reason.ELIGIBLE
+          : Reason.ASSIGNMENT_OUTSIDE_INIT;
     }
     // The put value has to be of the field type.
     if (field.getReference().type.toBaseType(factory) != enumClass.type) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
index f2fbb4c..9a88b2e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -221,6 +221,12 @@
 
       int fallthroughBlockIndex = switchInsn.getFallthroughBlockIndex();
       if (ordinalToTargetMap.size() < switchInsn.numberOfKeys()) {
+        if (block.numberOfNormalSuccessors() != switchInsn.numberOfKeys() + 1) {
+          // This can happen in extremely rare cases where several switch targets are the same
+          // block (See b/231804008).
+          // TODO(b/249052389): Support removing switch map for such switches.
+          continue;
+        }
         // There is at least one dead switch case. This can happen when some dependencies use
         // different versions of the same enum.
         int numberOfNormalSuccessors = switchInsn.numberOfKeys() + 1;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
index 63fc7fd..3e17a0a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
@@ -34,6 +34,7 @@
   public static final Reason INVALID_ARRAY_PUT = new StringReason("INVALID_ARRAY_PUT");
   public static final Reason TYPE_MISMATCH_FIELD_PUT = new StringReason("TYPE_MISMATCH_FIELD_PUT");
   public static final Reason INVALID_IF_TYPES = new StringReason("INVALID_IF_TYPES");
+  public static final Reason ASSIGNMENT_OUTSIDE_INIT = new StringReason("ASSIGNMENT_OUTSIDE_INIT");
   public static final Reason ENUM_METHOD_CALLED_WITH_NULL_RECEIVER =
       new StringReason("ENUM_METHOD_CALLED_WITH_NULL_RECEIVER");
   public static final Reason OTHER_UNSUPPORTED_INSTRUCTION =
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 90eb8a9..15c762b 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -640,6 +640,11 @@
     return appView.options();
   }
 
+  @Override
+  public AppView<?> getAppView() {
+    return appView;
+  }
+
   private ImmutableList<BasicBlock> computeLivenessInformation() {
     ImmutableList<BasicBlock> blocks = code.numberInstructions();
     liveAtEntrySets = code.computeLiveAtEntrySets();
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
index 28fe1f9..6c2221c 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.regalloc;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.Value;
@@ -19,6 +20,8 @@
 
   InternalOptions options();
 
+  AppView<?> getAppView();
+
   void mergeBlocks(BasicBlock kept, BasicBlock removed);
 
   boolean hasEqualTypesAtEntry(BasicBlock first, BasicBlock second);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
index 9ddddfe..100d61a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -54,6 +54,7 @@
   private final String inlineClassUnderlyingPropertyName;
   private final KotlinTypeInfo inlineClassUnderlyingType;
   private final int jvmFlags;
+  private final String companionObjectName;
 
   // List of tracked assignments of kotlin metadata.
   private final KotlinMetadataMembersTracker originalMembersWithKotlinInfo;
@@ -78,7 +79,8 @@
       String inlineClassUnderlyingPropertyName,
       KotlinTypeInfo inlineClassUnderlyingType,
       KotlinMetadataMembersTracker originalMembersWithKotlinInfo,
-      int jvmFlags) {
+      int jvmFlags,
+      String companionObjectName) {
     this.flags = flags;
     this.name = name;
     this.nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin =
@@ -100,6 +102,7 @@
     this.inlineClassUnderlyingType = inlineClassUnderlyingType;
     this.originalMembersWithKotlinInfo = originalMembersWithKotlinInfo;
     this.jvmFlags = jvmFlags;
+    this.companionObjectName = companionObjectName;
   }
 
   public static KotlinClassInfo create(
@@ -154,7 +157,7 @@
             keepByteCode,
             extensionInformation,
             originalMembersWithKotlinInfo);
-    setCompanionObject(kmClass, hostClass, reporter);
+    String companionObjectName = setCompanionObject(kmClass, hostClass, reporter);
     KotlinTypeReference anonymousObjectOrigin = getAnonymousObjectOrigin(kmClass, factory);
     boolean nameCanBeDeducedFromClassOrOrigin =
         kmClass.name.equals(
@@ -183,7 +186,8 @@
         kmClass.getInlineClassUnderlyingPropertyName(),
         KotlinTypeInfo.create(kmClass.getInlineClassUnderlyingType(), factory, reporter),
         originalMembersWithKotlinInfo,
-        JvmExtensionsKt.getJvmFlags(kmClass));
+        JvmExtensionsKt.getJvmFlags(kmClass),
+        companionObjectName);
   }
 
   private static KotlinTypeReference getAnonymousObjectOrigin(
@@ -228,19 +232,20 @@
     return superTypeInfos.build();
   }
 
-  private static void setCompanionObject(KmClass kmClass, DexClass hostClass, Reporter reporter) {
+  private static String setCompanionObject(KmClass kmClass, DexClass hostClass, Reporter reporter) {
     String companionObjectName = kmClass.getCompanionObject();
     if (companionObjectName == null) {
-      return;
+      return companionObjectName;
     }
     for (DexEncodedField field : hostClass.fields()) {
       if (field.getReference().name.toString().equals(companionObjectName)) {
         field.setKotlinMemberInfo(new KotlinCompanionInfo(companionObjectName));
-        return;
+        return companionObjectName;
       }
     }
     reporter.warning(
         KotlinMetadataDiagnostic.missingCompanionObject(hostClass, companionObjectName));
+    return companionObjectName;
   }
 
   @Override
@@ -284,6 +289,7 @@
       rewritten |= !name.equals(rewrittenName);
     }
     // Find a companion object.
+    boolean foundCompanion = false;
     for (DexEncodedField field : clazz.fields()) {
       if (field.getKotlinInfo().isCompanion()) {
         rewritten |=
@@ -291,8 +297,14 @@
                 .getKotlinInfo()
                 .asCompanion()
                 .rewrite(kmClass, field.getReference(), appView.getNamingLens());
+        foundCompanion = true;
       }
     }
+    // If we did not find a companion but it was there on input we have to emit a new metadata
+    // object.
+    if (!foundCompanion && companionObjectName != null) {
+      rewritten = true;
+    }
     // Take all not backed constructors because we will never find them in definitions.
     for (KotlinConstructorInfo constructorInfo : constructorsWithNoBacking) {
       rewritten |= constructorInfo.rewrite(kmClass, null, appView);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 1b77e82..09c87e2 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -137,6 +137,7 @@
 import com.android.tools.r8.shaking.EnqueuerWorklist.TraceStaticFieldWriteAction;
 import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
 import com.android.tools.r8.shaking.KeepInfoCollection.MutableKeepInfoCollection;
+import com.android.tools.r8.shaking.KeepMethodInfo.Joiner;
 import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSet;
 import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
 import com.android.tools.r8.shaking.RootSetUtils.RootSet;
@@ -943,9 +944,19 @@
       if (clazz.hasDefaultInitializer()) {
         ProgramMethod defaultInitializer = clazz.getProgramDefaultInitializer();
         if (forceProguardCompatibility) {
-          workList.enqueueMarkMethodKeptAction(
-              defaultInitializer,
-              graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer));
+          Joiner joiner = KeepMethodInfo.newEmptyJoiner();
+          for (ProguardKeepRuleBase rule : rules) {
+            if (!rule.getType().equals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS)) {
+              joiner.addRule(rule);
+            }
+          }
+          if (!joiner.getRules().isEmpty()) {
+            workList.enqueueMarkMethodKeptAction(
+                defaultInitializer,
+                graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer));
+            applyMinimumKeepInfoWhenLiveOrTargeted(
+                defaultInitializer, joiner.disallowOptimization());
+          }
         }
         if (clazz.isExternalizable(appView)) {
           workList.enqueueMarkMethodLiveAction(defaultInitializer, defaultInitializer, witness);
@@ -2049,7 +2060,8 @@
             || appView.appInfo().getMainDexInfo().isTracedRoot(clazz, appView.getSyntheticItems())
         : "Class " + clazz.toSourceString() + " was not a main dex root in the first round";
 
-    assert !appView.unboxedEnums().isUnboxedEnum(clazz);
+    assert !appView.unboxedEnums().isUnboxedEnum(clazz)
+        : "Enum " + clazz.toSourceString() + " has been unboxed but is still in the program.";
 
     if (options.isGeneratingClassFiles() && clazz.hasPermittedSubclassAttributes()) {
       throw new CompilationError(
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index f07a328..6bf0a8f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1988,8 +1988,7 @@
     public boolean allowInvokeErrors = false;
     public boolean allowUnnecessaryDontWarnWildcards = true;
     public boolean allowUnusedDontWarnRules = true;
-    public boolean reportUnusedProguardConfigurationRules =
-        System.getProperty("com.android.tools.r8.reportUnusedProguardConfigurationRules") != null;
+    public boolean reportUnusedProguardConfigurationRules = true;
     public boolean alwaysUseExistingAccessInfoCollectionsInMemberRebinding = true;
     public boolean alwaysUsePessimisticRegisterAllocation = false;
     public boolean enableCheckCastAndInstanceOfRemoval = true;
diff --git a/src/test/java/com/android/tools/r8/L8TestBuilder.java b/src/test/java/com/android/tools/r8/L8TestBuilder.java
index 4a22769..7d2ea31 100644
--- a/src/test/java/com/android/tools/r8/L8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/L8TestBuilder.java
@@ -6,8 +6,10 @@
 
 import static junit.framework.Assert.assertNull;
 import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.errors.UnusedProguardKeepRuleDiagnostic;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
 import com.android.tools.r8.origin.Origin;
@@ -249,14 +251,28 @@
             || warnings.stream()
                 .allMatch(warn -> warn.getDiagnosticMessage().contains("org.testng.Assert")));
     List<Diagnostic> infos = diagnosticsMessages.getInfos();
-    // The rewriting confuses the generic signatures in some methods. Such signatures are never
-    // used by tools (they use the non library desugared version) and are stripped when compiling
-    // with R8 anyway.
-    // TODO(b/243483320): Investigate the Invalid signature.
-    assertTrue(
-        infos.isEmpty()
-            || infos.stream()
-                .allMatch(info -> info.getDiagnosticMessage().contains("Invalid signature ")));
+    for (Diagnostic info : infos) {
+      // The rewriting confuses the generic signatures in some methods. Such signatures are never
+      // used by tools (they use the non library desugared version) and are stripped when compiling
+      // with R8 anyway.
+      if (info instanceof UnusedProguardKeepRuleDiagnostic) {
+        // The default keep rules on desugared library may be unused. They should all be defined
+        // with keepclassmembers.
+        if (info.getDiagnosticMessage().contains("keepclassmembers")) {
+          continue;
+        }
+        // We allow info regarding the extended version of desugared library for JDK11 testing.
+        if (info.getDiagnosticMessage().contains("org.testng.")) {
+          continue;
+        }
+        fail("Unexpected unused proguard keep rule diagnostic: " + info.getDiagnosticMessage());
+      }
+      // TODO(b/243483320): Investigate the Invalid signature.
+      if (info.getDiagnosticMessage().contains("Invalid signature ")) {
+        continue;
+      }
+      fail("Unexpected info diagnostic: " + info.getDiagnosticMessage());
+    }
   }
 
   private L8Command.Builder addProgramClassFileData(L8Command.Builder builder) {
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
index 73dbba2..fd3e16d 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.compilerapi.assertionconfiguration.AssertionConfigurationTest;
 import com.android.tools.r8.compilerapi.classconflictresolver.ClassConflictResolverTest;
 import com.android.tools.r8.compilerapi.desugardependencies.DesugarDependenciesTest;
+import com.android.tools.r8.compilerapi.diagnostics.ProguardKeepRuleDiagnosticsApiTest;
 import com.android.tools.r8.compilerapi.diagnostics.UnsupportedFeaturesDiagnosticApiTest;
 import com.android.tools.r8.compilerapi.globalsynthetics.GlobalSyntheticsTest;
 import com.android.tools.r8.compilerapi.inputdependencies.InputDependenciesTest;
@@ -56,7 +57,8 @@
       ImmutableList.of(
           ArtProfilesForRewritingApiTest.ApiTest.class,
           StartupProfileApiTest.ApiTest.class,
-          ClassConflictResolverTest.ApiTest.class);
+          ClassConflictResolverTest.ApiTest.class,
+          ProguardKeepRuleDiagnosticsApiTest.ApiTest.class);
 
   private final TemporaryFolder temp;
 
diff --git a/src/test/java/com/android/tools/r8/compilerapi/diagnostics/ProguardKeepRuleDiagnosticsApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/diagnostics/ProguardKeepRuleDiagnosticsApiTest.java
new file mode 100644
index 0000000..fedb449
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/diagnostics/ProguardKeepRuleDiagnosticsApiTest.java
@@ -0,0 +1,117 @@
+// 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.compilerapi.diagnostics;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.DiagnosticsLevel;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.errors.ProguardKeepRuleDiagnostic;
+import com.android.tools.r8.errors.UnusedProguardKeepRuleDiagnostic;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import java.util.Collections;
+import org.junit.Test;
+
+public class ProguardKeepRuleDiagnosticsApiTest extends CompilerApiTestRunner {
+
+  public ProguardKeepRuleDiagnosticsApiTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    runTest(test::runR8);
+  }
+
+  private void runTest(ThrowingConsumer<DiagnosticsHandler, Exception> test) throws Exception {
+    TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
+    test.accept(diagnostics);
+    diagnostics
+        .assertOnlyWarnings()
+        // Check the diagnostic is an instance of both types.
+        .assertWarningsMatch(
+            allOf(
+                diagnosticType(UnusedProguardKeepRuleDiagnostic.class),
+                diagnosticMessage(containsString("does not match anything"))))
+        .assertWarningsMatch(
+            allOf(
+                diagnosticType(ProguardKeepRuleDiagnostic.class),
+                diagnosticMessage(containsString("does not match anything"))));
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    public void runR8(DiagnosticsHandler handler) throws Exception {
+      R8.run(
+          R8Command.builder(
+                  new DiagnosticsHandler() {
+                    @Override
+                    public DiagnosticsLevel modifyDiagnosticsLevel(
+                        DiagnosticsLevel level, Diagnostic diagnostic) {
+                      if (diagnostic instanceof ProguardKeepRuleDiagnostic
+                          || diagnostic instanceof UnusedProguardKeepRuleDiagnostic) {
+                        return handler.modifyDiagnosticsLevel(DiagnosticsLevel.WARNING, diagnostic);
+                      }
+                      return handler.modifyDiagnosticsLevel(level, diagnostic);
+                    }
+
+                    @Override
+                    public void error(Diagnostic error) {
+                      handler.error(error);
+                    }
+
+                    @Override
+                    public void warning(Diagnostic warning) {
+                      handler.warning(warning);
+                    }
+
+                    @Override
+                    public void info(Diagnostic info) {
+                      handler.info(info);
+                    }
+                  })
+              .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+              .addProguardConfiguration(getKeepMainRules(getMockClass()), Origin.unknown())
+              .addProguardConfiguration(
+                  Collections.singletonList("-keep class NotPresent {}"), Origin.unknown())
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+              .setMinApiLevel(1)
+              .build());
+    }
+
+    @Test
+    public void testR8() throws Exception {
+      runR8(
+          new DiagnosticsHandler() {
+            @Override
+            public void warning(Diagnostic warning) {
+              // ignore the warnings.
+            }
+          });
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryWarningTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryWarningTest.java
index 2bd0f31..40942f7 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryWarningTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryWarningTest.java
@@ -24,14 +24,32 @@
 @RunWith(Parameterized.class)
 public class DesugaredLibraryWarningTest extends DesugaredLibraryTestBase {
 
-  private static final String FUNCTION_KEEP =
-      "-keep class j$.util.function.Function$-CC {\n"
-          + "    j$.util.function.Function $default$compose(j$.util.function.Function,"
-          + " j$.util.function.Function);\n"
-          + "    j$.util.function.Function $default$andThen(j$.util.function.Function,"
-          + " j$.util.function.Function);\n"
-          + "}\n"
-          + "-keep class j$.util.function.Function { *; }";
+  private static final String getFunctionKeep(String prefix) {
+    return "-keep class j$.util.function.Function$-CC {\n"
+        + "    "
+        + prefix
+        + ".util.function.Function $default$compose("
+        + prefix
+        + ".util.function.Function,"
+        + " "
+        + prefix
+        + ".util.function.Function);\n"
+        + "    "
+        + prefix
+        + ".util.function.Function $default$andThen("
+        + prefix
+        + ".util.function.Function,"
+        + " "
+        + prefix
+        + ".util.function.Function);\n"
+        + "}\n"
+        + "-keep class "
+        + prefix
+        + ".util.function.Function { *; }";
+  }
+
+  private static final String FUNCTION_KEEP_J$ = getFunctionKeep("j$");
+  private static final String FUNCTION_KEEP_JAVA = getFunctionKeep("java");
 
   private final TestParameters parameters;
   private final CompilationSpecification compilationSpecification;
@@ -60,18 +78,24 @@
         .apply(
             l8TestBuilder ->
                 libraryDesugaringSpecification.configureL8TestBuilder(
-                    l8TestBuilder, compilationSpecification.isL8Shrink(), FUNCTION_KEEP))
+                    l8TestBuilder,
+                    compilationSpecification.isL8Shrink(),
+                    libraryDesugaringSpecification.hasEmulatedInterfaceDesugaring(parameters)
+                        ? libraryDesugaringSpecification.hasJDollarFunction(parameters)
+                            ? FUNCTION_KEEP_J$
+                            : FUNCTION_KEEP_JAVA
+                        : ""))
         .compile()
         .inspectDiagnosticMessages(
             diagnosticsHandler -> {
+              diagnosticsHandler.assertNoErrors();
               if (libraryDesugaringSpecification != JDK8) {
-                diagnosticsHandler.assertNoErrors();
                 diagnosticsHandler.assertAllWarningsMatch(
                     diagnosticMessage(containsString("Specification conversion")));
               } else {
-
-                diagnosticsHandler.assertNoMessages();
+                diagnosticsHandler.assertNoWarnings();
               }
+              diagnosticsHandler.assertNoInfos();
             });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DontKeepBootstrapClassesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DontKeepBootstrapClassesTest.java
index 542c71e..058f6cf 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DontKeepBootstrapClassesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DontKeepBootstrapClassesTest.java
@@ -61,9 +61,6 @@
                         .anyMatch(
                             kr ->
                                 kr.contains("-keep class " + prefix + ".util.function.Consumer")));
-                // TODO(b/158635415): Don't generate keep rules targeting items outside desugared
-                // library.
-                assertTrue(keepRule.stream().anyMatch(kr -> kr.contains("-keep class java.util")));
               }
             });
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/PathTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/PathTest.java
index 26e16bc..104876d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/PathTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/PathTest.java
@@ -66,8 +66,11 @@
   @Test
   public void test() throws Throwable {
     testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
-        .addL8KeepRules("-keepnames class j$.desugar.sun.nio.fs.**")
-        .addL8KeepRules("-keepnames class j$.nio.file.FileSystem**")
+        .applyIf(
+            libraryDesugaringSpecification.hasNioFileDesugaring(parameters),
+            b ->
+                b.addL8KeepRules("-keepnames class j$.desugar.sun.nio.fs.**")
+                    .addL8KeepRules("-keepnames class j$.nio.file.FileSystem**"))
         .addInnerClasses(PathTest.class)
         .addKeepMainRule(TestClass.class)
         .compile()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumInEnumFieldTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumInEnumFieldTest.java
new file mode 100644
index 0000000..d2fe030
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumInEnumFieldTest.java
@@ -0,0 +1,86 @@
+// 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.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** This is a regression for b/247146910. */
+@RunWith(Parameterized.class)
+public class EnumInEnumFieldTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final EnumKeepRules enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public EnumInEnumFieldTest(
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(EnumInEnumFieldTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("0");
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(EnumInEnumFieldTest.class)
+        .addKeepMainRule(Main.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .addEnumUnboxingInspector(
+            inspector -> {
+              inspector.assertNotUnboxed(MyEnum.class);
+              inspector.assertUnboxed(OtherEnum.class);
+            })
+        .addNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("0");
+  }
+
+  @NeverClassInline
+  public enum OtherEnum {
+    C,
+    D;
+  }
+
+  @NeverClassInline
+  public enum MyEnum {
+    A,
+    B;
+
+    public OtherEnum otherEnum;
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) throws Exception {
+      set(System.currentTimeMillis() > 0 ? OtherEnum.C : OtherEnum.D);
+      System.out.println(MyEnum.A.otherEnum.ordinal());
+    }
+
+    public static void set(OtherEnum otherEnum) {
+      MyEnum.A.otherEnum = otherEnum;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceInsideAndOutsideTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceInsideAndOutsideTest.java
new file mode 100644
index 0000000..55cdede
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceInsideAndOutsideTest.java
@@ -0,0 +1,83 @@
+// 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.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * This is a variant of a regression test for b/247146910 where the instance field on the enum is
+ * unrelated to enum unboxing.
+ */
+@RunWith(Parameterized.class)
+public class EnumInstanceFieldReferenceInsideAndOutsideTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final EnumKeepRules enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public EnumInstanceFieldReferenceInsideAndOutsideTest(
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(EnumInstanceFieldReferenceInsideAndOutsideTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("42");
+  }
+
+  @Test
+  public void testEnumUnboxingAllowNotPrunedEnums() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(EnumInstanceFieldReferenceInsideAndOutsideTest.class)
+        .addKeepMainRule(Main.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .addNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .addEnumUnboxingInspector(inspector -> inspector.assertNotUnboxed(MyEnum.class))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("42");
+  }
+
+  @NeverClassInline
+  public enum MyEnum {
+    A(10),
+    B(20);
+
+    public int instanceValue;
+
+    MyEnum(int instanceValue) {
+      this.instanceValue = instanceValue;
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) throws Exception {
+      set(System.currentTimeMillis() > 0 ? 42 : 0);
+      System.out.println(MyEnum.B.instanceValue);
+    }
+
+    public static void set(int newValue) {
+      MyEnum.B.instanceValue = newValue;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceOutsideTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceOutsideTest.java
new file mode 100644
index 0000000..4dac1af
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceOutsideTest.java
@@ -0,0 +1,79 @@
+// 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.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * This is a variant of a regression test for b/247146910 where the instance field on the enum is
+ * unrelated to enum unboxing.
+ */
+@RunWith(Parameterized.class)
+public class EnumInstanceFieldReferenceOutsideTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final EnumKeepRules enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public EnumInstanceFieldReferenceOutsideTest(
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(EnumInstanceFieldReferenceOutsideTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("42");
+  }
+
+  @Test
+  public void testEnumUnboxingAllowNotPrunedEnums() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(EnumInstanceFieldReferenceOutsideTest.class)
+        .addKeepMainRule(Main.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertNotUnboxed(MyEnum.class))
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .addNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("42");
+  }
+
+  @NeverClassInline
+  public enum MyEnum {
+    A,
+    B;
+
+    public int instanceValue = 1;
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) throws Exception {
+      set(System.currentTimeMillis() > 0 ? 42 : 0);
+      System.out.println(MyEnum.B.instanceValue);
+    }
+
+    public static void set(int newValue) {
+      MyEnum.B.instanceValue = newValue;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java b/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java
new file mode 100644
index 0000000..d69d055
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java
@@ -0,0 +1,191 @@
+// 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.experimental.keepanno.ast;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.experimental.keepanno.ast.KeepOptions.KeepOption;
+import com.android.tools.r8.experimental.keepanno.keeprules.KeepRuleExtractor;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepEdgeApiTest extends TestBase {
+
+  private static String CLASS = "com.example.Foo";
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public KeepEdgeApiTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  public static String extract(KeepEdge edge) {
+    StringBuilder builder = new StringBuilder();
+    KeepRuleExtractor extractor = new KeepRuleExtractor(rule -> builder.append(rule).append('\n'));
+    extractor.extract(edge);
+    return builder.toString();
+  }
+
+  @Test
+  public void testKeepAll() {
+    KeepEdge edge =
+        KeepEdge.builder()
+            .setConsequences(
+                KeepConsequences.builder()
+                    .addTarget(KeepTarget.builder().setItem(KeepItemPattern.any()).build())
+                    .build())
+            .build();
+    assertEquals(StringUtils.unixLines("-keep class * { *; }"), extract(edge));
+  }
+
+  @Test
+  public void testSoftPinViaDisallow() {
+    KeepEdge edge =
+        KeepEdge.builder()
+            .setConsequences(
+                KeepConsequences.builder()
+                    .addTarget(
+                        KeepTarget.builder()
+                            .setItem(KeepItemPattern.any())
+                            .setOptions(KeepOptions.disallow(KeepOption.OPTIMIZING))
+                            .build())
+                    .build())
+            .build();
+    // Disallow will issue the full inverse of the known options, e.g., 'allowaccessmodification'.
+    assertEquals(
+        StringUtils.unixLines(
+            "-keep,allowshrinking,allowobfuscation,allowaccessmodification class * { *; }"),
+        extract(edge));
+  }
+
+  @Test
+  public void testSoftPinViaAllow() {
+    KeepEdge edge =
+        KeepEdge.builder()
+            .setConsequences(
+                KeepConsequences.builder()
+                    .addTarget(
+                        KeepTarget.builder()
+                            .setItem(KeepItemPattern.any())
+                            .setOptions(
+                                KeepOptions.allow(KeepOption.OBFUSCATING, KeepOption.SHRINKING))
+                            .build())
+                    .build())
+            .build();
+    // Allow is just the ordered list of options.
+    assertEquals(
+        StringUtils.unixLines("-keep,allowshrinking,allowobfuscation class * { *; }"),
+        extract(edge));
+  }
+
+  @Test
+  public void testKeepClass() throws Exception {
+    KeepTarget target = target(classItem(CLASS));
+    KeepConsequences consequences = KeepConsequences.builder().addTarget(target).build();
+    KeepEdge edge = KeepEdge.builder().setConsequences(consequences).build();
+    assertEquals(StringUtils.unixLines("-keep class " + CLASS), extract(edge));
+  }
+
+  @Test
+  public void testKeepInitIfReferenced() throws Exception {
+    KeepEdge edge =
+        KeepEdge.builder()
+            .setPreconditions(
+                KeepPreconditions.builder()
+                    .addCondition(
+                        KeepCondition.builder()
+                            .setItem(classItem(CLASS))
+                            .build())
+                    .build())
+            .setConsequences(
+                KeepConsequences.builder()
+                    .addTarget(
+                        target(
+                            buildClassItem(CLASS)
+                                .setMembersPattern(defaultInitializerPattern())
+                                .build()))
+                    .build())
+            .build();
+    assertEquals(
+        StringUtils.unixLines("-keepclassmembers class " + CLASS + " { void <init>(); }"),
+        extract(edge));
+  }
+
+  @Test
+  public void testKeepInstanceIfReferenced() throws Exception {
+    KeepEdge edge =
+        KeepEdge.builder()
+            .setPreconditions(
+                KeepPreconditions.builder()
+                    .addCondition(
+                        KeepCondition.builder()
+                            .setItem(classItem(CLASS))
+                            .build())
+                    .build())
+            .setConsequences(KeepConsequences.builder().addTarget(target(classItem(CLASS))).build())
+            .build();
+    assertEquals(
+        StringUtils.unixLines("-if class " + CLASS + " -keep class " + CLASS), extract(edge));
+  }
+
+  @Test
+  public void testKeepInstanceAndInitIfReferenced() throws Exception {
+    KeepEdge edge =
+        KeepEdge.builder()
+            .setPreconditions(
+                KeepPreconditions.builder()
+                    .addCondition(
+                        KeepCondition.builder()
+                            .setItem(classItem(CLASS))
+                            .build())
+                    .build())
+            .setConsequences(
+                KeepConsequences.builder()
+                    .addTarget(target(classItem(CLASS)))
+                    .addTarget(
+                        target(
+                            buildClassItem(CLASS)
+                                .setMembersPattern(defaultInitializerPattern())
+                                .build()))
+                    .build())
+            .build();
+    assertEquals(
+        StringUtils.unixLines(
+            "-if class " + CLASS + " -keep class " + CLASS,
+            "-keepclassmembers class " + CLASS + " { void <init>(); }"),
+        extract(edge));
+  }
+
+  private KeepTarget target(KeepItemPattern item) {
+    return KeepTarget.builder().setItem(item).build();
+  }
+
+  private KeepItemPattern classItem(String typeName) {
+    return buildClassItem(typeName).build();
+  }
+
+  private KeepItemPattern.Builder buildClassItem(String typeName) {
+    return KeepItemPattern.builder().setClassPattern(KeepQualifiedClassNamePattern.exact(typeName));
+  }
+
+  private KeepMembersPattern defaultInitializerPattern() {
+    return KeepMembersPattern.builder()
+        .addMethodPattern(
+            KeepMethodPattern.builder()
+                .setNamePattern(KeepMethodNamePattern.initializer())
+                .setParametersPattern(KeepMethodParametersPattern.none())
+                .setReturnTypeVoid()
+                .build())
+        .build();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index c8760d8..5db89dc 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -6,6 +6,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -78,6 +79,11 @@
     }
 
     @Override
+    public AppView<?> getAppView() {
+      return null;
+    }
+
+    @Override
     public void mergeBlocks(BasicBlock kept, BasicBlock removed) {
       // Intentionally empty, we don't need to track merging in this allocator.
     }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteRemovedCompanionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteRemovedCompanionTest.java
new file mode 100644
index 0000000..145cb4b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteRemovedCompanionTest.java
@@ -0,0 +1,116 @@
+// 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_4_20;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.MIN_SUPPORTED_VERSION;
+
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteRemovedCompanionTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED = StringUtils.lines("Hello World!");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}, {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(),
+        getKotlinTestParameters()
+            .withOldCompilersStartingFrom(KOTLINC_1_4_20)
+            .withCompilersStartingFromIncluding(MIN_SUPPORTED_VERSION)
+            .withAllTargetVersions()
+            .build());
+  }
+
+  public MetadataRewriteRemovedCompanionTest(
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
+    this.parameters = parameters;
+  }
+
+  private static final KotlinCompileMemoizer companionRemoveJarMap =
+      getCompileMemoizer(getKotlinFileInTest(PKG_PREFIX + "/companion_remove_lib", "lib"));
+
+  @Test
+  public void smokeTest() throws Exception {
+    Path libJar = companionRemoveJarMap.getForConfiguration(kotlinc, targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/companion_remove_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".companion_remove_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testMetadataInCompanion_kept() throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
+            .addProgramFiles(companionRemoveJarMap.getForConfiguration(kotlinc, targetVersion))
+            // Keep everything
+            .addKeepRules("-keep class **.companion_remove_lib.** { *; }")
+            .addKeepKotlinMetadata()
+            // To keep ...$Companion structure
+            .addKeepAttributeInnerClassesAndEnclosingMethod()
+            .compile()
+            .writeToZip();
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/companion_remove_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".companion_remove_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testMetadataInCompanion_removedField() throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
+            .addProgramFiles(companionRemoveJarMap.getForConfiguration(kotlinc, targetVersion))
+            // Keep the ClasWithCompanion class.
+            .addKeepKotlinMetadata()
+            .addKeepRules("-keep class **.ClassWithCompanion { void <init>(); void doStuff(); }")
+            .addKeepRules("-keep class **.ClassWithCompanion$Companion")
+            .compile()
+            .writeToZip();
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/companion_remove_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".companion_remove_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_remove_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_remove_app/main.kt
new file mode 100644
index 0000000..d08a2d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_remove_app/main.kt
@@ -0,0 +1,10 @@
+// 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.kotlin.metadata.companion_remove_app
+
+import com.android.tools.r8.kotlin.metadata.companion_remove_lib.ClassWithCompanion
+
+fun main() {
+  ClassWithCompanion().doStuff()
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_remove_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_remove_lib/lib.kt
new file mode 100644
index 0000000..a16595e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_remove_lib/lib.kt
@@ -0,0 +1,14 @@
+// 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.kotlin.metadata.companion_remove_lib
+
+class ClassWithCompanion {
+  fun doStuff() {
+    println(foo)
+  }
+  companion object {
+    val foo: String
+      get() = "Hello World!"
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassMembersDefaultCtorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassMembersDefaultCtorTest.java
new file mode 100644
index 0000000..2e726b0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassMembersDefaultCtorTest.java
@@ -0,0 +1,111 @@
+// 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.shaking.forceproguardcompatibility.defaultctor;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepClassMembersDefaultCtorTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("A()");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultCfRuntime().build();
+  }
+
+  public KeepClassMembersDefaultCtorTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(KeepClassMembersDefaultCtorTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testCompatR8() throws Exception {
+    run(testForR8Compat(parameters.getBackend()), false);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    run(testForR8(parameters.getBackend()), true);
+  }
+
+  @Test
+  public void testPG() throws Exception {
+    run(testForProguard(ProguardVersion.getLatest()).addDontWarn(getClass()), false);
+  }
+
+  private TestRunResult<?> run(TestShrinkerBuilder<?, ?, ?, ?, ?> builder, boolean fullMode)
+      throws Exception {
+    return builder
+        .addInnerClasses(KeepClassMembersDefaultCtorTest.class)
+        .addKeepRules("-keepclassmembers class * { <fields>; }")
+        .addKeepClassAndMembersRules(TestClass.class)
+        .addDontObfuscate()
+        .run(parameters.getRuntime(), TestClass.class)
+        .inspectFailure(
+            inspector -> {
+              assertThat(inspector.clazz(A.class), isPresent());
+              assertThat(inspector.clazz(A.class).init(), isAbsent());
+              assertThat(inspector.clazz(A.class).uniqueFieldWithName("y"), isPresent());
+              assertThat(
+                  inspector.clazz(A.class).uniqueFieldWithName("x"),
+                  fullMode ? isAbsent() : isPresent());
+            })
+        .assertFailureWithErrorThatThrows(NoSuchMethodException.class);
+  }
+
+  static class A {
+
+    public long x = System.nanoTime();
+    public static long y = System.nanoTime();
+
+    public A() {
+      System.out.println("A()");
+    }
+  }
+
+  static class TestClass {
+
+    public static A getA() {
+      // Since TestClass.A is hard kept, the shrinker can't assume anything about the return value.
+      return null;
+    }
+
+    public static void main(String[] args) throws Exception {
+      String name = args.length == 0 ? "A" : null;
+      Class<?> clazz =
+          Class.forName(
+              TestClass.class.getPackage().getName() + ".KeepClassMembersDefaultCtorTest$" + name);
+      Object obj = clazz.getConstructor().newInstance();
+      if (args.length > 0) {
+        // Use the field so we are sure that the keep rule triggers.
+        A a = getA();
+        System.out.println(a.y);
+        System.out.println(a.x);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassesWithMembersDefaultCtorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassesWithMembersDefaultCtorTest.java
new file mode 100644
index 0000000..54ee56f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassesWithMembersDefaultCtorTest.java
@@ -0,0 +1,112 @@
+// 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.shaking.forceproguardcompatibility.defaultctor;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepClassesWithMembersDefaultCtorTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("A()");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultCfRuntime().build();
+  }
+
+  public KeepClassesWithMembersDefaultCtorTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(KeepClassesWithMembersDefaultCtorTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testCompatR8() throws Exception {
+    checkInitKept(run(testForR8Compat(parameters.getBackend())));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    checkInitNotKept(run(testForR8(parameters.getBackend())));
+  }
+
+  @Test
+  public void testPG() throws Exception {
+    checkInitKept(run(testForProguard(ProguardVersion.getLatest()).addDontWarn(getClass())));
+  }
+
+  private TestRunResult<?> run(TestShrinkerBuilder<?, ?, ?, ?, ?> builder) throws Exception {
+    return builder
+        .addInnerClasses(KeepClassesWithMembersDefaultCtorTest.class)
+        .addKeepRules("-keepclasseswithmembers class * { <fields>; }")
+        .addKeepClassAndMembersRules(TestClass.class)
+        .addDontObfuscate()
+        .run(parameters.getRuntime(), TestClass.class);
+  }
+
+  private TestRunResult<?> checkInitKept(TestRunResult<?> result) throws Exception {
+    return result
+        .inspect(inspector -> assertThat(inspector.clazz(A.class).init(), isPresent()))
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private TestRunResult<?> checkInitNotKept(TestRunResult<?> result) throws Exception {
+    return result
+        .inspectFailure(inspector -> assertThat(inspector.clazz(A.class).init(), isAbsent()))
+        .assertFailureWithErrorThatThrows(NoSuchMethodException.class);
+  }
+
+  static class A {
+
+    public long x = System.nanoTime();
+
+    public A() {
+      System.out.println("A()");
+    }
+  }
+
+  static class TestClass {
+
+    public static A getA() {
+      // Since TestClass.A is hard kept, the shrinker can't assume anything about the return value.
+      return null;
+    }
+
+    public static void main(String[] args) throws Exception {
+      String name = args.length == 0 ? "A" : null;
+      Class<?> clazz =
+          Class.forName(
+              TestClass.class.getPackage().getName()
+                  + ".KeepClassesWithMembersDefaultCtorTest$"
+                  + name);
+      Object obj = clazz.getConstructor().newInstance();
+      if (args.length > 0) {
+        // Use the field so we are sure that the keep rule triggers.
+        A a = getA();
+        System.out.println(a.x);
+      }
+    }
+  }
+}
diff --git a/third_party/opensource-apps/systemui.tar.gz.sha1 b/third_party/opensource-apps/systemui.tar.gz.sha1
index 9d5b06c..4300260 100644
--- a/third_party/opensource-apps/systemui.tar.gz.sha1
+++ b/third_party/opensource-apps/systemui.tar.gz.sha1
@@ -1 +1 @@
-19afeb80f13ccaadddd62d9e1f8f2b1e894f99c8
\ No newline at end of file
+b5b6321177b6f39ed446c5d408d570dc1598f32c
\ No newline at end of file