X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=blobdiff_plain;f=folly%2FExpected.h;h=4b736c6b9b63a0b8f384715ebc6ef9946b0d9d7e;hp=b4e63ee9dc5222d2c996128c4f9e7163429f87fe;hb=a5562cb52be583e617374b7ef66c5560946dc11b;hpb=c1ead85dff393f6e5c356073375bc955e1714403 diff --git a/folly/Expected.h b/folly/Expected.h index b4e63ee9..4b736c6b 100644 --- a/folly/Expected.h +++ b/folly/Expected.h @@ -1,5 +1,5 @@ /* - * Copyright 2016 Facebook, Inc. + * Copyright 2016-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - /** - * Like folly::Optional, but can store a value *or* and error. + * Like folly::Optional, but can store a value *or* an error. * * @author Eric Niebler (eniebler@fb.com) */ @@ -29,11 +28,18 @@ #include #include +#include + +#include +#include #include +#include #include #include -#include // for construct_in_place_t +#include #include +#include +#include #define FOLLY_EXPECTED_ID(X) FB_CONCATENATE(FB_CONCATENATE(Folly, X), __LINE__) @@ -92,6 +98,10 @@ using ExpectedErrorType = // Details... namespace expected_detail { + +template +struct PromiseReturn; + #ifdef _MSC_VER // MSVC 2015 can't handle the StrictConjunction, so we have // to use std::conjunction instead. @@ -233,6 +243,11 @@ struct ExpectedStorage { Value&& value() && { return std::move(value_); } + // TODO (t17322426): remove when VS2015 support is deprecated + // VS2015 static analyzer incorrectly flags these as unreachable in certain + // circumstances. VS2017 does not have this problem on the same code. + FOLLY_PUSH_WARNING + FOLLY_MSVC_DISABLE_WARNING(4702) // unreachable code Error& error() & { return error_; } @@ -242,6 +257,7 @@ struct ExpectedStorage { Error&& error() && { return std::move(error_); } + FOLLY_POP_WARNING }; template @@ -445,7 +461,7 @@ struct ExpectedStorage this->which_ = Which::eError; } } - bool isThis(const ExpectedStorage* that) const { + bool isSelfAssign(const ExpectedStorage* that) const { return this == that; } constexpr bool isSelfAssign(const void*) const { @@ -453,8 +469,9 @@ struct ExpectedStorage } template void assign(Other&& that) { - if (isSelfAssign(&that)) + if (isSelfAssign(&that)) { return; + } switch (that.which_) { case Which::eValue: this->assignValue(static_cast(that).value()); @@ -527,6 +544,11 @@ struct ExpectedStorage { Value&& value() && { return std::move(value_); } + // TODO (t17322426): remove when VS2015 support is deprecated + // VS2015 static analyzer incorrectly flags these as unreachable in certain + // circumstances. VS2017 does not have this problem on the same code. + FOLLY_PUSH_WARNING + FOLLY_MSVC_DISABLE_WARNING(4702) // unreachable code Error& error() & { return error_; } @@ -536,6 +558,7 @@ struct ExpectedStorage { Error&& error() && { return std::move(error_); } + FOLLY_POP_WARNING }; namespace expected_detail_ExpectedHelper { @@ -577,13 +600,14 @@ struct ExpectedHelper { T::template return_( (std::declval()(std::declval().value()), unit)), std::declval()...)) { - if (LIKELY(ex.which_ == expected_detail::Which::eValue)) + if (LIKELY(ex.which_ == expected_detail::Which::eValue)) { return T::then_( T::template return_( // Uses the comma operator defined above IFF the lambda // returns non-void. (static_cast(fn)(static_cast(ex).value()), unit)), static_cast(fns)...); + } return makeUnexpected(static_cast(ex).error()); } @@ -595,8 +619,9 @@ struct ExpectedHelper { class Err = decltype(std::declval()(std::declval().error())) FOLLY_REQUIRES_TRAILING(!std::is_void::value)> static Ret thenOrThrow_(This&& ex, Yes&& yes, No&& no) { - if (LIKELY(ex.which_ == expected_detail::Which::eValue)) + if (LIKELY(ex.which_ == expected_detail::Which::eValue)) { return Ret(static_cast(yes)(static_cast(ex).value())); + } throw static_cast(no)(static_cast(ex).error()); } @@ -608,15 +633,16 @@ struct ExpectedHelper { class Err = decltype(std::declval()(std::declval().error())) FOLLY_REQUIRES_TRAILING(std::is_void::value)> static Ret thenOrThrow_(This&& ex, Yes&& yes, No&& no) { - if (LIKELY(ex.which_ == expected_detail::Which::eValue)) + if (LIKELY(ex.which_ == expected_detail::Which::eValue)) { return Ret(static_cast(yes)(static_cast(ex).value())); + } static_cast(no)(ex.error()); throw typename Unexpected>::MakeBadExpectedAccess()( static_cast(ex).error()); } FOLLY_POP_WARNING }; -} +} // namespace expected_detail_ExpectedHelper /* using override */ using expected_detail_ExpectedHelper::ExpectedHelper; struct UnexpectedTag {}; @@ -634,17 +660,23 @@ inline expected_detail::UnexpectedTag unexpected( /** * An exception type thrown by Expected on catastrophic logic errors. */ -class BadExpectedAccess : public std::logic_error { +class FOLLY_EXPORT BadExpectedAccess : public std::logic_error { public: BadExpectedAccess() : std::logic_error("bad Expected access") {} }; +namespace expected_detail { + +[[noreturn]] void throwBadExpectedAccess(); + +} // namespace expected_detail + /** * Unexpected - a helper type used to disambiguate the construction of * Expected objects in the error state. */ template -class Unexpected final { +class Unexpected final : ColdClass { template friend class Unexpected; template @@ -657,7 +689,7 @@ class Unexpected final { * when the user tries to access the nested value but the Expected object is * actually storing an error code. */ - class BadExpectedAccess : public folly::BadExpectedAccess { + class FOLLY_EXPORT BadExpectedAccess : public folly::BadExpectedAccess { public: explicit BadExpectedAccess(Error err) : folly::BadExpectedAccess{}, error_(std::move(err)) {} @@ -1017,6 +1049,12 @@ class Expected final : expected_detail::ExpectedStorage { return *this; } + // Used only when an Expected is used with coroutines on MSVC + /* implicit */ Expected(const expected_detail::PromiseReturn& p) + : Expected{} { + p.promise_->value_ = this; + } + template ::value)> void emplace(Ts&&... ts) { @@ -1028,8 +1066,9 @@ class Expected final : expected_detail::ExpectedStorage { */ void swap(Expected& that) noexcept( expected_detail::StrictAllOf::value) { - if (this->uninitializedByException() || that.uninitializedByException()) - throw BadExpectedAccess(); + if (this->uninitializedByException() || that.uninitializedByException()) { + expected_detail::throwBadExpectedAccess(); + } using std::swap; if (*this) { if (that) { @@ -1069,11 +1108,11 @@ class Expected final : expected_detail::ExpectedStorage { * Accessors */ constexpr bool hasValue() const noexcept { - return expected_detail::Which::eValue == this->which_; + return LIKELY(expected_detail::Which::eValue == this->which_); } constexpr bool hasError() const noexcept { - return expected_detail::Which::eError == this->which_; + return UNLIKELY(expected_detail::Which::eError == this->which_); } using Base::uninitializedByException; @@ -1167,8 +1206,9 @@ class Expected final : expected_detail::ExpectedStorage { expected_detail::ExpectedHelper::then_( std::declval(), std::declval()...)) { - if (this->uninitializedByException()) - throw BadExpectedAccess(); + if (this->uninitializedByException()) { + expected_detail::throwBadExpectedAccess(); + } return expected_detail::ExpectedHelper::then_( base(), static_cast(fns)...); } @@ -1177,8 +1217,9 @@ class Expected final : expected_detail::ExpectedStorage { auto then(Fns&&... fns) & -> decltype(expected_detail::ExpectedHelper::then_( std::declval(), std::declval()...)) { - if (this->uninitializedByException()) - throw BadExpectedAccess(); + if (this->uninitializedByException()) { + expected_detail::throwBadExpectedAccess(); + } return expected_detail::ExpectedHelper::then_( base(), static_cast(fns)...); } @@ -1187,8 +1228,9 @@ class Expected final : expected_detail::ExpectedStorage { auto then(Fns&&... fns) && -> decltype(expected_detail::ExpectedHelper::then_( std::declval(), std::declval()...)) { - if (this->uninitializedByException()) - throw BadExpectedAccess(); + if (this->uninitializedByException()) { + expected_detail::throwBadExpectedAccess(); + } return expected_detail::ExpectedHelper::then_( std::move(base()), static_cast(fns)...); } @@ -1200,8 +1242,9 @@ class Expected final : expected_detail::ExpectedStorage { auto thenOrThrow(Yes&& yes, No&& no = No{}) const& -> decltype( std::declval()(std::declval())) { using Ret = decltype(std::declval()(std::declval())); - if (this->uninitializedByException()) - throw BadExpectedAccess(); + if (this->uninitializedByException()) { + expected_detail::throwBadExpectedAccess(); + } return Ret(expected_detail::ExpectedHelper::thenOrThrow_( base(), static_cast(yes), static_cast(no))); } @@ -1210,8 +1253,9 @@ class Expected final : expected_detail::ExpectedStorage { auto thenOrThrow(Yes&& yes, No&& no = No{}) & -> decltype( std::declval()(std::declval())) { using Ret = decltype(std::declval()(std::declval())); - if (this->uninitializedByException()) - throw BadExpectedAccess(); + if (this->uninitializedByException()) { + expected_detail::throwBadExpectedAccess(); + } return Ret(expected_detail::ExpectedHelper::thenOrThrow_( base(), static_cast(yes), static_cast(no))); } @@ -1220,8 +1264,9 @@ class Expected final : expected_detail::ExpectedStorage { auto thenOrThrow(Yes&& yes, No&& no = No{}) && -> decltype( std::declval()(std::declval())) { using Ret = decltype(std::declval()(std::declval())); - if (this->uninitializedByException()) - throw BadExpectedAccess(); + if (this->uninitializedByException()) { + expected_detail::throwBadExpectedAccess(); + } return Ret(expected_detail::ExpectedHelper::thenOrThrow_( std::move(base()), static_cast(yes), static_cast(no))); } @@ -1229,15 +1274,16 @@ class Expected final : expected_detail::ExpectedStorage { private: void requireValue() const { if (UNLIKELY(!hasValue())) { - if (LIKELY(hasError())) + if (LIKELY(hasError())) { throw typename Unexpected::BadExpectedAccess(this->error_); - throw BadExpectedAccess(); + } + expected_detail::throwBadExpectedAccess(); } } void requireError() const { if (UNLIKELY(!hasError())) { - throw BadExpectedAccess(); + expected_detail::throwBadExpectedAccess(); } } @@ -1251,13 +1297,15 @@ inline typename std::enable_if::value, bool>::type operator==( const Expected& lhs, const Expected& rhs) { - if (UNLIKELY(lhs.which_ != rhs.which_)) - return UNLIKELY(lhs.uninitializedByException()) ? false - : throw BadExpectedAccess(); - if (UNLIKELY(lhs.uninitializedByException())) - throw BadExpectedAccess(); - if (UNLIKELY(lhs.hasError())) + if (UNLIKELY(lhs.uninitializedByException())) { + expected_detail::throwBadExpectedAccess(); + } + if (UNLIKELY(lhs.which_ != rhs.which_)) { + return false; + } + if (UNLIKELY(lhs.hasError())) { return true; // All error states are considered equal + } return lhs.value_ == rhs.value_; } @@ -1276,12 +1324,15 @@ operator<( const Expected& lhs, const Expected& rhs) { if (UNLIKELY( - lhs.uninitializedByException() || rhs.uninitializedByException())) - throw BadExpectedAccess(); - if (UNLIKELY(lhs.hasError())) + lhs.uninitializedByException() || rhs.uninitializedByException())) { + expected_detail::throwBadExpectedAccess(); + } + if (UNLIKELY(lhs.hasError())) { return !rhs.hasError(); - if (UNLIKELY(rhs.hasError())) + } + if (UNLIKELY(rhs.hasError())) { return false; + } return lhs.value_ < rhs.value_; } @@ -1382,3 +1433,99 @@ bool operator>(const Value& other, const Expected&) = delete; #undef FOLLY_REQUIRES #undef FOLLY_REQUIRES_TRAILING + +// Enable the use of folly::Expected with `co_await` +// Inspired by https://github.com/toby-allsopp/coroutine_monad +#if FOLLY_HAS_COROUTINES +#include + +namespace folly { +namespace expected_detail { +template +struct Promise; + +template +struct PromiseReturn { + Optional> storage_; + Promise* promise_; + /* implicit */ PromiseReturn(Promise& promise) noexcept + : promise_(&promise) { + promise_->value_ = &storage_; + } + PromiseReturn(PromiseReturn&& that) noexcept + : PromiseReturn{*that.promise_} {} + ~PromiseReturn() {} + /* implicit */ operator Expected() & { + return std::move(*storage_); + } +}; + +template +struct Promise { + Optional>* value_ = nullptr; + Promise() = default; + Promise(Promise const&) = delete; + // This should work regardless of whether the compiler generates: + // folly::Expected retobj{ p.get_return_object(); } // MSVC + // or: + // auto retobj = p.get_return_object(); // clang + PromiseReturn get_return_object() noexcept { + return *this; + } + std::experimental::suspend_never initial_suspend() const noexcept { + return {}; + } + std::experimental::suspend_never final_suspend() const { + return {}; + } + template + void return_value(U&& u) { + value_->emplace(static_cast(u)); + } + void unhandled_exception() { + // Technically, throwing from unhandled_exception is underspecified: + // https://github.com/GorNishanov/CoroutineWording/issues/17 + throw; + } +}; + +template +struct Awaitable { + Expected o_; + + explicit Awaitable(Expected o) : o_(std::move(o)) {} + + bool await_ready() const noexcept { + return o_.hasValue(); + } + Value await_resume() { + return std::move(o_.value()); + } + + // Explicitly only allow suspension into a Promise + template + void await_suspend(std::experimental::coroutine_handle> h) { + *h.promise().value_ = makeUnexpected(std::move(o_.error())); + // Abort the rest of the coroutine. resume() is not going to be called + h.destroy(); + } +}; +} // namespace expected_detail + +template +expected_detail::Awaitable +/* implicit */ operator co_await(Expected o) { + return expected_detail::Awaitable{std::move(o)}; +} +} // namespace folly + +// This makes folly::Expected useable as a coroutine return type... +namespace std { +namespace experimental { +template +struct coroutine_traits, Args...> { + using promise_type = folly::expected_detail::Promise; +}; +} // namespace experimental +} // namespace std +#endif // FOLLY_HAS_COROUTINES