Introduce a graph reporter with typed reporting methods.

Bug: b/120959039
Change-Id: I3fb87fa2332f1c671a773dea27e53b797c5c934c
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 f4afbbe..49fcd4b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -316,6 +316,7 @@
   private final Map<DexType, ScopedDexMethodSet> scopedMethodsForLiveTypes =
       new IdentityHashMap<>();
 
+  private final GraphReporter graphReporter;
   private final GraphConsumer keptGraphConsumer;
 
   Enqueuer(
@@ -329,6 +330,7 @@
     this.appView = appView;
     this.compatibility = compatibility;
     this.forceProguardCompatibility = options.forceProguardCompatibility;
+    this.graphReporter = new GraphReporter();
     this.keptGraphConsumer = keptGraphConsumer;
     this.mode = mode;
     this.options = options;
@@ -424,32 +426,19 @@
   }
 
   private void enqueueRootItem(DexDefinition item, Set<ProguardKeepRuleBase> rules) {
-    assert !rules.isEmpty();
-    if (keptGraphConsumer != null) {
-      GraphNode node = getGraphNode(item.toReference());
-      for (ProguardKeepRuleBase rule : rules) {
-        registerEdge(node, KeepReason.dueToKeepRule(rule));
-      }
-    }
-    internalEnqueueRootItem(item, KeepReason.dueToKeepRule(rules.iterator().next()));
+    internalEnqueueRootItem(item, rules, null);
   }
 
-  private void enqueueRootItem(DexDefinition item, KeepReason reason) {
-    if (keptGraphConsumer != null) {
-      registerEdge(getGraphNode(item.toReference()), reason);
-    }
-    internalEnqueueRootItem(item, reason);
-  }
-
-  private void internalEnqueueRootItem(DexDefinition item, KeepReason reason) {
-    // TODO(b/120959039): do we need to propagate the reason to the action now?
+  private void internalEnqueueRootItem(
+      DexDefinition item, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
     if (item.isDexClass()) {
       DexProgramClass clazz = item.asDexClass().asProgramClass();
+      KeepReasonWitness witness = graphReporter.reportKeepClass(precondition, rules, clazz);
       if (clazz.isInterface() && !clazz.accessFlags.isAnnotation()) {
         // TODO(zerny): This marking is due to a keep rule, so why add to the instantiatedLambdas?
-        markInterfaceAsInstantiatedByLambda(clazz, reason);
+        markInterfaceAsInstantiatedByLambda(clazz, witness);
       } else {
-        workList.enqueueMarkInstantiatedAction(clazz, reason);
+        workList.enqueueMarkInstantiatedAction(clazz, witness);
         if (clazz.hasDefaultInitializer()) {
           if (forceProguardCompatibility) {
             ProguardKeepRule compatRule =
@@ -459,14 +448,18 @@
                 KeepReason.dueToProguardCompatibilityKeepRule(compatRule));
           }
           if (clazz.isExternalizable(appView)) {
-            enqueueMarkMethodLiveAction(clazz, clazz.getDefaultInitializer(), reason);
+            enqueueMarkMethodLiveAction(clazz, clazz.getDefaultInitializer(), witness);
           }
         }
       }
     } else if (item.isDexEncodedField()) {
-      workList.enqueueMarkFieldKeptAction(item.asDexEncodedField(), reason);
+      KeepReasonWitness witness =
+          graphReporter.reportKeepField(precondition, rules, item.asDexEncodedField());
+      workList.enqueueMarkFieldKeptAction(item.asDexEncodedField(), witness);
     } else if (item.isDexEncodedMethod()) {
-      workList.enqueueMarkMethodKeptAction(item.asDexEncodedMethod(), reason);
+      KeepReasonWitness witness =
+          graphReporter.reportKeepMethod(precondition, rules, item.asDexEncodedMethod());
+      workList.enqueueMarkMethodKeptAction(item.asDexEncodedMethod(), witness);
     } else {
       throw new IllegalArgumentException(item.toString());
     }
@@ -1214,18 +1207,7 @@
 
   private void enqueueDependentItem(
       DexDefinition precondition, DexDefinition consequent, Set<ProguardKeepRuleBase> reasons) {
-    DexReference preconditionReference = precondition.toReference();
-    if (keptGraphConsumer != null) {
-      GraphNode consequentNode = getGraphNode(consequent.toReference());
-      for (ProguardKeepRuleBase rule : reasons) {
-        registerEdge(
-            consequentNode, KeepReason.dueToConditionalKeepRule(rule, preconditionReference));
-      }
-    }
-    // Note: the reason for keeping is reported above, so this just uses the first.
-    ProguardKeepRuleBase reason = reasons.iterator().next();
-    internalEnqueueRootItem(
-        consequent, KeepReason.dueToConditionalKeepRule(reason, preconditionReference));
+    internalEnqueueRootItem(consequent, reasons, precondition);
   }
 
   private void processAnnotations(DexDefinition holder, DexAnnotation[] annotations) {
@@ -1980,7 +1962,8 @@
     if (valuesMethod != null) {
       // TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
       // marking for not renaming it is in the root set.
-      enqueueRootItem(valuesMethod, reason);
+      workList.enqueueMarkMethodKeptAction(valuesMethod, reason);
+      pinnedItems.add(valuesMethod.toReference());
       rootSet.shouldNotBeMinified(valuesMethod.toReference());
     }
   }
@@ -2983,6 +2966,91 @@
     }
   }
 
