folly-clib-20260203.1245: folly/folly/result/detail/result_or_unwind.h
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/CppAttributes.h>
#include <folly/coro/Error.h>
#include <folly/lang/MustUseImmediately.h>
#include <folly/result/detail/result_promise.h>
#include <folly/result/result.h>
#if FOLLY_HAS_RESULT
#include <coroutine>
namespace folly {
template <typename>
class or_unwind;
template <typename>
class or_unwind_owning;
class Executor;
template <typename>
class ExecutorKeepAlive;
namespace coro::detail {
struct WithAsyncStackFunction;
class TaskPromisePrivate;
} // namespace coro::detail
namespace detail {
// Base class for non-owning `or_unwind` types that store refs.
//
// Lifetime safety: Storing a reference to a temporary would dangle, but we're
// protected by `must_use_immediately_crtp` (must be awaited in the same
// full-expression) and C++ temporary lifetime extension for the prvalue case.
// The `unsafe_mover` protocol lets `co_viaIfAsync` etc. "move" these types
// despite being must-use-immediately.
//
// KNOWN BUG (LLVM issue #177023): `auto&& ref = co_await or_unwind(rval())`
// dangles because `co_await` hides the lifetime chain from Clang's
// `lifetimebound` analysis. The annotations below DO work for non-coroutine
// code but NOT through `co_await`. See `result.md` for safe alternatives.
template <typename Derived, typename StorageRef>
class result_or_unwind_ref_base
: public ext::must_use_immediately_crtp<Derived> {
static_assert(std::is_reference_v<StorageRef>);
StorageRef storage_;
protected:
static constexpr bool kIsErrorOrStopped =
std::is_same_v<std::remove_cvref_t<StorageRef>, error_or_stopped>;
private:
struct my_mover {
StorageRef storage_;
explicit my_mover(StorageRef&& s) noexcept
: storage_(static_cast<StorageRef&&>(s)) {}
Derived operator()() && noexcept {
return Derived{static_cast<StorageRef&&>(storage_)};
}
};
protected:
StorageRef&& storage() && noexcept {
return static_cast<StorageRef&&>(storage_);
}
const auto& storage() const& noexcept { return storage_; }
public:
explicit result_or_unwind_ref_base(
StorageRef&& r [[FOLLY_ATTR_CLANG_LIFETIMEBOUND]]) noexcept
: storage_(static_cast<StorageRef&&>(r)) {}
[[nodiscard]] decltype(auto) await_resume() noexcept
[[FOLLY_ATTR_CLANG_LIFETIMEBOUND]] {
if constexpr (kIsErrorOrStopped) {
// `await_ready()` is false, the coro gets destroyed in `await_suspend()`
compiler_may_unsafely_assume_unreachable();
} else {
// Result type: return a reference into the stored result.
return static_cast<StorageRef&&>(storage_).value_or_throw();
}
}
// unsafe_mover protocol for must-use-immediately types
static my_mover unsafe_mover(
ext::must_use_immediately_private_t, Derived&& me) noexcept {
return my_mover{static_cast<StorageRef&&>(me.storage_)};
}
};
// Base class for `or_unwind_owning` types that own the result-like values.
// Unlike the ref base, these are movable types (no `must_use_immediately` or
// `unsafe_mover`). `await_resume` returns by value to avoid dangling.
template <typename Derived, typename StorageVal>
class result_or_unwind_value_base {
static_assert(!std::is_reference_v<StorageVal>);
StorageVal storage_;
protected:
static constexpr bool kIsErrorOrStopped =
std::is_same_v<StorageVal, error_or_stopped>;
StorageVal&& storage() && noexcept { return std::move(storage_); }
const StorageVal& storage() const& noexcept { return storage_; }
public:
explicit result_or_unwind_value_base(StorageVal s) noexcept
: storage_(std::move(s)) {
// It is unexpected for `or_unwind` to throw, so ban this usage until we
// encounter a use-case requiring more nuance.
static_assert(std::is_nothrow_move_constructible_v<StorageVal>);
}
explicit result_or_unwind_value_base(stopped_result_t s) noexcept
// Can remove -- this just communicates that `or_unwind{stopped_result}
// constructs `or_unwind<error_or_stopped>`, not `or_unwind<result<T>>`.
requires kIsErrorOrStopped
: storage_(s) {}
[[nodiscard]] decltype(auto) await_resume() noexcept {
if constexpr (kIsErrorOrStopped) {
// `await_ready()` is false, the coro gets destroyed in `await_suspend()`
compiler_may_unsafely_assume_unreachable();
} else if constexpr (std::is_reference_v<typename StorageVal::value_type>) {
return std::move(storage_).value_or_throw(); // Return ref into result.
} else {
// Owning result types: return by value to avoid dangling refs.
return
typename StorageVal::value_type(std::move(storage_).value_or_throw());
}
}
};
// Helper to select ref vs value base class.
template <typename Derived, typename Storage>
using result_or_unwind_base = std::conditional_t<
std::is_reference_v<Storage>,
result_or_unwind_ref_base<Derived, Storage>,
result_or_unwind_value_base<Derived, Storage>>;
// Shared awaitable logic for both ref and value storage.
template <typename Derived, typename Storage>
class result_or_unwind_crtp : public result_or_unwind_base<Derived, Storage> {
using Base = result_or_unwind_base<Derived, Storage>;
// `result<T>` has `error_or_stopped()`, `value_only_result` does not
static constexpr bool kHasErrorOrStopped = requires {
FOLLY_DECLVAL(Storage&&).error_or_stopped();
};
public:
using Base::await_resume;
using Base::Base;
[[nodiscard]] bool await_ready() const noexcept {
if constexpr (Base::kIsErrorOrStopped) {
return false;
} else {
return this->storage().has_value();
}
}
// Suspend into a `result<U>` coroutine.
template <typename U>
void await_suspend(result_promise_handle<U> h) noexcept {
auto toResult = [&](auto&& eos) {
auto& v = *h.promise().value_;
expected_detail::ExpectedHelper::assume_empty(v.exp_);
// For lvalue `Storage` (e.g. `result<T>&`), `storage()` returns a ref,
// and `result::error_or_stopped() const&` returns `const
// error_or_stopped&`, which gets copied into `Unexpected` -- preserving
// the original result. This is intentional: users don't expect `co_await
// or_unwind(r)` to mutate `r`, since `r` might outlive the current coro.
v.exp_ = Unexpected{static_cast<decltype(eos)>(eos)};
h.destroy(); // Abort the rest of the coroutine, resume() won't be called
};
if constexpr (Base::kIsErrorOrStopped) {
toResult(std::move(*this).storage());
} else if constexpr (kHasErrorOrStopped) {
toResult(std::move(*this).storage().error_or_stopped());
} else {
// `value_only_result`: not reached since `await_ready()` is always true.
compiler_may_unsafely_assume_unreachable();
}
}
// Suspend into a task-like coroutine (`now_task`, `Task`, etc.).
//
// NB: Do NOT support `AsyncGenerator` which also has `co_yield co_error`.
// It doesn't abort the generator, a surprising semantic for `or_unwind`.
template <typename Promise>
requires requires(Promise p, coro::detail::TaskPromisePrivate priv) {
p.continuationRef(priv);
}
auto await_suspend(std::coroutine_handle<Promise> h) noexcept {
// Uses the legacy `co_error` API because `folly::coro` models cancellation
// as an exception, and checking for `OperationCancelled` costs 50-100ns+.
auto toTask = [&](error_or_stopped&& eos) {
return h.promise()
.yield_value(
coro::co_error(
std::move(eos).get_legacy_error_or_cancellation_slow(
result_private_t{})))
.await_suspend(h);
};
if constexpr (Base::kIsErrorOrStopped) {
return toTask(std::move(*this).storage());
} else if constexpr (kHasErrorOrStopped) {
// `copy` since `get_legacy_error_or_cancellation_slow` lacks `const&`.
return toTask(
::folly::copy(std::move(*this).storage().error_or_stopped()));
} else {
// `value_only_result`: not reached since `await_ready()` is always true.
compiler_may_unsafely_assume_unreachable();
}
}
// Bypass `co_viaIfAsync` / `withAsyncStack`: `or_unwind` is synchronous.
friend auto co_viaIfAsync(
const ExecutorKeepAlive<Executor>&, Derived r) noexcept {
return ext::must_use_immediately_unsafe_mover(std::move(r))();
}
// Conventionally, the first arg would be `cpo_t<co_withAsyncStack>`, but
// that cannot be forward-declared.
friend auto tag_invoke(
const coro::detail::WithAsyncStackFunction&, Derived&& r) noexcept {
return ext::must_use_immediately_unsafe_mover(std::move(r))();
}
};
// Aliases to reduce CRTP boilerplate in `or_unwind` specializations.
template <typename R>
using result_or_unwind = result_or_unwind_crtp<::folly::or_unwind<R>, R>;
template <typename R>
using result_or_unwind_owning =
result_or_unwind_crtp<::folly::or_unwind_owning<R>, R>;
} // namespace detail
} // namespace folly
#endif // FOLLY_HAS_RESULT