Include number of contexts in missing definitions diagnostic message

Bug: 179461665
Change-Id: I1851a1ebb809e27c4fc406d8daa0831185a07839
diff --git a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionContextBase.java b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionContextBase.java
index 67515a2..cf6a0d1 100644
--- a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionContextBase.java
+++ b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionContextBase.java
@@ -24,7 +24,7 @@
 
     Origin origin;
 
-    B setOrigin(Origin origin) {
+    public B setOrigin(Origin origin) {
       this.origin = origin;
       return self();
     }
diff --git a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java
index 97a347e..f9455a0 100644
--- a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java
+++ b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java
@@ -95,25 +95,34 @@
           fieldReference -> fieldContext.setMin(fieldReference, getFieldReferenceComparator()),
           methodReference -> methodContext.setMin(methodReference, getMethodReferenceComparator()));
     }
-
     if (fieldContext.isSet()) {
-      builder
-          .append(" (referenced from: ")
-          .append(FieldReferenceUtils.toSourceString(fieldContext.get()))
-          .append(")");
+      writeReferencedFromSuffix(
+          builder, missingDefinitionInfo, FieldReferenceUtils.toSourceString(fieldContext.get()));
     } else if (methodContext.isSet()) {
-      builder
-          .append(" (referenced from: ")
-          .append(MethodReferenceUtils.toSourceString(methodContext.get()))
-          .append(")");
+      writeReferencedFromSuffix(
+          builder, missingDefinitionInfo, MethodReferenceUtils.toSourceString(methodContext.get()));
     } else if (classContext.isSet()) {
-      builder.append(" (referenced from: ").append(classContext.get().getTypeName()).append(")");
+      writeReferencedFromSuffix(builder, missingDefinitionInfo, classContext.get().getTypeName());
     } else {
       // TODO(b/175543745): Once legacy reporting is removed this should never happen.
       builder.append(" (referenced from: <not known>)");
     }
   }
 
+  private static void writeReferencedFromSuffix(
+      StringBuilder builder, MissingDefinitionInfo missingDefinitionInfo, String referencedFrom) {
+    int numberOfOtherContexts = missingDefinitionInfo.getReferencedFromContexts().size() - 1;
+    assert numberOfOtherContexts >= 0;
+    builder.append(" (referenced from: ").append(referencedFrom);
+    if (numberOfOtherContexts >= 1) {
+      builder.append(", and ").append(numberOfOtherContexts).append(" other context");
+      if (numberOfOtherContexts >= 2) {
+        builder.append("s");
+      }
+    }
+    builder.append(")");
+  }
+
   public static class Builder {
 
     private ImmutableList.Builder<MissingDefinitionInfo> missingDefinitionsBuilder =
diff --git a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
index 8b1ca9d..dbc2119 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
@@ -25,6 +25,7 @@
     return null;
   }
 
+  @Override
   public DexClassAndField getResolutionPair() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java
index 01294be..285859d 100644
--- a/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java
@@ -9,6 +9,10 @@
 public abstract class MemberResolutionResult<
     D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> {
 
+  public DexClassAndMember<D, R> getResolutionPair() {
+    return null;
+  }
+
   public abstract boolean isSuccessfulMemberResolutionResult();
 
   public abstract SuccessfulMemberResolutionResult<D, R> asSuccessfulMemberResolutionResult();
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index 3356777..041033e 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -74,6 +74,11 @@
     return null;
   }
 
+  @Override
+  public DexClassAndMethod getResolutionPair() {
+    return null;
+  }
+
   public abstract OptionalBool isAccessibleForVirtualDispatchFrom(
       ProgramDefinition context, AppInfoWithClassHierarchy appInfo);
 
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 a1baa1c..ba0a71a 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -574,13 +574,13 @@
     definitionFor(type, context, missingClassConsumer);
   }
 
