]> granicus.if.org Git - llvm/commitdiff
[Error] Add a handleExpected utility.
authorLang Hames <lhames@gmail.com>
Mon, 28 Aug 2017 03:36:46 +0000 (03:36 +0000)
committerLang Hames <lhames@gmail.com>
Mon, 28 Aug 2017 03:36:46 +0000 (03:36 +0000)
handleExpected is similar to handleErrors, but takes an Expected<T> as its first
input value and a fallback functor as its second, followed by an arbitary list
of error handlers (equivalent to the handler list of handleErrors). If the first
input value is a success value then it is returned from handleErrors
unmodified. Otherwise the contained error(s) are passed to handleErrors, along
with the handlers. If handleErrors returns success (indicating that all errors
have been handled) then handleExpected runs the fallback functor and returns its
result. If handleErrors returns a failure value then the failure value is
returned and the fallback functor is never run.

This simplifies the process of re-trying operations that return Expected values.
Without this utility such retry logic is cumbersome as the internal Error must
be explicitly extracted from the Expected value, inspected to see if its
handleable and then consumed:

enum FooStrategy { Aggressive, Conservative };
Expected<Foo> tryFoo(FooStrategy S);

Expected<Foo> Result;
(void)!!Result; // "Check" Result so that it can be safely overwritten.
if (auto ValOrErr = tryFoo(Aggressive))
  Result = std::move(ValOrErr);
else {
  auto Err = ValOrErr.takeError();
  if (Err.isA<HandleableError>()) {
    consumeError(std::move(Err));
    Result = tryFoo(Conservative);
  } else
    return std::move(Err);
}

with handleExpected, this can be re-written as:

auto Result =
  handleExpected(
    tryFoo(Aggressive),
    []() { return tryFoo(Conservative); },
    [](HandleableError&) { /* discard to handle */ });

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@311870 91177308-0d34-0410-b5e6-96231b3b80d8

include/llvm/Support/Error.h
unittests/Support/ErrorTest.cpp

index 2e75e5db6e45b2f33c5481244e5bc1e84695ba6a..6aca32bf076dcb00c5b3d8574b0f2c57c65a27eb 100644 (file)
@@ -878,6 +878,43 @@ inline void handleAllErrors(Error E) {
   cantFail(std::move(E));
 }
 
+/// Handle any errors (if present) in an Expected<T>, then try a recovery path.
+///
+/// If the incoming value is a success value it is returned unmodified. If it
+/// is a failure value then it the contained error is passed to handleErrors.
+/// If handleErrors is able to handle the error then the RecoveryPath functor
+/// is called to supply the final result. If handleErrors is not able to
+/// handle all errors then the unhandled errors are returned.
+///
+/// This utility enables the follow pattern:
+///
+///   @code{.cpp}
+///   enum FooStrategy { Aggressive, Conservative };
+///   Expected<Foo> foo(FooStrategy S);
+///
+///   auto ResultOrErr =
+///     handleExpected(
+///       foo(Aggressive),
+///       []() { return foo(Conservative); },
+///       [](AggressiveStrategyError&) {
+///         // Implicitly conusme this - we'll recover by using a conservative
+///         // strategy.
+///       });
+///
+///   @endcode
+template <typename T, typename RecoveryFtor, typename... HandlerTs>
+Expected<T> handleExpected(Expected<T> ValOrErr, RecoveryFtor &&RecoveryPath,
+                           HandlerTs &&... Handlers) {
+  if (ValOrErr)
+    return ValOrErr;
+
+  if (auto Err = handleErrors(ValOrErr.takeError(),
+                              std::forward<HandlerTs>(Handlers)...))
+    return std::move(Err);
+
+  return RecoveryPath();
+}
+
 /// Log all errors (if any) in E to OS. If there are any errors, ErrorBanner
 /// will be printed before the first one is logged. A newline will be printed
 /// after each error.
index 852753fbf04fa32d187a43517d55a4e07358b829..db332ebff7a2cb82a33925d1cd1a4972bac8523a 100644 (file)
@@ -605,6 +605,59 @@ TEST(Error, ExpectedCovariance) {
   (void)!!A2;
 }
 
+// Test that handleExpected just returns success values.
+TEST(Error, HandleExpectedSuccess) {
+  auto ValOrErr =
+    handleExpected(Expected<int>(42),
+                   []() { return Expected<int>(43); });
+  EXPECT_TRUE(!!ValOrErr)
+    << "handleExpected should have returned a success value here";
+  EXPECT_EQ(*ValOrErr, 42)
+    << "handleExpected should have returned the original success value here";
+}
+
+enum FooStrategy { Aggressive, Conservative };
+
+static Expected<int> foo(FooStrategy S) {
+  if (S == Aggressive)
+    return make_error<CustomError>(7);
+  return 42;
+}
+
+// Test that handleExpected invokes the error path if errors are not handled.
+TEST(Error, HandleExpectedUnhandledError) {
+  // foo(Aggressive) should return a CustomError which should pass through as
+  // there is no handler for CustomError.
+  auto ValOrErr =
+    handleExpected(
+      foo(Aggressive),
+      []() { return foo(Conservative); });
+
+  EXPECT_FALSE(!!ValOrErr)
+    << "handleExpected should have returned an error here";
+  auto Err = ValOrErr.takeError();
+  EXPECT_TRUE(Err.isA<CustomError>())
+    << "handleExpected should have returned the CustomError generated by "
+    "foo(Aggressive) here";
+  consumeError(std::move(Err));
+}
+
+// Test that handleExpected invokes the fallback path if errors are handled.
+TEST(Error, HandleExpectedHandledError) {
+  // foo(Aggressive) should return a CustomError which should handle triggering
+  // the fallback path.
+  auto ValOrErr =
+    handleExpected(
+      foo(Aggressive),
+      []() { return foo(Conservative); },
+      [](const CustomError&) { /* do nothing */ });
+
+  EXPECT_TRUE(!!ValOrErr)
+    << "handleExpected should have returned a success value here";
+  EXPECT_EQ(*ValOrErr, 42)
+    << "handleExpected returned the wrong success value";
+}
+
 TEST(Error, ErrorCodeConversions) {
   // Round-trip a success value to check that it converts correctly.
   EXPECT_EQ(errorToErrorCode(errorCodeToError(std::error_code())),