Allow expansion flags to have values.

This lets us change what it expands based on the argument passed to the flag.

RELNOTES: Allows flags that expand to take values.
PiperOrigin-RevId: 160298412
diff --git a/src/main/java/com/google/devtools/common/options/InvocationPolicyEnforcer.java b/src/main/java/com/google/devtools/common/options/InvocationPolicyEnforcer.java
index 651cd78..97be190 100644
--- a/src/main/java/com/google/devtools/common/options/InvocationPolicyEnforcer.java
+++ b/src/main/java/com/google/devtools/common/options/InvocationPolicyEnforcer.java
@@ -99,9 +99,9 @@
 
     // The effective policy returned is expanded, filtered for applicable commands, and cleaned of
     // redundancies and conflicts.
-    List<FlagPolicy> effectivePolicy = getEffectivePolicy(invocationPolicy, parser, command);
+    List<FlagPolicy> effectivePolicies = getEffectivePolicies(invocationPolicy, parser, command);
 
-    for (FlagPolicy flagPolicy : effectivePolicy) {
+    for (FlagPolicy flagPolicy : effectivePolicies) {
       String flagName = flagPolicy.getFlagName();
 
       OptionValueDescription valueDescription;
@@ -190,7 +190,7 @@
    *
    * <p>Expands any policies on expansion flags.
    */
-  public static List<FlagPolicy> getEffectivePolicy(
+  public static ImmutableList<FlagPolicy> getEffectivePolicies(
       InvocationPolicy invocationPolicy, OptionsParser parser, String command)
       throws OptionsParsingException {
     if (invocationPolicy == null) {
@@ -220,7 +220,71 @@
       effectivePolicy.put(flagName, expandedPolicy);
     }
 
-    return new ArrayList<>(effectivePolicy.values());
+    return ImmutableList.copyOf(effectivePolicy.values());
+  }
+
+  private static void throwAllowValuesOnExpansionFlagException(String flagName)
+      throws OptionsParsingException {
+    throw new OptionsParsingException(
+        String.format("Allow_Values on expansion flags like %s is not allowed.", flagName));
+  }
+
+  private static void throwDisallowValuesOnExpansionFlagException(String flagName)
+      throws OptionsParsingException {
+    throw new OptionsParsingException(
+        String.format("Disallow_Values on expansion flags like %s is not allowed.", flagName));
+  }
+
+  private static ImmutableList<OptionValueDescription> getExpansionsFromFlagPolicy(
+      FlagPolicy expansionPolicy, OptionDescription optionDescription, OptionsParser parser)
+      throws OptionsParsingException {
+    if (!optionDescription.isExpansion()) {
+      return ImmutableList.of();
+    }
+
+    String expansionFlagName = expansionPolicy.getFlagName();
+
+    ImmutableList.Builder<OptionValueDescription> resultsBuilder =
+        ImmutableList.<OptionValueDescription>builder();
+    switch (expansionPolicy.getOperationCase()) {
+      case SET_VALUE:
+        {
+          SetValue setValue = expansionPolicy.getSetValue();
+          if (setValue.getFlagValueCount() > 0) {
+            for (String value : setValue.getFlagValueList()) {
+              resultsBuilder.addAll(
+                  parser.getExpansionOptionValueDescriptions(expansionFlagName, value));
+            }
+          } else {
+            resultsBuilder.addAll(
+                parser.getExpansionOptionValueDescriptions(expansionFlagName, null));
+          }
+        }
+        break;
+      case USE_DEFAULT:
+        resultsBuilder.addAll(parser.getExpansionOptionValueDescriptions(expansionFlagName, null));
+        break;
+      case ALLOW_VALUES:
+        // All expansions originally given to the parser have been expanded by now, so these two
+        // cases aren't necessary (the values given in the flag policy shouldn't need to be
+        // checked). If you care about blocking specific flag values you should block the behavior
+        // on the specific ones, not the expansion that contains them.
+        throwAllowValuesOnExpansionFlagException(expansionPolicy.getFlagName());
+        break;
+      case DISALLOW_VALUES:
+        throwDisallowValuesOnExpansionFlagException(expansionPolicy.getFlagName());
+        break;
+      case OPERATION_NOT_SET:
+        throw new PolicyOperationNotSetException(expansionPolicy.getFlagName());
+      default:
+        log.warning(
+            String.format(
+                "Unknown operation '%s' from invocation policy for flag '%s'",
+                expansionPolicy.getOperationCase(), expansionFlagName));
+        break;
+    }
+
+    return resultsBuilder.build();
   }
 
   /**
@@ -234,21 +298,24 @@
       FlagPolicy originalPolicy,
       OptionsParser parser)
       throws OptionsParsingException {
-    List<FlagPolicy> expandedPolicy = new ArrayList<>();
+    List<FlagPolicy> expandedPolicies = new ArrayList<>();
 
-    OptionDescription originalDesc = parser.getOptionDescription(originalPolicy.getFlagName());
-    if (originalDesc == null) {
+    OptionDescription originalOptionDescription =
+        parser.getOptionDescription(originalPolicy.getFlagName());
+    if (originalOptionDescription == null) {
       // InvocationPolicy ignores policy on non-existing flags by design, for version compatibility.
-      return expandedPolicy;
+      return expandedPolicies;
     }
 
+    ImmutableList<OptionValueDescription> expansions =
+        getExpansionsFromFlagPolicy(originalPolicy, originalOptionDescription, parser);
     ImmutableList.Builder<OptionValueDescription> subflagBuilder = new ImmutableList.Builder<>();
     ImmutableList<OptionValueDescription> subflags =
         subflagBuilder
-            .addAll(originalDesc.getImplicitRequirements())
-            .addAll(originalDesc.getExpansions())
+            .addAll(originalOptionDescription.getImplicitRequirements())
+            .addAll(expansions)
             .build();
-    boolean isExpansion = !originalDesc.getExpansions().isEmpty();
+    boolean isExpansion = originalOptionDescription.isExpansion();
 
     if (!subflags.isEmpty() && log.isLoggable(Level.FINE)) {
       // Log the expansion. Since this is logged regardless of user provided command line, it is
@@ -283,10 +350,10 @@
           && originalPolicy.getOperationCase().equals(OperationCase.SET_VALUE)) {
         repeatableSubflagsInSetValues.put(currentSubflag.getName(), currentSubflag);
       } else {
-        FlagPolicy subflagAsPolicy = getSubflagAsPolicy(
-            currentSubflag, originalPolicy, originalDesc);
+        FlagPolicy subflagAsPolicy =
+            getSubflagAsPolicy(currentSubflag, originalPolicy, isExpansion);
         // In case any of the expanded flags are themselves expansions, recurse.
-        expandedPolicy.addAll(expandPolicy(subflagAsPolicy, parser));
+        expandedPolicies.addAll(expandPolicy(subflagAsPolicy, parser));
       }
     }
 
@@ -299,22 +366,18 @@
       for (OptionValueDescription setValue : repeatableSubflagsInSetValues.get(repeatableFlag)) {
         newValues.add(setValue.getOriginalValueString());
       }
-      expandedPolicy.add(
+      expandedPolicies.add(
           getSetValueSubflagAsPolicy(
-              repeatableFlag,
-              newValues,
-              /* allowMultiple */ true,
-              originalPolicy));
-
+              repeatableFlag, newValues, /* allowMultiple */ true, originalPolicy));
     }
 
     // Don't add the original policy if it was an expansion flag, which have no value, but do add
     // it if there was either no expansion or if it was a valued flag with implicit requirements.
     if (!isExpansion) {
-      expandedPolicy.add(originalPolicy);
+      expandedPolicies.add(originalPolicy);
     }
 
