From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- mfbt/Result.h | 873 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 873 insertions(+) create mode 100644 mfbt/Result.h (limited to 'mfbt/Result.h') diff --git a/mfbt/Result.h b/mfbt/Result.h new file mode 100644 index 0000000000..052920fdbf --- /dev/null +++ b/mfbt/Result.h @@ -0,0 +1,873 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* A type suitable for returning either a value or an error from a function. */ + +#ifndef mozilla_Result_h +#define mozilla_Result_h + +#include +#include +#include +#include +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/CompactPair.h" +#include "mozilla/MaybeStorageBase.h" + +namespace mozilla { + +/** + * Empty struct, indicating success for operations that have no return value. + * For example, if you declare another empty struct `struct OutOfMemory {};`, + * then `Result` represents either success or OOM. + */ +struct Ok {}; + +/** + * A tag used to differentiate between GenericErrorResult created by the Err + * function (completely new error) and GenericErrorResult created by the + * Result::propagateErr function (propagated error). This can be used to track + * error propagation and eventually produce error stacks for logging/debugging + * purposes. + */ +struct ErrorPropagationTag {}; + +template +class GenericErrorResult; +template +class Result; + +namespace detail { + +enum class PackingStrategy { + Variant, + NullIsOk, + LowBitTagIsError, + PackedVariant, + ZeroIsEmptyError, +}; + +template +struct UnusedZero; + +template +class ResultImplementation; + +template +struct EmptyWrapper : V { + constexpr EmptyWrapper() = default; + explicit constexpr EmptyWrapper(const V&) {} + explicit constexpr EmptyWrapper(std::in_place_t) {} + + constexpr V* addr() { return this; } + constexpr const V* addr() const { return this; } +}; + +// The purpose of AlignedStorageOrEmpty is to make an empty class look like +// std::aligned_storage_t for the purposes of the PackingStrategy::NullIsOk +// specializations of ResultImplementation below. We can't use +// std::aligned_storage_t itself with an empty class, since it would no longer +// be empty. +template +using AlignedStorageOrEmpty = + std::conditional_t, EmptyWrapper, + MaybeStorageBase>; + +template +class ResultImplementationNullIsOkBase { + protected: + using ErrorStorageType = typename UnusedZero::StorageType; + + static constexpr auto kNullValue = UnusedZero::nullValue; + + static_assert(std::is_trivially_copyable_v); + + // XXX This can't be statically asserted in general, if ErrorStorageType is + // not a basic type. With C++20 bit_cast, we could probably re-add such as + // assertion. static_assert(kNullValue == decltype(kNullValue)(0)); + + CompactPair, ErrorStorageType> mValue; + + public: + explicit constexpr ResultImplementationNullIsOkBase(const V& aSuccessValue) + : mValue(aSuccessValue, kNullValue) {} + explicit constexpr ResultImplementationNullIsOkBase(V&& aSuccessValue) + : mValue(std::move(aSuccessValue), kNullValue) {} + template + explicit constexpr ResultImplementationNullIsOkBase(std::in_place_t, + Args&&... aArgs) + : mValue(std::piecewise_construct, + std::tuple(std::in_place, std::forward(aArgs)...), + std::tuple(kNullValue)) {} + explicit constexpr ResultImplementationNullIsOkBase(E aErrorValue) + : mValue(std::piecewise_construct, std::tuple<>(), + std::tuple(UnusedZero::Store(std::move(aErrorValue)))) { + MOZ_ASSERT(mValue.second() != kNullValue); + } + + constexpr ResultImplementationNullIsOkBase( + ResultImplementationNullIsOkBase&& aOther) + : mValue(std::piecewise_construct, std::tuple<>(), + std::tuple(aOther.mValue.second())) { + if constexpr (!std::is_empty_v) { + if (isOk()) { + new (mValue.first().addr()) V(std::move(*aOther.mValue.first().addr())); + } + } + } + ResultImplementationNullIsOkBase& operator=( + ResultImplementationNullIsOkBase&& aOther) { + if constexpr (!std::is_empty_v) { + if (isOk()) { + mValue.first().addr()->~V(); + } + } + mValue.second() = std::move(aOther.mValue.second()); + if constexpr (!std::is_empty_v) { + if (isOk()) { + new (mValue.first().addr()) V(std::move(*aOther.mValue.first().addr())); + } + } + return *this; + } + + constexpr bool isOk() const { return mValue.second() == kNullValue; } + + constexpr const V& inspect() const { return *mValue.first().addr(); } + constexpr V unwrap() { return std::move(*mValue.first().addr()); } + constexpr void updateAfterTracing(V&& aValue) { + MOZ_ASSERT(isOk()); + if (!std::is_empty_v) { + mValue.first().addr()->~V(); + new (mValue.first().addr()) V(std::move(aValue)); + } + } + + constexpr decltype(auto) inspectErr() const { + return UnusedZero::Inspect(mValue.second()); + } + constexpr E unwrapErr() { return UnusedZero::Unwrap(mValue.second()); } + constexpr void updateErrorAfterTracing(E&& aErrorValue) { + mValue.second() = UnusedZero::Store(std::move(aErrorValue)); + } +}; + +template > +class ResultImplementationNullIsOk; + +template +class ResultImplementationNullIsOk + : public ResultImplementationNullIsOkBase { + public: + using ResultImplementationNullIsOkBase::ResultImplementationNullIsOkBase; +}; + +template +class ResultImplementationNullIsOk + : public ResultImplementationNullIsOkBase { + public: + using ResultImplementationNullIsOkBase::ResultImplementationNullIsOkBase; + + ResultImplementationNullIsOk(ResultImplementationNullIsOk&&) = default; + ResultImplementationNullIsOk& operator=(ResultImplementationNullIsOk&&) = + default; + + ~ResultImplementationNullIsOk() { + if (this->isOk()) { + this->mValue.first().addr()->~V(); + } + } +}; + +/** + * Specialization for when the success type is one of integral, pointer, or + * enum, where 0 is unused, and the error type is an empty struct. + */ +template +class ResultImplementation { + static_assert(std::is_integral_v || std::is_pointer_v || + std::is_enum_v); + static_assert(std::is_empty_v); + + V mValue; + + public: + static constexpr PackingStrategy Strategy = PackingStrategy::ZeroIsEmptyError; + + explicit constexpr ResultImplementation(V aValue) : mValue(aValue) {} + explicit constexpr ResultImplementation(E aErrorValue) : mValue(V(0)) {} + + constexpr bool isOk() const { return mValue != V(0); } + + constexpr V inspect() const { return mValue; } + constexpr V unwrap() { return inspect(); } + + constexpr E inspectErr() const { return E(); } + constexpr E unwrapErr() { return inspectErr(); } + + constexpr void updateAfterTracing(V&& aValue) { + this->~ResultImplementation(); + new (this) ResultImplementation(std::move(aValue)); + } + constexpr void updateErrorAfterTracing(E&& aErrorValue) { + this->~ResultImplementation(); + new (this) ResultImplementation(std::move(aErrorValue)); + } +}; + +/** + * Specialization for when the success type is default-constructible and the + * error type is a value type which can never have the value 0 (as determined by + * UnusedZero<>). + */ +template +class ResultImplementation + : public ResultImplementationNullIsOk { + public: + static constexpr PackingStrategy Strategy = PackingStrategy::NullIsOk; + using ResultImplementationNullIsOk::ResultImplementationNullIsOk; +}; + +template +using UnsignedIntType = std::conditional_t< + S == 1, std::uint8_t, + std::conditional_t< + S == 2, std::uint16_t, + std::conditional_t>>>; + +/** + * Specialization for when alignment permits using the least significant bit + * as a tag bit. + */ +template +class ResultImplementation { + static_assert(std::is_trivially_copyable_v && + std::is_trivially_destructible_v); + static_assert(std::is_trivially_copyable_v && + std::is_trivially_destructible_v); + + static constexpr size_t kRequiredSize = std::max(sizeof(V), sizeof(E)); + + using StorageType = UnsignedIntType; + +#if defined(__clang__) + alignas(std::max(alignof(V), alignof(E))) StorageType mBits; +#else + // Some gcc versions choke on using std::max with alignas, see + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94929 (and this seems to have + // regressed in some gcc 9.x version before being fixed again) Keeping the + // code above since we would eventually drop this when we no longer support + // gcc versions with the bug. + alignas(alignof(V) > alignof(E) ? alignof(V) : alignof(E)) StorageType mBits; +#endif + + public: + static constexpr PackingStrategy Strategy = PackingStrategy::LowBitTagIsError; + + explicit constexpr ResultImplementation(V aValue) : mBits(0) { + if constexpr (!std::is_empty_v) { + std::memcpy(&mBits, &aValue, sizeof(V)); + MOZ_ASSERT((mBits & 1) == 0); + } else { + (void)aValue; + } + } + explicit constexpr ResultImplementation(E aErrorValue) : mBits(1) { + if constexpr (!std::is_empty_v) { + std::memcpy(&mBits, &aErrorValue, sizeof(E)); + MOZ_ASSERT((mBits & 1) == 0); + mBits |= 1; + } else { + (void)aErrorValue; + } + } + + constexpr bool isOk() const { return (mBits & 1) == 0; } + + constexpr V inspect() const { + V res; + std::memcpy(&res, &mBits, sizeof(V)); + return res; + } + constexpr V unwrap() { return inspect(); } + + constexpr E inspectErr() const { + const auto bits = mBits ^ 1; + E res; + std::memcpy(&res, &bits, sizeof(E)); + return res; + } + constexpr E unwrapErr() { return inspectErr(); } + + constexpr void updateAfterTracing(V&& aValue) { + this->~ResultImplementation(); + new (this) ResultImplementation(std::move(aValue)); + } + constexpr void updateErrorAfterTracing(E&& aErrorValue) { + this->~ResultImplementation(); + new (this) ResultImplementation(std::move(aErrorValue)); + } +}; + +// Return true if any of the struct can fit in a word. +template +struct IsPackableVariant { + struct VEbool { + explicit constexpr VEbool(V&& aValue) : v(std::move(aValue)), ok(true) {} + explicit constexpr VEbool(E&& aErrorValue) + : e(std::move(aErrorValue)), ok(false) {} + V v; + E e; + bool ok; + }; + struct EVbool { + explicit constexpr EVbool(V&& aValue) : v(std::move(aValue)), ok(true) {} + explicit constexpr EVbool(E&& aErrorValue) + : e(std::move(aErrorValue)), ok(false) {} + E e; + V v; + bool ok; + }; + + using Impl = + std::conditional_t; + + static const bool value = sizeof(Impl) <= sizeof(uintptr_t); +}; + +/** + * Specialization for when both type are not using all the bytes, in order to + * use one byte as a tag. + */ +template +class ResultImplementation { + using Impl = typename IsPackableVariant::Impl; + Impl data; + + public: + static constexpr PackingStrategy Strategy = PackingStrategy::PackedVariant; + + explicit constexpr ResultImplementation(V aValue) : data(std::move(aValue)) {} + explicit constexpr ResultImplementation(E aErrorValue) + : data(std::move(aErrorValue)) {} + + constexpr bool isOk() const { return data.ok; } + + constexpr const V& inspect() const { return data.v; } + constexpr V unwrap() { return std::move(data.v); } + + constexpr const E& inspectErr() const { return data.e; } + constexpr E unwrapErr() { return std::move(data.e); } + + constexpr void updateAfterTracing(V&& aValue) { + MOZ_ASSERT(data.ok); + this->~ResultImplementation(); + new (this) ResultImplementation(std::move(aValue)); + } + constexpr void updateErrorAfterTracing(E&& aErrorValue) { + MOZ_ASSERT(!data.ok); + this->~ResultImplementation(); + new (this) ResultImplementation(std::move(aErrorValue)); + } +}; + +// To use nullptr as a special value, we need the counter part to exclude zero +// from its range of valid representations. +// +// By default assume that zero can be represented. +template +struct UnusedZero { + static const bool value = false; +}; + +// This template can be used as a helper for specializing UnusedZero for scoped +// enum types which never use 0 as an error value, e.g. +// +// namespace mozilla::detail { +// +// template <> +// struct UnusedZero : UnusedZeroEnum {}; +// +// } // namespace mozilla::detail +// +template +struct UnusedZeroEnum { + using StorageType = std::underlying_type_t; + + static constexpr bool value = true; + static constexpr StorageType nullValue = 0; + + static constexpr T Inspect(const StorageType& aValue) { + return static_cast(aValue); + } + static constexpr T Unwrap(StorageType aValue) { + return static_cast(aValue); + } + static constexpr StorageType Store(T aValue) { + return static_cast(aValue); + } +}; + +// A bit of help figuring out which of the above specializations to use. +// +// We begin by safely assuming types don't have a spare bit, unless they are +// empty. +template +struct HasFreeLSB { + static const bool value = std::is_empty_v; +}; + +// As an incomplete type, void* does not have a spare bit. +template <> +struct HasFreeLSB { + static const bool value = false; +}; + +// The lowest bit of a properly-aligned pointer is always zero if the pointee +// type is greater than byte-aligned. That bit is free to use if it's masked +// out of such pointers before they're dereferenced. +template +struct HasFreeLSB { + static const bool value = (alignof(T) & 1) == 0; +}; + +// Select one of the previous result implementation based on the properties of +// the V and E types. +template +struct SelectResultImpl { + static const PackingStrategy value = + (UnusedZero::value && std::is_empty_v) + ? PackingStrategy::ZeroIsEmptyError + : (HasFreeLSB::value && HasFreeLSB::value) + ? PackingStrategy::LowBitTagIsError + : (UnusedZero::value && sizeof(E) <= sizeof(uintptr_t)) + ? PackingStrategy::NullIsOk + : (std::is_default_constructible_v && + std::is_default_constructible_v && IsPackableVariant::value) + ? PackingStrategy::PackedVariant + : PackingStrategy::Variant; + + using Type = ResultImplementation; +}; + +template +struct IsResult : std::false_type {}; + +template +struct IsResult> : std::true_type {}; + +} // namespace detail + +template +constexpr auto ToResult(Result&& aValue) + -> decltype(std::forward>(aValue)) { + return std::forward>(aValue); +} + +/** + * Result represents the outcome of an operation that can either succeed + * or fail. It contains either a success value of type V or an error value of + * type E. + * + * All Result methods are const, so results are basically immutable. + * This is just like Variant but with a slightly different API, and the + * following cases are optimized so Result can be stored more efficiently: + * + * - If both the success and error types do not use their least significant bit, + * are trivially copyable and destructible, Result is guaranteed to be as + * large as the larger type. This is determined via the HasFreeLSB trait. By + * default, empty classes (in particular Ok) and aligned pointer types are + * assumed to have a free LSB, but you can specialize this trait for other + * types. If the success type is empty, the representation is guaranteed to be + * all zero bits on success. Do not change this representation! There is JIT + * code that depends on it. (Implementation note: The lowest bit is used as a + * tag bit: 0 to indicate the Result's bits are a success value, 1 to indicate + * the Result's bits (with the 1 masked out) encode an error value) + * + * - Else, if the error type can't have a all-zero bits representation and is + * not larger than a pointer, a CompactPair is used to represent this rather + * than a Variant. This has shown to be better optimizable, and the template + * code is much simpler than that of Variant, so it should also compile faster. + * Whether an error type can't be all-zero bits, is determined via the + * UnusedZero trait. MFBT doesn't declare any public type UnusedZero, but + * nsresult is declared UnusedZero in XPCOM. + * + * The purpose of Result is to reduce the screwups caused by using `false` or + * `nullptr` to indicate errors. + * What screwups? See for + * a partial list. + * + * Result or Result are not meaningful. The success or + * error values in a Result instance are non-modifiable in-place anyway. This + * guarantee must also be maintained when evolving Result. They can be + * unwrap()ped, but this loses const qualification. However, Result + * or Result may be misleading and prevent movability. Just use + * Result. (Result may make sense though, just Result is not possible.) + */ +template +class [[nodiscard]] Result final { + // See class comment on Result and Result. + static_assert(!std::is_const_v); + static_assert(!std::is_const_v); + static_assert(!std::is_reference_v); + static_assert(!std::is_reference_v); + + using Impl = typename detail::SelectResultImpl::Type; + + Impl mImpl; + // Are you getting this error? + // > error: implicit instantiation of undefined template + // > 'mozilla::detail::ResultImplementation<$V,$E, + // > mozilla::detail::PackingStrategy::Variant>' + // You need to include "ResultVariant.h"! + + public: + static constexpr detail::PackingStrategy Strategy = Impl::Strategy; + using ok_type = V; + using err_type = E; + + /** Create a success result. */ + MOZ_IMPLICIT constexpr Result(V&& aValue) : mImpl(std::move(aValue)) { + MOZ_ASSERT(isOk()); + } + + /** Create a success result. */ + MOZ_IMPLICIT constexpr Result(const V& aValue) : mImpl(aValue) { + MOZ_ASSERT(isOk()); + } + + /** Create a success result in-place. */ + template + explicit constexpr Result(std::in_place_t, Args&&... aArgs) + : mImpl(std::in_place, std::forward(aArgs)...) { + MOZ_ASSERT(isOk()); + } + + /** Create an error result. */ + explicit constexpr Result(const E& aErrorValue) : mImpl(aErrorValue) { + MOZ_ASSERT(isErr()); + } + explicit constexpr Result(E&& aErrorValue) : mImpl(std::move(aErrorValue)) { + MOZ_ASSERT(isErr()); + } + + /** + * Create a (success/error) result from another (success/error) result with + * different but convertible value and error types. + */ + template && + std::is_convertible_v>> + MOZ_IMPLICIT constexpr Result(Result&& aOther) + : mImpl(aOther.isOk() ? Impl{aOther.unwrap()} + : Impl{aOther.unwrapErr()}) {} + + /** + * Implementation detail of MOZ_TRY(). + * Create an error result from another error result. + */ + template + MOZ_IMPLICIT constexpr Result(GenericErrorResult&& aErrorResult) + : mImpl(std::move(aErrorResult.mErrorValue)) { + static_assert(std::is_convertible_v, "E2 must be convertible to E"); + MOZ_ASSERT(isErr()); + } + + /** + * Implementation detail of MOZ_TRY(). + * Create an error result from another error result. + */ + template + MOZ_IMPLICIT constexpr Result(const GenericErrorResult& aErrorResult) + : mImpl(aErrorResult.mErrorValue) { + static_assert(std::is_convertible_v, "E2 must be convertible to E"); + MOZ_ASSERT(isErr()); + } + + Result(const Result&) = delete; + Result(Result&&) = default; + Result& operator=(const Result&) = delete; + Result& operator=(Result&&) = default; + + /** True if this Result is a success result. */ + constexpr bool isOk() const { return mImpl.isOk(); } + + /** True if this Result is an error result. */ + constexpr bool isErr() const { return !mImpl.isOk(); } + + /** Take the success value from this Result, which must be a success result. + */ + constexpr V unwrap() { + MOZ_ASSERT(isOk()); + return mImpl.unwrap(); + } + + /** + * Take the success value from this Result, which must be a success result. + * If it is an error result, then return the aValue. + */ + constexpr V unwrapOr(V aValue) { + return MOZ_LIKELY(isOk()) ? mImpl.unwrap() : std::move(aValue); + } + + /** Take the error value from this Result, which must be an error result. */ + constexpr E unwrapErr() { + MOZ_ASSERT(isErr()); + return mImpl.unwrapErr(); + } + + /** Used only for GC tracing. If used in Rooted>, V must have a + * GCPolicy for tracing it. */ + constexpr void updateAfterTracing(V&& aValue) { + mImpl.updateAfterTracing(std::move(aValue)); + } + + /** Used only for GC tracing. If used in Rooted>, E must have a + * GCPolicy for tracing it. */ + constexpr void updateErrorAfterTracing(E&& aErrorValue) { + mImpl.updateErrorAfterTracing(std::move(aErrorValue)); + } + + /** See the success value from this Result, which must be a success result. */ + constexpr decltype(auto) inspect() const { + static_assert(!std::is_reference_v< + std::invoke_result_t> || + std::is_const_v>>); + MOZ_ASSERT(isOk()); + return mImpl.inspect(); + } + + /** See the error value from this Result, which must be an error result. */ + constexpr decltype(auto) inspectErr() const { + static_assert( + !std::is_reference_v< + std::invoke_result_t> || + std::is_const_v>>); + MOZ_ASSERT(isErr()); + return mImpl.inspectErr(); + } + + /** Propagate the error value from this Result, which must be an error result. + * + * This can be used to propagate an error from a function call to the caller + * with a different value type, but the same error type: + * + * Result Func1() { + * Result res = Func2(); + * if (res.isErr()) { return res.propagateErr(); } + * } + */ + constexpr GenericErrorResult propagateErr() { + MOZ_ASSERT(isErr()); + return GenericErrorResult{mImpl.unwrapErr(), ErrorPropagationTag{}}; + } + + /** + * Map a function V -> V2 over this result's success variant. If this result + * is an error, do not invoke the function and propagate the error. + * + * Mapping over success values invokes the function to produce a new success + * value: + * + * // Map Result to another Result + * Result res(5); + * Result res2 = res.map([](int x) { return x * x; }); + * MOZ_ASSERT(res.isOk()); + * MOZ_ASSERT(res2.unwrap() == 25); + * + * // Map Result to Result + * Result res("hello, map!"); + * Result res2 = res.map(strlen); + * MOZ_ASSERT(res.isOk()); + * MOZ_ASSERT(res2.unwrap() == 11); + * + * Mapping over an error does not invoke the function and propagates the + * error: + * + * Result res(5); + * MOZ_ASSERT(res.isErr()); + * Result res2 = res.map([](V v) { ... }); + * MOZ_ASSERT(res2.isErr()); + * MOZ_ASSERT(res2.unwrapErr() == 5); + */ + template + constexpr auto map(F f) -> Result, E> { + using RetResult = Result, E>; + return MOZ_LIKELY(isOk()) ? RetResult(f(unwrap())) : RetResult(unwrapErr()); + } + + /** + * Map a function E -> E2 over this result's error variant. If this result is + * a success, do not invoke the function and move the success over. + * + * Mapping over error values invokes the function to produce a new error + * value: + * + * // Map Result to another Result + * Result res(5); + * Result res2 = res.mapErr([](int x) { return x * x; }); + * MOZ_ASSERT(res2.isErr()); + * MOZ_ASSERT(res2.unwrapErr() == 25); + * + * // Map Result to Result + * Result res("hello, mapErr!"); + * Result res2 = res.mapErr(strlen); + * MOZ_ASSERT(res2.isErr()); + * MOZ_ASSERT(res2.unwrapErr() == 14); + * + * Mapping over a success does not invoke the function and moves the success: + * + * Result res(5); + * MOZ_ASSERT(res.isOk()); + * Result res2 = res.mapErr([](E e) { ... }); + * MOZ_ASSERT(res2.isOk()); + * MOZ_ASSERT(res2.unwrap() == 5); + */ + template + constexpr auto mapErr(F f) { + using RetResult = Result>; + return MOZ_UNLIKELY(isErr()) ? RetResult(f(unwrapErr())) + : RetResult(unwrap()); + } + + /** + * Map a function E -> Result over this result's error variant. If + * this result is a success, do not invoke the function and move the success + * over. + * + * `orElse`ing over error values invokes the function to produce a new + * result: + * + * // `orElse` Result error variant to another Result + * // error variant or Result success variant + * auto orElse = [](int x) -> Result { + * if (x != 6) { + * return Err(x * x); + * } + * return V(...); + * }; + * + * Result res(5); + * auto res2 = res.orElse(orElse); + * MOZ_ASSERT(res2.isErr()); + * MOZ_ASSERT(res2.unwrapErr() == 25); + * + * Result res3(6); + * auto res4 = res3.orElse(orElse); + * MOZ_ASSERT(res4.isOk()); + * MOZ_ASSERT(res4.unwrap() == ...); + * + * // `orElse` Result error variant to Result + * // error variant or Result success variant + * auto orElse = [](const char* s) -> Result { + * if (strcmp(s, "foo")) { + * return Err(strlen(s)); + * } + * return V(...); + * }; + * + * Result res("hello, orElse!"); + * auto res2 = res.orElse(orElse); + * MOZ_ASSERT(res2.isErr()); + * MOZ_ASSERT(res2.unwrapErr() == 14); + * + * Result res3("foo"); + * auto res4 = ress.orElse(orElse); + * MOZ_ASSERT(res4.isOk()); + * MOZ_ASSERT(res4.unwrap() == ...); + * + * `orElse`ing over a success does not invoke the function and moves the + * success: + * + * Result res(5); + * MOZ_ASSERT(res.isOk()); + * Result res2 = res.orElse([](E e) { ... }); + * MOZ_ASSERT(res2.isOk()); + * MOZ_ASSERT(res2.unwrap() == 5); + */ + template + auto orElse(F f) -> Result::err_type> { + return MOZ_UNLIKELY(isErr()) ? f(unwrapErr()) : unwrap(); + } + + /** + * Given a function V -> Result, apply it to this result's success + * value and return its result. If this result is an error value, it is + * propagated. + * + * This is sometimes called "flatMap" or ">>=" in other contexts. + * + * `andThen`ing over success values invokes the function to produce a new + * result: + * + * Result res("hello, andThen!"); + * Result res2 = res.andThen([](const char* s) { + * return containsHtmlTag(s) + * ? Result(Error("Invalid: contains HTML")) + * : Result(HtmlFreeString(s)); + * } + * }); + * MOZ_ASSERT(res2.isOk()); + * MOZ_ASSERT(res2.unwrap() == HtmlFreeString("hello, andThen!"); + * + * `andThen`ing over error results does not invoke the function, and just + * propagates the error result: + * + * Result res("some error"); + * auto res2 = res.andThen([](int x) { ... }); + * MOZ_ASSERT(res2.isErr()); + * MOZ_ASSERT(res.unwrapErr() == res2.unwrapErr()); + */ + template >::value>> + constexpr auto andThen(F f) -> std::invoke_result_t { + return MOZ_LIKELY(isOk()) ? f(unwrap()) : propagateErr(); + } +}; + +/** + * A type that auto-converts to an error Result. This is like a Result without + * a success type. It's the best return type for functions that always return + * an error--functions designed to build and populate error objects. It's also + * useful in error-handling macros; see MOZ_TRY for an example. + */ +template +class [[nodiscard]] GenericErrorResult { + E mErrorValue; + + template + friend class Result; + + public: + explicit constexpr GenericErrorResult(const E& aErrorValue) + : mErrorValue(aErrorValue) {} + + explicit constexpr GenericErrorResult(E&& aErrorValue) + : mErrorValue(std::move(aErrorValue)) {} + + constexpr GenericErrorResult(const E& aErrorValue, const ErrorPropagationTag&) + : GenericErrorResult(aErrorValue) {} + + constexpr GenericErrorResult(E&& aErrorValue, const ErrorPropagationTag&) + : GenericErrorResult(std::move(aErrorValue)) {} +}; + +template +inline constexpr auto Err(E&& aErrorValue) { + return GenericErrorResult>(std::forward(aErrorValue)); +} + +} // namespace mozilla + +#endif // mozilla_Result_h -- cgit v1.2.3