Simplify AbstractSkyFunctionEnvironment to more directly call getValueOrUntypedExceptions.

PiperOrigin-RevId: 192329649
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctionEnvironmentForTesting.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctionEnvironmentForTesting.java
index 0acb49c..e4cd196 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctionEnvironmentForTesting.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctionEnvironmentForTesting.java
@@ -25,7 +25,6 @@
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
-import com.google.devtools.build.skyframe.ValueOrExceptionUtils;
 import com.google.devtools.build.skyframe.ValueOrUntypedException;
 import java.util.Map;
 
@@ -56,7 +55,7 @@
         skyframeExecutor.evaluateSkyKeys(eventHandler, keysToEvaluate, true);
         buildDriver.evaluate(depKeys, true, ResourceUsage.getAvailableProcessors(), eventHandler);
     for (SkyKey depKey : ImmutableSet.copyOf(depKeys)) {
-      resultMap.put(depKey, ValueOrExceptionUtils.ofValue(evaluationResult.get(depKey)));
+      resultMap.put(depKey, ValueOrUntypedException.ofValueUntyped(evaluationResult.get(depKey)));
     }
     return resultMap.build();
   }
diff --git a/src/main/java/com/google/devtools/build/skyframe/AbstractSkyFunctionEnvironment.java b/src/main/java/com/google/devtools/build/skyframe/AbstractSkyFunctionEnvironment.java
index 2b0984d..e7bda39 100644
--- a/src/main/java/com/google/devtools/build/skyframe/AbstractSkyFunctionEnvironment.java
+++ b/src/main/java/com/google/devtools/build/skyframe/AbstractSkyFunctionEnvironment.java
@@ -14,11 +14,9 @@
 package com.google.devtools.build.skyframe;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 import com.google.devtools.build.lib.util.GroupedList;
-import com.google.devtools.build.skyframe.ValueOrExceptionUtils.BottomException;
 import java.util.Collections;
 import java.util.Map;
 import javax.annotation.Nullable;
@@ -32,12 +30,6 @@
   protected boolean valuesMissing = false;
   @Nullable private final GroupedList<SkyKey> temporaryDirectDeps;
 
-  private <E extends Exception> ValueOrException<E> getValueOrException(
-      SkyKey depKey, Class<E> exceptionClass) throws InterruptedException {
-    return ValueOrExceptionUtils.downconvert(
-        getValueOrException(depKey, exceptionClass, BottomException.class), exceptionClass);
-  }
-
   public AbstractSkyFunctionEnvironment(@Nullable GroupedList<SkyKey> temporaryDirectDeps) {
     this.temporaryDirectDeps = temporaryDirectDeps;
   }
@@ -51,138 +43,6 @@
     return temporaryDirectDeps;
   }
 
-  private <E1 extends Exception, E2 extends Exception>
-      ValueOrException2<E1, E2> getValueOrException(
-          SkyKey depKey, Class<E1> exceptionClass1, Class<E2> exceptionClass2)
-          throws InterruptedException {
-    return ValueOrExceptionUtils.downconvert(getValueOrException(depKey, exceptionClass1,
-        exceptionClass2, BottomException.class), exceptionClass1, exceptionClass2);
-  }
-
-  private <E1 extends Exception, E2 extends Exception, E3 extends Exception>
-      ValueOrException3<E1, E2, E3> getValueOrException(
-          SkyKey depKey,
-          Class<E1> exceptionClass1,
-          Class<E2> exceptionClass2,
-          Class<E3> exceptionClass3)
-          throws InterruptedException {
-    return ValueOrExceptionUtils.downconvert(
-        getValueOrException(depKey, exceptionClass1, exceptionClass2, exceptionClass3,
-            BottomException.class),
-        exceptionClass1,
-        exceptionClass2,
-        exceptionClass3);
-  }
-
-  private <E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception>
-      ValueOrException4<E1, E2, E3, E4> getValueOrException(
-          SkyKey depKey,
-          Class<E1> exceptionClass1,
-          Class<E2> exceptionClass2,
-          Class<E3> exceptionClass3,
-          Class<E4> exceptionClass4)
-          throws InterruptedException {
-    return ValueOrExceptionUtils.downconvert(
-        getValueOrException(depKey, exceptionClass1, exceptionClass2, exceptionClass3,
-            exceptionClass4, BottomException.class),
-        exceptionClass1,
-        exceptionClass2,
-        exceptionClass3,
-        exceptionClass4);
-  }
-
-  private <
-          E1 extends Exception,
-          E2 extends Exception,
-          E3 extends Exception,
-          E4 extends Exception,
-          E5 extends Exception>
-      ValueOrException5<E1, E2, E3, E4, E5> getValueOrException(
-          SkyKey depKey,
-          Class<E1> exceptionClass1,
-          Class<E2> exceptionClass2,
-          Class<E3> exceptionClass3,
-          Class<E4> exceptionClass4,
-          Class<E5> exceptionClass5)
-          throws InterruptedException {
-    return getValueOrExceptions(
-        ImmutableSet.of(depKey),
-        exceptionClass1,
-        exceptionClass2,
-        exceptionClass3,
-        exceptionClass4,
-        exceptionClass5).get(depKey);
-  }
-
-  private <
-          E1 extends Exception,
-          E2 extends Exception,
-          E3 extends Exception,
-          E4 extends Exception,
-          E5 extends Exception>
-      Map<SkyKey, ValueOrException5<E1, E2, E3, E4, E5>> getValueOrExceptions(
-          Iterable<? extends SkyKey> depKeys,
-          final Class<E1> exceptionClass1,
-          final Class<E2> exceptionClass2,
-          final Class<E3> exceptionClass3,
-          final Class<E4> exceptionClass4,
-          final Class<E5> exceptionClass5)
-          throws InterruptedException {
-    SkyFunctionException.validateExceptionType(exceptionClass1);
-    SkyFunctionException.validateExceptionType(exceptionClass2);
-    SkyFunctionException.validateExceptionType(exceptionClass3);
-    SkyFunctionException.validateExceptionType(exceptionClass4);
-    SkyFunctionException.validateExceptionType(exceptionClass5);
-    Map<SkyKey, ValueOrUntypedException> valueOrExceptions =
-        getValueOrUntypedExceptions(depKeys);
-    for (ValueOrUntypedException voe : valueOrExceptions.values()) {
-      SkyValue value = voe.getValue();
-      if (value == null) {
-        Exception e = voe.getException();
-        if (e == null
-            || (!exceptionClass1.isInstance(e)
-                && !exceptionClass2.isInstance(e)
-                && !exceptionClass3.isInstance(e)
-                && !exceptionClass4.isInstance(e)
-                && !exceptionClass5.isInstance(e))) {
-          valuesMissing = true;
-          break;
-        }
-      }
-    }
-    // We transform the values directly to avoid the transient memory cost of creating a new map.
-    return Maps.transformValues(
-        valueOrExceptions,
-        new Function<ValueOrUntypedException, ValueOrException5<E1, E2, E3, E4, E5>>() {
-          @Override
-          public ValueOrException5<E1, E2, E3, E4, E5> apply(ValueOrUntypedException voe) {
-            SkyValue value = voe.getValue();
-            if (value != null) {
-              return ValueOrExceptionUtils.<E1, E2, E3, E4, E5>ofValue(value);
-            }
-            Exception e = voe.getException();
-            if (e != null) {
-              if (exceptionClass1.isInstance(e)) {
-                return ValueOrExceptionUtils.<E1, E2, E3, E4, E5>ofExn1(exceptionClass1.cast(e));
-              }
-              if (exceptionClass2.isInstance(e)) {
-                return ValueOrExceptionUtils.<E1, E2, E3, E4, E5>ofExn2(exceptionClass2.cast(e));
-              }
-              if (exceptionClass3.isInstance(e)) {
-                return ValueOrExceptionUtils.<E1, E2, E3, E4, E5>ofExn3(exceptionClass3.cast(e));
-              }
-              if (exceptionClass4.isInstance(e)) {
-                return ValueOrExceptionUtils.<E1, E2, E3, E4, E5>ofExn4(exceptionClass4.cast(e));
-              }
-              if (exceptionClass5.isInstance(e)) {
-                return ValueOrExceptionUtils.<E1, E2, E3, E4, E5>ofExn5(exceptionClass5.cast(e));
-              }
-            }
-            return ValueOrExceptionUtils.<E1, E2, E3, E4, E5>ofNullValue();
-          }
-        });
-  }
-
   /** Implementations should set {@link #valuesMissing} as necessary. */
   protected abstract Map<SkyKey, ValueOrUntypedException> getValueOrUntypedExceptions(
       Iterable<? extends SkyKey> depKeys) throws InterruptedException;
@@ -190,18 +50,14 @@
   @Override
   @Nullable
   public SkyValue getValue(SkyKey depKey) throws InterruptedException {
-    try {
-      return getValueOrThrow(depKey, BottomException.class);
-    } catch (BottomException e) {
-      throw new IllegalStateException("shouldn't reach here");
-    }
+    return getValues(ImmutableSet.of(depKey)).get(depKey);
   }
 
   @Override
   @Nullable
   public <E extends Exception> SkyValue getValueOrThrow(SkyKey depKey, Class<E> exceptionClass)
       throws E, InterruptedException {
-    return getValueOrException(depKey, exceptionClass).get();
+    return getValuesOrThrow(ImmutableSet.of(depKey), exceptionClass).get(depKey).get();
   }
 
   @Override
@@ -209,7 +65,9 @@
   public <E1 extends Exception, E2 extends Exception> SkyValue getValueOrThrow(
       SkyKey depKey, Class<E1> exceptionClass1, Class<E2> exceptionClass2)
       throws E1, E2, InterruptedException {
-    return getValueOrException(depKey, exceptionClass1, exceptionClass2).get();
+    return getValuesOrThrow(ImmutableSet.of(depKey), exceptionClass1, exceptionClass2)
+        .get(depKey)
+        .get();
   }
 
   @Override