+  class GraphReporter {
+
+    private EdgeKind reportPrecondition(KeepRuleGraphNode keepRuleGraphNode) {
+      if (keepRuleGraphNode.getPreconditions().isEmpty()) {
+        return EdgeKind.KeepRule;
+      }
+      for (GraphNode precondition : keepRuleGraphNode.getPreconditions()) {
+        reportEdge(precondition, keepRuleGraphNode, EdgeKind.KeepRulePrecondition);
+      }
+      return EdgeKind.ConditionalKeepRule;
+    }
+
+    KeepReasonWitness reportKeepClass(
+        DexDefinition precondition, ProguardKeepRuleBase rule, DexProgramClass clazz) {
+      if (keptGraphConsumer == null) {
+        return KeepReasonWitness.INSTANCE;
+      }
+      KeepRuleGraphNode ruleNode = getKeepRuleGraphNode(precondition, rule);
+      EdgeKind edgeKind = reportPrecondition(ruleNode);
+      return reportEdge(ruleNode, getClassGraphNode(clazz.type), edgeKind);
+    }
+
+    KeepReasonWitness reportKeepClass(
+        DexDefinition precondition, Collection<ProguardKeepRuleBase> rules, DexProgramClass clazz) {
+      assert !rules.isEmpty();
+      if (keptGraphConsumer != null) {
+        for (ProguardKeepRuleBase rule : rules) {
+          reportKeepClass(precondition, rule, clazz);
+        }
+      }
+      return KeepReasonWitness.INSTANCE;
+    }
+
+    KeepReasonWitness reportKeepMethod(
+        DexDefinition precondition, ProguardKeepRuleBase rule, DexEncodedMethod method) {
+      if (keptGraphConsumer == null) {
+        return KeepReasonWitness.INSTANCE;
+      }
+      KeepRuleGraphNode ruleNode = getKeepRuleGraphNode(precondition, rule);
+      EdgeKind edgeKind = reportPrecondition(ruleNode);
+      return reportEdge(ruleNode, getMethodGraphNode(method.method), edgeKind);
+    }
+
+    KeepReasonWitness reportKeepMethod(
+        DexDefinition precondition,
+        Collection<ProguardKeepRuleBase> rules,
+        DexEncodedMethod method) {
+      assert !rules.isEmpty();
+      if (keptGraphConsumer != null) {
+        for (ProguardKeepRuleBase rule : rules) {
+          reportKeepMethod(precondition, rule, method);
+        }
+      }
+      return KeepReasonWitness.INSTANCE;
+    }
+
+    KeepReasonWitness reportKeepField(
+        DexDefinition precondition, ProguardKeepRuleBase rule, DexEncodedField field) {
+      if (keptGraphConsumer == null) {
+        return KeepReasonWitness.INSTANCE;
+      }
+      KeepRuleGraphNode ruleNode = getKeepRuleGraphNode(precondition, rule);
+      EdgeKind edgeKind = reportPrecondition(ruleNode);
+      return reportEdge(ruleNode, getFieldGraphNode(field.field), edgeKind);
+    }
+
+    KeepReasonWitness reportKeepField(
+        DexDefinition precondition, Collection<ProguardKeepRuleBase> rules, DexEncodedField field) {
+      assert !rules.isEmpty();
+      if (keptGraphConsumer != null) {
+        for (ProguardKeepRuleBase rule : rules) {
+          reportKeepField(precondition, rule, field);
+        }
+      }
+      return KeepReasonWitness.INSTANCE;
+    }
+
+    private KeepReasonWitness reportEdge(
+        GraphNode source, GraphNode target, GraphEdgeInfo.EdgeKind kind) {
+      assert keptGraphConsumer != null;
+      keptGraphConsumer.acceptEdge(source, target, getEdgeInfo(kind));
+      return KeepReasonWitness.INSTANCE;
+    }
+  }
+
   /**
    * Sentinel value indicating that a keep reason has been reported.
    *
@@ -3070,13 +3138,6 @@
     if (!sourceNode.isLibraryNode()) {
       GraphEdgeInfo edgeInfo = getEdgeInfo(reason);
       keptGraphConsumer.acceptEdge(sourceNode, target, edgeInfo);
-      if (reason.isDueToConditionalKeepRule()) {
-        GraphEdgeInfo conditionEdge = new GraphEdgeInfo(EdgeKind.KeepRulePrecondition);
-        for (DexReference precondition : reason.getPreconditions()) {
-          GraphNode preconditionNode = getGraphNode(precondition);
-          keptGraphConsumer.acceptEdge(preconditionNode, sourceNode, conditionEdge);
-        }
-      }
     }
     return KeepReasonWitness.INSTANCE;
   }
@@ -3104,7 +3165,11 @@
   }
 
   GraphEdgeInfo getEdgeInfo(KeepReason reason) {
-    return reasonInfo.computeIfAbsent(reason.edgeKind(), k -> new GraphEdgeInfo(k));
+    return getEdgeInfo(reason.edgeKind());
+  }
+
+  GraphEdgeInfo getEdgeInfo(GraphEdgeInfo.EdgeKind kind) {
+    return reasonInfo.computeIfAbsent(kind, k -> new GraphEdgeInfo(k));
   }
 
   AnnotationGraphNode getAnnotationGraphNode(DexItem type) {
@@ -3162,19 +3227,24 @@
         });
   }
 
-  GraphNode getKeepRuleGraphNode(ProguardKeepRuleBase rule) {
+  KeepRuleGraphNode getKeepRuleGraphNode(DexDefinition precondition, ProguardKeepRuleBase rule) {
     if (rule instanceof ProguardKeepRule) {
-      return ruleNodes.computeIfAbsent(rule, key -> new KeepRuleGraphNode((ProguardKeepRule) rule));
+      Set<GraphNode> preconditions =
+          precondition != null
+              ? Collections.singleton(getGraphNode(precondition.toReference()))
+              : Collections.emptySet();
+      return ruleNodes.computeIfAbsent(rule, key -> new KeepRuleGraphNode(rule, preconditions));
     }
     if (rule instanceof ProguardIfRule) {
       ProguardIfRule ifRule = (ProguardIfRule) rule;
       assert !ifRule.getPreconditions().isEmpty();
+      assert precondition == null;
       return ruleNodes.computeIfAbsent(
           ifRule,
           key -> {
             Set<GraphNode> preconditions = new HashSet<>(ifRule.getPreconditions().size());
-            for (DexReference precondition : ifRule.getPreconditions()) {
-              preconditions.add(getGraphNode(precondition));
+            for (DexReference condition : ifRule.getPreconditions()) {
+              preconditions.add(getGraphNode(condition));
             }
             return new KeepRuleGraphNode(ifRule, preconditions);
           });
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index 9f0fb1c..a59f9e1 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -15,8 +15,6 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.Set;
 
 // TODO(herhut): Canonicalize reason objects.
 public abstract class KeepReason {
@@ -29,21 +27,6 @@
     return new AnnotatedOn(definition);
   }
 
-  static KeepReason dueToKeepRule(ProguardKeepRuleBase rule) {
-    if (rule instanceof ProguardKeepRule) {
-      return new DueToKeepRule(rule);
-    }
-    if (rule instanceof ProguardIfRule) {
-      ProguardIfRule ifRule = (ProguardIfRule) rule;
-      return new DueToConditionalKeepRule(ifRule, ifRule.getPreconditions());
-    }
-    throw new Unreachable("Unexpected proguard keep rule: " + rule);
-  }
-
-  static KeepReason dueToConditionalKeepRule(ProguardKeepRuleBase rule, DexReference reference) {
-    return new DueToConditionalKeepRule(rule, reference);
-  }
-
   static KeepReason dueToProguardCompatibilityKeepRule(ProguardKeepRule rule) {
     return new DueToProguardCompatibilityKeepRule(rule);
   }
@@ -92,10 +75,6 @@
     return false;
   }
 
-  public boolean isDueToConditionalKeepRule() {
-    return false;
-  }
-
   public boolean isInstantiatedIn() {
     return false;
   }
@@ -153,7 +132,7 @@
 
     @Override
     public GraphNode getSourceNode(Enqueuer enqueuer) {
-      return enqueuer.getKeepRuleGraphNode(keepRule);
+      return enqueuer.getKeepRuleGraphNode(null, keepRule);
     }
   }
 
@@ -173,37 +152,6 @@
     }
   }
 
-  private static class DueToConditionalKeepRule extends DueToKeepRule {
-
-    private final Set<DexReference> preconditions;
-
-    public DueToConditionalKeepRule(ProguardKeepRuleBase rule, DexReference precondition) {
-      this(rule, Collections.singleton(precondition));
-      assert precondition != null;
-    }
-
-    public DueToConditionalKeepRule(ProguardKeepRuleBase rule, Set<DexReference> preconditions) {
-      super(rule);
-      assert !preconditions.isEmpty();
-      this.preconditions = preconditions;
-    }
-
-    @Override
-    public Set<DexReference> getPreconditions() {
-      return preconditions;
-    }
-
-    @Override
-    public boolean isDueToConditionalKeepRule() {
-      return true;
-    }
-
-    @Override
-    public EdgeKind edgeKind() {
-      return EdgeKind.ConditionalKeepRule;
-    }
-  }
-
   private abstract static class BasedOnOtherMethod extends KeepReason {
 
     private final DexEncodedMethod method;
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByKeepClassMembersTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByKeepClassMembersTestRunner.java
index 50e381c..d07d918 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByKeepClassMembersTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByKeepClassMembersTestRunner.java
@@ -81,7 +81,7 @@
             .assertSuccessWithOutput(EXPECTED)
             .graphInspector();
 
-    // The only root should be the keep annotation rule.
+    // The only root should be the keep main rule.
     assertEquals(1, inspector.getRoots().size());
     QueryNode keepMainRule = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
     QueryNode keepClassMembersRule = inspector.rule(keepClassMembersRuleContent).assertNotRoot();