blob: 4342e076402c0bbeeb96a9665cec263cc1a9c8ce [file] [log] [blame]
// 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_RESULT_H_
#define SRC_TINT_UTILS_RESULT_RESULT_H_
#include <utility>
#include <variant>
#include "src/tint/utils/diagnostic/diagnostic.h"
#include "src/tint/utils/ice/ice.h"
#include "src/tint/utils/text/string_stream.h"
#include "src/tint/utils/traits/traits.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.
static 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);
/// Constructor with a single diagnostic
/// @param diagnostic the failure diagnostic
explicit Failure(diag::Diagnostic diagnostic);
/// Constructor with a list of diagnostics
/// @param diagnostics the failure diagnostics
explicit Failure(diag::List diagnostics);
/// The diagnostics explaining the failure reason
diag::List reason;
};
/// Write the Failure to the given stream
/// @param out the output stream
/// @param failure the Failure
/// @returns the output stream
template <typename STREAM, typename = traits::EnableIfIsOStream<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
Result(const SUCCESS_TYPE& success) // NOLINT(runtime/explicit):
: value{success} {}
/// Constructor
/// @param success the success result
Result(SUCCESS_TYPE&& success) // NOLINT(runtime/explicit):
: value(std::move(SUCCESS_TYPE(std::move(success)))) {}
/// Constructor
/// @param failure the failure result
Result(const FAILURE_TYPE& failure) // NOLINT(runtime/explicit):
: value{failure} {}
/// Constructor
/// @param failure the failure result
Result(FAILURE_TYPE&& failure) // NOLINT(runtime/explicit):
: 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>()})>>
Result(const Result<S, F>& other) { // NOLINT(runtime/explicit):
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,
typename = traits::EnableIfIsOStream<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";
}
}
}
} // namespace tint
#endif // SRC_TINT_UTILS_RESULT_RESULT_H_