-  private void recordMethodReference(DexMethod method, ProgramDefinition context) {
+  private void recordMethodReference(DexMethod method, ProgramDerivedContext context) {
     recordMethodReference(method, context, this::reportMissingClass);
   }
 
   private void recordMethodReference(
       DexMethod method,
-      ProgramDefinition context,
+      ProgramDerivedContext context,
       BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer) {
     recordTypeReference(method.holder, context, missingClassConsumer);
     recordTypeReference(method.proto.returnType, context, missingClassConsumer);
@@ -1988,11 +1988,15 @@
   private SingleResolutionResult resolveMethod(
       DexMethod method, ProgramDefinition context, KeepReason reason, boolean interfaceInvoke) {
     // Record the references in case they are not program types.
-    recordMethodReference(method, context);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method, interfaceInvoke);
-    if (resolutionResult.isFailedResolution()) {
+    if (resolutionResult.isSingleResolution()) {
+      recordMethodReference(
+          method, resolutionResult.getResolutionPair().asProgramDerivedContext(context));
+    } else {
+      assert resolutionResult.isFailedResolution();
       markFailedMethodResolutionTargets(
           method, resolutionResult.asFailedResolution(), context, reason);
+      recordMethodReference(method, context);
     }
     return resolutionResult.asSingleResolution();
   }
@@ -2252,19 +2256,23 @@
             // TODO(b/157107464): See if we can clean this up.
             || (initialPrunedTypes != null && initialPrunedTypes.contains(clazz))
         : "Unexpected missing class `" + clazz.toSourceString() + "`";
-    missingClassesBuilder.addNewMissingClass(clazz, context);
-  }
-
-  @Deprecated
-  private void reportMissingClassWithoutContext(DexType clazz) {
-    assert !mode.isFinalTreeShaking()
-            || missingClassesBuilder.wasAlreadyMissing(clazz)
-            || appView.dexItemFactory().isPossiblyCompilerSynthesizedType(clazz)
-            || initialDeadProtoTypes.contains(clazz)
-            // TODO(b/157107464): See if we can clean this up.
-            || (initialPrunedTypes != null && initialPrunedTypes.contains(clazz))
-        : "Unexpected missing class `" + clazz.toSourceString() + "`";
-    missingClassesBuilder.legacyAddNewMissingClass(clazz);
+    // Do not report missing classes from D8/R8 synthesized methods on non-synthetic classes (for
+    // example, lambda accessibility bridges).
+    // TODO(b/180376674): Clean this up. Ideally the D8/R8 synthesized methods would be synthesized
+    //  using synthetic items, such that the synthetic items infrastructure would track the
+    //  synthesizing contexts for these methods as well. That way, this would just work without any
+    //  special handling because the mapping to the synthesizing contexts would also work for these
+    //  synthetic methods.
+    if (context.isProgramContext()
+        && context.getContext().isMethod()
+        && context.getContext().asMethod().getDefinition().isD8R8Synthesized()
+        && !appView
+            .getSyntheticItems()
+            .isSyntheticClass(context.getContext().asProgramDefinition().getContextClass())) {
+      missingClassesBuilder.ignoreNewMissingClass(clazz);
+    } else {
+      missingClassesBuilder.addNewMissingClass(clazz, context);
+    }
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
index bde1d07..bdca121 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -113,7 +113,9 @@
     }
 
     public boolean contains(DexType type) {
-      return alreadyMissingClasses.contains(type) || newMissingClasses.containsKey(type);
+      return alreadyMissingClasses.contains(type)
+          || newMissingClasses.containsKey(type)
+          || newIgnoredMissingClasses.contains(type);
     }
 
     Builder removeAlreadyMissingClasses(Iterable<DexType> types) {
diff --git a/src/main/java/com/android/tools/r8/utils/Box.java b/src/main/java/com/android/tools/r8/utils/Box.java
index 615b321..de67cf6 100644
--- a/src/main/java/com/android/tools/r8/utils/Box.java
+++ b/src/main/java/com/android/tools/r8/utils/Box.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils;
 
 import java.util.Comparator;
+import java.util.Objects;
 import java.util.function.Supplier;
 
 public class Box<T> {
@@ -47,4 +48,18 @@
     value = newValue;
     return oldValue;
   }
+
+  @Override
+  public boolean equals(Object object) {
+    if (object == null || getClass() != object.getClass()) {
+      return false;
+    }
+    Box<?> box = (Box<?>) object;
+    return Objects.equals(value, box.value);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(value);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 8648de9..276f921 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -39,6 +39,7 @@
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
@@ -1691,6 +1692,10 @@
     return DescriptorUtils.javaTypeToDescriptor(typeName(clazz));
   }
 
+  public static PathOrigin getOrigin(Class<?> clazz) {
+    return new PathOrigin(ToolHelper.getClassFileForTestClass(clazz));
+  }
+
   public static String typeName(Class<?> clazz) {
     return clazz.getTypeName();
   }
diff --git a/src/test/java/com/android/tools/r8/ThrowableConsumer.java b/src/test/java/com/android/tools/r8/ThrowableConsumer.java
index f19039b..5720c7d 100644
--- a/src/test/java/com/android/tools/r8/ThrowableConsumer.java
+++ b/src/test/java/com/android/tools/r8/ThrowableConsumer.java
@@ -27,4 +27,8 @@
       consumer.accept(formal);
     };
   }
