Nest desugaring should raise warning

- Desugaring should raise warnings and no errors
- Updated tests to catch warnings
- Logic now relies on nest host equality, so the logic
  partially works if the nest host is absent, and the
  map kept in desugaring are smaller.

Bug:132682295
Change-Id: I10bcaa692bd191ea71357b51a06b73d3bcc80e2f
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
index 3b0a46b..c79e13a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
@@ -34,18 +34,14 @@
 // - Optimize bridges (Bridges processed concurrently).
 public class D8NestBasedAccessDesugaring extends NestBasedAccessDesugaring {
 
-  // Map the nest host to its nest members, including the nest host itself.
-  private final Map<DexType, List<DexType>> metNests = new ConcurrentHashMap<>();
+  // Maps a nest host to a class met which has that nest host.
+  // The value is used because the nest host might be missing.
+  private final Map<DexType, DexClass> metNestHosts = new ConcurrentHashMap<>();
 
   public D8NestBasedAccessDesugaring(AppView<?> appView) {
     super(appView);
   }
 
-  private List<DexType> getNestFor(DexClass clazz) {
-    DexType nestHostType = clazz.getNestHost();
-    return metNests.computeIfAbsent(nestHostType, host -> extractNest(clazz));
-  }
-
   public void rewriteNestBasedAccesses(
       DexEncodedMethod encodedMethod, IRCode code, AppView<?> appView) {
     DexClass currentClass = appView.definitionFor(encodedMethod.method.holder);
@@ -53,7 +49,7 @@
     if (!currentClass.isInANest()) {
       return;
     }
-    List<DexType> nest = getNestFor(currentClass);
+    metNestHosts.put(currentClass.getNestHost(), currentClass);
 
     ListIterator<BasicBlock> blocks = code.listIterator();
     while (blocks.hasNext()) {
@@ -66,7 +62,7 @@
           DexMethod methodCalled = invokeMethod.getInvokedMethod();
           DexEncodedMethod encodedMethodCalled = appView.definitionFor(methodCalled);
           if (encodedMethodCalled != null
-              && invokeRequiresRewriting(encodedMethodCalled, nest, encodedMethod.method.holder)) {
+              && invokeRequiresRewriting(encodedMethodCalled, currentClass, appView)) {
             DexMethod bridge = ensureInvokeBridge(encodedMethodCalled);
             if (encodedMethodCalled.isInstanceInitializer()) {
               instructions.previous();
@@ -87,7 +83,7 @@
           DexEncodedField encodedField =
               appView.definitionFor(instruction.asFieldInstruction().getField());
           if (encodedField != null
-              && fieldAccessRequiresRewriting(encodedField, nest, encodedMethod.method.holder)) {
+              && fieldAccessRequiresRewriting(encodedField, currentClass, appView)) {
             if (instruction.isInstanceGet() || instruction.isStaticGet()) {
               DexMethod bridge = ensureFieldAccessBridge(encodedField, true);
               instructions.replaceCurrentInstruction(
@@ -104,11 +100,10 @@
     }
   }
 
-  private void processNestsConcurrently(
-      List<List<DexType>> liveNests, ExecutorService executorService) throws ExecutionException {
+  private void processNestsConcurrently(ExecutorService executorService) throws ExecutionException {
     List<Future<?>> futures = new ArrayList<>();
-    for (List<DexType> nest : liveNests) {
-      futures.add(asyncProcessNest(nest, executorService));
+    for (DexClass clazz : metNestHosts.values()) {
+      futures.add(asyncProcessNest(clazz, executorService));
     }
     ThreadUtils.awaitFutures(futures);
   }
@@ -116,8 +111,7 @@
   public void desugarNestBasedAccess(
       DexApplication.Builder<?> builder, ExecutorService executorService, IRConverter converter)
       throws ExecutionException {
-    List<List<DexType>> metNests = new ArrayList<>(this.metNests.values());
-    processNestsConcurrently(metNests, executorService);
+    processNestsConcurrently(executorService);
     addDeferredBridges();
     synthetizeNestConstructor(builder);
     optimizeDeferredBridgesConcurrently(executorService, converter);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
index 865049c..8086767 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -2,6 +2,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -21,6 +22,7 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -71,11 +73,12 @@
   }
 
   // Extract the list of types in the programClass' nest, of host hostClass
-  List<DexType> extractNest(DexClass clazz) {
+  private List<DexType> extractNest(DexClass clazz) {
     assert clazz != null;
     DexClass hostClass = clazz.isNestHost() ? clazz : appView.definitionFor(clazz.getNestHost());
     if (hostClass == null) {
-      throw abortCompilationDueToMissingNestHost(clazz);
+      reportMissingNestHost(clazz);
+      return null;
     }
     List<DexType> classesInNest =
         new ArrayList<>(hostClass.getNestMembersClassAttributes().size() + 1);
@@ -86,26 +89,33 @@
     return classesInNest;
   }
 
-  Future<?> asyncProcessNest(List<DexType> nest, ExecutorService executorService) {
+  Future<?> asyncProcessNest(DexClass clazz, ExecutorService executorService) {
     return executorService.submit(
         () -> {
-          processNest(nest);
+          List<DexType> nest = extractNest(clazz);
+          if (nest != null) {
+            processNest(nest);
+          }
           return null; // we want a Callable not a Runnable to be able to throw
         });
   }
 
   private void processNest(List<DexType> nest) {
+    boolean reported = false;
     for (DexType type : nest) {
       DexClass clazz = appView.definitionFor(type);
       if (clazz == null) {
-        // TODO(b/130529338) We could throw only a warning if a class is missing.
-        throw abortCompilationDueToIncompleteNest(nest);
-      }
-      if (shouldProcessClassInNest(clazz, nest)) {
-        NestBasedAccessDesugaringUseRegistry registry =
-            new NestBasedAccessDesugaringUseRegistry(nest, clazz);
-        for (DexEncodedMethod method : clazz.methods()) {
-          method.registerCodeReferences(registry);
+        if (!reported) {
+          reportIncompleteNest(nest);
+          reported = true;
+        }
+      } else {
+        if (shouldProcessClassInNest(clazz, nest)) {
+          NestBasedAccessDesugaringUseRegistry registry =
+              new NestBasedAccessDesugaringUseRegistry(clazz);
+          for (DexEncodedMethod method : clazz.methods()) {
+            method.registerCodeReferences(registry);
+          }
         }
       }
     }
@@ -139,7 +149,7 @@
     ThreadUtils.awaitFutures(futures);
   }
 
-  private RuntimeException abortCompilationDueToIncompleteNest(List<DexType> nest) {
+  private void reportIncompleteNest(List<DexType> nest) {
     List<String> programClassesFromNest = new ArrayList<>();
     List<String> unavailableClasses = new ArrayList<>();
     List<String> classPathClasses = new ArrayList<>();
@@ -174,17 +184,26 @@
           .append(String.join(", ", classPathClasses))
           .append(" from the same nest are on class path).");
     }
-    throw new CompilationError(stringBuilder.toString());
+    if (!libraryClasses.isEmpty()) {
+      throw new CompilationError(stringBuilder.toString());
+    }
+    // TODO (b/132676197): Use desugaring warning
+    appView.options().reporter.warning(new StringDiagnostic(stringBuilder.toString()));
   }
 
-  private RuntimeException abortCompilationDueToMissingNestHost(DexClass compiledClass) {
-    String nestHostName = compiledClass.getNestHostClassAttribute().getNestHost().getName();
-    throw new CompilationError(
+  private void reportMissingNestHost(DexClass compiledClass) {
+    String nestHostName = compiledClass.getNestHost().getName();
+    String message =
         "Class "
             + compiledClass.type.getName()
             + " requires its nest host "
             + nestHostName
-            + " to be on program or class path for compilation to succeed.");
+            + " to be on program or class path for compilation to succeed.";
+    if (compiledClass.isLibraryClass()) {
+      throw new CompilationError(message);
+    }
+    // TODO (b/132676197): Use desugaring warning
+    appView.options().reporter.warning(new StringDiagnostic(message));
   }
 
   private DexProgramClass createNestAccessConstructor() {
@@ -286,19 +305,27 @@
   }
 
   static boolean invokeRequiresRewriting(
-      DexEncodedMethod method, List<DexType> contextNest, DexType contextType) {
+      DexEncodedMethod method, DexClass contextClass, AppView<?> appView) {
+    assert method != null;
     // Rewrite only when targeting other nest members private fields.
-    return method.accessFlags.isPrivate()
-        && method.method.holder != contextType
-        && contextNest.contains(method.method.holder);
+    if (!method.accessFlags.isPrivate() || method.method.holder == contextClass.type) {
+      return false;
+    }
+    DexClass methodHolder = appView.definitionFor(method.method.holder);
+    assert methodHolder != null; // from encodedMethod
+    return methodHolder.getNestHost() == contextClass.getNestHost();
   }
 
   static boolean fieldAccessRequiresRewriting(
-      DexEncodedField field, List<DexType> contextNest, DexType contextType) {
+      DexEncodedField field, DexClass contextClass, AppView<?> appView) {
+    assert field != null;
     // Rewrite only when targeting other nest members private fields.
-    return field.accessFlags.isPrivate()
-        && field.field.holder != contextType
-        && contextNest.contains(field.field.holder);
+    if (!field.accessFlags.isPrivate() || field.field.holder == contextClass.type) {
+      return false;
+    }
+    DexClass fieldHolder = appView.definitionFor(field.field.holder);
+    assert fieldHolder != null; // from encodedField
+    return fieldHolder.getNestHost() == contextClass.getNestHost();
   }
 
   private boolean holderRequiresBridge(DexClass holder) {
@@ -310,7 +337,11 @@
       return true;
     }
     assert holder.isLibraryClass();
-    throw abortCompilationDueToIncompleteNest(extractNest(holder));
+    List<DexType> nest = extractNest(holder);
+    assert nest != null : "Should be a compilation error if missing nest host on library class.";
+    reportIncompleteNest(nest);
+    throw new Unreachable(
+        "Incomplete nest due to missing library class should raise a compilation error.");
   }
 
   DexMethod ensureFieldAccessBridge(DexEncodedField field, boolean isGet) {
@@ -358,19 +389,16 @@
 
   protected class NestBasedAccessDesugaringUseRegistry extends UseRegistry {
 
-    private final List<DexType> nest;
     private final DexClass currentClass;
 
-    NestBasedAccessDesugaringUseRegistry(List<DexType> nest, DexClass currentClass) {
+    NestBasedAccessDesugaringUseRegistry(DexClass currentClass) {
       super(appView.options().itemFactory);
-      this.nest = nest;
       this.currentClass = currentClass;
     }
 
     private boolean registerInvoke(DexMethod method) {
       DexEncodedMethod encodedMethod = appView.definitionFor(method);
-      if (encodedMethod != null
-          && invokeRequiresRewriting(encodedMethod, nest, currentClass.type)) {
+      if (encodedMethod != null && invokeRequiresRewriting(encodedMethod, currentClass, appView)) {
         ensureInvokeBridge(encodedMethod);
         return true;
       }
@@ -380,7 +408,7 @@
     private boolean registerFieldAccess(DexField field, boolean isGet) {
       DexEncodedField encodedField = appView.definitionFor(field);
       if (encodedField != null
-          && fieldAccessRequiresRewriting(encodedField, nest, currentClass.type)) {
+          && fieldAccessRequiresRewriting(encodedField, currentClass, appView)) {
         ensureFieldAccessBridge(encodedField, isGet);
         return true;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java
index a37b8ac..c782edf 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java
@@ -11,20 +11,15 @@
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.ir.code.Invoke;
 import com.google.common.collect.ImmutableMap;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
 
 public class NestedPrivateMethodLense extends NestedGraphLense {
 
   private final AppView<?> appView;
   // Map from nestHost to nest members including nest hosts
-  private final Map<DexType, List<DexType>> nestMap;
   private final DexType nestConstructorType;
 
   public NestedPrivateMethodLense(
       AppView<?> appView,
-      Map<DexType, List<DexType>> nestMap,
       DexType nestConstructorType,
       GraphLense previousLense) {
     super(
@@ -36,23 +31,16 @@
         previousLense,
         appView.dexItemFactory());
     this.appView = appView;
-    // We do not want a concurrent Map here.
-    assert nestMap instanceof IdentityHashMap;
-    this.nestMap = nestMap;
     this.nestConstructorType = nestConstructorType;
   }
 
-  private List<DexType> getNestFor(DexType type) {
-    DexClass clazz = appView.definitionFor(type);
-    DexType hostType = clazz.isNestHost() ? clazz.type : clazz.getNestHost();
-    return nestMap.get(hostType);
-  }
-
   private DexMethod lookupFieldForMethod(DexField field, DexMethod context, boolean isGet) {
     DexEncodedField encodedField = appView.definitionFor(field);
+    DexClass contextClass = appView.definitionFor(context.holder);
+    assert contextClass != null;
     if (encodedField != null
         && NestBasedAccessDesugaring.fieldAccessRequiresRewriting(
-            encodedField, getNestFor(context.holder), context.holder)) {
+            encodedField, contextClass, appView)) {
       return NestBasedAccessDesugaring.computeFieldBridge(encodedField, isGet, appView);
     }
     return null;
@@ -107,9 +95,11 @@
       return previous;
     }
     DexEncodedMethod encodedMethod = appView.definitionFor(method);
+    DexClass contextClass = appView.definitionFor(context.holder);
+    assert contextClass != null;
     if (encodedMethod == null
         || !NestBasedAccessDesugaring.invokeRequiresRewriting(
-            encodedMethod, getNestFor(context.holder), context.holder)) {
+            encodedMethod, contextClass, appView)) {
       return previous;
     }
     DexMethod bridge;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
index e3e14a3..977c7d9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
@@ -7,9 +7,10 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.utils.ThreadUtils;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.List;
-import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
@@ -20,7 +21,7 @@
 // - Add bridges to be processed by further passes (Sequential).
 public class R8NestBasedAccessDesugaring extends NestBasedAccessDesugaring {
 
-  private Map<DexType, List<DexType>> nestMap = new IdentityHashMap<>();
+  private boolean atLeast1Nest = false;
 
   public R8NestBasedAccessDesugaring(AppView<?> appView) {
     super(appView);
@@ -31,16 +32,17 @@
       return appView.graphLense();
     }
     computeAndProcessNestsConcurrently(executorService);
-    if (nestMap.isEmpty()) {
+    if (!atLeast1Nest) {
+      // Common path when no class file was generated by JDK11+.
       return appView.graphLense();
     }
     addDeferredBridges();
-    return new NestedPrivateMethodLense(
-        appView, nestMap, getNestConstructorType(), appView.graphLense());
+    return new NestedPrivateMethodLense(appView, getNestConstructorType(), appView.graphLense());
   }
 
   private void computeAndProcessNestsConcurrently(ExecutorService executorService)
       throws ExecutionException {
+    Set<DexType> nestHosts = Collections.newSetFromMap(new IdentityHashMap<>());
     List<Future<?>> futures = new ArrayList<>();
     // It is possible that a nest member is on the program path but its nest host
     // is only in the class path (or missing, raising an error).
@@ -49,10 +51,10 @@
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       if (clazz.isInANest()) {
         DexType hostType = clazz.getNestHost();
-        if (!nestMap.containsKey(hostType)) {
-          List<DexType> nest = extractNest(clazz);
-          nestMap.put(hostType, nest);
-          futures.add(asyncProcessNest(nest, executorService));
+        if (!nestHosts.contains(hostType)) {
+          nestHosts.add(hostType);
+          atLeast1Nest = true;
+          futures.add(asyncProcessNest(clazz, executorService));
         }
       }
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationErrorTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationErrorTest.java
deleted file mode 100644
index 7b6e9f0..0000000
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationErrorTest.java
+++ /dev/null
@@ -1,107 +0,0 @@
-// 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.desugar.nestaccesscontrol;
-
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASSES_PATH;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASS_NAMES;
-import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
-import static java.util.stream.Collectors.toList;
-import static org.hamcrest.core.StringContains.containsString;
-import static org.hamcrest.core.StringEndsWith.endsWith;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm;
-import java.nio.file.Path;
-import java.util.List;
-import org.hamcrest.Matcher;
-import org.junit.Assume;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class NestCompilationErrorTest extends TestBase {
-
-  public NestCompilationErrorTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  private final TestParameters parameters;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters()
-        .withDexRuntime(DexVm.Version.first())
-        .withDexRuntime(DexVm.Version.last())
-        .withAllApiLevels()
-        .build();
-  }
-
-  @Test
-  public void testErrorD8() {
-    // TODO (b/132147492): use diagnosis handler
-    Assume.assumeTrue(parameters.isDexRuntime());
-    testMissingNestHostError(true);
-    testIncompleteNestError(true);
-  }
-
-  @Test
-  public void testErrorR8() {
-    // TODO (b/132147492): use diagnosis handler
-    Assume.assumeTrue(parameters.isDexRuntime());
-    testMissingNestHostError(false);
-    testIncompleteNestError(false);
-  }
-
-  private void compileOnlyClassesMatching(Matcher<String> matcher, boolean d8) throws Exception {
-    List<Path> matchingClasses =
-        CLASS_NAMES.stream()
-            .filter(matcher::matches)
-            .map(name -> CLASSES_PATH.resolve(name + CLASS_EXTENSION))
-            .collect(toList());
-    if (d8) {
-      testForD8()
-          .setMinApi(parameters.getApiLevel())
-          .addProgramFiles(matchingClasses)
-          .addOptionsModification(options -> options.enableNestBasedAccessDesugaring = true)
-          .compile();
-    } else {
-      testForR8(parameters.getBackend())
-          .noTreeShaking()
-          .noMinification()
-          .addKeepAllAttributes()
-          .setMinApi(parameters.getApiLevel())
-          .addProgramFiles(matchingClasses)
-          .addOptionsModification(options -> options.enableNestBasedAccessDesugaring = true)
-          .compile();
-    }
-  }
-
-  private void testMissingNestHostError(boolean d8) {
-    try {
-      Matcher<String> innerClassMatcher =
-          containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass");
-      compileOnlyClassesMatching(innerClassMatcher, d8);
-      fail("Should have raised an exception for missing nest host");
-    } catch (Exception e) {
-      assertTrue(e.getCause().getMessage().contains("requires its nest host"));
-    }
-  }
-
-  private void testIncompleteNestError(boolean d8) {
-    try {
-      Matcher<String> innerClassMatcher = endsWith("BasicNestHostWithInnerClassMethods");
-      compileOnlyClassesMatching(innerClassMatcher, d8);
-      fail("Should have raised an exception for incomplete nest");
-    } catch (Exception e) {
-      assertTrue(e.getCause().getMessage().contains("requires its nest mates"));
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
new file mode 100644
index 0000000..601e634
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
@@ -0,0 +1,148 @@
+// 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.desugar.nestaccesscontrol;
+
+import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASSES_PATH;
+import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASS_NAMES;
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static java.util.stream.Collectors.toList;
+import static org.hamcrest.core.StringContains.containsString;
+import static org.hamcrest.core.StringEndsWith.endsWith;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.DexVm;
+import java.nio.file.Path;
+import java.util.List;
+import org.hamcrest.Matcher;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NestCompilationExceptionTest extends TestBase {
+
+  public NestCompilationExceptionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK11)
+        .withDexRuntime(DexVm.Version.first())
+        .withDexRuntime(DexVm.Version.last())
+        .withAllApiLevels()
+        .build();
+  }
+
+  @Test
+  public void testWarningD8() throws Exception {
+    // TODO (b/132676197): use desugaring handling
+    Assume.assumeTrue(parameters.isDexRuntime());
+    testIncompleteNestWarning(true);
+    testMissingNestHostWarning(true);
+  }
+
+  @Test
+  public void testWarningR8() throws Exception {
+    // TODO (b/132676197): use desugaring handling
+    // TODO (b/132676197): Cf backend should raise a warning
+    // Remove Assume when fixed.
+    Assume.assumeTrue(parameters.isDexRuntime());
+    testIncompleteNestWarning(false);
+    testMissingNestHostWarning(false);
+  }
+
+  @Test
+  public void testErrorR8() {
+    // TODO (b/132676197): Cf back should raise an error
+    // TODO (b/132676197): Dex back-end should raise an error
+    // Remove Assume when fixed.
+    Assume.assumeTrue(false);
+    testMissingNestHostError();
+    testIncompleteNestError();
+  }
+
+  private TestCompileResult compileOnlyClassesMatching(
+      Matcher<String> matcher, boolean d8, boolean ignoreMissingClasses) throws Exception {
+    List<Path> matchingClasses =
+        CLASS_NAMES.stream()
+            .filter(matcher::matches)
+            .map(name -> CLASSES_PATH.resolve(name + CLASS_EXTENSION))
+            .collect(toList());
+    if (d8) {
+      return testForD8()
+          .setMinApi(parameters.getApiLevel())
+          .addProgramFiles(matchingClasses)
+          .addOptionsModification(options -> options.enableNestBasedAccessDesugaring = true)
+          .compile();
+    } else {
+      return testForR8(parameters.getBackend())
+          .noTreeShaking()
+          .noMinification()
+          .addKeepAllAttributes()
+          .setMinApi(parameters.getApiLevel())
+          .addProgramFiles(matchingClasses)
+          .addOptionsModification(
+              options -> {
+                options.enableNestBasedAccessDesugaring = true;
+                options.ignoreMissingClasses = ignoreMissingClasses;
+              })
+          .compile();
+    }
+  }
+
+  private void testMissingNestHostError() {
+    try {
+      Matcher<String> innerClassMatcher =
+          containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass");
+      compileOnlyClassesMatching(innerClassMatcher, false, false);
+      fail("Should have raised an exception for missing nest host");
+    } catch (Exception e) {
+      assertTrue(e.getCause().getMessage().contains("requires its nest host"));
+    }
+  }
+
+  private void testIncompleteNestError() {
+    try {
+      Matcher<String> innerClassMatcher = endsWith("BasicNestHostWithInnerClassMethods");
+      compileOnlyClassesMatching(innerClassMatcher, false, false);
+      fail("Should have raised an exception for incomplete nest");
+    } catch (Exception e) {
+      assertTrue(e.getCause().getMessage().contains("requires its nest mates"));
+    }
+  }
+
+  private void testMissingNestHostWarning(boolean d8) throws Exception {
+    Matcher<String> innerClassMatcher =
+        containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass");
+    TestCompileResult compileResult = compileOnlyClassesMatching(innerClassMatcher, d8, true);
+    assertTrue(compileResult.getDiagnosticMessages().getWarnings().size() >= 1);
+    assertTrue(
+        compileResult.getDiagnosticMessages().getWarnings().stream()
+            .anyMatch(
+                warning -> warning.getDiagnosticMessage().contains("requires its nest host")));
+  }
+
+  private void testIncompleteNestWarning(boolean d8) throws Exception {
+    Matcher<String> innerClassMatcher = endsWith("BasicNestHostWithInnerClassMethods");
+    TestCompileResult compileResult = compileOnlyClassesMatching(innerClassMatcher, d8, true);
+    assertTrue(compileResult.getDiagnosticMessages().getWarnings().size() >= 1);
+    assertTrue(
+        compileResult.getDiagnosticMessages().getWarnings().stream()
+            .anyMatch(
+                warning -> warning.getDiagnosticMessage().contains("requires its nest mates")));
+  }
+}