| // Copyright 2022 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| #ifndef SRC_TINT_UTILS_RESULT_H_ | |
| #define SRC_TINT_UTILS_RESULT_H_ | |
| #include <string> | |
| #include <string_view> | |
| #include <utility> | |
| #include <variant> | |
| #include "src/tint/utils/ice/ice.h" | |
| #include "src/tint/utils/rtti/traits.h" | |
| #include "src/utils/compiler.h" | |
| namespace tint { | |
| /// Empty structure that can be used as the SUCCESS_TYPE for a Result. | |
| struct SuccessType {}; | |
| /// An instance of SuccessType that can be used as a generic success value for a Result. | |
| inline constexpr const SuccessType Success; | |
| /// The default Result error type. | |
| struct Failure { | |
| /// Constructor with no diagnostics | |
| Failure(); | |
| /// Constructor with a single diagnostic | |
| /// @param err the single error diagnostic | |
| explicit Failure(std::string_view err); | |
| std::string reason; | |
| }; | |
| /// Write the Failure to the given stream | |
| /// @param out the output stream | |
| /// @param failure the Failure | |
| /// @returns the output stream | |
| template <typename STREAM> | |
| requires(traits::IsOStream<STREAM>) | |
| auto& operator<<(STREAM& out, const Failure& failure) { | |
| return out << failure.reason; | |
| } | |
| /// Result is a helper for functions that need to return a value, or an failure value. | |
| /// Result can be constructed with either a 'success' or 'failure' value. | |
| /// @tparam SUCCESS_TYPE the 'success' value type. | |
| /// @tparam FAILURE_TYPE the 'failure' value type. Defaults to FailureType which provides no | |
| /// information about the failure, except that something failed. Must not be the same type | |
| /// as SUCCESS_TYPE. | |
| template <typename SUCCESS_TYPE, typename FAILURE_TYPE = Failure> | |
| struct [[nodiscard]] Result { | |
| static_assert(!std::is_same_v<SUCCESS_TYPE, FAILURE_TYPE>, | |
| "Result must not have the same type for SUCCESS_TYPE and FAILURE_TYPE"); | |
| /// Default constructor initializes to invalid state | |
| Result() : value(std::monostate{}) {} | |
| /// Constructor | |
| /// @param success the success result | |
| explicit(false) Result(const SUCCESS_TYPE& success) : value{success} {} | |
| /// Constructor | |
| /// @param success the success result | |
| explicit(false) Result(SUCCESS_TYPE&& success) | |
| : value(std::move(SUCCESS_TYPE(std::move(success)))) {} | |
| /// Constructor | |
| /// @param failure the failure result | |
| explicit(false) Result(const FAILURE_TYPE& failure) : value{failure} {} | |
| /// Constructor | |
| /// @param failure the failure result | |
| explicit(false) Result(FAILURE_TYPE&& failure) : value{std::move(failure)} {} | |
| /// Copy constructor with success / failure casting | |
| /// @param other the Result to copy | |
| template <typename S, | |
| typename F, | |
| typename = std::void_t<decltype(SUCCESS_TYPE{std::declval<S>()}), | |
| decltype(FAILURE_TYPE{std::declval<F>()})>> | |
| explicit(false) Result(const Result<S, F>& other) { | |
| if (other == Success) { | |
| value = SUCCESS_TYPE{other.Get()}; | |
| } else { | |
| value = FAILURE_TYPE{other.Failure()}; | |
| } | |
| } | |
| /// @returns the success value | |
| /// @warning attempting to call this when the Result holds an failure will result in UB. | |
| const SUCCESS_TYPE* operator->() const { | |
| Validate(); | |
| return &(Get()); | |
| } | |
| /// @returns the success value | |
| /// @warning attempting to call this when the Result holds an failure will result in UB. | |
| SUCCESS_TYPE* operator->() { | |
| Validate(); | |
| return &(Get()); | |
| } | |
| /// @returns the success value | |
| /// @warning attempting to call this when the Result holds an failure value will result in UB. | |
| const SUCCESS_TYPE& Get() const { | |
| Validate(); | |
| return std::get<SUCCESS_TYPE>(value); | |
| } | |
| /// @returns the success value | |
| /// @warning attempting to call this when the Result holds an failure value will result in UB. | |
| SUCCESS_TYPE& Get() { | |
| Validate(); | |
| return std::get<SUCCESS_TYPE>(value); | |
| } | |
| /// @returns the success value | |
| /// @warning attempting to call this when the Result holds an failure value will result in UB. | |
| SUCCESS_TYPE&& Move() { | |
| Validate(); | |
| return std::get<SUCCESS_TYPE>(std::move(value)); | |
| } | |
| /// @returns the failure value | |
| /// @warning attempting to call this when the Result holds a success value will result in UB. | |
| const FAILURE_TYPE& Failure() const { | |
| Validate(); | |
| return std::get<FAILURE_TYPE>(value); | |
| } | |
| /// Equality operator | |
| /// @param other the Result to compare this Result to | |
| /// @returns true if this Result is equal to @p other | |
| template <typename T> | |
| bool operator==(const Result& other) const { | |
| return value == other.value; | |
| } | |
| /// Equality operator | |
| /// @param val the value to compare this Result to | |
| /// @returns true if this result holds a success or failure value equal to `value` | |
| template <typename T> | |
| bool operator==(const T& val) const { | |
| Validate(); | |
| using D = std::decay_t<T>; | |
| static constexpr bool is_success = std::is_same_v<D, tint::SuccessType>; // T == Success | |
| static constexpr bool is_success_ty = | |
| std::is_same_v<D, SUCCESS_TYPE> || | |
| (traits::IsStringLike<SUCCESS_TYPE> && traits::IsStringLike<D>); // T == SUCCESS_TYPE | |
| static constexpr bool is_failure_ty = | |
| std::is_same_v<D, FAILURE_TYPE> || | |
| (traits::IsStringLike<FAILURE_TYPE> && traits::IsStringLike<D>); // T == FAILURE_TYPE | |
| static_assert(is_success || is_success_ty || is_failure_ty, | |
| "unsupported type for Result equality operator"); | |
| static_assert(!(is_success_ty && is_failure_ty), | |
| "ambiguous success / failure type for Result equality operator"); | |
| if constexpr (is_success) { | |
| return std::holds_alternative<SUCCESS_TYPE>(value); | |
| } else if constexpr (is_success_ty) { | |
| if (auto* v = std::get_if<SUCCESS_TYPE>(&value)) { | |
| return *v == val; | |
| } | |
| return false; | |
| } else if constexpr (is_failure_ty) { | |
| if (auto* v = std::get_if<FAILURE_TYPE>(&value)) { | |
| return *v == val; | |
| } | |
| return false; | |
| } | |
| } | |
| /// Inequality operator | |
| /// @param val the value to compare this Result to | |
| /// @returns false if this result holds a success or failure value equal to `value` | |
| template <typename T> | |
| bool operator!=(const T& val) const { | |
| return !(*this == val); | |
| } | |
| private: | |
| void Validate() const { TINT_ASSERT(!std::holds_alternative<std::monostate>(value)); } | |
| /// The result. Either a success of failure value. | |
| std::variant<std::monostate, SUCCESS_TYPE, FAILURE_TYPE> value; | |
| }; | |
| /// Writes the result to the stream. | |
| /// @param out the stream to write to | |
| /// @param res the result | |
| /// @return the stream so calls can be chained | |
| template <typename STREAM, typename SUCCESS, typename FAILURE> | |
| requires(traits::IsOStream<STREAM>) | |
| auto& operator<<(STREAM& out, const Result<SUCCESS, FAILURE>& res) { | |
| if (res == Success) { | |
| if constexpr (traits::HasOperatorShiftLeft<STREAM&, SUCCESS>) { | |
| return out << "success: " << res.Get(); | |
| } else { | |
| return out << "success"; | |
| } | |
| } else { | |
| if constexpr (traits::HasOperatorShiftLeft<STREAM&, FAILURE>) { | |
| return out << "failure: " << res.Failure(); | |
| } else { | |
| return out << "failure"; | |
| } | |
| } | |
| } | |
| /// Check that @p result is Success and propagate the failure if not. | |
| #define TINT_CHECK_RESULT(result) \ | |
| if (auto&& tint_check_result_value = (result); \ | |
| DAWN_UNLIKELY(tint_check_result_value != tint::Success)) { \ | |
| return tint_check_result_value.Failure(); \ | |
| } \ | |
| TINT_REQUIRE_SEMICOLON | |
| #define TINT_CHECK_RESULT_UNWRAP_IMPL(to, from, result) \ | |
| auto from = (result); \ | |
| if (DAWN_UNLIKELY(from != tint::Success)) { \ | |
| return from.Failure(); \ | |
| } \ | |
| auto to = from.Move(); \ | |
| TINT_REQUIRE_SEMICOLON | |
| /// Check that @p result is Success and propagate the failure if not. | |
| #define TINT_CHECK_RESULT_UNWRAP(to, result) \ | |
| TINT_CHECK_RESULT_UNWRAP_IMPL(to, TINT_CONCAT(tint_check_result_value, __LINE__), result) | |
| } // namespace tint | |
| #endif // SRC_TINT_UTILS_RESULT_H_ |