+
+  static <Formal> ThrowableConsumer<Formal> empty() {
+    return ignore -> {};
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/diagnosticinspector/FoundMissingDefinitionContextSubject.java b/src/test/java/com/android/tools/r8/diagnosticinspector/FoundMissingDefinitionContextSubject.java
new file mode 100644
index 0000000..1138239
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/diagnosticinspector/FoundMissingDefinitionContextSubject.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2021, 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.diagnosticinspector;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.diagnostic.MissingDefinitionContext;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.Box;
+
+public class FoundMissingDefinitionContextSubject {
+
+  private final MissingDefinitionContext context;
+
+  public FoundMissingDefinitionContextSubject(MissingDefinitionContext context) {
+    this.context = context;
+  }
+
+  public FoundMissingDefinitionContextSubject assertEqualTo(
+      MissingDefinitionContext expectedContext) {
+    Box<ClassReference> classReference = new Box<>();
+    Box<FieldReference> fieldReference = new Box<>();
+    Box<MethodReference> methodReference = new Box<>();
+    context.getReference(classReference::set, fieldReference::set, methodReference::set);
+    Box<ClassReference> expectedClassReference = new Box<>();
+    Box<FieldReference> expectedFieldReference = new Box<>();
+    Box<MethodReference> expectedMethodReference = new Box<>();
+    expectedContext.getReference(
+        expectedClassReference::set, expectedFieldReference::set, expectedMethodReference::set);
+    assertEquals(classReference, expectedClassReference);
+    assertEquals(fieldReference, expectedFieldReference);
+    assertEquals(methodReference, expectedMethodReference);
+    return this;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/diagnosticinspector/FoundMissingDefinitionInfoSubject.java b/src/test/java/com/android/tools/r8/diagnosticinspector/FoundMissingDefinitionInfoSubject.java
new file mode 100644
index 0000000..f51752f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/diagnosticinspector/FoundMissingDefinitionInfoSubject.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2021, 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.diagnosticinspector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.diagnostic.MissingDefinitionContext;
+import com.android.tools.r8.diagnostic.MissingDefinitionInfo;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class FoundMissingDefinitionInfoSubject {
+
+  private final MissingDefinitionInfo missingDefinitionInfo;
+
+  private final Map<ClassReference, FoundMissingDefinitionContextSubject> classContexts =
+      new HashMap<>();
+  private final Map<FieldReference, FoundMissingDefinitionContextSubject> fieldContexts =
+      new HashMap<>();
+  private final Map<MethodReference, FoundMissingDefinitionContextSubject> methodContexts =
+      new HashMap<>();
+
+  public FoundMissingDefinitionInfoSubject(MissingDefinitionInfo missingDefinitionInfo) {
+    this.missingDefinitionInfo = missingDefinitionInfo;
+    missingDefinitionInfo
+        .getReferencedFromContexts()
+        .forEach(
+            context ->
+                context.getReference(
+                    classContext ->
+                        classContexts.put(
+                            classContext, new FoundMissingDefinitionContextSubject(context)),
+                    fieldContext ->
+                        fieldContexts.put(
+                            fieldContext, new FoundMissingDefinitionContextSubject(context)),
+                    methodContext ->
+                        methodContexts.put(
+                            methodContext, new FoundMissingDefinitionContextSubject(context))));
+  }
+
+  public FoundMissingDefinitionInfoSubject assertExactContexts(
+      List<MissingDefinitionContext> expectedContexts) {
+    assertEquals(expectedContexts.size(), missingDefinitionInfo.getReferencedFromContexts().size());
+    expectedContexts.forEach(
+        expectedContext ->
+            expectedContext.getReference(
+                classContext -> {
+                  FoundMissingDefinitionContextSubject subject = classContexts.get(classContext);
+                  assertNotNull(subject);
+                  subject.assertEqualTo(expectedContext);
+                },
+                fieldContext -> {
+                  FoundMissingDefinitionContextSubject subject = fieldContexts.get(fieldContext);
+                  assertNotNull(subject);
+                  subject.assertEqualTo(expectedContext);
+                },
+                methodContext -> {
+                  FoundMissingDefinitionContextSubject subject = methodContexts.get(methodContext);
+                  assertNotNull(subject);
+                  subject.assertEqualTo(expectedContext);
+                }));
+    return this;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/diagnosticinspector/FoundMissingDefinitionsDiagnosticSubject.java b/src/test/java/com/android/tools/r8/diagnosticinspector/FoundMissingDefinitionsDiagnosticSubject.java
index c7aea09..ac63c98 100644
--- a/src/test/java/com/android/tools/r8/diagnosticinspector/FoundMissingDefinitionsDiagnosticSubject.java
+++ b/src/test/java/com/android/tools/r8/diagnosticinspector/FoundMissingDefinitionsDiagnosticSubject.java
@@ -4,16 +4,37 @@
 
 package com.android.tools.r8.diagnosticinspector;
 
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.diagnostic.MissingDefinitionContext;
+import com.android.tools.r8.diagnostic.MissingDefinitionInfo;
 import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic;
 import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 public class FoundMissingDefinitionsDiagnosticSubject
     extends FoundDiagnosticSubject<MissingDefinitionsDiagnostic> {
 
+  private final Map<ClassReference, MissingDefinitionInfo> missingClasses = new HashMap<>();
+
   public FoundMissingDefinitionsDiagnosticSubject(MissingDefinitionsDiagnostic diagnostic) {
     super(diagnostic);
+    diagnostic
+        .getMissingDefinitions()
+        .forEach(
+            missingDefinitionInfo ->
+                missingDefinitionInfo.getMissingDefinition(
+                    classReference -> missingClasses.put(classReference, missingDefinitionInfo),
+                    emptyConsumer(),
+                    emptyConsumer()));
   }
 
   public FoundMissingDefinitionsDiagnosticSubject assertHasMessage(String expectedMessage) {
@@ -22,16 +43,56 @@
   }
 
   public FoundMissingDefinitionsDiagnosticSubject assertIsMissingClass(Class<?> clazz) {
-    return this;
+    return assertIsMissingClass(Reference.classFromClass(clazz));
   }
 
   public FoundMissingDefinitionsDiagnosticSubject assertIsMissingClass(
       ClassReference classReference) {
+    assertTrue(missingClasses.containsKey(classReference));
     return this;
   }
 
+  public FoundMissingDefinitionsDiagnosticSubject assertIsMissingClassWithExactContexts(
+      ClassReference classReference, MissingDefinitionContext... expectedContexts) {
+    return assertIsMissingClassWithExactContexts(classReference, Arrays.asList(expectedContexts));
+  }
+
+  public FoundMissingDefinitionsDiagnosticSubject assertIsMissingClassWithExactContexts(
+      ClassReference classReference, List<MissingDefinitionContext> expectedContexts) {
+    return inspectMissingClassInfo(
+        classReference,
+        missingClassInfoSubject -> missingClassInfoSubject.assertExactContexts(expectedContexts));
+  }
+
   public FoundMissingDefinitionsDiagnosticSubject assertNumberOfMissingClasses(int expected) {
     assertEquals(expected, getDiagnostic().getMissingDefinitions().size());
     return this;
   }
+
+  public FoundMissingDefinitionsDiagnosticSubject applyIf(
+      boolean condition, ThrowableConsumer<FoundMissingDefinitionsDiagnosticSubject> thenConsumer) {
+    return applyIf(condition, thenConsumer, ThrowableConsumer.empty());
+  }
+
+  public FoundMissingDefinitionsDiagnosticSubject applyIf(
+      boolean condition,
+      ThrowableConsumer<FoundMissingDefinitionsDiagnosticSubject> thenConsumer,
+      ThrowableConsumer<FoundMissingDefinitionsDiagnosticSubject> elseConsumer) {
+    if (condition) {
+      thenConsumer.acceptWithRuntimeException(this);
+    } else {
+      elseConsumer.acceptWithRuntimeException(this);
+    }
+    return this;
+  }
+
+  public FoundMissingDefinitionsDiagnosticSubject inspectMissingClassInfo(
+      ClassReference classReference,
+      ThrowableConsumer<FoundMissingDefinitionInfoSubject> inspector) {
+    MissingDefinitionInfo missingDefinitionInfo = missingClasses.get(classReference);
+    assertNotNull(missingDefinitionInfo);
+    inspector.acceptWithRuntimeException(
+        new FoundMissingDefinitionInfoSubject(missingDefinitionInfo));
+    return this;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaParameterTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaParameterTest.java
index a09c4d0..334b107 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaParameterTest.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaParameterTest.java
@@ -11,19 +11,35 @@
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ThrowableConsumer;
-import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.diagnostic.MissingDefinitionContext;
+import com.android.tools.r8.diagnostic.internal.MissingDefinitionMethodContext;
 import com.android.tools.r8.references.Reference;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
 public class MissingClassReferencedFromUnusedLambdaParameterTest extends MissingClassesTestBase {
 
-  private static final MethodReference referencedFrom =
-      Reference.method(
-          Reference.classFromClass(Main.class),
-          "lambda$main$0",
-          ImmutableList.of(Reference.classFromClass(MissingClass.class)),
-          null);
+  private static final MissingDefinitionContext[] referencedFrom =
+      new MissingDefinitionContext[] {
+        MissingDefinitionMethodContext.builder()
+            .setMethodContext(
+                Reference.method(
+                    Reference.classFromClass(Main.class),
+                    "lambda$main$0",
+                    ImmutableList.of(Reference.classFromClass(MissingClass.class)),
+                    null))
+            .setOrigin(getOrigin(Main.class))
+            .build(),
+        MissingDefinitionMethodContext.builder()
+            .setMethodContext(
+                Reference.method(
+                    Reference.classFromClass(Main.class),
+                    "main",
+                    ImmutableList.of(Reference.array(Reference.classFromClass(String.class), 1)),
+                    null))
+            .setOrigin(getOrigin(Main.class))
+            .build(),
+      };
 
   public MissingClassReferencedFromUnusedLambdaParameterTest(TestParameters parameters) {
     super(parameters);
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaReturnTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaReturnTest.java
index c196013..bd122d9 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaReturnTest.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaReturnTest.java
@@ -11,19 +11,36 @@
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ThrowableConsumer;
-import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.diagnostic.MissingDefinitionContext;
+import com.android.tools.r8.diagnostic.internal.MissingDefinitionMethodContext;
 import com.android.tools.r8.references.Reference;
+import com.google.common.collect.ImmutableList;
 import java.util.Collections;
 import org.junit.Test;
 
 public class MissingClassReferencedFromUnusedLambdaReturnTest extends MissingClassesTestBase {
 
-  private static final MethodReference referencedFrom =
-      Reference.method(
-          Reference.classFromClass(Main.class),
-          "lambda$main$0",
-          Collections.emptyList(),
-          Reference.classFromClass(MissingClass.class));
+  private static final MissingDefinitionContext[] referencedFrom =
+      new MissingDefinitionContext[] {
+        MissingDefinitionMethodContext.builder()
+            .setMethodContext(
+                Reference.method(
+                    Reference.classFromClass(Main.class),
+                    "lambda$main$0",
+                    Collections.emptyList(),
+                    Reference.classFromClass(MissingClass.class)))
+            .setOrigin(getOrigin(Main.class))
+            .build(),
+        MissingDefinitionMethodContext.builder()
+            .setMethodContext(
+                Reference.method(
+                    Reference.classFromClass(Main.class),
+                    "main",
+                    ImmutableList.of(Reference.array(Reference.classFromClass(String.class), 1)),
+                    null))
+            .setOrigin(getOrigin(Main.class))
+            .build(),
+      };
 
   public MissingClassReferencedFromUnusedLambdaReturnTest(TestParameters parameters) {
     super(parameters);
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaParameterTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaParameterTest.java
index 2da56a8..271ab9a 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaParameterTest.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaParameterTest.java
@@ -9,21 +9,44 @@
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ThrowableConsumer;
-import com.android.tools.r8.missingclasses.MissingClassReferencedFromUsedLambdaReturnTest.I;
-import com.android.tools.r8.missingclasses.MissingClassReferencedFromUsedLambdaReturnTest.Main;
-import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.diagnostic.MissingDefinitionContext;
+import com.android.tools.r8.diagnostic.internal.MissingDefinitionMethodContext;
 import com.android.tools.r8.references.Reference;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
 public class MissingClassReferencedFromUsedLambdaParameterTest extends MissingClassesTestBase {
 
-  private static final MethodReference referencedFrom =
-      Reference.method(
-          Reference.classFromClass(I.class),
-          "m",
-          ImmutableList.of(Reference.classFromClass(MissingClass.class)),
-          null);
+  private final MissingDefinitionContext[] referencedFrom =
+      new MissingDefinitionContext[] {
+        MissingDefinitionMethodContext.builder()
+            .setMethodContext(
+                Reference.method(
+                    Reference.classFromClass(I.class),
+                    "m",
+                    ImmutableList.of(Reference.classFromClass(MissingClass.class)),
+                    null))
+            .setOrigin(getOrigin(I.class))
+            .build(),
+        MissingDefinitionMethodContext.builder()
+            .setMethodContext(
+                Reference.method(
+                    Reference.classFromClass(Main.class),
+                    "lambda$main$0",
+                    ImmutableList.of(Reference.classFromClass(MissingClass.class)),
+                    null))
+            .setOrigin(getOrigin(Main.class))
+            .build(),
+        MissingDefinitionMethodContext.builder()
+            .setMethodContext(
+                Reference.method(
+                    Reference.classFromClass(Main.class),
+                    "main",
+                    ImmutableList.of(Reference.array(Reference.classFromClass(String.class), 1)),
+                    null))
+            .setOrigin(getOrigin(Main.class))
+            .build()
+      };
 
   public MissingClassReferencedFromUsedLambdaParameterTest(TestParameters parameters) {
     super(parameters);
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaReturnTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaReturnTest.java
index 1b6781d..704f0d5 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaReturnTest.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaReturnTest.java
@@ -9,19 +9,45 @@
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ThrowableConsumer;
-import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.diagnostic.MissingDefinitionContext;
+import com.android.tools.r8.diagnostic.internal.MissingDefinitionMethodContext;
 import com.android.tools.r8.references.Reference;
+import com.google.common.collect.ImmutableList;
 import java.util.Collections;
 import org.junit.Test;
 
 public class MissingClassReferencedFromUsedLambdaReturnTest extends MissingClassesTestBase {
 
-  private static final MethodReference referencedFrom =
-      Reference.method(
-          Reference.classFromClass(I.class),
-          "m",
-          Collections.emptyList(),
-          Reference.classFromClass(MissingClass.class));
+  private final MissingDefinitionContext[] referencedFrom =
+      new MissingDefinitionContext[] {
+        MissingDefinitionMethodContext.builder()
+            .setMethodContext(
+                Reference.method(
+                    Reference.classFromClass(I.class),
+                    "m",
+                    Collections.emptyList(),
+                    Reference.classFromClass(MissingClass.class)))
+            .setOrigin(getOrigin(I.class))
+            .build(),
+        MissingDefinitionMethodContext.builder()
+            .setMethodContext(
+                Reference.method(
+                    Reference.classFromClass(Main.class),
+                    "lambda$main$0",
+                    Collections.emptyList(),
+                    Reference.classFromClass(MissingClass.class)))
+            .setOrigin(getOrigin(Main.class))
+            .build(),
+        MissingDefinitionMethodContext.builder()
+            .setMethodContext(
+                Reference.method(
+                    Reference.classFromClass(Main.class),
+                    "main",
+                    ImmutableList.of(Reference.array(Reference.classFromClass(String.class), 1)),
+                    null))
+            .setOrigin(getOrigin(Main.class))
+            .build()
+      };
 
   public MissingClassReferencedFromUsedLambdaReturnTest(TestParameters parameters) {
     super(parameters);
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
index 869f0f5..2a9833d 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.missingclasses;
 
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.R8FullTestBuilder;
@@ -12,10 +13,12 @@
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.diagnostic.MissingDefinitionContext;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.FieldReferenceUtils;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.MethodReferenceUtils;
@@ -107,13 +110,16 @@
   void inspectDiagnosticsWithIgnoreWarnings(
       TestDiagnosticMessages diagnostics, ClassReference referencedFrom) {
     inspectDiagnosticsWithIgnoreWarnings(
-        diagnostics, getExpectedDiagnosticMessage(referencedFrom, ClassReference::getTypeName));
+        diagnostics,
+        null,
+        getExpectedDiagnosticMessage(referencedFrom, ClassReference::getTypeName));
   }
 
   void inspectDiagnosticsWithIgnoreWarnings(
       TestDiagnosticMessages diagnostics, FieldReference referencedFrom) {
     inspectDiagnosticsWithIgnoreWarnings(
         diagnostics,
+        null,
         getExpectedDiagnosticMessage(referencedFrom, FieldReferenceUtils::toSourceString));
   }
 
@@ -121,31 +127,59 @@
       TestDiagnosticMessages diagnostics, MethodReference referencedFrom) {
     inspectDiagnosticsWithIgnoreWarnings(
         diagnostics,
+        null,
         getExpectedDiagnosticMessage(referencedFrom, MethodReferenceUtils::toSourceString));
   }
 
   void inspectDiagnosticsWithIgnoreWarnings(
-      TestDiagnosticMessages diagnostics, String expectedDiagnosticMessage) {
+      TestDiagnosticMessages diagnostics, MissingDefinitionContext[] referencedFrom) {
+    assertTrue(referencedFrom.length > 0);
+    Box<String> referencedFromSourceString = new Box<>();
+    referencedFrom[0].getReference(
+        classReference -> referencedFromSourceString.set(classReference.getTypeName()),
+        fieldReference ->
+            referencedFromSourceString.set(FieldReferenceUtils.toSourceString(fieldReference)),
+        methodReference ->
+            referencedFromSourceString.set(MethodReferenceUtils.toSourceString(methodReference)));
+    inspectDiagnosticsWithIgnoreWarnings(
+        diagnostics,
+        referencedFrom,
+        getExpectedDiagnosticMessage(referencedFromSourceString.get(), referencedFrom.length));
+  }
+
+  void inspectDiagnosticsWithIgnoreWarnings(
+      TestDiagnosticMessages diagnostics,
+      MissingDefinitionContext[] referencedFrom,
+      String expectedDiagnosticMessage) {
     diagnostics
         .assertOnlyWarnings()
         .inspectWarnings(
             diagnostic ->
                 diagnostic
                     .assertIsMissingDefinitionsDiagnostic()
-                    .assertIsMissingClass(getMissingClassReference())
-                    .assertHasMessage(expectedDiagnosticMessage));
+                    .applyIf(
+                        referencedFrom != null,
+                        checker ->
+                            checker.assertIsMissingClassWithExactContexts(
+                                getMissingClassReference(), referencedFrom),
+                        checker -> checker.assertIsMissingClass(getMissingClassReference()))
+                    .assertHasMessage(expectedDiagnosticMessage)
+                    .assertNumberOfMissingClasses(1));
   }
 
   void inspectDiagnosticsWithNoRules(
       TestDiagnosticMessages diagnostics, ClassReference referencedFrom) {
     inspectDiagnosticsWithNoRules(
-        diagnostics, getExpectedDiagnosticMessage(referencedFrom, ClassReference::getTypeName));
+        diagnostics,
+        null,
+        getExpectedDiagnosticMessage(referencedFrom, ClassReference::getTypeName));
   }
 
   void inspectDiagnosticsWithNoRules(
       TestDiagnosticMessages diagnostics, FieldReference referencedFrom) {
     inspectDiagnosticsWithNoRules(
         diagnostics,
+        null,
         getExpectedDiagnosticMessage(referencedFrom, FieldReferenceUtils::toSourceString));
   }
 
@@ -153,27 +187,63 @@
       TestDiagnosticMessages diagnostics, MethodReference referencedFrom) {
     inspectDiagnosticsWithNoRules(
         diagnostics,
+        null,
         getExpectedDiagnosticMessage(referencedFrom, MethodReferenceUtils::toSourceString));
   }
 
   void inspectDiagnosticsWithNoRules(
-      TestDiagnosticMessages diagnostics, String expectedDiagnosticMessage) {
+      TestDiagnosticMessages diagnostics, MissingDefinitionContext[] referencedFrom) {
+    assertTrue(referencedFrom.length > 0);
+    Box<String> referencedFromSourceString = new Box<>();
+    referencedFrom[0].getReference(
+        classReference -> referencedFromSourceString.set(classReference.getTypeName()),
+        fieldReference ->
+            referencedFromSourceString.set(FieldReferenceUtils.toSourceString(fieldReference)),
+        methodReference ->
+            referencedFromSourceString.set(MethodReferenceUtils.toSourceString(methodReference)));
+    inspectDiagnosticsWithNoRules(
+        diagnostics,
+        referencedFrom,
+        getExpectedDiagnosticMessage(referencedFromSourceString.get(), referencedFrom.length));
+  }
+
+  void inspectDiagnosticsWithNoRules(
+      TestDiagnosticMessages diagnostics,
+      MissingDefinitionContext[] referencedFrom,
+      String expectedDiagnosticMessage) {
     diagnostics
         .assertOnlyErrors()
         .inspectErrors(
             diagnostic ->
                 diagnostic
                     .assertIsMissingDefinitionsDiagnostic()
+                    .applyIf(
+                        referencedFrom != null,
+                        checker ->
+                            checker.assertIsMissingClassWithExactContexts(
+                                getMissingClassReference(), referencedFrom),
+                        checker -> checker.assertIsMissingClass(getMissingClassReference()))
                     .assertHasMessage(expectedDiagnosticMessage)
-                    .assertIsMissingClass(getMissingClassReference()));
+                    .assertNumberOfMissingClasses(1));
   }
 
   private <T> String getExpectedDiagnosticMessage(
       T referencedFrom, Function<T, String> toSourceStringFunction) {
-    return "Missing class "
-        + getMissingClassReference().getTypeName()
-        + " (referenced from: "
-        + toSourceStringFunction.apply(referencedFrom)
-        + ")";
+    return getExpectedDiagnosticMessage(toSourceStringFunction.apply(referencedFrom), 1);
+  }
+
+  private <T> String getExpectedDiagnosticMessage(String referencedFrom, int numberOfContexts) {
+    StringBuilder builder =
+        new StringBuilder("Missing class ")
+            .append(getMissingClassReference().getTypeName())
+            .append(" (referenced from: ")
+            .append(referencedFrom);
+    if (numberOfContexts > 1) {
+      builder.append(", and ").append(numberOfContexts - 1).append(" other context");
+      if (numberOfContexts > 2) {
+        builder.append("s");
+      }
+    }
+    return builder.append(")").toString();
   }
 }