mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 22:02:23 +01:00
Add support for a custom error type in ErrorOr (#5834)
This doesn't split apart the current error type into one that tracks location and one that doesn't, although that might be easier to do once we have this. Instead, this is primarily intended to support custom error types that lazily materialize the error message in case that can be avoided by completely handling the error. For example, many file system operations are *expected* to produce errors even in the hot path and we don't want to render `ENOENT` (for example) to a pretty string and instead will directly query the error to understand and handle it in code. The type parameter ordering isn't the most obvious, but helpfully allows us to default the error type in a useful way. --------- Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
This commit is contained in:
co-authored by
Jon Ross-Perkins
parent
b8ca7bf18f
commit
e99448eecf
+181
-78
@@ -6,6 +6,8 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <concepts>
|
||||
|
||||
#include "common/error_test_helpers.h"
|
||||
#include "common/raw_string_ostream.h"
|
||||
|
||||
@@ -29,84 +31,6 @@ auto IndirectError() -> Error { return Error("test"); }
|
||||
|
||||
TEST(ErrorTest, IndirectError) { EXPECT_EQ(IndirectError().message(), "test"); }
|
||||
|
||||
TEST(ErrorTest, ErrorOr) {
|
||||
ErrorOr<int> err(Error("test"));
|
||||
|
||||
EXPECT_THAT(err, IsError("test"));
|
||||
}
|
||||
|
||||
TEST(ErrorTest, ErrorOrValue) { EXPECT_TRUE(ErrorOr<int>(0).ok()); }
|
||||
|
||||
auto IndirectErrorOrTest() -> ErrorOr<int> { return Error("test"); }
|
||||
|
||||
TEST(ErrorTest, IndirectErrorOr) { EXPECT_FALSE(IndirectErrorOrTest().ok()); }
|
||||
|
||||
struct Val {
|
||||
int val;
|
||||
};
|
||||
|
||||
TEST(ErrorTest, ErrorOrArrowOp) {
|
||||
ErrorOr<Val> err({1});
|
||||
EXPECT_EQ(err->val, 1);
|
||||
}
|
||||
|
||||
TEST(ErrorTest, ErrorOrReference) {
|
||||
Val val = {1};
|
||||
ErrorOr<Val&> maybe_val(val);
|
||||
EXPECT_EQ(maybe_val->val, 1);
|
||||
}
|
||||
|
||||
auto IndirectErrorOrSuccessTest() -> ErrorOr<Success> { return Success(); }
|
||||
|
||||
TEST(ErrorTest, IndirectErrorOrSuccess) {
|
||||
EXPECT_TRUE(IndirectErrorOrSuccessTest().ok());
|
||||
}
|
||||
|
||||
TEST(ErrorTest, ReturnIfErrorNoError) {
|
||||
auto result = []() -> ErrorOr<Success> {
|
||||
CARBON_RETURN_IF_ERROR(ErrorOr<Success>(Success()));
|
||||
CARBON_RETURN_IF_ERROR(ErrorOr<Success>(Success()));
|
||||
return Success();
|
||||
}();
|
||||
EXPECT_TRUE(result.ok());
|
||||
}
|
||||
|
||||
TEST(ErrorTest, ReturnIfErrorHasError) {
|
||||
auto result = []() -> ErrorOr<Success> {
|
||||
CARBON_RETURN_IF_ERROR(ErrorOr<Success>(Success()));
|
||||
CARBON_RETURN_IF_ERROR(ErrorOr<Success>(Error("error")));
|
||||
return Success();
|
||||
}();
|
||||
EXPECT_THAT(result, IsError("error"));
|
||||
}
|
||||
|
||||
TEST(ErrorTest, AssignOrReturnNoError) {
|
||||
auto result = []() -> ErrorOr<int> {
|
||||
CARBON_ASSIGN_OR_RETURN(int a, ErrorOr<int>(1));
|
||||
CARBON_ASSIGN_OR_RETURN(const int b, ErrorOr<int>(2));
|
||||
int c = 0;
|
||||
CARBON_ASSIGN_OR_RETURN(c, ErrorOr<int>(3));
|
||||
return a + b + c;
|
||||
}();
|
||||
EXPECT_THAT(result, IsSuccess(Eq(6)));
|
||||
}
|
||||
|
||||
TEST(ErrorTest, AssignOrReturnHasDirectError) {
|
||||
auto result = []() -> ErrorOr<int> {
|
||||
CARBON_RETURN_IF_ERROR(ErrorOr<int>(Error("error")));
|
||||
return 0;
|
||||
}();
|
||||
EXPECT_THAT(result, IsError("error"));
|
||||
}
|
||||
|
||||
TEST(ErrorTest, AssignOrReturnHasErrorInExpected) {
|
||||
auto result = []() -> ErrorOr<int> {
|
||||
CARBON_ASSIGN_OR_RETURN(int a, ErrorOr<int>(Error("error")));
|
||||
return a;
|
||||
}();
|
||||
EXPECT_THAT(result, IsError("error"));
|
||||
}
|
||||
|
||||
TEST(ErrorTest, ErrorBuilderOperatorImplicitCast) {
|
||||
ErrorOr<int> result = ErrorBuilder() << "msg";
|
||||
EXPECT_THAT(result, IsError("msg"));
|
||||
@@ -119,5 +43,184 @@ TEST(ErrorTest, StreamError) {
|
||||
EXPECT_EQ(result_stream.TakeStr(), "TestFunc: msg");
|
||||
}
|
||||
|
||||
class CustomError : public ErrorBase<CustomError> {
|
||||
public:
|
||||
auto Print(llvm::raw_ostream& os) const -> void {
|
||||
os << "Custom test error!";
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ErrorT>
|
||||
class ErrorOrTest : public ::testing::Test {
|
||||
public:
|
||||
auto ErrorStr() -> std::string {
|
||||
if constexpr (std::same_as<ErrorT, Error>) {
|
||||
return "test error";
|
||||
} else if constexpr (std::same_as<ErrorT, CustomError>) {
|
||||
return CustomError().ToString();
|
||||
} else {
|
||||
static_assert(false, "Unsupported custom error type!");
|
||||
}
|
||||
}
|
||||
|
||||
auto MakeError() -> ErrorT {
|
||||
if constexpr (std::same_as<ErrorT, Error>) {
|
||||
return Error("test error");
|
||||
} else if constexpr (std::same_as<ErrorT, CustomError>) {
|
||||
return CustomError();
|
||||
} else {
|
||||
static_assert(false, "Unsupported custom error type!");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using ErrorOrTestParams = ::testing::Types<Error, CustomError>;
|
||||
TYPED_TEST_SUITE(ErrorOrTest, ErrorOrTestParams);
|
||||
|
||||
TYPED_TEST(ErrorOrTest, ErrorOr) {
|
||||
using TestErrorOr = ErrorOr<int, TypeParam>;
|
||||
TestErrorOr err(this->MakeError());
|
||||
|
||||
EXPECT_THAT(err, IsError(this->ErrorStr()));
|
||||
}
|
||||
|
||||
TYPED_TEST(ErrorOrTest, ErrorOrValue) {
|
||||
using TestErrorOr = ErrorOr<int, TypeParam>;
|
||||
EXPECT_TRUE(TestErrorOr(0).ok());
|
||||
}
|
||||
|
||||
template <typename ErrorT, typename Fixture>
|
||||
auto IndirectErrorOrTest(Fixture& fixture) -> ErrorOr<int, ErrorT> {
|
||||
return fixture.MakeError();
|
||||
}
|
||||
|
||||
TYPED_TEST(ErrorOrTest, IndirectErrorOr) {
|
||||
EXPECT_FALSE(IndirectErrorOrTest<TypeParam>(*this).ok());
|
||||
}
|
||||
|
||||
struct Val {
|
||||
int val;
|
||||
};
|
||||
|
||||
TYPED_TEST(ErrorOrTest, ErrorOrArrowOp) {
|
||||
using TestErrorOr = ErrorOr<Val, TypeParam>;
|
||||
TestErrorOr err({1});
|
||||
EXPECT_EQ(err->val, 1);
|
||||
}
|
||||
|
||||
TYPED_TEST(ErrorOrTest, ErrorOrReference) {
|
||||
using TestErrorOr = ErrorOr<Val&, TypeParam>;
|
||||
Val val = {1};
|
||||
TestErrorOr maybe_val(val);
|
||||
EXPECT_EQ(maybe_val->val, 1);
|
||||
}
|
||||
|
||||
template <typename ErrorT>
|
||||
auto IndirectErrorOrSuccessTest() -> ErrorOr<Success, ErrorT> {
|
||||
return Success();
|
||||
}
|
||||
|
||||
TYPED_TEST(ErrorOrTest, IndirectErrorOrSuccess) {
|
||||
EXPECT_TRUE(IndirectErrorOrSuccessTest<TypeParam>().ok());
|
||||
}
|
||||
|
||||
TYPED_TEST(ErrorOrTest, ReturnIfErrorNoError) {
|
||||
using TestErrorOr = ErrorOr<Success, TypeParam>;
|
||||
auto result = []() -> TestErrorOr {
|
||||
CARBON_RETURN_IF_ERROR(TestErrorOr(Success()));
|
||||
CARBON_RETURN_IF_ERROR(TestErrorOr(Success()));
|
||||
return Success();
|
||||
}();
|
||||
EXPECT_TRUE(result.ok());
|
||||
}
|
||||
|
||||
TYPED_TEST(ErrorOrTest, ReturnIfErrorHasError) {
|
||||
using TestErrorOr = ErrorOr<Success, TypeParam>;
|
||||
auto result = [this]() -> TestErrorOr {
|
||||
CARBON_RETURN_IF_ERROR(TestErrorOr(Success()));
|
||||
CARBON_RETURN_IF_ERROR(TestErrorOr(this->MakeError()));
|
||||
return Success();
|
||||
}();
|
||||
EXPECT_THAT(result, IsError(this->ErrorStr()));
|
||||
}
|
||||
|
||||
TYPED_TEST(ErrorOrTest, AssignOrReturnNoError) {
|
||||
using TestErrorOr = ErrorOr<int, TypeParam>;
|
||||
auto result = []() -> TestErrorOr {
|
||||
CARBON_ASSIGN_OR_RETURN(int a, TestErrorOr(1));
|
||||
CARBON_ASSIGN_OR_RETURN(const int b, TestErrorOr(2));
|
||||
int c = 0;
|
||||
CARBON_ASSIGN_OR_RETURN(c, TestErrorOr(3));
|
||||
return a + b + c;
|
||||
}();
|
||||
EXPECT_THAT(result, IsSuccess(Eq(6)));
|
||||
}
|
||||
|
||||
TYPED_TEST(ErrorOrTest, AssignOrReturnHasDirectError) {
|
||||
using TestErrorOr = ErrorOr<int, TypeParam>;
|
||||
auto result = [this]() -> TestErrorOr {
|
||||
CARBON_RETURN_IF_ERROR(TestErrorOr(this->MakeError()));
|
||||
return 0;
|
||||
}();
|
||||
EXPECT_THAT(result, IsError(this->ErrorStr()));
|
||||
}
|
||||
|
||||
TYPED_TEST(ErrorOrTest, AssignOrReturnHasErrorInExpected) {
|
||||
using TestErrorOr = ErrorOr<int, TypeParam>;
|
||||
auto result = [this]() -> TestErrorOr {
|
||||
CARBON_ASSIGN_OR_RETURN(int a, TestErrorOr(this->MakeError()));
|
||||
return a;
|
||||
}();
|
||||
EXPECT_THAT(result, IsError(this->ErrorStr()));
|
||||
}
|
||||
|
||||
class AnotherCustomError : public ErrorBase<AnotherCustomError> {
|
||||
public:
|
||||
auto Print(llvm::raw_ostream& os) const -> void {
|
||||
os << "Another custom test error!";
|
||||
}
|
||||
|
||||
explicit operator CustomError() { return CustomError(); }
|
||||
};
|
||||
|
||||
TYPED_TEST(ErrorOrTest, AssignOrReturnNoErrorAcrossErrorTypes) {
|
||||
using TestErrorOr = ErrorOr<int, TypeParam>;
|
||||
auto result = []() -> ErrorOr<int> {
|
||||
CARBON_ASSIGN_OR_RETURN(int a, TestErrorOr(1));
|
||||
CARBON_ASSIGN_OR_RETURN(const int b, []() -> TestErrorOr {
|
||||
CARBON_ASSIGN_OR_RETURN(int inner, (ErrorOr<int, AnotherCustomError>(2)));
|
||||
return inner;
|
||||
}());
|
||||
int c = 0;
|
||||
CARBON_ASSIGN_OR_RETURN(c, TestErrorOr(3));
|
||||
return a + b + c;
|
||||
}();
|
||||
EXPECT_THAT(result, IsSuccess(Eq(6)));
|
||||
}
|
||||
|
||||
TYPED_TEST(ErrorOrTest, AssignOrReturnErrorAcrossErrorTypes) {
|
||||
using TestErrorOr = ErrorOr<int, TypeParam>;
|
||||
auto result = []() -> ErrorOr<int> {
|
||||
CARBON_ASSIGN_OR_RETURN(int a, TestErrorOr(1));
|
||||
CARBON_ASSIGN_OR_RETURN(const int b, []() -> TestErrorOr {
|
||||
CARBON_ASSIGN_OR_RETURN(
|
||||
int inner, (ErrorOr<int, AnotherCustomError>(AnotherCustomError())));
|
||||
return inner;
|
||||
}());
|
||||
int c = 0;
|
||||
CARBON_ASSIGN_OR_RETURN(c, TestErrorOr(3));
|
||||
return a + b + c;
|
||||
}();
|
||||
|
||||
// When directly using the `Error` type, the explicit custom type above has
|
||||
// its message preserved. When testing against `CustomError`, that one
|
||||
// overrides the message.
|
||||
if constexpr (std::same_as<TypeParam, Error>) {
|
||||
EXPECT_THAT(result, IsError("Another custom test error!"));
|
||||
} else {
|
||||
EXPECT_THAT(result, IsError("Custom test error!"));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace Carbon
|
||||
|
||||
Reference in New Issue
Block a user