@@ -221,7 +79,10 @@
           Class<E2> exceptionClass2,
           Class<E3> exceptionClass3)
           throws E1, E2, E3, InterruptedException {
-    return getValueOrException(depKey, exceptionClass1, exceptionClass2, exceptionClass3).get();
+    return getValuesOrThrow(
+            ImmutableSet.of(depKey), exceptionClass1, exceptionClass2, exceptionClass3)
+        .get(depKey)
+        .get();
   }
 
   @Override
@@ -233,12 +94,14 @@
           Class<E3> exceptionClass3,
           Class<E4> exceptionClass4)
           throws E1, E2, E3, E4, InterruptedException {
-    return getValueOrException(
-        depKey,
-        exceptionClass1,
-        exceptionClass2,
-        exceptionClass3,
-        exceptionClass4).get();
+    return getValuesOrThrow(
+            ImmutableSet.of(depKey),
+            exceptionClass1,
+            exceptionClass2,
+            exceptionClass3,
+            exceptionClass4)
+        .get(depKey)
+        .get();
   }
 
   @Override
@@ -256,27 +119,34 @@
           Class<E4> exceptionClass4,
           Class<E5> exceptionClass5)
           throws E1, E2, E3, E4, E5, InterruptedException {
-    return getValueOrException(
-        depKey,
-        exceptionClass1,
-        exceptionClass2,
-        exceptionClass3,
-        exceptionClass4,
-        exceptionClass5).get();
+    return getValuesOrThrow(
+            ImmutableSet.of(depKey),
+            exceptionClass1,
+            exceptionClass2,
+            exceptionClass3,
+            exceptionClass4,
+            exceptionClass5)
+        .get(depKey)
+        .get();
   }
 
   @Override
   public Map<SkyKey, SkyValue> getValues(Iterable<SkyKey> depKeys) throws InterruptedException {
-    return Maps.transformValues(getValuesOrThrow(depKeys, BottomException.class),
-        GET_VALUE_FROM_VOE);
+    Map<SkyKey, ValueOrUntypedException> valuesOrExceptions = getValueOrUntypedExceptions(depKeys);
+    checkValuesAreMissing5(valuesOrExceptions, null, null, null, null, null);
+    return Collections.unmodifiableMap(
+        Maps.transformValues(valuesOrExceptions, ValueOrUntypedException::getValue));
   }
 
   @Override
   public <E extends Exception> Map<SkyKey, ValueOrException<E>> getValuesOrThrow(
       Iterable<? extends SkyKey> depKeys, Class<E> exceptionClass) throws InterruptedException {
-    return Maps.transformValues(
-        getValuesOrThrow(depKeys, exceptionClass, BottomException.class),
-        makeSafeDowncastToVOEFunction(exceptionClass));
+    SkyFunctionException.validateExceptionType(exceptionClass);
+    Map<SkyKey, ValueOrUntypedException> valuesOrExceptions = getValueOrUntypedExceptions(depKeys);
+    checkValuesAreMissing5(valuesOrExceptions, exceptionClass, null, null, null, null);
+    return Collections.unmodifiableMap(
+        Maps.transformValues(
+            valuesOrExceptions, voe -> ValueOrException.fromUntypedException(voe, exceptionClass)));
   }
 
   @Override
@@ -284,9 +154,14 @@
       Map<SkyKey, ValueOrException2<E1, E2>> getValuesOrThrow(
           Iterable<? extends SkyKey> depKeys, Class<E1> exceptionClass1, Class<E2> exceptionClass2)
               throws InterruptedException {
-    return Maps.transformValues(
-        getValuesOrThrow(depKeys, exceptionClass1, exceptionClass2, BottomException.class),
-        makeSafeDowncastToVOE2Function(exceptionClass1, exceptionClass2));
+    SkyFunctionException.validateExceptionType(exceptionClass1);
+    SkyFunctionException.validateExceptionType(exceptionClass2);
+    Map<SkyKey, ValueOrUntypedException> valuesOrExceptions = getValueOrUntypedExceptions(depKeys);
+    checkValuesAreMissing5(valuesOrExceptions, exceptionClass1, exceptionClass2, null, null, null);
+    return Collections.unmodifiableMap(
+        Maps.transformValues(
+            valuesOrExceptions,
+            voe -> ValueOrException2.fromUntypedException(voe, exceptionClass1, exceptionClass2)));
   }
 
   @Override
@@ -297,10 +172,18 @@
           Class<E2> exceptionClass2,
           Class<E3> exceptionClass3)
               throws InterruptedException {
-    return Maps.transformValues(
-        getValuesOrThrow(depKeys, exceptionClass1, exceptionClass2, exceptionClass3,
-            BottomException.class),
-        makeSafeDowncastToVOE3Function(exceptionClass1, exceptionClass2, exceptionClass3));
+    SkyFunctionException.validateExceptionType(exceptionClass1);
+    SkyFunctionException.validateExceptionType(exceptionClass2);
+    SkyFunctionException.validateExceptionType(exceptionClass3);
+    Map<SkyKey, ValueOrUntypedException> valuesOrExceptions = getValueOrUntypedExceptions(depKeys);
+    checkValuesAreMissing5(
+        valuesOrExceptions, exceptionClass1, exceptionClass2, exceptionClass3, null, null);
+    return Collections.unmodifiableMap(
+        Maps.transformValues(
+            valuesOrExceptions,
+            voe ->
+                ValueOrException3.fromUntypedException(
+                    voe, exceptionClass1, exceptionClass2, exceptionClass3)));
   }
 
   @Override
@@ -312,11 +195,24 @@
           Class<E3> exceptionClass3,
           Class<E4> exceptionClass4)
            throws InterruptedException {
-    return Maps.transformValues(
-        getValuesOrThrow(depKeys, exceptionClass1, exceptionClass2, exceptionClass3,
-            exceptionClass4, BottomException.class),
-        makeSafeDowncastToVOE4Function(exceptionClass1, exceptionClass2, exceptionClass3,
-            exceptionClass4));
+    SkyFunctionException.validateExceptionType(exceptionClass1);
+    SkyFunctionException.validateExceptionType(exceptionClass2);
+    SkyFunctionException.validateExceptionType(exceptionClass3);
+    SkyFunctionException.validateExceptionType(exceptionClass4);
+    Map<SkyKey, ValueOrUntypedException> valuesOrExceptions = getValueOrUntypedExceptions(depKeys);
+    checkValuesAreMissing5(
+        valuesOrExceptions,
+        exceptionClass1,
+        exceptionClass2,
+        exceptionClass3,
+        exceptionClass4,
+        null);
+    return Collections.unmodifiableMap(
+        Maps.transformValues(
+            valuesOrExceptions,
+            voe ->
+                ValueOrException4.fromUntypedException(
+                    voe, exceptionClass1, exceptionClass2, exceptionClass3, exceptionClass4)));
   }
 
   @Override
@@ -334,89 +230,64 @@
           Class<E4> exceptionClass4,
           Class<E5> exceptionClass5)
               throws InterruptedException {
-    Map<SkyKey, ValueOrException5<E1, E2, E3, E4, E5>> result =
-        getValueOrExceptions(
-            depKeys,
-            exceptionClass1,
-            exceptionClass2,
-            exceptionClass3,
-            exceptionClass4,
-            exceptionClass5);
-    return Collections.unmodifiableMap(result);
+    SkyFunctionException.validateExceptionType(exceptionClass1);
+    SkyFunctionException.validateExceptionType(exceptionClass2);
+    SkyFunctionException.validateExceptionType(exceptionClass3);
+    SkyFunctionException.validateExceptionType(exceptionClass4);
+    SkyFunctionException.validateExceptionType(exceptionClass5);
+    Map<SkyKey, ValueOrUntypedException> valuesOrExceptions = getValueOrUntypedExceptions(depKeys);
+    checkValuesAreMissing5(
+        valuesOrExceptions,
+        exceptionClass1,
+        exceptionClass2,
+        exceptionClass3,
+        exceptionClass4,
+        exceptionClass5);
+    return Collections.unmodifiableMap(
+        Maps.transformValues(
+            valuesOrExceptions,
+            voe ->
+                ValueOrException5.fromUntypedException(
+                    voe,
+                    exceptionClass1,
+                    exceptionClass2,
+                    exceptionClass3,
+                    exceptionClass4,
+                    exceptionClass5)));
   }
 
+  private <
+          E1 extends Exception,
+          E2 extends Exception,
+          E3 extends Exception,
+          E4 extends Exception,
+          E5 extends Exception>
+      void checkValuesAreMissing5(
+          Map<SkyKey, ValueOrUntypedException> voes,
+          @Nullable Class<E1> exceptionClass1,
+          @Nullable Class<E2> exceptionClass2,
+          @Nullable Class<E3> exceptionClass3,
+          @Nullable Class<E4> exceptionClass4,
+          @Nullable Class<E5> exceptionClass5) {
+    for (ValueOrUntypedException voe : voes.values()) {
+      SkyValue value = voe.getValue();
+      if (value == null) {
+        Exception e = voe.getException();
+        if (e == null
+            || ((exceptionClass1 == null || !exceptionClass1.isInstance(e))
+                && (exceptionClass2 == null || !exceptionClass2.isInstance(e))
+                && (exceptionClass3 == null || !exceptionClass3.isInstance(e))
+                && (exceptionClass4 == null || !exceptionClass4.isInstance(e))
+                && (exceptionClass5 == null || !exceptionClass5.isInstance(e)))) {
+          valuesMissing = true;
+          return;
+        }
+      }
+    }
+  }
 
   @Override
   public boolean valuesMissing() {
     return valuesMissing;
   }
