Provide placeholder rule class for deserialized Skylark rules

At this time, Skylark-defined rule classes don't get serialized, and
aren't available at package deserialization time. To allow packages
with Skylark-defined rule classes to deserialize, we provide a
placeholder rule class implementation for deserialized Skylark rules.

Resubmitting after previous rollback.

--
MOS_MIGRATED_REVID=97972209
diff --git a/src/main/java/com/google/devtools/build/lib/packages/ProtoUtils.java b/src/main/java/com/google/devtools/build/lib/packages/ProtoUtils.java
index 653e283..b566ba7 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/ProtoUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/ProtoUtils.java
@@ -35,11 +35,17 @@
 import static com.google.devtools.build.lib.packages.Type.STRING_LIST_DICT;
 import static com.google.devtools.build.lib.packages.Type.TRISTATE;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
+import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
 import com.google.devtools.build.lib.query2.proto.proto2api.Build.Attribute.Discriminator;
 
-import java.util.Map;
+import java.util.Set;
 
 /**
  * Shared code used in proto buffer output for rules and rule classes.
@@ -48,9 +54,13 @@
   /**
    * This map contains all attribute types that are recognized by the protocol
    * output formatter.
+   *
+   * <p>If you modify this map, please ensure that {@link #getTypeFromDiscriminator} can still
+   * resolve a {@link Discriminator} value to exactly one {@link Type} (using an optional nodep
+   * hint, as described below).
    */
-  private static final Map<Type<?>, Discriminator> TYPE_MAP
-      = new ImmutableMap.Builder<Type<?>, Discriminator>()
+  static final ImmutableMap<Type<?>, Discriminator> TYPE_MAP =
+      new ImmutableMap.Builder<Type<?>, Discriminator>()
           .put(INTEGER, Discriminator.INTEGER)
           .put(DISTRIBUTIONS, Discriminator.DISTRIBUTION_SET)
           .put(LABEL, Discriminator.LABEL)
@@ -75,11 +85,71 @@
           .put(STRING_DICT_UNARY, Discriminator.STRING_DICT_UNARY)
           .build();
 
+  static final ImmutableSet<Type<?>> NODEP_TYPES =
+      ImmutableSet.of(NODEP_LABEL, NODEP_LABEL_LIST);
+
+  static final ImmutableSetMultimap<Discriminator, Type<?>> INVERSE_TYPE_MAP =
+      TYPE_MAP.asMultimap().inverse();
+
   /**
    * Returns the appropriate Attribute.Discriminator value from an internal attribute type.
    */
   public static Discriminator getDiscriminatorFromType(Type<?> type) {
-    Preconditions.checkArgument(TYPE_MAP.containsKey(type));
+    Preconditions.checkArgument(TYPE_MAP.containsKey(type), type);
     return TYPE_MAP.get(type);
   }
+
+  /**
+   * Returns the appropriate set of internal attribute types from an Attribute.Discriminator value.
+   */
+  public static ImmutableSet<Type<?>> getTypesFromDiscriminator(Discriminator discriminator) {
+    Preconditions.checkArgument(INVERSE_TYPE_MAP.containsKey(discriminator), discriminator);
+    return INVERSE_TYPE_MAP.get(discriminator);
+  }
+
+  /**
+   * Returns the appropriate internal attribute type from an Attribute.Discriminator value, given
+   * an optional nodeps hint.
+   */
+  public static Type<?> getTypeFromDiscriminator(Discriminator discriminator,
+      Optional<Boolean> nodeps, String ruleClassName, String attrName) {
+    Preconditions.checkArgument(INVERSE_TYPE_MAP.containsKey(discriminator));
+    ImmutableSet<Type<?>> possibleTypes = ProtoUtils.getTypesFromDiscriminator(discriminator);
+    Type<?> preciseType;
+    if (possibleTypes.size() == 1) {
+      preciseType = Iterables.getOnlyElement(possibleTypes);
+    } else {
+      // If there is more than one possible type associated with the discriminator, then the
+      // discriminator must be either Discriminator.STRING or Discriminator.STRING_LIST.
+      //
+      // If it is Discriminator.STRING, then its possible Type<?>s are {NODEP_LABEL, STRING}. The
+      // nodeps hint must be present in order to distinguish between them. If nodeps is true,
+      // then the Type<?> must be NODEP_LABEL, and if false, it must be STRING.
+      //
+      // A similar relation holds for the Discriminator value STRING_LIST, and its possible
+      // Type<?>s {NODEP_LABEL_LIST, STRING_LIST}.
+
+      Preconditions.checkArgument(nodeps.isPresent(),
+          "Nodeps hint is required when discriminator is associated with more than one type."
+              + " Discriminator: \"%s\", Rule class: \"%s\", Attr: \"%s\"", discriminator,
+          ruleClassName, attrName);
+      if (nodeps.get()) {
+        Set<Type<?>> nodepType = Sets.filter(possibleTypes, Predicates.in(NODEP_TYPES));
+        Preconditions.checkState(nodepType.size() == 1,
+            "There should be exactly one NODEP type associated with discriminator \"%s\""
+                + ", but found these: %s. Rule class: \"%s\", Attr: \"%s\"", discriminator,
+            nodepType, ruleClassName, attrName);
+        preciseType = Iterables.getOnlyElement(nodepType);
+      } else {
+        Set<Type<?>> notNodepType =
+            Sets.filter(possibleTypes, Predicates.not(Predicates.in(NODEP_TYPES)));
+        Preconditions.checkState(notNodepType.size() == 1,
+            "There should be exactly one non-NODEP type associated with discriminator \"%s\""
+                + ", but found these: %s. Rule class: \"%s\", Attr: \"%s\"", discriminator,
+            notNodepType, ruleClassName, attrName);
+        preciseType = Iterables.getOnlyElement(notNodepType);
+      }
+    }
+    return preciseType;
+  }
 }