-    return expandedPolicy;
+    return expandedPolicies;
   }
 
   /**
@@ -365,19 +428,18 @@
    * corresponding policy.
    */
   private static FlagPolicy getSubflagAsPolicy(
-      OptionValueDescription currentSubflag,
-      FlagPolicy originalPolicy,
-      OptionDescription originalDesc) throws OptionsParsingException {
-    boolean isExpansion = !originalDesc.getExpansions().isEmpty();
+      OptionValueDescription currentSubflag, FlagPolicy originalPolicy, boolean isExpansion)
+      throws OptionsParsingException {
     FlagPolicy subflagAsPolicy = null;
     switch (originalPolicy.getOperationCase()) {
       case SET_VALUE:
-        assert(!currentSubflag.getAllowMultiple());
-        subflagAsPolicy = getSetValueSubflagAsPolicy(
-            currentSubflag.getName(),
-            ImmutableList.of(currentSubflag.getOriginalValueString()),
-            /* allowMultiple */ false,
-            originalPolicy);
+        assert (!currentSubflag.getAllowMultiple());
+        subflagAsPolicy =
+            getSetValueSubflagAsPolicy(
+                currentSubflag.getName(),
+                ImmutableList.of(currentSubflag.getOriginalValueString()),
+                /*allowMultiple=*/ false,
+                originalPolicy);
         break;
 
       case USE_DEFAULT:
@@ -394,10 +456,7 @@
 
       case ALLOW_VALUES:
         if (isExpansion) {
-          throw new OptionsParsingException(
-              String.format(
-                  "Allow_Values on expansion flags like %s is not allowed.",
-                  originalPolicy.getFlagName()));
+          throwAllowValuesOnExpansionFlagException(originalPolicy.getFlagName());
         }
         // If this flag is an implicitRequirement, and some values for the parent flag are
         // allowed, nothing needs to happen on the implicitRequirement that is set for all
@@ -406,10 +465,7 @@
 
       case DISALLOW_VALUES:
         if (isExpansion) {
-          throw new OptionsParsingException(
-              String.format(
-                  "Disallow_Values on expansion flags like %s is not allowed.",
-                  originalPolicy.getFlagName()));
+          throwDisallowValuesOnExpansionFlagException(originalPolicy.getFlagName());
         }
         // If this flag is an implicitRequirement, and some values for the parent flag are
         // disallowed, that implies that all others are allowed, so nothing needs to happen
@@ -684,7 +740,7 @@
         OptionDescription optionDescription,
         Set<Object> convertedPolicyValues)
         throws OptionsParsingException {
-      
+
       if (optionDescription.getAllowMultiple()) {
         // allowMultiple requires that the type of the option be List<T>, so cast from Object
         // to List<?>.