-
-  private static final Function<ValueOrException<BottomException>, SkyValue> GET_VALUE_FROM_VOE =
-      new Function<ValueOrException<BottomException>, SkyValue>() {
-        @Override
-        public SkyValue apply(ValueOrException<BottomException> voe) {
-          return ValueOrExceptionUtils.downconvert(voe);
-        }
-      };
-
-  private static <E extends Exception>
-      Function<ValueOrException2<E, BottomException>, ValueOrException<E>>
-          makeSafeDowncastToVOEFunction(final Class<E> exceptionClass) {
-    return new Function<ValueOrException2<E, BottomException>, ValueOrException<E>>() {
-      @Override
-      public ValueOrException<E> apply(ValueOrException2<E, BottomException> voe) {
-        return ValueOrExceptionUtils.downconvert(voe, exceptionClass);
-      }
-    };
-  }
-
-  private static <E1 extends Exception, E2 extends Exception>
-      Function<ValueOrException3<E1, E2, BottomException>, ValueOrException2<E1, E2>>
-      makeSafeDowncastToVOE2Function(
-          final Class<E1> exceptionClass1,
-          final Class<E2> exceptionClass2) {
-    return new Function<ValueOrException3<E1, E2, BottomException>, ValueOrException2<E1, E2>>() {
-      @Override
-      public ValueOrException2<E1, E2> apply(ValueOrException3<E1, E2, BottomException> voe) {
-        return ValueOrExceptionUtils.downconvert(voe, exceptionClass1, exceptionClass2);
-      }
-    };
-  }
-
-  private static <E1 extends Exception, E2 extends Exception, E3 extends Exception>
-      Function<ValueOrException4<E1, E2, E3, BottomException>, ValueOrException3<E1, E2, E3>>
-      makeSafeDowncastToVOE3Function(
-          final Class<E1> exceptionClass1,
-          final Class<E2> exceptionClass2,
-          final Class<E3> exceptionClass3) {
-    return new Function<ValueOrException4<E1, E2, E3, BottomException>,
-        ValueOrException3<E1, E2, E3>>() {
-      @Override
-      public ValueOrException3<E1, E2, E3> apply(
-          ValueOrException4<E1, E2, E3, BottomException> voe) {
-        return ValueOrExceptionUtils.downconvert(
-            voe, exceptionClass1, exceptionClass2, exceptionClass3);
-      }
-    };
-  }
-
-  private static <E1 extends Exception, E2 extends Exception, E3 extends Exception,
-                  E4 extends Exception>
-      Function<ValueOrException5<E1, E2, E3, E4, BottomException>,
-          ValueOrException4<E1, E2, E3, E4>>
-      makeSafeDowncastToVOE4Function(
-          final Class<E1> exceptionClass1,
-          final Class<E2> exceptionClass2,
-          final Class<E3> exceptionClass3,
-          final Class<E4> exceptionClass4) {
-    return new Function<ValueOrException5<E1, E2, E3, E4, BottomException>,
-                        ValueOrException4<E1, E2, E3, E4>>() {
-      @Override
-      public ValueOrException4<E1, E2, E3, E4> apply(
-          ValueOrException5<E1, E2, E3, E4, BottomException> voe) {
-        return ValueOrExceptionUtils.downconvert(
-            voe, exceptionClass1, exceptionClass2, exceptionClass3, exceptionClass4);
-      }
-    };
-  }
 }
