Merge "Avoid adding static / private interface methods to the root set."
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
new file mode 100644
index 0000000..f593f45
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
@@ -0,0 +1,212 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.graph.Descriptor;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.function.Predicate;
+
+// Per-class collection of member signatures.
+public abstract class MemberPoolCollection<T extends Descriptor> {
+
+  final Equivalence<T> equivalence;
+  final DexApplication application;
+  final Map<DexClass, MemberPool<T>> memberPools = new ConcurrentHashMap<>();
+
+  MemberPoolCollection(DexApplication application, Equivalence<T> equivalence) {
+    this.application = application;
+    this.equivalence = equivalence;
+  }
+
+  public void buildAll(ExecutorService executorService, Timing timing) throws ExecutionException {
+    timing.begin("Building member pool collection");
+    try {
+      List<Future<?>> futures = new ArrayList<>();
+      List<? extends DexClass> classes = application.classes();
+      submitAll(classes, futures, executorService);
+      ThreadUtils.awaitFutures(futures);
+    } finally {
+      timing.end();
+    }
+  }
+
+  public MemberPool<T> buildForHierarchy(
+      DexClass clazz, ExecutorService executorService, Timing timing) throws ExecutionException {
+    timing.begin("Building member pool collection");
+    try {
+      List<Future<?>> futures = new ArrayList<>();
+      submitAll(
+          getAllSuperTypesInclusive(clazz, memberPools::containsKey), futures, executorService);
+      submitAll(getAllSubTypesExclusive(clazz, memberPools::containsKey), futures, executorService);
+      ThreadUtils.awaitFutures(futures);
+    } finally {
+      timing.end();
+    }
+    return get(clazz);
+  }
+
+  public boolean hasPool(DexClass clazz) {
+    return memberPools.containsKey(clazz);
+  }
+
+  public MemberPool<T> get(DexClass clazz) {
+    assert hasPool(clazz);
+    return memberPools.get(clazz);
+  }
+
+  public boolean markIfNotSeen(DexClass clazz, T reference) {
+    MemberPool<T> memberPool = get(clazz);
+    Wrapper<T> key = equivalence.wrap(reference);
+    if (memberPool.hasSeen(key)) {
+      return true;
+    }
+    memberPool.seen(key);
+    return false;
+  }
+
+  private void submitAll(
+      Iterable<? extends DexClass> classes,
+      List<Future<?>> futures,
+      ExecutorService executorService) {
+    for (DexClass clazz : classes) {
+      futures.add(executorService.submit(computeMemberPoolForClass(clazz)));
+    }
+  }
+
+  abstract Runnable computeMemberPoolForClass(DexClass clazz);
+
+  // TODO(jsjeon): maybe be part of AppInfoWithSubtyping?
+  private Set<DexClass> getAllSuperTypesInclusive(
+      DexClass subject, Predicate<DexClass> stoppingCriterion) {
+    Set<DexClass> superTypes = new HashSet<>();
+    Deque<DexClass> worklist = new ArrayDeque<>();
+    worklist.add(subject);
+    while (!worklist.isEmpty()) {
+      DexClass clazz = worklist.pop();
+      if (stoppingCriterion.test(clazz)) {
+        continue;
+      }
+      if (superTypes.add(clazz)) {
+        if (clazz.superType != null) {
+          addNonNull(worklist, application.definitionFor(clazz.superType));
+        }
+        for (DexType interfaceType : clazz.interfaces.values) {
+          addNonNull(worklist, application.definitionFor(interfaceType));
+        }
+      }
+    }
+    return superTypes;
+  }
+
+  // TODO(jsjeon): maybe be part of AppInfoWithSubtyping?
+  private Set<DexClass> getAllSubTypesExclusive(
+      DexClass subject, Predicate<DexClass> stoppingCriterion) {
+    Set<DexClass> subTypes = new HashSet<>();
+    Deque<DexClass> worklist = new ArrayDeque<>();
+    subject.type.forAllExtendsSubtypes(
+        type -> addNonNull(worklist, application.definitionFor(type)));
+    subject.type.forAllImplementsSubtypes(
+        type -> addNonNull(worklist, application.definitionFor(type)));
+    while (!worklist.isEmpty()) {
+      DexClass clazz = worklist.pop();
+      if (stoppingCriterion.test(clazz)) {
+        continue;
+      }
+      if (subTypes.add(clazz)) {
+        clazz.type.forAllExtendsSubtypes(
+            type -> addNonNull(worklist, application.definitionFor(type)));
+        clazz.type.forAllImplementsSubtypes(
+            type -> addNonNull(worklist, application.definitionFor(type)));
+      }
+    }
+    return subTypes;
+  }
+
+  public static class MemberPool<T> {
+    private Equivalence<T> equivalence;
+    private MemberPool<T> superType;
+    private final Set<MemberPool<T>> interfaces = new HashSet<>();
+    private final Set<MemberPool<T>> subTypes = new HashSet<>();
+    private final Set<Wrapper<T>> memberPool = new HashSet<>();
+
+    MemberPool(Equivalence<T> equivalence) {
+      this.equivalence = equivalence;
+    }
+
+    synchronized void linkSupertype(MemberPool<T> superType) {
+      assert this.superType == null;
+      this.superType = superType;
+    }
+
+    synchronized void linkSubtype(MemberPool<T> subType) {
+      boolean added = subTypes.add(subType);
+      assert added;
+    }
+
+    synchronized void linkInterface(MemberPool<T> itf) {
+      boolean added = interfaces.add(itf);
+      assert added;
+    }
+
+    public void seen(T descriptor) {
+      seen(equivalence.wrap(descriptor));
+    }
+
+    public synchronized void seen(Wrapper<T> descriptor) {
+      boolean added = memberPool.add(descriptor);
+      assert added;
+    }
+
+    public boolean hasSeen(T descriptor) {
+      return hasSeen(equivalence.wrap(descriptor));
+    }
+
+    public boolean hasSeen(Wrapper<T> descriptor) {
+      return hasSeenUpwardRecursive(descriptor) || hasSeenDownwardRecursive(descriptor);
+    }
+
+    public boolean hasSeenDirectly(T descriptor) {
+      return hasSeenDirectly(equivalence.wrap(descriptor));
+    }
+
+    public boolean hasSeenDirectly(Wrapper<T> descriptor) {
+      return memberPool.contains(descriptor);
+    }
+
+    private boolean hasSeenUpwardRecursive(Wrapper<T> descriptor) {
+      return memberPool.contains(descriptor)
+          || (superType != null && superType.hasSeenUpwardRecursive(descriptor))
+          || interfaces.stream().anyMatch(itf -> itf.hasSeenUpwardRecursive(descriptor));
+    }
+
+    private boolean hasSeenDownwardRecursive(Wrapper<T> reference) {
+      return memberPool.contains(reference)
+          || subTypes.stream().anyMatch(subType -> subType.hasSeenDownwardRecursive(reference));
+    }
+  }
+
+  private static <T> void addNonNull(Collection<T> collection, T item) {
+    if (item != null) {
+      collection.add(item);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
index ca38f09..68b32ea 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
@@ -7,93 +7,30 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.Timing;
-import com.google.common.base.Equivalence;
-import com.google.common.base.Equivalence.Wrapper;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Deque;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.function.Predicate;
 
 // Per-class collection of method signatures.
 //
-// Example use case: to determine if a certain method can be publicized or not.
-public class MethodPoolCollection {
-
-  private static final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
-
-  private final DexApplication application;
-  private final Map<DexClass, MethodPool> methodPools = new ConcurrentHashMap<>();
+// Example use cases:
+// *) in publicizer,
+//   to determine if a private method does not collide with methods in that class hierarchy.
+// *) in vertical class merger,
+//   before moving a default interface method to its subtype, check if it does not collide with one
+//   in the given class hierarchy.
+// *) in uninstantiated type optimizer,
+//   to avoid signature collisions while discarding unused return type or parameters.
+// TODO(b/66369976): to determine if a certain method can be made `final`.
+public class MethodPoolCollection extends MemberPoolCollection<DexMethod> {
 
   public MethodPoolCollection(DexApplication application) {
-    this.application = application;
+    super(application, MethodSignatureEquivalence.get());
   }
 
-  public void buildAll(ExecutorService executorService, Timing timing) throws ExecutionException {
-    timing.begin("Building method pool collection");
-    try {
-      List<Future<?>> futures = new ArrayList<>();
-      @SuppressWarnings("unchecked")
-      List<DexClass> classes = (List) application.classes();
-      submitAll(classes, futures, executorService);
-      ThreadUtils.awaitFutures(futures);
-    } finally {
-      timing.end();
-    }
-  }
-
-  public MethodPool buildForHierarchy(
-      DexClass clazz, ExecutorService executorService, Timing timing) throws ExecutionException {
-    timing.begin("Building method pool collection");
-    try {
-      List<Future<?>> futures = new ArrayList<>();
-      submitAll(
-          getAllSuperTypesInclusive(clazz, methodPools::containsKey), futures, executorService);
-      submitAll(getAllSubTypesExclusive(clazz, methodPools::containsKey), futures, executorService);
-      ThreadUtils.awaitFutures(futures);
-    } finally {
-      timing.end();
-    }
-    return get(clazz);
-  }
-
-  public MethodPool get(DexClass clazz) {
-    assert methodPools.containsKey(clazz);
-    return methodPools.get(clazz);
-  }
-
-  public boolean markIfNotSeen(DexClass clazz, DexMethod method) {
-    MethodPool methodPool = get(clazz);
-    Wrapper<DexMethod> key = equivalence.wrap(method);
-    if (methodPool.hasSeen(key)) {
-      return true;
-    }
-    methodPool.seen(key);
-    return false;
-  }
-
-  private void submitAll(
-      Iterable<DexClass> classes, List<Future<?>> futures, ExecutorService executorService) {
-    for (DexClass clazz : classes) {
-      futures.add(executorService.submit(computeMethodPoolPerClass(clazz)));
-    }
-  }
-
-  private Runnable computeMethodPoolPerClass(DexClass clazz) {
+  @Override
+  Runnable computeMemberPoolForClass(DexClass clazz) {
     return () -> {
-      MethodPool methodPool = methodPools.computeIfAbsent(clazz, k -> new MethodPool());
+      MemberPool<DexMethod> methodPool =
+          memberPools.computeIfAbsent(clazz, k -> new MemberPool<>(equivalence));
       clazz.forEachMethod(
           encodedMethod -> {
             // We will add private instance methods when we promote them.
@@ -104,7 +41,8 @@
       if (clazz.superType != null) {
         DexClass superClazz = application.definitionFor(clazz.superType);
         if (superClazz != null) {
-          MethodPool superPool = methodPools.computeIfAbsent(superClazz, k -> new MethodPool());
+          MemberPool<DexMethod> superPool =
+              memberPools.computeIfAbsent(superClazz, k -> new MemberPool<>(equivalence));
           superPool.linkSubtype(methodPool);
           methodPool.linkSupertype(superPool);
         }
@@ -114,7 +52,8 @@
             implementer -> {
               DexClass subClazz = application.definitionFor(implementer);
               if (subClazz != null) {
-                MethodPool childPool = methodPools.computeIfAbsent(subClazz, k -> new MethodPool());
+                MemberPool<DexMethod> childPool =
+                    memberPools.computeIfAbsent(subClazz, k -> new MemberPool<>(equivalence));
                 childPool.linkInterface(methodPool);
               }
             });
@@ -122,106 +61,4 @@
     };
   }
 
-  private Set<DexClass> getAllSuperTypesInclusive(
-      DexClass subject, Predicate<DexClass> stoppingCriterion) {
-    Set<DexClass> superTypes = new HashSet<>();
-    Deque<DexClass> worklist = new ArrayDeque<>();
-    worklist.add(subject);
-    while (!worklist.isEmpty()) {
-      DexClass clazz = worklist.pop();
-      if (stoppingCriterion.test(clazz)) {
-        continue;
-      }
-      if (superTypes.add(clazz)) {
-        if (clazz.superType != null) {
-          addNonNull(worklist, application.definitionFor(clazz.superType));
-        }
-        for (DexType interfaceType : clazz.interfaces.values) {
-          addNonNull(worklist, application.definitionFor(interfaceType));
-        }
-      }
-    }
-    return superTypes;
-  }
-
-  private Set<DexClass> getAllSubTypesExclusive(
-      DexClass subject, Predicate<DexClass> stoppingCriterion) {
-    Set<DexClass> subTypes = new HashSet<>();
-    Deque<DexClass> worklist = new ArrayDeque<>();
-    subject.type.forAllExtendsSubtypes(
-        type -> addNonNull(worklist, application.definitionFor(type)));
-    subject.type.forAllImplementsSubtypes(
-        type -> addNonNull(worklist, application.definitionFor(type)));
-    while (!worklist.isEmpty()) {
-      DexClass clazz = worklist.pop();
-      if (stoppingCriterion.test(clazz)) {
-        continue;
-      }
-      if (subTypes.add(clazz)) {
-        clazz.type.forAllExtendsSubtypes(
-            type -> addNonNull(worklist, application.definitionFor(type)));
-        clazz.type.forAllImplementsSubtypes(
-            type -> addNonNull(worklist, application.definitionFor(type)));
-      }
-    }
-    return subTypes;
-  }
-
-  public static class MethodPool {
-    private MethodPool superType;
-    private final Set<MethodPool> interfaces = new HashSet<>();
-    private final Set<MethodPool> subTypes = new HashSet<>();
-    private final Set<Wrapper<DexMethod>> methodPool = new HashSet<>();
-
-    private MethodPool() {}
-
-    synchronized void linkSupertype(MethodPool superType) {
-      assert this.superType == null;
-      this.superType = superType;
-    }
-
-    synchronized void linkSubtype(MethodPool subType) {
-      boolean added = subTypes.add(subType);
-      assert added;
-    }
-
-    synchronized void linkInterface(MethodPool itf) {
-      boolean added = interfaces.add(itf);
-      assert added;
-    }
-
-    public void seen(DexMethod method) {
-      seen(MethodSignatureEquivalence.get().wrap(method));
-    }
-
-    public synchronized void seen(Wrapper<DexMethod> method) {
-      boolean added = methodPool.add(method);
-      assert added;
-    }
-
-    public boolean hasSeen(Wrapper<DexMethod> method) {
-      return hasSeenUpwardRecursive(method) || hasSeenDownwardRecursive(method);
-    }
-
-    public boolean hasSeenDirectly(Wrapper<DexMethod> method) {
-      return methodPool.contains(method);
-    }
-
-    private boolean hasSeenUpwardRecursive(Wrapper<DexMethod> method) {
-      return methodPool.contains(method)
-          || (superType != null && superType.hasSeenUpwardRecursive(method))
-          || interfaces.stream().anyMatch(itf -> itf.hasSeenUpwardRecursive(method));
-    }
-
-    private boolean hasSeenDownwardRecursive(Wrapper<DexMethod> method) {
-      return methodPool.contains(method)
-          || subTypes.stream().anyMatch(subType -> subType.hasSeenDownwardRecursive(method));
-    }
-  }
-
-  private static <T> void addNonNull(Collection<T> collection, T item) {
-    if (item != null) {
-      collection.add(item);
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index e41593f..4cc3207 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -37,7 +37,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.MethodPoolCollection.MethodPool;
+import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -139,7 +139,7 @@
         appView,
         appView.appInfo().classes(),
         clazz -> {
-          MethodPool methodPool = methodPoolCollection.get(clazz);
+          MemberPool<DexMethod> methodPool = methodPoolCollection.get(clazz);
 
           if (clazz.isInterface()) {
             // Do not allow changing the prototype of methods that override an interface method.
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index fd90187..fe10045 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -36,8 +36,8 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
 import com.android.tools.r8.ir.optimize.MethodPoolCollection;
-import com.android.tools.r8.ir.optimize.MethodPoolCollection.MethodPool;
 import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.logging.Log;
@@ -941,7 +941,7 @@
           // due to the way invoke-super works on default interface methods. In order to be able
           // to hit this method directly after the merge, we need to make it public, and find a
           // method name that does not collide with one in the hierarchy of this class.
-          MethodPool methodPoolForTarget =
+          MemberPool<DexMethod> methodPoolForTarget =
               methodPoolCollection.buildForHierarchy(target, executorService, timing);
           resultingDirectMethod =
               renameMethod(
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithReprocessingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithReprocessingTest.java
new file mode 100644
index 0000000..31db483
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithReprocessingTest.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2019, 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;
+
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.function.Consumer;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class KotlinLambdaMergingWithReprocessingTest extends AbstractR8KotlinTestBase {
+  private Consumer<InternalOptions> optionsModifier =
+    o -> {
+      o.enableTreeShaking = true;
+      o.enableMinification = false;
+      o.enableInlining = true;
+      o.enableClassInlining = true;
+      o.enableLambdaMerging = true;
+    };
+
+  @Ignore("b/123737770")
+  @Test
+  public void testMergingKStyleLambdasAndReprocessing() throws Exception {
+    final String mainClassName = "reprocess_merged_lambdas_kstyle.MainKt";
+    runTest("reprocess_merged_lambdas_kstyle", mainClassName, optionsModifier, null);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/b123068484/FieldRenamingTest.java b/src/test/java/com/android/tools/r8/naming/b123068484/FieldRenamingTest.java
index 119cb98..6908fda 100644
--- a/src/test/java/com/android/tools/r8/naming/b123068484/FieldRenamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/b123068484/FieldRenamingTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.naming.b123068484;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThat;
 
@@ -14,6 +15,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.nio.file.Path;
@@ -74,6 +76,9 @@
   }
 
   private void inspect(CodeInspector inspector) {
+    FieldSubject fld = inspector.clazz(CONCRETE1.getTypeName().replace("Concrete1", "Abs"))
+        .uniqueFieldWithName("strField");
+
     ClassSubject main = inspector.clazz(MAIN);
     assertThat(main, isPresent());
     MethodSubject methodSubject = main.mainMethod();
@@ -83,7 +88,9 @@
         .iterateInstructions(InstructionSubject::isInstanceGet)
         .forEachRemaining(instructionSubject -> {
           String fieldName = instructionSubject.getField().name.toString();
+          // All of those field references will be renamed.
           assertNotEquals("strField", fieldName);
+          assertEquals(fld.getFinalName(), fieldName);
         });
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTest.java b/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTest.java
index 7733339..8d35333 100644
--- a/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTest.java
@@ -1,22 +1,16 @@
 // Copyright (c) 2018, 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.resolution;
 
-public class PublicFieldInnerClassTest {
-  public static final Class<?>[] CLASSES = {
-      PrivateBase.class,
-      PrivateSubclass.class,
-      PackageBase.class,
-      PackageSubclass.class,
-      ProtectedBase.class,
-      ProtectedSubclass.class,
-      PublicBase.class,
-      PublicSubclass.class,
-      PublicFieldInnerClassTest.class
-  };
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+class PublicFieldInnerClassTestMain {
   private static class PrivateBase {
     public int value;
   }
@@ -68,3 +62,30 @@
     System.out.println(getPublic(new PublicSubclass()));
   }
 }
+
+@RunWith(Parameterized.class)
+public class PublicFieldInnerClassTest extends TestBase {
+  private static final Class CLASS = PublicFieldInnerClassTestMain.class;
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("0", "0", "0", "0");
+
+  private final Backend backend;
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Object[] data() {
+    return Backend.values();
+  }
+
+  public PublicFieldInnerClassTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(backend)
+        .setMode(CompilationMode.DEBUG)
+        .addProgramClassesAndInnerClasses(CLASS)
+        .addKeepMainRule(CLASS)
+        .run(CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java b/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java
deleted file mode 100644
index d1fc83e..0000000
--- a/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (c) 2018, 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.resolution;
-
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.ClassFileConsumer;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.ProgramConsumer;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.R8Command.Builder;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.origin.Origin;
-import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.List;
-import org.junit.Test;
-
-public class PublicFieldInnerClassTestRunner extends TestBase {
-  static final Class CLASS = PublicFieldInnerClassTest.class;
-  static final Class<?>[] CLASSES = PublicFieldInnerClassTest.CLASSES;
-
-  @Test
-  public void testCf() throws Exception {
-    ProcessResult runInput =
-        ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
-    assertEquals(0, runInput.exitCode);
-    Path outCf = temp.getRoot().toPath().resolve("cf.jar");
-    build(new ClassFileConsumer.ArchiveConsumer(outCf));
-    ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName());
-    assertEquals(runInput.toString(), runCf.toString());
-    assertEquals(
-        -1,
-        runCf.stderr.indexOf("java.lang.NoSuchFieldError"));
-  }
-
-  @Test
-  public void testDex() throws Exception {
-    ProcessResult runInput =
-        ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
-    assertEquals(0, runInput.exitCode);
-    Path outDex = temp.getRoot().toPath().resolve("dex.zip");
-    build(new DexIndexedConsumer.ArchiveConsumer(outDex));
-    // TODO(b/76191597): Change to runArtNoVerificationErrors + assertEquals when bug is fixed
-    ProcessResult runDex = ToolHelper.runArtNoVerificationErrorsRaw(
-        outDex.toString(), CLASS.getCanonicalName());
-    assertEquals(runInput.stdout, runDex.stdout);
-    assertEquals(runInput.exitCode, runDex.exitCode);
-    assertEquals(
-        -1,
-        runDex.stderr.indexOf("java.lang.NoSuchFieldError"));
-  }
-
-  private void build(ProgramConsumer consumer) throws Exception {
-    List<String> config = Arrays.asList(
-        "-keep public class " + CLASS.getCanonicalName() + " {",
-        "  public static void main(...);",
-        "}"
-    );
-    Builder builder = R8Command.builder()
-        .setMode(CompilationMode.DEBUG)
-        .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
-        .setProgramConsumer(consumer)
-        .addProguardConfiguration(config, Origin.unknown());
-    for (Class<?> c : CLASSES) {
-      builder.addClassProgramData(ToolHelper.getClassAsBytes(c), Origin.unknown());
-    }
-    ToolHelper.runR8(builder.build());
-  }
-}
diff --git a/src/test/kotlinR8TestResources/reprocess_merged_lambdas_kstyle/main.kt b/src/test/kotlinR8TestResources/reprocess_merged_lambdas_kstyle/main.kt
new file mode 100644
index 0000000..a71b9af
--- /dev/null
+++ b/src/test/kotlinR8TestResources/reprocess_merged_lambdas_kstyle/main.kt
@@ -0,0 +1,15 @@
+// Copyright (c) 2019, 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 reprocess_merged_lambdas_kstyle
+
+private var COUNT = 11
+
+private fun next() = "${COUNT++}"
+
+fun consumeOne(l: (x: String) -> String): String = l(next())
+
+fun main(args: Array<String>) {
+  println(consumeOne { consumeOne { consumeOne { _ -> "A" } } })
+  println(consumeOne { consumeOne { consumeOne { _ -> "B" } } })
+}
\ No newline at end of file