diff --git a/folly/Expected.h b/folly/Expected.h index e06ba7a8a31..378d80ac06f 100644 --- a/folly/Expected.h +++ b/folly/Expected.h @@ -213,7 +213,7 @@ using ExpectedErrorType = // Details... namespace expected_detail { -template +template struct Promise; template struct PromiseReturn; @@ -1563,14 +1563,14 @@ bool operator>(const Value& other, const Expected&) = delete; namespace folly { namespace expected_detail { template -struct Promise; +struct PromiseBase; template struct PromiseReturn { Expected storage_{EmptyTag{}}; Expected*& pointer_; - /* implicit */ PromiseReturn(Promise& p) noexcept + /* implicit */ PromiseReturn(PromiseBase& p) noexcept : pointer_{p.value_} { pointer_ = &storage_; } @@ -1592,25 +1592,48 @@ struct PromiseReturn { }; template -struct Promise { +struct PromiseBase { Expected* value_ = nullptr; - Promise() = default; - Promise(Promise const&) = delete; + PromiseBase() = default; + PromiseBase(PromiseBase const&) = delete; + void operator=(PromiseBase const&) = delete; + + [[nodiscard]] coro::suspend_never initial_suspend() const noexcept { + return {}; + } + [[nodiscard]] coro::suspend_never final_suspend() const noexcept { + return {}; + } + [[noreturn]] void unhandled_exception() { + // Technically, throwing from unhandled_exception is underspecified: + // https://github.com/GorNishanov/CoroutineWording/issues/17 + rethrow_current_exception(); + } + PromiseReturn get_return_object() noexcept { return *this; } - coro::suspend_never initial_suspend() const noexcept { return {}; } - coro::suspend_never final_suspend() const noexcept { return {}; } +}; + +template +inline constexpr bool ReturnsVoid = + std::is_trivial_v && std::is_empty_v; + +template +struct Promise>::type> + : public PromiseBase { template void return_value(U&& u) { - auto& v = *value_; + auto& v = *this->value_; ExpectedHelper::assume_empty(v); v = static_cast(u); } - void unhandled_exception() { - // Technically, throwing from unhandled_exception is underspecified: - // https://github.com/GorNishanov/CoroutineWording/issues/17 - rethrow_current_exception(); - } +}; + +template +struct Promise>::type> + : public PromiseBase { + // When the coroutine uses `return;` you can fail via `co_await err`. + void return_void() { this->value_->emplace(Value{}); } }; template diff --git a/folly/test/ExpectedCoroutinesTest.cpp b/folly/test/ExpectedCoroutinesTest.cpp index fcfd1baae1c..c7c26d1276e 100644 --- a/folly/test/ExpectedCoroutinesTest.cpp +++ b/folly/test/ExpectedCoroutinesTest.cpp @@ -121,6 +121,43 @@ TEST(Expected, CoroutineReturnUnexpected) { EXPECT_EQ(Err::badder(), r1.error()); } +TEST(Expected, CoroutineReturnsVoid) { + int x = 0; + auto r = [&]() -> Expected { + x = co_await f1(); + co_return; + }(); + EXPECT_TRUE(r.hasValue()); + EXPECT_EQ(folly::unit, *r); + EXPECT_EQ(7, x); +} + +TEST(Expected, CoroutineReturnsVoidThrows) { + auto fnThrows = [&]() -> Expected { + throws(); + co_return; + }; + ASSERT_THROW(({ fnThrows(); }), Exn); +} + +TEST(Expected, CoroutineReturnsVoidError) { + auto fnErr = [&]() -> Expected { + return makeUnexpected(Err::bad()); + }; + auto r = [&]() -> Expected { co_await fnErr(); }(); + EXPECT_TRUE(r.hasError()); + EXPECT_EQ(Err::bad(), r.error()); +} + +TEST(Expected, VoidCoroutineAwaitsError) { + auto r = []() -> Expected { + co_await makeUnexpected(Err::badder()); + ADD_FAILURE(); + }(); + EXPECT_TRUE(r.hasError()); + EXPECT_EQ(Err::badder(), r.error()); +} + TEST(Expected, CoroutineException) { EXPECT_THROW( ([]() -> Expected {