From: Lang Hames Date: Mon, 28 Aug 2017 03:36:46 +0000 (+0000) Subject: [Error] Add a handleExpected utility. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=68a7b2d85a2ba3c7a97587415474046129ab5605;p=llvm [Error] Add a handleExpected utility. handleExpected is similar to handleErrors, but takes an Expected 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 tryFoo(FooStrategy S); Expected 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()) { 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 --- diff --git a/include/llvm/Support/Error.h b/include/llvm/Support/Error.h index 2e75e5db6e4..6aca32bf076 100644 --- a/include/llvm/Support/Error.h +++ b/include/llvm/Support/Error.h @@ -878,6 +878,43 @@ inline void handleAllErrors(Error E) { cantFail(std::move(E)); } +/// Handle any errors (if present) in an Expected, 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(FooStrategy S); +/// +/// auto ResultOrErr = +/// handleExpected( +/// foo(Aggressive), +/// []() { return foo(Conservative); }, +/// [](AggressiveStrategyError&) { +/// // Implicitly conusme this - we'll recover by using a conservative +/// // strategy. +/// }); +/// +/// @endcode +template +Expected handleExpected(Expected ValOrErr, RecoveryFtor &&RecoveryPath, + HandlerTs &&... Handlers) { + if (ValOrErr) + return ValOrErr; + + if (auto Err = handleErrors(ValOrErr.takeError(), + std::forward(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. diff --git a/unittests/Support/ErrorTest.cpp b/unittests/Support/ErrorTest.cpp index 852753fbf04..db332ebff7a 100644 --- a/unittests/Support/ErrorTest.cpp +++ b/unittests/Support/ErrorTest.cpp @@ -605,6 +605,59 @@ TEST(Error, ExpectedCovariance) { (void)!!A2; } +// Test that handleExpected just returns success values. +TEST(Error, HandleExpectedSuccess) { + auto ValOrErr = + handleExpected(Expected(42), + []() { return Expected(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 foo(FooStrategy S) { + if (S == Aggressive) + return make_error(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()) + << "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())),