blob: 1158240fd9b9b65d890f6e26606783152d0cfe30 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_PROMISE_HELPERS_H_
#define BASE_TASK_PROMISE_HELPERS_H_
#include <tuple>
#include <type_traits>
#include "base/bind.h"
#include "base/callback.h"
#include "base/parameter_pack.h"
#include "base/task/promise/abstract_promise.h"
#include "base/task/promise/promise_result.h"
namespace base {
class DoNothing;
namespace internal {
// A wrapper around SequencedTaskRunnerHandle::Get(). This file is included by
// base/task_runner.h which means we can't include anything that depends on
// that!
scoped_refptr<TaskRunner> BASE_EXPORT GetCurrentSequence();
template <typename T>
using ToNonVoidT = std::conditional_t<std::is_void<T>::value, Void, T>;
template <typename T>
using UndoToNonVoidT =
std::conditional_t<std::is_same<Void, T>::value, void, T>;
// Tag dispatch helper for PostTaskExecutor and ThenAndCatchExecutor.
struct CouldResolveOrReject {};
struct CanOnlyResolve {};
struct CanOnlyReject {};
// PromiseCallbackTraits computes the resolve and reject types of a Promise
// from the return type of a resolve or reject callback.
//
// Usage example:
// using Traits = PromiseCallbackTraits<int>;
//
// Traits::
// ResolveType is int
// RejectType is NoReject
// could_resolve is true
// could_reject is false
template <typename T>
struct PromiseCallbackTraits {
using ResolveType = T;
using RejectType = NoReject;
using TagType = CanOnlyResolve;
static constexpr bool could_resolve = true;
static constexpr bool could_reject = false;
};
template <typename T>
struct PromiseCallbackTraits<Resolved<T>> {
using ResolveType = T;
using RejectType = NoReject;
using TagType = CanOnlyResolve;
static constexpr bool could_resolve = true;
static constexpr bool could_reject = false;
};
template <typename T>
struct PromiseCallbackTraits<Rejected<T>> {
using ResolveType = NoResolve;
using RejectType = T;
using TagType = CanOnlyReject;
static constexpr bool could_resolve = false;
static constexpr bool could_reject = true;
};
template <typename Reject>
struct PromiseCallbackTraits<Promise<NoResolve, Reject>> {
using ResolveType = NoResolve;
using RejectType = Reject;
using TagType = CanOnlyReject;
static constexpr bool could_resolve = false;
static constexpr bool could_reject = true;
};
template <typename Resolve>
struct PromiseCallbackTraits<Promise<Resolve, NoReject>> {
using ResolveType = Resolve;
using RejectType = NoReject;
using TagType = CanOnlyResolve;
static constexpr bool could_resolve = true;
static constexpr bool could_reject = false;
};
template <typename Resolve, typename Reject>
struct PromiseCallbackTraits<Promise<Resolve, Reject>> {
using ResolveType = Resolve;
using RejectType = Reject;
using TagType = CouldResolveOrReject;
static constexpr bool could_resolve = true;
static constexpr bool could_reject = true;
};
template <typename Reject>
struct PromiseCallbackTraits<PromiseResult<NoResolve, Reject>> {
using ResolveType = NoResolve;
using RejectType = Reject;
using TagType = CanOnlyReject;
static constexpr bool could_resolve = false;
static constexpr bool could_reject = true;
};
template <typename Resolve>
struct PromiseCallbackTraits<PromiseResult<Resolve, NoReject>> {
using ResolveType = Resolve;
using RejectType = NoReject;
using TagType = CanOnlyResolve;
static constexpr bool could_resolve = true;
static constexpr bool could_reject = false;
};
template <typename Resolve, typename Reject>
struct PromiseCallbackTraits<PromiseResult<Resolve, Reject>> {
using ResolveType = Resolve;
using RejectType = Reject;
using TagType = CouldResolveOrReject;
static constexpr bool could_resolve = true;
static constexpr bool could_reject = true;
};
template <typename T>
struct IsScopedRefPtr {
static constexpr bool value = false;
};
template <typename T>
struct IsScopedRefPtr<scoped_refptr<T>> {
static constexpr bool value = true;
};
// UseMoveSemantics determines whether move semantics should be used to
// pass |T| as a function parameter.
//
// Usage example:
//
// UseMoveSemantics<std::unique_ptr<int>>::value; // is true
// UseMoveSemantics<int>::value; // is false
// UseMoveSemantics<scoped_refptr<Dummy>>::value; // is false
//
// Will give false positives for some copyable types, but that should be
// harmless.
template <typename T>
constexpr bool UseMove() {
return !std::is_reference<T>::value && !std::is_pointer<T>::value &&
!std::is_fundamental<std::decay_t<T>>::value &&
!IsScopedRefPtr<T>::value;
}
template <typename T>
struct UseMoveSemantics : public std::integral_constant<bool, UseMove<T>()> {
static_assert(!std::is_rvalue_reference<T>::value,
"Promise<T&&> not supported");
static constexpr PromiseExecutor::ArgumentPassingType argument_passing_type =
UseMove<T>() ? PromiseExecutor::ArgumentPassingType::kMove
: PromiseExecutor::ArgumentPassingType::kNormal;
};
// A std::tuple is deemed to need move semantics if any of it's members need
// to be moved according to UseMove<>.
template <typename... Ts>
struct UseMoveSemantics<std::tuple<Ts...>>
: public std::integral_constant<bool, any_of({UseMove<Ts>()...})> {
static constexpr PromiseExecutor::ArgumentPassingType argument_passing_type =
any_of({UseMove<Ts>()...})
? PromiseExecutor::ArgumentPassingType::kMove
: PromiseExecutor::ArgumentPassingType::kNormal;
};
// CallbackTraits extracts properties relevant to Promises from a callback.
//
// Usage example:
//
// using Traits = CallbackTraits<
// base::OnceCallback<PromiseResult<int, std::string>(float)>;
//
// Traits::
// ResolveType is int
// RejectType is std::string
// ArgType is float
// ReturnType is PromiseResult<int, std::string>
// SignatureType is PromiseResult<int, std::string>(float)
// argument_passing_type is kNormal
template <typename T>
struct CallbackTraits;
template <typename T>
struct CallbackTraits<T()> {
using ResolveType = typename internal::PromiseCallbackTraits<T>::ResolveType;
using RejectType = typename internal::PromiseCallbackTraits<T>::RejectType;
using ArgType = void;
using ReturnType = T;
using SignatureType = T();
static constexpr PromiseExecutor::ArgumentPassingType argument_passing_type =
PromiseExecutor::ArgumentPassingType::kNormal;
};
template <typename T, typename Arg>
struct CallbackTraits<T(Arg)> {
using ResolveType = typename internal::PromiseCallbackTraits<T>::ResolveType;
using RejectType = typename internal::PromiseCallbackTraits<T>::RejectType;
using ArgType = Arg;
using ReturnType = T;
using SignatureType = T(Arg);
static constexpr PromiseExecutor::ArgumentPassingType argument_passing_type =
UseMoveSemantics<Arg>::argument_passing_type;
};
template <typename T, typename... Args>
struct CallbackTraits<T(Args...)> {
using ResolveType = typename internal::PromiseCallbackTraits<T>::ResolveType;
using RejectType = typename internal::PromiseCallbackTraits<T>::RejectType;
using ArgType =
std::conditional_t<(sizeof...(Args) > 0), std::tuple<Args...>, void>;
using ReturnType = T;
using SignatureType = T(Args...);
// If any arguments need move semantics, treat as if they all do.
static constexpr PromiseExecutor::ArgumentPassingType argument_passing_type =
any_of({UseMoveSemantics<Args>::value...})
? PromiseExecutor::ArgumentPassingType::kMove
: PromiseExecutor::ArgumentPassingType::kNormal;
};
template <>
struct CallbackTraits<DoNothing> {
using ResolveType = void;
using RejectType = NoReject;
using ArgType = void;
using ReturnType = void;
using SignatureType = void();
static constexpr PromiseExecutor::ArgumentPassingType argument_passing_type =
PromiseExecutor::ArgumentPassingType::kNormal;
};
// Adaptors for OnceCallback and RepeatingCallback
template <typename T, typename... Args>
struct CallbackTraits<OnceCallback<T(Args...)>>
: public CallbackTraits<T(Args...)> {};
template <typename T, typename... Args>
struct CallbackTraits<RepeatingCallback<T(Args...)>>
: public CallbackTraits<T(Args...)> {};
// Helper for combining the resolve types of two promises.
template <typename A, typename B>
struct ResolveCombinerHelper {
using Type = A;
static constexpr bool valid = false;
};
template <typename A>
struct ResolveCombinerHelper<A, A> {
using Type = A;
static constexpr bool valid = true;
};
template <typename B>
struct ResolveCombinerHelper<NoResolve, B> {
using Type = B;
static constexpr bool valid = true;
};
template <typename A>
struct ResolveCombinerHelper<A, NoResolve> {
using Type = A;
static constexpr bool valid = true;
};
template <>
struct ResolveCombinerHelper<NoResolve, NoResolve> {
using Type = NoResolve;
static constexpr bool valid = true;
};
// Helper for combining the reject types of two promises.
template <typename A, typename B>
struct RejectCombinerHelper {
using Type = A;
static constexpr bool valid = false;
};
template <typename A>
struct RejectCombinerHelper<A, A> {
using Type = A;
static constexpr bool valid = true;
};
template <typename B>
struct RejectCombinerHelper<NoReject, B> {
using Type = B;
static constexpr bool valid = true;
};
template <typename A>
struct RejectCombinerHelper<A, NoReject> {
using Type = A;
static constexpr bool valid = true;
};
template <>
struct RejectCombinerHelper<NoReject, NoReject> {
using Type = NoReject;
static constexpr bool valid = true;
};
// Helper that computes and validates the return type for combining promises.
// Essentially the promise types have to match unless there's NoResolve or
// or NoReject in which case they can be combined.
template <typename ThenReturnResolveT,
typename ThenReturnRejectT,
typename CatchReturnResolveT,
typename CatchReturnRejectT>
struct PromiseCombiner {
using ResolveHelper =
ResolveCombinerHelper<ThenReturnResolveT, CatchReturnResolveT>;
using RejectHelper =
RejectCombinerHelper<ThenReturnRejectT, CatchReturnRejectT>;
using ResolveType = typename ResolveHelper::Type;
using RejectType = typename RejectHelper::Type;
static constexpr bool valid = ResolveHelper::valid && RejectHelper::valid;
};
template <typename RejectStorage>
struct EmplaceInnerHelper {
template <typename Resolve, typename Reject>
static void Emplace(AbstractPromise* promise,
PromiseResult<Resolve, Reject>&& result) {
promise->emplace(std::move(result.value()));
}
};
// TODO(alexclarke): Specialize |EmplaceInnerHelper| where RejectStorage is
// base::Variant to support Promises::All.
template <typename ResolveStorage, typename RejectStorage>
struct EmplaceHelper {
template <typename Resolve, typename Reject>
static void Emplace(AbstractPromise* promise,
PromiseResult<Resolve, Reject>&& result) {
static_assert(std::is_same<typename ResolveStorage::Type, Resolve>::value ||
std::is_same<NoResolve, Resolve>::value,
"Resolve should match ResolveStorage");
static_assert(std::is_same<typename RejectStorage::Type, Reject>::value ||
std::is_same<NoReject, Reject>::value,
"Reject should match RejectStorage");
EmplaceInnerHelper<RejectStorage>::Emplace(promise, std::move(result));
}
template <typename Resolve, typename Reject>
static void Emplace(AbstractPromise* promise,
Promise<Resolve, Reject>&& result) {
static_assert(std::is_same<typename ResolveStorage::Type, Resolve>::value ||
std::is_same<NoResolve, Resolve>::value,
"Resolve should match ResolveStorage");
static_assert(std::is_same<typename RejectStorage::Type, Reject>::value ||
std::is_same<NoReject, Reject>::value,
"Reject should match RejectStorage");
promise->emplace(std::move(result.abstract_promise_));
}
template <typename Result>
static void Emplace(AbstractPromise* promise, Result&& result) {
static_assert(std::is_same<typename ResolveStorage::Type, Result>::value,
"Result should match ResolveStorage");
promise->emplace(in_place_type_t<Resolved<Result>>(),
std::forward<Result>(result));
}
template <typename Resolve>
static void Emplace(AbstractPromise* promise, Resolved<Resolve>&& resolved) {
static_assert(std::is_same<typename ResolveStorage::Type, Resolve>::value,
"Resolve should match ResolveStorage");
promise->emplace(std::move(resolved));
}
template <typename Reject>
static void Emplace(AbstractPromise* promise, Rejected<Reject>&& rejected) {
static_assert(std::is_same<typename RejectStorage::Type, Reject>::value,
"Reject should match RejectStorage");
promise->emplace(std::move(rejected));
}
};
// Helper that decides whether or not to std::move arguments for a callback
// based on the type the callback specifies (i.e. we don't need to move if the
// callback requests a reference).
template <typename CbArg, typename ArgStorageType>
class ArgMoveSemanticsHelper {
public:
static CbArg Get(AbstractPromise* arg) {
return GetImpl(arg, UseMoveSemantics<CbArg>());
}
private:
static CbArg GetImpl(AbstractPromise* arg, std::true_type should_move) {
return std::move(arg->TakeValue().value().Get<ArgStorageType>()->value);
}
static CbArg GetImpl(AbstractPromise* arg, std::false_type should_move) {
return arg->value().Get<ArgStorageType>()->value;
}
};
// Helper for running a promise callback and storing the result if any.
//
// Callback = signature of the callback to execute,
// ArgStorageType = type of the callback parameter (or void if none)
// ResolveStorage = type to use for resolve, usually Resolved<T>.
// RejectStorage = type to use for reject, usually Rejected<T>.
// TODO(alexclarke): Add support for Rejected<Variant<...>>.
template <typename Callback,
typename ArgStorageType,
typename ResolveStorage,
typename RejectStorage>
struct RunHelper;
// Run helper for callbacks with a single argument.
template <typename CbResult,
typename CbArg,
typename ArgStorageType,
typename ResolveStorage,
typename RejectStorage>
struct RunHelper<OnceCallback<CbResult(CbArg)>,
ArgStorageType,
ResolveStorage,
RejectStorage> {
using Callback = OnceCallback<CbResult(CbArg)>;
static void Run(Callback&& executor,
AbstractPromise* arg,
AbstractPromise* result) {
EmplaceHelper<ResolveStorage, RejectStorage>::Emplace(
result, std::move(executor).Run(
ArgMoveSemanticsHelper<CbArg, ArgStorageType>::Get(arg)));
}
};
// Run helper for callbacks with a single argument and void return value.
template <typename CbArg,
typename ArgStorageType,
typename ResolveStorage,
typename RejectStorage>
struct RunHelper<OnceCallback<void(CbArg)>,
ArgStorageType,
ResolveStorage,
RejectStorage> {
using Callback = OnceCallback<void(CbArg)>;
static void Run(Callback&& executor,
AbstractPromise* arg,
AbstractPromise* result) {
static_assert(std::is_void<typename ResolveStorage::Type>::value, "");
std::move(executor).Run(
ArgMoveSemanticsHelper<CbArg, ArgStorageType>::Get(arg));
result->emplace(Resolved<void>());
}
};
// Run helper for callbacks with no arguments.
template <typename CbResult,
typename ArgStorageType,
typename ResolveStorage,
typename RejectStorage>
struct RunHelper<OnceCallback<CbResult()>,
ArgStorageType,
ResolveStorage,
RejectStorage> {
using Callback = OnceCallback<CbResult()>;
static void Run(Callback&& executor,
AbstractPromise* arg,
AbstractPromise* result) {
EmplaceHelper<ResolveStorage, RejectStorage>::Emplace(
result, std::move(executor).Run());
}
};
// Run helper for callbacks with no arguments and void return type.
template <typename ArgStorageType,
typename ResolveStorage,
typename RejectStorage>
struct RunHelper<OnceCallback<void()>,
ArgStorageType,
ResolveStorage,
RejectStorage> {
static void Run(OnceCallback<void()>&& executor,
AbstractPromise* arg,
AbstractPromise* result) {
static_assert(std::is_void<typename ResolveStorage::Type>::value, "");
std::move(executor).Run();
result->emplace(Resolved<void>());
}
};
template <typename T>
struct UnwrapCallback;
template <typename R, typename... Args>
struct UnwrapCallback<R(Args...)> {
using ArgsTuple = std::tuple<Args...>;
};
// Helper for getting callback arguments from a tuple, which works out if move
// semantics are needed.
template <typename Callback, typename Tuple, size_t Index>
struct TupleArgMoveSemanticsHelper {
using CallbackArgsTuple =
typename UnwrapCallback<typename Callback::RunType>::ArgsTuple;
using CbArg = std::tuple_element_t<Index, CallbackArgsTuple>;
static CbArg Get(Tuple& tuple) {
return GetImpl(tuple, UseMoveSemantics<CbArg>());
}
private:
static CbArg GetImpl(Tuple& tuple, std::true_type should_move) {
return std::move(std::get<Index>(tuple));
}
static CbArg GetImpl(Tuple& tuple, std::false_type should_move) {
return std::get<Index>(tuple);
}
};
// Run helper for running a callbacks with the arguments unpacked from a tuple.
template <typename CbResult,
typename... CbArgs,
typename ResolveStorage,
typename RejectStorage>
struct RunHelper<OnceCallback<CbResult(CbArgs...)>,
Resolved<std::tuple<CbArgs...>>,
ResolveStorage,
RejectStorage> {
using Callback = OnceCallback<CbResult(CbArgs...)>;
using StorageType = Resolved<std::tuple<CbArgs...>>;
using IndexSequence = std::index_sequence_for<CbArgs...>;
static void Run(Callback&& executor,
AbstractPromise* arg,
AbstractPromise* result) {
AbstractPromise::ValueHandle value = arg->TakeValue();
std::tuple<CbArgs...>& tuple = value.value().Get<StorageType>()->value;
RunInternal(std::move(executor), tuple, result,
std::integral_constant<bool, std::is_void<CbResult>::value>(),
IndexSequence{});
}
private:
template <typename Callback, size_t... Indices>
static void RunInternal(Callback&& executor,
std::tuple<CbArgs...>& tuple,
AbstractPromise* result,
std::false_type void_result,
std::index_sequence<Indices...>) {
EmplaceHelper<ResolveStorage, RejectStorage>::Emplace(
std::move(executor).Run(
TupleArgMoveSemanticsHelper<Callback, std::tuple<CbArgs...>,
Indices>::Get(tuple)...));
}
template <typename Callback, size_t... Indices>
static void RunInternal(Callback&& executor,
std::tuple<CbArgs...>& tuple,
AbstractPromise* result,
std::true_type void_result,
std::index_sequence<Indices...>) {
std::move(executor).Run(
TupleArgMoveSemanticsHelper<Callback, std::tuple<CbArgs...>,
Indices>::Get(tuple)...);
result->emplace(Resolved<void>());
}
};
// Used by ManualPromiseResolver<> to generate callbacks. Note the use of
// WrappedPromise, this is necessary because we want to cancel the promise (to
// release memory) if the callback gets deleted without having being run.
template <typename T, typename... Args>
class PromiseCallbackHelper {
public:
using Callback = base::OnceCallback<void(Args...)>;
using RepeatingCallback = base::RepeatingCallback<void(Args...)>;
static Callback GetResolveCallback(scoped_refptr<AbstractPromise>& promise) {
return base::BindOnce(
[](scoped_refptr<AbstractPromise> promise, Args... args) {
promise->emplace(in_place_type_t<Resolved<T>>(),
std::forward<Args>(args)...);
promise->OnResolved();
},
promise);
}
static RepeatingCallback GetRepeatingResolveCallback(
scoped_refptr<AbstractPromise>& promise) {
return base::BindRepeating(
[](scoped_refptr<AbstractPromise> promise, Args... args) {
promise->emplace(in_place_type_t<Resolved<T>>(),
std::forward<Args>(args)...);
promise->OnResolved();
},
promise);
}
static Callback GetRejectCallback(scoped_refptr<AbstractPromise>& promise) {
return base::BindOnce(
[](scoped_refptr<AbstractPromise> promise, Args... args) {
promise->emplace(in_place_type_t<Rejected<T>>(),
std::forward<Args>(args)...);
promise->OnRejected();
},
promise);
}
static RepeatingCallback GetRepeatingRejectCallback(
scoped_refptr<AbstractPromise>& promise) {
return base::BindRepeating(
[](scoped_refptr<AbstractPromise> promise, Args... args) {
promise->emplace(in_place_type_t<Rejected<T>>(),
std::forward<Args>(args)...);
promise->OnRejected();
},
promise);
}
};
// Validates that the argument type |CallbackArgType| of a resolve or
// reject callback is compatible with the resolve or reject type
// |PromiseType| of this Promise.
template <typename PromiseType, typename CallbackArgType>
struct IsValidPromiseArg {
static constexpr bool value =
std::is_convertible<PromiseType, std::decay_t<CallbackArgType>>::value;
};
template <typename PromiseType, typename CallbackArgType>
struct IsValidPromiseArg<PromiseType&, CallbackArgType> {
static constexpr bool value =
std::is_convertible<PromiseType&, CallbackArgType>::value;
};
// This template helps assign the reject value from a prerequisite into the
// rejection storage type.
template <typename RejectT>
struct AllPromiseRejectHelper {
static void Reject(AbstractPromise* result, AbstractPromise* prerequisite) {
result->emplace(scoped_refptr<AbstractPromise>(prerequisite));
}
};
// TODO(alexclarke): Specalize AllPromiseRejectHelper for variants.
// To reduce template bloat executors hold CallbackBase. These functions convert
// various types to CallbackBase.
DoNothing BASE_EXPORT ToCallbackBase(DoNothing task);
template <typename CallbackT>
CallbackBase&& ToCallbackBase(CallbackT&& task) {
static_assert(sizeof(CallbackBase) == sizeof(CallbackT),
"We assume it's possible to cast from CallbackBase to "
"CallbackT");
return static_cast<CallbackBase&&>(task);
}
template <typename CallbackT>
CallbackBase&& ToCallbackBase(const CallbackT&& task) {
static_assert(sizeof(CallbackBase) == sizeof(CallbackT),
"We assume it's possible to cast from CallbackBase to "
"CallbackT");
return static_cast<CallbackBase&&>(const_cast<CallbackT&&>(task));
}
// Helps reduce template bloat by moving AbstractPromise construction out of
// line.
PassedPromise BASE_EXPORT ConstructAbstractPromiseWithSinglePrerequisite(
const scoped_refptr<TaskRunner>& task_runner,
const Location& from_here,
AbstractPromise* prerequsite,
PromiseExecutor::Data&& executor_data) noexcept;
// Like ConstructAbstractPromiseWithSinglePrerequisite except tasks are posted
// onto SequencedTaskRunnerHandle::Get.
PassedPromise BASE_EXPORT ConstructHereAbstractPromiseWithSinglePrerequisite(
const Location& from_here,
AbstractPromise* prerequsite,
PromiseExecutor::Data&& executor_data) noexcept;
PassedPromise BASE_EXPORT
ConstructManualPromiseResolverPromise(const Location& from_here,
RejectPolicy reject_policy,
bool can_resolve,
bool can_reject);
} // namespace internal
} // namespace base
#endif // BASE_TASK_PROMISE_HELPERS_H_