[llvm] r311870 - [Error] Add a handleExpected utility.

Lang Hames via llvm-commits llvm-commits at lists.llvm.org
Sun Aug 27 20:36:47 PDT 2017


Author: lhames
Date: Sun Aug 27 20:36:46 2017
New Revision: 311870

URL: http://llvm.org/viewvc/llvm-project?rev=311870&view=rev
Log:
[Error] Add a handleExpected utility.

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 */ });


Modified:
    llvm/trunk/include/llvm/Support/Error.h
    llvm/trunk/unittests/Support/ErrorTest.cpp

Modified: llvm/trunk/include/llvm/Support/Error.h
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/Support/Error.h?rev=311870&r1=311869&r2=311870&view=diff
==============================================================================
--- llvm/trunk/include/llvm/Support/Error.h (original)
+++ llvm/trunk/include/llvm/Support/Error.h Sun Aug 27 20:36:46 2017
@@ -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.

Modified: llvm/trunk/unittests/Support/ErrorTest.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/unittests/Support/ErrorTest.cpp?rev=311870&r1=311869&r2=311870&view=diff
==============================================================================
--- llvm/trunk/unittests/Support/ErrorTest.cpp (original)
+++ llvm/trunk/unittests/Support/ErrorTest.cpp Sun Aug 27 20:36:46 2017
@@ -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())),




More information about the llvm-commits mailing list