diff --git a/common/BUILD b/common/BUILD index 1833f6f880b9..6d255447004d 100644 --- a/common/BUILD +++ b/common/BUILD @@ -22,6 +22,25 @@ cc_test( ], ) +cc_library( + name = "error", + hdrs = ["error.h"], + deps = [ + ":check", + ":ostream", + "@llvm-project//llvm:Support", + ], +) + +cc_test( + name = "error_test", + srcs = ["error_test.cpp"], + deps = [ + ":error", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "indirect_value", hdrs = ["indirect_value.h"], @@ -56,6 +75,7 @@ cc_library( hdrs = ["string_helpers.h"], deps = [ ":check", + ":error", "@llvm-project//llvm:Support", ], ) diff --git a/common/error.h b/common/error.h new file mode 100644 index 000000000000..6db614e78324 --- /dev/null +++ b/common/error.h @@ -0,0 +1,105 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef COMMON_ERROR_H_ +#define COMMON_ERROR_H_ + +#include + +#include "common/check.h" +#include "common/ostream.h" +#include "llvm/ADT/Twine.h" + +namespace Carbon { + +// Success values should be represented as the presence of a value in ErrorOr, +// using `ErrorOr` and `return Success();` if no value needs to be +// returned. +struct Success {}; + +// Tracks an error message. +class [[nodiscard]] Error { + public: + // Represents an error state. + explicit Error(llvm::Twine message) : message_(message.str()) { + CHECK(!message_.empty()) << "Errors must have a message."; + } + + Error(Error&& other) noexcept : message_(std::move(other.message_)) {} + + // Prints the error string. Note this marks as used. + void Print(llvm::raw_ostream& out) const { out << message(); } + + // Returns the error message. + auto message() const -> const std::string& { return message_; } + + private: + // The error message. + std::string message_; +}; + +// Holds a value of type `T`, or an Error explaining why the value is +// unavailable. +template +class [[nodiscard]] ErrorOr { + public: + // Constructs with an error; the error must not be Error::Success(). + // Implicit for easy construction on returns. + // NOLINTNEXTLINE(google-explicit-constructor) + ErrorOr(Error err) : val_(std::move(err)) {} + + // Constructs with a value. + // Implicit for easy construction on returns. + // NOLINTNEXTLINE(google-explicit-constructor) + ErrorOr(T val) : val_(std::move(val)) {} + + // Moves held state. + ErrorOr(ErrorOr&& other) noexcept : val_(std::move(other.val_)) {} + + // Returns true for success. + auto ok() const -> bool { return std::holds_alternative(val_); } + + // Returns the contained error. + // REQUIRES: `ok()` is false. + auto error() const -> const Error& { + CHECK(!ok()); + return std::get(val_); + } + + // Returns the contained value. + // REQUIRES: `ok()` is true. + auto operator*() -> T& { + CHECK(ok()); + return std::get(val_); + } + + // Returns the contained value. + // REQUIRES: `ok()` is true. + auto operator*() const -> const T& { + CHECK(ok()); + return std::get(val_); + } + + // Returns the contained value. + // REQUIRES: `ok()` is true. + auto operator->() -> T* { + CHECK(ok()); + return &std::get(val_); + } + + // Returns the contained value. + // REQUIRES: `ok()` is true. + auto operator->() const -> const T* { + CHECK(ok()); + return &std::get(val_); + } + + private: + // Either an error message or + std::variant val_; +}; + +} // namespace Carbon + +#endif // COMMON_ERROR_H_ diff --git a/common/error_test.cpp b/common/error_test.cpp new file mode 100644 index 000000000000..166ae1cc4f80 --- /dev/null +++ b/common/error_test.cpp @@ -0,0 +1,47 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "common/error.h" + +#include + +namespace Carbon::Testing { +namespace { + +TEST(ErrorTest, Error) { + Error err("test"); + EXPECT_EQ(err.message(), "test"); +} + +TEST(ErrorTest, ErrorEmptyString) { + ASSERT_DEATH({ Error err(""); }, "CHECK failure at"); +} + +auto IndirectError() -> Error { return Error("test"); } + +TEST(ErrorTest, IndirectError) { EXPECT_EQ(IndirectError().message(), "test"); } + +TEST(ErrorTest, ErrorOr) { + ErrorOr err(Error("test")); + EXPECT_FALSE(err.ok()); + EXPECT_EQ(err.error().message(), "test"); +} + +TEST(ErrorTest, ErrorOrValue) { EXPECT_TRUE(ErrorOr(0).ok()); } + +auto IndirectErrorOrTest() -> ErrorOr { return Error("test"); } + +TEST(ErrorTest, IndirectErrorOr) { EXPECT_FALSE(IndirectErrorOrTest().ok()); } + +struct Val { + int val; +}; + +TEST(ErrorTest, ErrorOrArrowOp) { + ErrorOr err({1}); + EXPECT_EQ(err->val, 1); +} + +} // namespace +} // namespace Carbon::Testing diff --git a/common/string_helpers.cpp b/common/string_helpers.cpp index f9b9147f4da3..f3ceb1eb5461 100644 --- a/common/string_helpers.cpp +++ b/common/string_helpers.cpp @@ -27,11 +27,6 @@ static auto FromHex(char c) -> std::optional { return std::nullopt; } -// Creates an error instance with the specified `message`. -static auto MakeError(llvm::Twine message) -> llvm::Expected { - return llvm::createStringError(llvm::inconvertibleErrorCode(), message); -} - auto UnescapeStringLiteral(llvm::StringRef source, bool is_block_string) -> std::optional { std::string ret; @@ -112,23 +107,22 @@ auto UnescapeStringLiteral(llvm::StringRef source, bool is_block_string) return ret; } -auto ParseBlockStringLiteral(llvm::StringRef source) - -> llvm::Expected { +auto ParseBlockStringLiteral(llvm::StringRef source) -> ErrorOr { llvm::SmallVector lines; source.split(lines, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/true); if (lines.size() < 2) { - return MakeError("Too few lines"); + return Error("Too few lines"); } llvm::StringRef first = lines[0]; if (!first.consume_front(TripleQuotes)) { - return MakeError("Should start with triple quotes: " + first); + return Error("Should start with triple quotes: " + first); } first = first.rtrim(HorizontalWhitespaceChars); // Remaining chars, if any, are a file type indicator. if (first.find_first_of("\"#") != llvm::StringRef::npos || first.find_first_of(HorizontalWhitespaceChars) != llvm::StringRef::npos) { - return MakeError("Invalid characters in file type indicator: " + first); + return Error("Invalid characters in file type indicator: " + first); } llvm::StringRef last = lines[lines.size() - 1]; @@ -136,7 +130,7 @@ auto ParseBlockStringLiteral(llvm::StringRef source) last = last.ltrim(HorizontalWhitespaceChars); const size_t indent = last_length - last.size(); if (last != TripleQuotes) { - return MakeError("Should end with triple quotes: " + last); + return Error("Should end with triple quotes: " + last); } std::string parsed; @@ -149,8 +143,8 @@ auto ParseBlockStringLiteral(llvm::StringRef source) line = ""; } else { if (first_non_ws < indent) { - return MakeError("Wrong indent for line: " + line + ", expected " + - llvm::Twine(indent)); + return Error("Wrong indent for line: " + line + ", expected " + + llvm::Twine(indent)); } line = line.drop_front(indent).rtrim(HorizontalWhitespaceChars); } @@ -159,7 +153,7 @@ auto ParseBlockStringLiteral(llvm::StringRef source) std::optional unescaped = UnescapeStringLiteral( (line + "\n").toStringRef(buffer), /*is_block_string=*/true); if (!unescaped.has_value()) { - return MakeError("Invalid escaping in " + line); + return Error("Invalid escaping in " + line); } // A \ string collapses into nothing. if (!unescaped->empty()) { diff --git a/common/string_helpers.h b/common/string_helpers.h index 1b0a79911da8..858ef611ecb9 100644 --- a/common/string_helpers.h +++ b/common/string_helpers.h @@ -8,8 +8,8 @@ #include #include +#include "common/error.h" #include "llvm/ADT/StringRef.h" -#include "llvm/Support/Error.h" namespace Carbon { @@ -23,8 +23,7 @@ auto UnescapeStringLiteral(llvm::StringRef source, bool is_block_string = false) -> std::optional; // Parses a block string literal in `source`. -auto ParseBlockStringLiteral(llvm::StringRef source) - -> llvm::Expected; +auto ParseBlockStringLiteral(llvm::StringRef source) -> ErrorOr; // Returns true if the pointer is in the string ref (including equality with // `ref.end()`). This should be used instead of `<=` comparisons for diff --git a/common/string_helpers_test.cpp b/common/string_helpers_test.cpp index 321830633df9..73a1fc36d78c 100644 --- a/common/string_helpers_test.cpp +++ b/common/string_helpers_test.cpp @@ -56,23 +56,22 @@ TEST(UnescapeStringLiteral, Nul) { } TEST(ParseBlockStringLiteral, FailTooFewLines) { - EXPECT_THAT(toString(ParseBlockStringLiteral("").takeError()), + EXPECT_THAT(ParseBlockStringLiteral("").error().message(), Eq("Too few lines")); } TEST(ParseBlockStringLiteral, FailNoLeadingTripleQuotes) { - EXPECT_THAT(toString(ParseBlockStringLiteral("'a'\n").takeError()), + EXPECT_THAT(ParseBlockStringLiteral("'a'\n").error().message(), Eq("Should start with triple quotes: 'a'")); } TEST(ParseBlockStringLiteral, FailInvalideFiletypeIndicator) { - EXPECT_THAT( - toString(ParseBlockStringLiteral("\"\"\"carbon file\n").takeError()), - Eq("Invalid characters in file type indicator: carbon file")); + EXPECT_THAT(ParseBlockStringLiteral("\"\"\"carbon file\n").error().message(), + Eq("Invalid characters in file type indicator: carbon file")); } TEST(ParseBlockStringLiteral, FailEndingTripleQuotes) { - EXPECT_THAT(toString(ParseBlockStringLiteral("\"\"\"\n").takeError()), + EXPECT_THAT(ParseBlockStringLiteral("\"\"\"\n").error().message(), Eq("Should end with triple quotes: ")); } @@ -81,7 +80,7 @@ TEST(ParseBlockStringLiteral, FailWrongIndent) { A block string literal with wrong indent """)"; - EXPECT_THAT(toString(ParseBlockStringLiteral(Input).takeError()), + EXPECT_THAT(ParseBlockStringLiteral(Input).error().message(), Eq("Wrong indent for line: with wrong indent, expected 5")); } @@ -89,14 +88,14 @@ TEST(ParseBlockStringLiteral, FailInvalidEscaping) { constexpr char Input[] = R"(""" \q """)"; - EXPECT_THAT(toString(ParseBlockStringLiteral(Input).takeError()), + EXPECT_THAT(ParseBlockStringLiteral(Input).error().message(), Eq("Invalid escaping in \\q")); } TEST(ParseBlockStringLiteral, OkEmptyString) { constexpr char Input[] = R"(""" """)"; - EXPECT_THAT(ParseBlockStringLiteral(Input).get(), Eq("")); + EXPECT_THAT(*ParseBlockStringLiteral(Input), Eq("")); } TEST(ParseBlockStringLiteral, OkOneLineString) { @@ -105,7 +104,7 @@ TEST(ParseBlockStringLiteral, OkOneLineString) { """)"; constexpr char Expected[] = R"(A block string literal )"; - EXPECT_THAT(ParseBlockStringLiteral(Input).get(), Eq(Expected)); + EXPECT_THAT(*ParseBlockStringLiteral(Input), Eq(Expected)); } TEST(ParseBlockStringLiteral, OkTwoLineString) { @@ -116,7 +115,7 @@ TEST(ParseBlockStringLiteral, OkTwoLineString) { constexpr char Expected[] = R"(A block string literal with indent. )"; - EXPECT_THAT(ParseBlockStringLiteral(Input).get(), Eq(Expected)); + EXPECT_THAT(*ParseBlockStringLiteral(Input), Eq(Expected)); } TEST(ParseBlockStringLiteral, OkWithFileTypeIndicator) { @@ -127,7 +126,7 @@ TEST(ParseBlockStringLiteral, OkWithFileTypeIndicator) { constexpr char Expected[] = R"(A block string literal with file type indicator. )"; - EXPECT_THAT(ParseBlockStringLiteral(Input).get(), Eq(Expected)); + EXPECT_THAT(*ParseBlockStringLiteral(Input), Eq(Expected)); } TEST(ParseBlockStringLiteral, OkWhitespaceAfterOpeningQuotes) { @@ -136,7 +135,7 @@ TEST(ParseBlockStringLiteral, OkWhitespaceAfterOpeningQuotes) { """)"; constexpr char Expected[] = R"(A block string literal )"; - EXPECT_THAT(ParseBlockStringLiteral(Input).get(), Eq(Expected)); + EXPECT_THAT(*ParseBlockStringLiteral(Input), Eq(Expected)); } TEST(ParseBlockStringLiteral, OkWithEmptyLines) { @@ -157,7 +156,7 @@ TEST(ParseBlockStringLiteral, OkWithEmptyLines) { lines. )"; - EXPECT_THAT(ParseBlockStringLiteral(Input).get(), Eq(Expected)); + EXPECT_THAT(*ParseBlockStringLiteral(Input), Eq(Expected)); } TEST(ParseBlockStringLiteral, OkWithSlashNewlineEscape) { @@ -165,7 +164,7 @@ TEST(ParseBlockStringLiteral, OkWithSlashNewlineEscape) { A block string literal\ """)"; constexpr char Expected[] = "A block string literal"; - EXPECT_THAT(ParseBlockStringLiteral(Input).get(), Eq(Expected)); + EXPECT_THAT(*ParseBlockStringLiteral(Input), Eq(Expected)); } TEST(ParseBlockStringLiteral, OkWithDoubleSlashNewline) { @@ -174,7 +173,7 @@ TEST(ParseBlockStringLiteral, OkWithDoubleSlashNewline) { """)"; constexpr char Expected[] = R"(A block string literal\ )"; - EXPECT_THAT(ParseBlockStringLiteral(Input).get(), Eq(Expected)); + EXPECT_THAT(*ParseBlockStringLiteral(Input), Eq(Expected)); } TEST(ParseBlockStringLiteral, OkWithTripleSlashNewline) { @@ -182,7 +181,7 @@ TEST(ParseBlockStringLiteral, OkWithTripleSlashNewline) { A block string literal\\\ """)"; constexpr char Expected[] = R"(A block string literal\)"; - EXPECT_THAT(ParseBlockStringLiteral(Input).get(), Eq(Expected)); + EXPECT_THAT(*ParseBlockStringLiteral(Input), Eq(Expected)); } TEST(ParseBlockStringLiteral, OkMultipleSlashes) { @@ -193,7 +192,7 @@ TEST(ParseBlockStringLiteral, OkMultipleSlashes) { \ """)"; constexpr char Expected[] = "A block string literal"; - EXPECT_THAT(ParseBlockStringLiteral(Input).get(), Eq(Expected)); + EXPECT_THAT(*ParseBlockStringLiteral(Input), Eq(Expected)); } } // namespace diff --git a/executable_semantics/syntax/lexer.lpp b/executable_semantics/syntax/lexer.lpp index 3d653d6eadd6..0ade76991de8 100644 --- a/executable_semantics/syntax/lexer.lpp +++ b/executable_semantics/syntax/lexer.lpp @@ -314,10 +314,11 @@ string_literal \"([^\\\"\n\v\f\r]|\\.)*\" break; } } - llvm::Expected block_string = Carbon::ParseBlockStringLiteral(s); - if (!block_string) { + Carbon::ErrorOr block_string = + Carbon::ParseBlockStringLiteral(s); + if (!block_string.ok()) { FATAL_SYNTAX_ERROR(context) - << "Invalid block string: " << toString(block_string.takeError()); + << "Invalid block string: " << block_string.error(); } return ARG_TOKEN(string_literal, *block_string); }