diff --git a/src/main/java/com/google/devtools/build/skyframe/QueryableGraphBackedSkyFunctionEnvironment.java b/src/main/java/com/google/devtools/build/skyframe/QueryableGraphBackedSkyFunctionEnvironment.java
index dea50d6..3be1c6a 100644
--- a/src/main/java/com/google/devtools/build/skyframe/QueryableGraphBackedSkyFunctionEnvironment.java
+++ b/src/main/java/com/google/devtools/build/skyframe/QueryableGraphBackedSkyFunctionEnvironment.java
@@ -38,12 +38,12 @@
   private static ValueOrUntypedException toUntypedValue(NodeEntry nodeEntry)
       throws InterruptedException {
     if (nodeEntry == null || !nodeEntry.isDone()) {
-      return ValueOrExceptionUtils.ofNull();
+      return ValueOrUntypedException.ofNull();
     }
     SkyValue maybeWrappedValue = nodeEntry.getValueMaybeWithMetadata();
     SkyValue justValue = ValueWithMetadata.justValue(maybeWrappedValue);
     if (justValue != null) {
-      return ValueOrExceptionUtils.ofValueUntyped(justValue);
+      return ValueOrUntypedException.ofValueUntyped(justValue);
     }
     ErrorInfo errorInfo =
         Preconditions.checkNotNull(ValueWithMetadata.getMaybeErrorInfo(maybeWrappedValue));
@@ -51,12 +51,12 @@
 
     if (exception != null) {
       // Give SkyFunction#compute a chance to handle this exception.
-      return ValueOrExceptionUtils.ofExn(exception);
+      return ValueOrUntypedException.ofExn(exception);
     }
     // In a cycle.
     Preconditions.checkState(
         !Iterables.isEmpty(errorInfo.getCycleInfo()), "%s %s", errorInfo, maybeWrappedValue);
-    return ValueOrExceptionUtils.ofNull();
+    return ValueOrUntypedException.ofNull();
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java b/src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java
index 6546cc8..920c6c1 100644
--- a/src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java
+++ b/src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java
@@ -16,7 +16,6 @@
 import static com.google.devtools.build.skyframe.AbstractParallelEvaluator.isDoneForBuild;
 import static com.google.devtools.build.skyframe.ParallelEvaluator.maybeGetValueFromError;
 
-import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
@@ -349,6 +348,7 @@
   protected Map<SkyKey, ValueOrUntypedException> getValueOrUntypedExceptions(
       Iterable<? extends SkyKey> depKeys) throws InterruptedException {
     checkActive();
+    newlyRequestedDeps.startGroup();
     Map<SkyKey, SkyValue> values = getValuesMaybeFromError(depKeys);
     for (Map.Entry<SkyKey, SkyValue> depEntry : values.entrySet()) {
       SkyKey depKey = depEntry.getKey();
@@ -401,78 +401,48 @@
             .visit(ValueWithMetadata.getEvents(depValue));
       }
     }
+    newlyRequestedDeps.endGroup();
 
     return Maps.transformValues(
         values,
-        new Function<SkyValue, ValueOrUntypedException>() {
-          @Override
-          public ValueOrUntypedException apply(SkyValue maybeWrappedValue) {
-            if (maybeWrappedValue == NULL_MARKER) {
-              return ValueOrExceptionUtils.ofNull();
-            }
-            SkyValue justValue = ValueWithMetadata.justValue(maybeWrappedValue);
-            ErrorInfo errorInfo = ValueWithMetadata.getMaybeErrorInfo(maybeWrappedValue);
-
-            if (justValue != null && (evaluatorContext.keepGoing() || errorInfo == null)) {
-              // If the dep did compute a value, it is given to the caller if we are in
-              // keepGoing mode or if we are in noKeepGoingMode and there were no errors computing
-              // it.
-              return ValueOrExceptionUtils.ofValueUntyped(justValue);
-            }
-
-            // There was an error building the value, which we will either report by throwing an
-            // exception or insulate the caller from by returning null.
-            Preconditions.checkNotNull(errorInfo, "%s %s", skyKey, maybeWrappedValue);
-            Exception exception = errorInfo.getException();
-
-            if (!evaluatorContext.keepGoing() && exception != null && bubbleErrorInfo == null) {
-              // Child errors should not be propagated in noKeepGoing mode (except during error
-              // bubbling). Instead we should fail fast.
-              return ValueOrExceptionUtils.ofNull();
-            }
-
-            if (exception != null) {
-              // Give builder a chance to handle this exception.
-              return ValueOrExceptionUtils.ofExn(exception);
-            }
-            // In a cycle.
-            Preconditions.checkState(
-                !Iterables.isEmpty(errorInfo.getCycleInfo()),
-                "%s %s %s",
-                skyKey,
-                errorInfo,
-                maybeWrappedValue);
-            return ValueOrExceptionUtils.ofNull();
+        maybeWrappedValue -> {
+          if (maybeWrappedValue == NULL_MARKER) {
+            return ValueOrUntypedException.ofNull();
           }
-        });
-  }
+          SkyValue justValue = ValueWithMetadata.justValue(maybeWrappedValue);
+          ErrorInfo errorInfo = ValueWithMetadata.getMaybeErrorInfo(maybeWrappedValue);
 
-  @Override
-  public <
-          E1 extends Exception,
-          E2 extends Exception,
-          E3 extends Exception,
-          E4 extends Exception,
-          E5 extends Exception>
-      Map<SkyKey, ValueOrException5<E1, E2, E3, E4, E5>> getValuesOrThrow(
-          Iterable<? extends SkyKey> depKeys,
-          Class<E1> exceptionClass1,
-          Class<E2> exceptionClass2,
-          Class<E3> exceptionClass3,
-          Class<E4> exceptionClass4,
-          Class<E5> exceptionClass5)
-              throws InterruptedException {
-    newlyRequestedDeps.startGroup();
-    Map<SkyKey, ValueOrException5<E1, E2, E3, E4, E5>> result =
-        super.getValuesOrThrow(
-            depKeys,
-            exceptionClass1,
-            exceptionClass2,
-            exceptionClass3,
-            exceptionClass4,
-            exceptionClass5);
-    newlyRequestedDeps.endGroup();
-    return result;
+          if (justValue != null && (evaluatorContext.keepGoing() || errorInfo == null)) {
+            // If the dep did compute a value, it is given to the caller if we are in
+            // keepGoing mode or if we are in noKeepGoingMode and there were no errors computing
+            // it.
+            return ValueOrUntypedException.ofValueUntyped(justValue);
+          }
+
+          // There was an error building the value, which we will either report by throwing an
+          // exception or insulate the caller from by returning null.
+          Preconditions.checkNotNull(errorInfo, "%s %s", skyKey, maybeWrappedValue);
+          Exception exception = errorInfo.getException();
+
+          if (!evaluatorContext.keepGoing() && exception != null && bubbleErrorInfo == null) {
+            // Child errors should not be propagated in noKeepGoing mode (except during error
+            // bubbling). Instead we should fail fast.
+            return ValueOrUntypedException.ofNull();
+          }
+
+          if (exception != null) {
+            // Give builder a chance to handle this exception.
+            return ValueOrUntypedException.ofExn(exception);
+          }
+          // In a cycle.
+          Preconditions.checkState(
+              !Iterables.isEmpty(errorInfo.getCycleInfo()),
+              "%s %s %s",
+              skyKey,
+              errorInfo,
+              maybeWrappedValue);
+          return ValueOrUntypedException.ofNull();
+        });
   }
 
   private void addDep(SkyKey key) {
diff --git a/src/main/java/com/google/devtools/build/skyframe/SkyFunctionException.java b/src/main/java/com/google/devtools/build/skyframe/SkyFunctionException.java
index c967daf..da9c6cc 100644
--- a/src/main/java/com/google/devtools/build/skyframe/SkyFunctionException.java
+++ b/src/main/java/com/google/devtools/build/skyframe/SkyFunctionException.java
@@ -97,10 +97,6 @@
   }
 
   static <E extends Exception> void validateExceptionType(Class<E> exceptionClass) {
-    if (exceptionClass.equals(ValueOrExceptionUtils.BottomException.class)) {
-      return;
-    }
-
     if (exceptionClass.isAssignableFrom(RuntimeException.class)) {
       throw new IllegalStateException(exceptionClass.getSimpleName() + " is a supertype of "
           + "RuntimeException. Don't do this since then you would potentially swallow all "
diff --git a/src/main/java/com/google/devtools/build/skyframe/ValueOrException.java b/src/main/java/com/google/devtools/build/skyframe/ValueOrException.java
index a4ad569..e26cb1d 100644
--- a/src/main/java/com/google/devtools/build/skyframe/ValueOrException.java
+++ b/src/main/java/com/google/devtools/build/skyframe/ValueOrException.java
@@ -21,4 +21,89 @@
   /** Gets the stored value. Throws an exception if one was thrown when computing this value. */
   @Nullable
   public abstract SkyValue get() throws E;
+
+  static <E extends Exception> ValueOrException<E> fromUntypedException(
+      ValueOrUntypedException voe, Class<E> exceptionClass) {
+    SkyValue value = voe.getValue();
+    if (value != null) {
+      return ofValue(value);
+    }
+    Exception e = voe.getException();
+    if (e != null) {
+      if (exceptionClass.isInstance(e)) {
+        return ofExn1(exceptionClass.cast(e));
+      }
+    }
+    return ofNullValue();
+  }
+
+  private static <E extends Exception> ValueOrException<E> ofExn1(E e) {
+    return new ValueOrExceptionExnImpl<>(e);
+  }
+
+  private static <E extends Exception> ValueOrException<E> ofNullValue() {
+    return ValueOrExceptionValueImpl.ofNullValue();
+  }
+
+  private static <E extends Exception> ValueOrException<E> ofValue(SkyValue value) {
+    return new ValueOrExceptionValueImpl<>(value);
+  }
+
+  private static class ValueOrExceptionValueImpl<E extends Exception> extends ValueOrException<E> {
+    private static final ValueOrExceptionValueImpl<Exception> NULL =
+        new ValueOrExceptionValueImpl<>(null);
+
+    @Nullable private final SkyValue value;
+
+    private ValueOrExceptionValueImpl(@Nullable SkyValue value) {
+      this.value = value;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue get() {
+      return value;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return value;
+    }
+
+    @Override
+    @Nullable
+    public Exception getException() {
+      return null;
+    }
+
+    @SuppressWarnings("unchecked")
+    static <E extends Exception> ValueOrExceptionValueImpl<E> ofNullValue() {
+      return (ValueOrExceptionValueImpl<E>) NULL;
+    }
+  }
+
+  private static class ValueOrExceptionExnImpl<E extends Exception> extends ValueOrException<E> {
+    private final E e;
+
+    private ValueOrExceptionExnImpl(E e) {
+      this.e = e;
+    }
+
+    @Override
+    public SkyValue get() throws E {
+      throw e;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return null;
+    }
+
+    @Override
+    public Exception getException() {
+      return e;
+    }
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/skyframe/ValueOrException2.java b/src/main/java/com/google/devtools/build/skyframe/ValueOrException2.java
index 9e8649f..7c8bdc3 100644
--- a/src/main/java/com/google/devtools/build/skyframe/ValueOrException2.java
+++ b/src/main/java/com/google/devtools/build/skyframe/ValueOrException2.java
@@ -22,4 +22,128 @@
   /** Gets the stored value. Throws an exception if one was thrown when computing this value. */
   @Nullable
   public abstract SkyValue get() throws E1, E2;
+
+  static <E1 extends Exception, E2 extends Exception>
+      ValueOrException2<E1, E2> fromUntypedException(
+          ValueOrUntypedException voe, Class<E1> exceptionClass1, Class<E2> exceptionClass2) {
+    SkyValue value = voe.getValue();
+    if (value != null) {
+      return ofValue(value);
+    }
+    Exception e = voe.getException();
+    if (e != null) {
+      if (exceptionClass1.isInstance(e)) {
+        return ofExn1(exceptionClass1.cast(e));
+      }
+      if (exceptionClass2.isInstance(e)) {
+        return ofExn2(exceptionClass2.cast(e));
+      }
+    }
+    return ofNullValue();
+  }
+
+  private static <E1 extends Exception, E2 extends Exception>
+      ValueOrException2<E1, E2> ofNullValue() {
+    return ValueOrException2ValueImpl.ofNullValue();
+  }
+
+  private static <E1 extends Exception, E2 extends Exception> ValueOrException2<E1, E2> ofValue(
+      SkyValue value) {
+    return new ValueOrException2ValueImpl<>(value);
+  }
+
+  private static <E1 extends Exception, E2 extends Exception> ValueOrException2<E1, E2> ofExn1(
+      E1 e) {
+    return new ValueOrException2Exn1Impl<>(e);
+  }
+
+  private static <E1 extends Exception, E2 extends Exception> ValueOrException2<E1, E2> ofExn2(
+      E2 e) {
+    return new ValueOrException2Exn2Impl<>(e);
+  }
+
+  private static class ValueOrException2ValueImpl<E1 extends Exception, E2 extends Exception>
+      extends ValueOrException2<E1, E2> {
+    private static final ValueOrException2ValueImpl<Exception, Exception> NULL =
+        new ValueOrException2ValueImpl<>(null);
+    @Nullable private final SkyValue value;
+
+    ValueOrException2ValueImpl(@Nullable SkyValue value) {
+      this.value = value;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue get() {
+      return value;
+    }
+
+    @Override
+    @Nullable
+    public Exception getException() {
+      return null;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return value;
+    }
+
+    @SuppressWarnings("unchecked")
+    static <E1 extends Exception, E2 extends Exception>
+        ValueOrException2ValueImpl<E1, E2> ofNullValue() {
+      return (ValueOrException2ValueImpl<E1, E2>) NULL;
+    }
+  }
+
+  private static class ValueOrException2Exn1Impl<E1 extends Exception, E2 extends Exception>
+      extends ValueOrException2<E1, E2> {
+    private final E1 e;
+
+    private ValueOrException2Exn1Impl(E1 e) {
+      this.e = e;
+    }
+
+    @Override
+    public SkyValue get() throws E1 {
+      throw e;
+    }
+
+    @Override
+    public Exception getException() {
+      return e;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return null;
+    }
+  }
+
+  private static class ValueOrException2Exn2Impl<E1 extends Exception, E2 extends Exception>
+      extends ValueOrException2<E1, E2> {
+    private final E2 e;
+
+    private ValueOrException2Exn2Impl(E2 e) {
+      this.e = e;
+    }
+
+    @Override
+    public SkyValue get() throws E2 {
+      throw e;
+    }
+
+    @Override
+    public Exception getException() {
+      return e;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return null;
+    }
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/skyframe/ValueOrException3.java b/src/main/java/com/google/devtools/build/skyframe/ValueOrException3.java
index a7db2be..106a50f 100644
--- a/src/main/java/com/google/devtools/build/skyframe/ValueOrException3.java
+++ b/src/main/java/com/google/devtools/build/skyframe/ValueOrException3.java
@@ -22,4 +22,169 @@
   /** Gets the stored value. Throws an exception if one was thrown when computing this value. */
   @Nullable
   public abstract SkyValue get() throws E1, E2, E3;
+
+  static <E1 extends Exception, E2 extends Exception, E3 extends Exception>
+      ValueOrException3<E1, E2, E3> fromUntypedException(
+          ValueOrUntypedException voe,
+          Class<E1> exceptionClass1,
+          Class<E2> exceptionClass2,
+          Class<E3> exceptionClass3) {
+    SkyValue value = voe.getValue();
+    if (value != null) {
+      return ofValue(value);
+    }
+    Exception e = voe.getException();
+    if (e != null) {
+      if (exceptionClass1.isInstance(e)) {
+        return ofExn1(exceptionClass1.cast(e));
+      }
+      if (exceptionClass2.isInstance(e)) {
+        return ofExn2(exceptionClass2.cast(e));
+      }
+      if (exceptionClass3.isInstance(e)) {
+        return ofExn3(exceptionClass3.cast(e));
+      }
+    }
+    return ofNullValue();
+  }
+
+  private static <E1 extends Exception, E2 extends Exception, E3 extends Exception>
+      ValueOrException3<E1, E2, E3> ofNullValue() {
+    return ValueOrException3ValueImpl.ofNullValue();
+  }
+
+  private static <E1 extends Exception, E2 extends Exception, E3 extends Exception>
+      ValueOrException3<E1, E2, E3> ofValue(SkyValue value) {
+    return new ValueOrException3ValueImpl<>(value);
+  }
+
+  private static <E1 extends Exception, E2 extends Exception, E3 extends Exception>
+      ValueOrException3<E1, E2, E3> ofExn1(E1 e) {
+    return new ValueOrException3Exn1Impl<>(e);
+  }
+
+  private static <E1 extends Exception, E2 extends Exception, E3 extends Exception>
+      ValueOrException3<E1, E2, E3> ofExn2(E2 e) {
+    return new ValueOrException3Exn2Impl<>(e);
+  }
+
+  private static <E1 extends Exception, E2 extends Exception, E3 extends Exception>
+      ValueOrException3<E1, E2, E3> ofExn3(E3 e) {
+    return new ValueOrException3Exn3Impl<>(e);
+  }
+
+  private static class ValueOrException3ValueImpl<
+          E1 extends Exception, E2 extends Exception, E3 extends Exception>
+      extends ValueOrException3<E1, E2, E3> {
+    private static final ValueOrException3ValueImpl<Exception, Exception, Exception> NULL =
+        new ValueOrException3ValueImpl<>(null);
+
+    @Nullable private final SkyValue value;
+
+    ValueOrException3ValueImpl(@Nullable SkyValue value) {
+      this.value = value;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue get() {
+      return value;
+    }
+
+    @Override
+    @Nullable
+    public Exception getException() {
+      return null;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return value;
+    }
+
+    @SuppressWarnings("unchecked")
+    static <E1 extends Exception, E2 extends Exception, E3 extends Exception>
+        ValueOrException3ValueImpl<E1, E2, E3> ofNullValue() {
+      return (ValueOrException3ValueImpl<E1, E2, E3>) NULL;
+    }
+  }
+
+  private static class ValueOrException3Exn1Impl<
+          E1 extends Exception, E2 extends Exception, E3 extends Exception>
+      extends ValueOrException3<E1, E2, E3> {
+    private final E1 e;
+
+    private ValueOrException3Exn1Impl(E1 e) {
+      this.e = e;
+    }
+
+    @Override
+    public SkyValue get() throws E1 {
+      throw e;
+    }
+
+    @Override
+    public Exception getException() {
+      return e;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return null;
+    }
+  }
+
+  private static class ValueOrException3Exn2Impl<
+          E1 extends Exception, E2 extends Exception, E3 extends Exception>
+      extends ValueOrException3<E1, E2, E3> {
+    private final E2 e;
+
+    private ValueOrException3Exn2Impl(E2 e) {
+      this.e = e;
+    }
+
+    @Override
+    public SkyValue get() throws E2 {
+      throw e;
+    }
+
+    @Override
+    public Exception getException() {
+      return e;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return null;
+    }
+  }
+
+  private static class ValueOrException3Exn3Impl<
+          E1 extends Exception, E2 extends Exception, E3 extends Exception>
+      extends ValueOrException3<E1, E2, E3> {
+    private final E3 e;
+
+    private ValueOrException3Exn3Impl(E3 e) {
+      this.e = e;
+    }
+
+    @Override
+    public SkyValue get() throws E3 {
+      throw e;
+    }
+
+    @Override
+    public Exception getException() {
+      return e;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return null;
+    }
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/skyframe/ValueOrException4.java b/src/main/java/com/google/devtools/build/skyframe/ValueOrException4.java
index 32be929..dbefece 100644
--- a/src/main/java/com/google/devtools/build/skyframe/ValueOrException4.java
+++ b/src/main/java/com/google/devtools/build/skyframe/ValueOrException4.java
@@ -22,4 +22,210 @@
   /** Gets the stored value. Throws an exception if one was thrown when computing this value. */
   @Nullable
   public abstract SkyValue get() throws E1, E2, E3, E4;
+
+  static <E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception>
+      ValueOrException4<E1, E2, E3, E4> fromUntypedException(
+          ValueOrUntypedException voe,
+          Class<E1> exceptionClass1,
+          Class<E2> exceptionClass2,
+          Class<E3> exceptionClass3,
+          Class<E4> exceptionClass4) {
+    SkyValue value = voe.getValue();
+    if (value != null) {
+      return ofValue(value);
+    }
+    Exception e = voe.getException();
+    if (e != null) {
+      if (exceptionClass1.isInstance(e)) {
+        return ofExn1(exceptionClass1.cast(e));
+      }
+      if (exceptionClass2.isInstance(e)) {
+        return ofExn2(exceptionClass2.cast(e));
+      }
+      if (exceptionClass3.isInstance(e)) {
+        return ofExn3(exceptionClass3.cast(e));
+      }
+      if (exceptionClass4.isInstance(e)) {
+        return ofExn4(exceptionClass4.cast(e));
+      }
+    }
+    return ofNullValue();
+  }
+
+  private static <
+          E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception>
+      ValueOrException4<E1, E2, E3, E4> ofNullValue() {
+    return ValueOrException4ValueImpl.ofNullValue();
+  }
+
+  private static <
+          E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception>
+      ValueOrException4<E1, E2, E3, E4> ofValue(SkyValue value) {
+    return new ValueOrException4ValueImpl<>(value);
+  }
+
+  private static <
+          E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception>
+      ValueOrException4<E1, E2, E3, E4> ofExn1(E1 e) {
+    return new ValueOrException4Exn1Impl<>(e);
+  }
+
+  private static <
+          E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception>
+      ValueOrException4<E1, E2, E3, E4> ofExn2(E2 e) {
+    return new ValueOrException4Exn2Impl<>(e);
+  }
+
+  private static <
+          E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception>
+      ValueOrException4<E1, E2, E3, E4> ofExn3(E3 e) {
+    return new ValueOrException4Exn3Impl<>(e);
+  }
+
+  private static <
+          E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception>
+      ValueOrException4<E1, E2, E3, E4> ofExn4(E4 e) {
+    return new ValueOrException4Exn4Impl<>(e);
+  }
+
+  private static class ValueOrException4ValueImpl<
+          E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception>
+      extends ValueOrException4<E1, E2, E3, E4> {
+
+    private static final ValueOrException4ValueImpl<Exception, Exception, Exception, Exception>
+        NULL = new ValueOrException4ValueImpl<>(null);
+    @Nullable private final SkyValue value;
+
+    ValueOrException4ValueImpl(@Nullable SkyValue value) {
+      this.value = value;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue get() {
+      return value;
+    }
+
+    @Override
+    @Nullable
+    public Exception getException() {
+      return null;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return value;
+    }
+
+    @SuppressWarnings("unchecked")
+    static <E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception>
+        ValueOrException4ValueImpl<E1, E2, E3, E4> ofNullValue() {
+      return (ValueOrException4ValueImpl<E1, E2, E3, E4>) NULL;
+    }
+  }
+
+  private static class ValueOrException4Exn1Impl<
+          E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception>
+      extends ValueOrException4<E1, E2, E3, E4> {
+    private final E1 e;
+
+    private ValueOrException4Exn1Impl(E1 e) {
+      this.e = e;
+    }
+
+    @Override
+    public SkyValue get() throws E1 {
+      throw e;
+    }
+
+    @Override
+    public Exception getException() {
+      return e;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return null;
+    }
+  }
+
+  private static class ValueOrException4Exn2Impl<
+          E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception>
+      extends ValueOrException4<E1, E2, E3, E4> {
+    private final E2 e;
+
+    private ValueOrException4Exn2Impl(E2 e) {
+      this.e = e;
+    }
+
+    @Override
+    public SkyValue get() throws E2 {
+      throw e;
+    }
+
+    @Override
+    public Exception getException() {
+      return e;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return null;
+    }
+  }
+
+  private static class ValueOrException4Exn3Impl<
+          E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception>
+      extends ValueOrException4<E1, E2, E3, E4> {
+    private final E3 e;
+
+    private ValueOrException4Exn3Impl(E3 e) {
+      this.e = e;
+    }
+
+    @Override
+    public SkyValue get() throws E3 {
+      throw e;
+    }
+
+    @Override
+    public Exception getException() {
+      return e;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return null;
+    }
+  }
+
+  private static class ValueOrException4Exn4Impl<
+          E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception>
+      extends ValueOrException4<E1, E2, E3, E4> {
+    private final E4 e;
+
+    private ValueOrException4Exn4Impl(E4 e) {
+      this.e = e;
+    }
+
+    @Override
+    public SkyValue get() throws E4 {
+      throw e;
+    }
+
+    @Override
+    public Exception getException() {
+      return e;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return null;
+    }
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/skyframe/ValueOrException5.java b/src/main/java/com/google/devtools/build/skyframe/ValueOrException5.java
index 36efa2e..89a5d70 100644
--- a/src/main/java/com/google/devtools/build/skyframe/ValueOrException5.java
+++ b/src/main/java/com/google/devtools/build/skyframe/ValueOrException5.java
@@ -23,4 +23,309 @@
   /** Gets the stored value. Throws an exception if one was thrown when computing this value. */
   @Nullable
   public abstract SkyValue get() throws E1, E2, E3, E4, E5;
+
+  static <
+          E1 extends Exception,
+          E2 extends Exception,
+          E3 extends Exception,
+          E4 extends Exception,
+          E5 extends Exception>
+      ValueOrException5<E1, E2, E3, E4, E5> fromUntypedException(
+          ValueOrUntypedException voe,
+          Class<E1> exceptionClass1,
+          Class<E2> exceptionClass2,
+          Class<E3> exceptionClass3,
+          Class<E4> exceptionClass4,
+          Class<E5> exceptionClass5) {
+    SkyValue value = voe.getValue();
+    if (value != null) {
+      return ofValue(value);
+    }
+    Exception e = voe.getException();
+    if (e != null) {
+      if (exceptionClass1.isInstance(e)) {
+        return ofExn1(exceptionClass1.cast(e));
+      }
+      if (exceptionClass2.isInstance(e)) {
+        return ofExn2(exceptionClass2.cast(e));
+      }
+      if (exceptionClass3.isInstance(e)) {
+        return ofExn3(exceptionClass3.cast(e));
+      }
+      if (exceptionClass4.isInstance(e)) {
+        return ofExn4(exceptionClass4.cast(e));
+      }
+      if (exceptionClass5.isInstance(e)) {
+        return ofExn5(exceptionClass5.cast(e));
+      }
+    }
+    return ofNullValue();
+  }
+
+  private static <
+          E1 extends Exception,
+          E2 extends Exception,
+          E3 extends Exception,
+          E4 extends Exception,
+          E5 extends Exception>
+      ValueOrException5<E1, E2, E3, E4, E5> ofNullValue() {
+    return ValueOrException5ValueImpl.ofNullValue();
+  }
+
+  private static <
+          E1 extends Exception,
+          E2 extends Exception,
+          E3 extends Exception,
+          E4 extends Exception,
+          E5 extends Exception>
+      ValueOrException5<E1, E2, E3, E4, E5> ofValue(SkyValue value) {
+    return new ValueOrException5ValueImpl<>(value);
+  }
+
+  private static <
+          E1 extends Exception,
+          E2 extends Exception,
+          E3 extends Exception,
+          E4 extends Exception,
+          E5 extends Exception>
+      ValueOrException5<E1, E2, E3, E4, E5> ofExn1(E1 e) {
+    return new ValueOrException5Exn1Impl<>(e);
+  }
+
+  private static <
+          E1 extends Exception,
+          E2 extends Exception,
+          E3 extends Exception,
+          E4 extends Exception,
+          E5 extends Exception>
+      ValueOrException5<E1, E2, E3, E4, E5> ofExn2(E2 e) {
+    return new ValueOrException5Exn2Impl<>(e);
+  }
+
+  private static <
+          E1 extends Exception,
+          E2 extends Exception,
+          E3 extends Exception,
+          E4 extends Exception,
+          E5 extends Exception>
+      ValueOrException5<E1, E2, E3, E4, E5> ofExn3(E3 e) {
+    return new ValueOrException5Exn3Impl<>(e);
+  }
+
+  private static <
+          E1 extends Exception,
+          E2 extends Exception,
+          E3 extends Exception,
+          E4 extends Exception,
+          E5 extends Exception>
+      ValueOrException5<E1, E2, E3, E4, E5> ofExn4(E4 e) {
+    return new ValueOrException5Exn4Impl<>(e);
+  }
+
+  private static <
+          E1 extends Exception,
+          E2 extends Exception,
+          E3 extends Exception,
+          E4 extends Exception,
+          E5 extends Exception>
+      ValueOrException5<E1, E2, E3, E4, E5> ofExn5(E5 e) {
+    return new ValueOrException5Exn5Impl<>(e);
+  }
+
+  private static class ValueOrException5ValueImpl<
+          E1 extends Exception,
+          E2 extends Exception,
+          E3 extends Exception,
+          E4 extends Exception,
+          E5 extends Exception>
+      extends ValueOrException5<E1, E2, E3, E4, E5> {
+    private static final ValueOrException5ValueImpl<
+            Exception, Exception, Exception, Exception, Exception>
+        NULL = new ValueOrException5ValueImpl<>(null);
+
+    @Nullable private final SkyValue value;
+
+    ValueOrException5ValueImpl(@Nullable SkyValue value) {
+      this.value = value;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue get() {
+      return value;
+    }
+
+    @Override
+    @Nullable
+    public Exception getException() {
+      return null;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return value;
+    }
+
+    @SuppressWarnings("unchecked")
+    static <
+            E1 extends Exception,
+            E2 extends Exception,
+            E3 extends Exception,
+            E4 extends Exception,
+            E5 extends Exception>
+        ValueOrException5ValueImpl<E1, E2, E3, E4, E5> ofNullValue() {
+      return (ValueOrException5ValueImpl<E1, E2, E3, E4, E5>) NULL;
+    }
+  }
+
+  private static class ValueOrException5Exn1Impl<
+          E1 extends Exception,
+          E2 extends Exception,
+          E3 extends Exception,
+          E4 extends Exception,
+          E5 extends Exception>
+      extends ValueOrException5<E1, E2, E3, E4, E5> {
+    private final E1 e;
+
+    private ValueOrException5Exn1Impl(E1 e) {
+      this.e = e;
+    }
+
+    @Override
+    public SkyValue get() throws E1 {
+      throw e;
+    }
+
+    @Override
+    public Exception getException() {
+      return e;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return null;
+    }
+  }
+
+  private static class ValueOrException5Exn2Impl<
+          E1 extends Exception,
+          E2 extends Exception,
+          E3 extends Exception,
+          E4 extends Exception,
+          E5 extends Exception>
+      extends ValueOrException5<E1, E2, E3, E4, E5> {
+    private final E2 e;
+
+    private ValueOrException5Exn2Impl(E2 e) {
+      this.e = e;
+    }
+
+    @Override
+    public SkyValue get() throws E2 {
+      throw e;
+    }
+
+    @Override
+    public Exception getException() {
+      return e;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return null;
+    }
+  }
+
+  private static class ValueOrException5Exn3Impl<
+          E1 extends Exception,
+          E2 extends Exception,
+          E3 extends Exception,
+          E4 extends Exception,
+          E5 extends Exception>
+      extends ValueOrException5<E1, E2, E3, E4, E5> {
+    private final E3 e;
+
+    private ValueOrException5Exn3Impl(E3 e) {
+      this.e = e;
+    }
+
+    @Override
+    public SkyValue get() throws E3 {
+      throw e;
+    }
+
+    @Override
+    public Exception getException() {
+      return e;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return null;
+    }
+  }
+
+  private static class ValueOrException5Exn4Impl<
+          E1 extends Exception,
+          E2 extends Exception,
+          E3 extends Exception,
+          E4 extends Exception,
+          E5 extends Exception>
+      extends ValueOrException5<E1, E2, E3, E4, E5> {
+    private final E4 e;
+
+    private ValueOrException5Exn4Impl(E4 e) {
+      this.e = e;
+    }
+
+    @Override
+    public SkyValue get() throws E4 {
+      throw e;
+    }
+
+    @Override
+    public Exception getException() {
+      return e;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return null;
+    }
+  }
+
+  private static class ValueOrException5Exn5Impl<
+          E1 extends Exception,
+          E2 extends Exception,
+          E3 extends Exception,
+          E4 extends Exception,
+          E5 extends Exception>
+      extends ValueOrException5<E1, E2, E3, E4, E5> {
+    private final E5 e;
+
+    private ValueOrException5Exn5Impl(E5 e) {
+      this.e = e;
+    }
+
+    @Override
+    public SkyValue get() throws E5 {
+      throw e;
+    }
+
+    @Override
+    public Exception getException() {
+      return e;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return null;
+    }
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/skyframe/ValueOrExceptionUtils.java b/src/main/java/com/google/devtools/build/skyframe/ValueOrExceptionUtils.java
deleted file mode 100644
index 3785685..0000000
--- a/src/main/java/com/google/devtools/build/skyframe/ValueOrExceptionUtils.java
+++ /dev/null
@@ -1,730 +0,0 @@
-// Copyright 2014 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package com.google.devtools.build.skyframe;
-
-import com.google.common.annotations.VisibleForTesting;
-
-import javax.annotation.Nullable;
-
-/** Utilities for producing and consuming ValueOrException(2|3|4)? instances. */
-@VisibleForTesting
-public class ValueOrExceptionUtils {
-
-  /** The bottom exception type. */
-  class BottomException extends Exception {
-  }
-
-  @Nullable
-  public static SkyValue downconvert(ValueOrException<BottomException> voe) {
-    return voe.getValue();
-  }
-
-  public static <E1 extends Exception> ValueOrException<E1> downconvert(
-      ValueOrException2<E1, BottomException> voe, Class<E1> exceptionClass1) {
-    Exception e = voe.getException();
-    if (e == null) {
-      return new ValueOrExceptionValueImpl<>(voe.getValue());
-    }
-    // Here and below, we use type-safe casts for performance reasons. Another approach would be
-    // cascading try-catch-rethrow blocks, but that has a higher performance penalty.
-    if (exceptionClass1.isInstance(e)) {
-      return new ValueOrExceptionExnImpl<>(exceptionClass1.cast(e));
-    }
-    throw new IllegalStateException("shouldn't reach here " + e.getClass() + " " + exceptionClass1,
-        e);
-  }
-
-  public static <E1 extends Exception, E2 extends Exception> ValueOrException2<E1, E2> downconvert(
-      ValueOrException3<E1, E2, BottomException> voe,
-      Class<E1> exceptionClass1,
-      Class<E2> exceptionClass2) {
-    Exception e = voe.getException();
-    if (e == null) {
-      return new ValueOrException2ValueImpl<>(voe.getValue());
-    }
-    if (exceptionClass1.isInstance(e)) {
-      return new ValueOrException2Exn1Impl<>(exceptionClass1.cast(e));
-    }
-    if (exceptionClass2.isInstance(e)) {
-      return new ValueOrException2Exn2Impl<>(exceptionClass2.cast(e));
-    }
-    throw new IllegalStateException("shouldn't reach here " + e.getClass() + " " + exceptionClass1
-        + " " + exceptionClass2, e);
-  }
-
-  public static <E1 extends Exception, E2 extends Exception, E3 extends Exception>
-      ValueOrException3<E1, E2, E3> downconvert(
-          ValueOrException4<E1, E2, E3, BottomException> voe,
-          Class<E1> exceptionClass1,
-          Class<E2> exceptionClass2,
-          Class<E3> exceptionClass3) {
-    Exception e = voe.getException();
-    if (e == null) {
-      return new ValueOrException3ValueImpl<>(voe.getValue());
-    }
-    if (exceptionClass1.isInstance(e)) {
-      return new ValueOrException3Exn1Impl<>(exceptionClass1.cast(e));
-    }
-    if (exceptionClass2.isInstance(e)) {
-      return new ValueOrException3Exn2Impl<>(exceptionClass2.cast(e));
-    }
-    if (exceptionClass3.isInstance(e)) {
-      return new ValueOrException3Exn3Impl<>(exceptionClass3.cast(e));
-    }
-    throw new IllegalStateException("shouldn't reach here " + e.getClass() + " " + exceptionClass1
-        + " " + exceptionClass2 + " " + exceptionClass3, e);
-  }
-
-  public static <E1 extends Exception, E2 extends Exception, E3 extends Exception,
-                 E4 extends Exception>
-      ValueOrException4<E1, E2, E3, E4> downconvert(
-          ValueOrException5<E1, E2, E3, E4, BottomException> voe,
-          Class<E1> exceptionClass1,
-          Class<E2> exceptionClass2,
-          Class<E3> exceptionClass3,
-          Class<E4> exceptionClass4) {
-    Exception e = voe.getException();
-    if (e == null) {
-      return new ValueOrException4ValueImpl<>(voe.getValue());
-    }
-    if (exceptionClass1.isInstance(e)) {
-      return new ValueOrException4Exn1Impl<>(exceptionClass1.cast(e));
-    }
-    if (exceptionClass2.isInstance(e)) {
-      return new ValueOrException4Exn2Impl<>(exceptionClass2.cast(e));
-    }
-    if (exceptionClass3.isInstance(e)) {
-      return new ValueOrException4Exn3Impl<>(exceptionClass3.cast(e));
-    }
-    if (exceptionClass4.isInstance(e)) {
-      return new ValueOrException4Exn4Impl<>(exceptionClass4.cast(e));
-    }
-    throw new IllegalStateException("shouldn't reach here " + e.getClass() + " " + exceptionClass1
-        + " " + exceptionClass2 + " " + exceptionClass3 + " " + exceptionClass4, e);
-  }
-
-
-  public static <E extends Exception> ValueOrException<E> ofNull() {
-    return ValueOrExceptionValueImpl.ofNull();
-  }
-
-  public static ValueOrUntypedException ofValueUntyped(SkyValue value) {
-    return new ValueOrUntypedExceptionImpl(value);
-  }
-
-  public static <E extends Exception> ValueOrException<E> ofExn(E e) {
-    return new ValueOrExceptionExnImpl<>(e);
-  }
-
-  public static <E1 extends Exception, E2 extends Exception, E3 extends Exception,
-                 E4 extends Exception, E5 extends Exception>
-      ValueOrException5<E1, E2, E3, E4, E5> ofNullValue() {
-    return ValueOrException5ValueImpl.ofNullValue();
-  }
-
-  public static <E1 extends Exception, E2 extends Exception, E3 extends Exception,
-                 E4 extends Exception, E5 extends Exception>
-      ValueOrException5<E1, E2, E3, E4, E5> ofValue(SkyValue value) {
-    return new ValueOrException5ValueImpl<>(value);
-  }
-
-  public static <E1 extends Exception, E2 extends Exception, E3 extends Exception,
-                 E4 extends Exception, E5 extends Exception>
-      ValueOrException5<E1, E2, E3, E4, E5> ofExn1(E1 e) {
-    return new ValueOrException5Exn1Impl<>(e);
-  }
-
-  public static <E1 extends Exception, E2 extends Exception, E3 extends Exception,
-                 E4 extends Exception, E5 extends Exception>
-      ValueOrException5<E1, E2, E3, E4, E5> ofExn2(E2 e) {
-    return new ValueOrException5Exn2Impl<>(e);
-  }
-
-  public static <E1 extends Exception, E2 extends Exception, E3 extends Exception,
-                 E4 extends Exception, E5 extends Exception>
-      ValueOrException5<E1, E2, E3, E4, E5> ofExn3(E3 e) {
-    return new ValueOrException5Exn3Impl<>(e);
-  }
-
-  public static <E1 extends Exception, E2 extends Exception, E3 extends Exception,
-                 E4 extends Exception, E5 extends Exception>
-      ValueOrException5<E1, E2, E3, E4, E5> ofExn4(E4 e) {
-    return new ValueOrException5Exn4Impl<>(e);
-  }
-
-  public static <E1 extends Exception, E2 extends Exception, E3 extends Exception,
-                 E4 extends Exception, E5 extends Exception>
-      ValueOrException5<E1, E2, E3, E4, E5> ofExn5(E5 e) {
-    return new ValueOrException5Exn5Impl<>(e);
-  }
-
-  private static class ValueOrUntypedExceptionImpl extends ValueOrUntypedException {
-    @Nullable
-    private final SkyValue value;
-
-    ValueOrUntypedExceptionImpl(@Nullable SkyValue value) {
-      this.value = value;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return value;
-    }
-
-    @Override
-    public Exception getException() {
-      return null;
-    }
-  }
-
-  private static class ValueOrExceptionValueImpl<E extends Exception> extends ValueOrException<E> {
-    private static final ValueOrExceptionValueImpl<Exception> NULL =
-        new ValueOrExceptionValueImpl<Exception>((SkyValue) null);
-
-    @Nullable
-    private final SkyValue value;
-
-    private ValueOrExceptionValueImpl(@Nullable SkyValue value) {
-      this.value = value;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue get() {
-      return value;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return value;
-    }
-
-    @Override
-    @Nullable
-    public Exception getException() {
-      return null;
-    }
-
-    @SuppressWarnings("unchecked")
-    public static <E extends Exception> ValueOrExceptionValueImpl<E> ofNull() {
-      return (ValueOrExceptionValueImpl<E>) NULL;
-    }
-  }
-
-  private static class ValueOrExceptionExnImpl<E extends Exception> extends ValueOrException<E> {
-    private final E e;
-
-    private ValueOrExceptionExnImpl(E e) {
-      this.e = e;
-    }
-
-    @Override
-    public SkyValue get() throws E {
-      throw e;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return null;
-    }
-
-    @Override
-    public Exception getException() {
-      return e;
-    }
-  }
-
-  private static class ValueOrException2ValueImpl<E1 extends Exception, E2 extends Exception>
-      extends ValueOrException2<E1, E2> {
-    @Nullable
-    private final SkyValue value;
-
-    ValueOrException2ValueImpl(@Nullable SkyValue value) {
-      this.value = value;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue get() {
-      return value;
-    }
-
-    @Override
-    @Nullable
-    public Exception getException() {
-      return null;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return value;
-    }
-  }
-
-  private static class ValueOrException2Exn1Impl<E1 extends Exception, E2 extends Exception>
-      extends ValueOrException2<E1, E2> {
-    private final E1 e;
-
-    private ValueOrException2Exn1Impl(E1 e) {
-      this.e = e;
-    }
-
-    @Override
-    public SkyValue get() throws E1 {
-      throw e;
-    }
-
-    @Override
-    public Exception getException() {
-      return e;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return null;
-    }
-  }
-
-  private static class ValueOrException2Exn2Impl<E1 extends Exception, E2 extends Exception>
-      extends ValueOrException2<E1, E2> {
-    private final E2 e;
-
-    private ValueOrException2Exn2Impl(E2 e) {
-      this.e = e;
-    }
-
-    @Override
-    public SkyValue get() throws E2 {
-      throw e;
-    }
-
-    @Override
-    public Exception getException() {
-      return e;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return null;
-    }
-  }
-
-  private static class ValueOrException3ValueImpl<E1 extends Exception, E2 extends Exception,
-      E3 extends Exception> extends ValueOrException3<E1, E2, E3> {
-    @Nullable
-    private final SkyValue value;
-
-    ValueOrException3ValueImpl(@Nullable SkyValue value) {
-      this.value = value;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue get() {
-      return value;
-    }
-
-    @Override
-    @Nullable
-    public Exception getException() {
-      return null;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return value;
-    }
-  }
-
-  private static class ValueOrException3Exn1Impl<E1 extends Exception, E2 extends Exception,
-      E3 extends Exception> extends ValueOrException3<E1, E2, E3> {
-    private final E1 e;
-
-    private ValueOrException3Exn1Impl(E1 e) {
-      this.e = e;
-    }
-
-    @Override
-    public SkyValue get() throws E1 {
-      throw e;
-    }
-
-    @Override
-    public Exception getException() {
-      return e;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return null;
-    }
-  }
-
-  private static class ValueOrException3Exn2Impl<E1 extends Exception, E2 extends Exception,
-      E3 extends Exception> extends ValueOrException3<E1, E2, E3> {
-    private final E2 e;
-
-    private ValueOrException3Exn2Impl(E2 e) {
-      this.e = e;
-    }
-
-    @Override
-    public SkyValue get() throws E2 {
-      throw e;
-    }
-
-    @Override
-    public Exception getException() {
-      return e;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return null;
-    }
-  }
-
-  private static class ValueOrException3Exn3Impl<E1 extends Exception, E2 extends Exception,
-      E3 extends Exception> extends ValueOrException3<E1, E2, E3> {
-    private final E3 e;
-
-    private ValueOrException3Exn3Impl(E3 e) {
-      this.e = e;
-    }
-
-    @Override
-    public SkyValue get() throws E3 {
-      throw e;
-    }
-
-    @Override
-    public Exception getException() {
-      return e;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return null;
-    }
-  }
-
-  private static class ValueOrException4ValueImpl<E1 extends Exception, E2 extends Exception,
-      E3 extends Exception, E4 extends Exception> extends ValueOrException4<E1, E2, E3, E4> {
- 
-    @Nullable
-    private final SkyValue value;
-
-    ValueOrException4ValueImpl(@Nullable SkyValue value) {
-      this.value = value;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue get() {
-      return value;
-    }
-
-    @Override
-    @Nullable
-    public Exception getException() {
-      return null;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return value;
-    }
-  }
-
-  private static class ValueOrException4Exn1Impl<E1 extends Exception, E2 extends Exception,
-      E3 extends Exception, E4 extends Exception> extends ValueOrException4<E1, E2, E3, E4> {
-    private final E1 e;
-
-    private ValueOrException4Exn1Impl(E1 e) {
-      this.e = e;
-    }
-
-    @Override
-    public SkyValue get() throws E1 {
-      throw e;
-    }
-
-    @Override
-    public Exception getException() {
-      return e;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return null;
-    }
-  }
-
-  private static class ValueOrException4Exn2Impl<E1 extends Exception, E2 extends Exception,
-      E3 extends Exception, E4 extends Exception> extends ValueOrException4<E1, E2, E3, E4> {
-    private final E2 e;
-
-    private ValueOrException4Exn2Impl(E2 e) {
-      this.e = e;
-    }
-
-    @Override
-    public SkyValue get() throws E2 {
-      throw e;
-    }
-
-    @Override
-    public Exception getException() {
-      return e;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return null;
-    }
-  }
-
-  private static class ValueOrException4Exn3Impl<E1 extends Exception, E2 extends Exception,
-      E3 extends Exception, E4 extends Exception> extends ValueOrException4<E1, E2, E3, E4> {
-    private final E3 e;
-
-    private ValueOrException4Exn3Impl(E3 e) {
-      this.e = e;
-    }
-
-    @Override
-    public SkyValue get() throws E3 {
-      throw e;
-    }
-
-    @Override
-    public Exception getException() {
-      return e;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return null;
-    }
-  }
-
-  private static class ValueOrException4Exn4Impl<E1 extends Exception, E2 extends Exception,
-      E3 extends Exception, E4 extends Exception> extends ValueOrException4<E1, E2, E3, E4> {
-    private final E4 e;
-
-    private ValueOrException4Exn4Impl(E4 e) {
-      this.e = e;
-    }
-
-    @Override
-    public SkyValue get() throws E4 {
-      throw e;
-    }
-
-    @Override
-    public Exception getException() {
-      return e;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return null;
-    }
-  }
-
-  private static class ValueOrException5ValueImpl<E1 extends Exception, E2 extends Exception,
-      E3 extends Exception, E4 extends Exception, E5 extends Exception>
-      extends ValueOrException5<E1, E2, E3, E4, E5> {
-    private static final ValueOrException5ValueImpl<Exception, Exception, Exception,
-        Exception, Exception> NULL = new ValueOrException5ValueImpl<>((SkyValue) null);
-
-    @Nullable
-    private final SkyValue value;
-
-    ValueOrException5ValueImpl(@Nullable SkyValue value) {
-      this.value = value;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue get() {
-      return value;
-    }
-
-    @Override
-    @Nullable
-    public Exception getException() {
-      return null;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return value;
-    }
-
-    @SuppressWarnings("unchecked")
-    private static <E1 extends Exception, E2 extends Exception, E3 extends Exception,
-                    E4 extends Exception, E5 extends Exception>
-        ValueOrException5ValueImpl<E1, E2, E3, E4, E5> ofNullValue() {
-      return (ValueOrException5ValueImpl<E1, E2, E3, E4, E5>) NULL;
-    }
-  }
-
-  private static class ValueOrException5Exn1Impl<E1 extends Exception, E2 extends Exception,
-      E3 extends Exception, E4 extends Exception, E5 extends Exception>
-      extends ValueOrException5<E1, E2, E3, E4, E5> {
-    private final E1 e;
-
-    private ValueOrException5Exn1Impl(E1 e) {
-      this.e = e;
-    }
-
-    @Override
-    public SkyValue get() throws E1 {
-      throw e;
-    }
-
-    @Override
-    public Exception getException() {
-      return e;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return null;
-    }
-  }
-
-  private static class ValueOrException5Exn2Impl<E1 extends Exception, E2 extends Exception,
-      E3 extends Exception, E4 extends Exception, E5 extends Exception>
-      extends ValueOrException5<E1, E2, E3, E4, E5> {
-    private final E2 e;
-
-    private ValueOrException5Exn2Impl(E2 e) {
-      this.e = e;
-    }
-
-    @Override
-    public SkyValue get() throws E2 {
-      throw e;
-    }
-
-    @Override
-    public Exception getException() {
-      return e;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return null;
-    }
-  }
-
-  private static class ValueOrException5Exn3Impl<E1 extends Exception, E2 extends Exception,
-      E3 extends Exception, E4 extends Exception, E5 extends Exception>
-      extends ValueOrException5<E1, E2, E3, E4, E5> {
-    private final E3 e;
-
-    private ValueOrException5Exn3Impl(E3 e) {
-      this.e = e;
-    }
-
-    @Override
-    public SkyValue get() throws E3 {
-      throw e;
-    }
-
-    @Override
-    public Exception getException() {
-      return e;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return null;
-    }
-  }
-
-  private static class ValueOrException5Exn4Impl<E1 extends Exception, E2 extends Exception,
-      E3 extends Exception, E4 extends Exception, E5 extends Exception>
-      extends ValueOrException5<E1, E2, E3, E4, E5> {
-    private final E4 e;
-
-    private ValueOrException5Exn4Impl(E4 e) {
-      this.e = e;
-    }
-
-    @Override
-    public SkyValue get() throws E4 {
-      throw e;
-    }
-
-    @Override
-    public Exception getException() {
-      return e;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return null;
-    }
-  }
-
-    private static class ValueOrException5Exn5Impl<E1 extends Exception, E2 extends Exception,
-        E3 extends Exception, E4 extends Exception, E5 extends Exception>
-        extends ValueOrException5<E1, E2, E3, E4, E5> {
-    private final E5 e;
-
-    private ValueOrException5Exn5Impl(E5 e) {
-      this.e = e;
-    }
-
-    @Override
-    public SkyValue get() throws E5 {
-      throw e;
-    }
-
-    @Override
-    public Exception getException() {
-      return e;
-    }
-
-    @Override
-    @Nullable
-    public SkyValue getValue() {
-      return null;
-    }
-  }
-
-}
diff --git a/src/main/java/com/google/devtools/build/skyframe/ValueOrUntypedException.java b/src/main/java/com/google/devtools/build/skyframe/ValueOrUntypedException.java
index f9ba554..7147c29 100644
--- a/src/main/java/com/google/devtools/build/skyframe/ValueOrUntypedException.java
+++ b/src/main/java/com/google/devtools/build/skyframe/ValueOrUntypedException.java
@@ -14,19 +14,16 @@
 package com.google.devtools.build.skyframe;
 
 import com.google.common.annotations.VisibleForTesting;
-
 import javax.annotation.Nullable;
 
 /**
  * Wrapper for a value or the untyped exception thrown when trying to compute it.
  *
- * <p>This is an implementation detail of {@link ParallelEvaluator} and
- * {@link ValueOrExceptionUtils}. It's an abstract class (as opposed to an interface) to avoid
- * exposing the methods outside the package.
+ * <p>This is an implementation detail of {@link ParallelEvaluator}. It's an abstract class (as
+ * opposed to an interface) to avoid exposing the methods outside the package.
  */
 @VisibleForTesting
 public abstract class ValueOrUntypedException {
-
   /** Returns the stored value, if there was one. */
   @Nullable
   abstract SkyValue getValue();
@@ -34,4 +31,55 @@
   /** Returns the stored exception, if there was one. */
   @Nullable
   abstract Exception getException();
+
+  public static ValueOrUntypedException ofValueUntyped(SkyValue value) {
+    return new ValueOrUntypedExceptionImpl(value);
+  }
+
+  public static ValueOrUntypedException ofNull() {
+    return ValueOrUntypedExceptionImpl.NULL;
+  }
+
+  public static ValueOrUntypedException ofExn(Exception e) {
+    return new ValueOrUntypedExceptionExnImpl(e);
+  }
+
+  private static class ValueOrUntypedExceptionImpl extends ValueOrUntypedException {
+    static final ValueOrUntypedExceptionImpl NULL = new ValueOrUntypedExceptionImpl(null);
+    @Nullable private final SkyValue value;
+
+    ValueOrUntypedExceptionImpl(@Nullable SkyValue value) {
+      this.value = value;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return value;
+    }
+
+    @Override
+    public Exception getException() {
+      return null;
+    }
+  }
+
+  private static class ValueOrUntypedExceptionExnImpl extends ValueOrUntypedException {
+    private final Exception exception;
+
+    ValueOrUntypedExceptionExnImpl(Exception exception) {
+      this.exception = exception;
+    }
+
+    @Override
+    @Nullable
+    public SkyValue getValue() {
+      return null;
+    }
+
+    @Override
+    public Exception getException() {
+      return exception;
+    }
+  }
 }