Просмотр исходного кода

Create Error type (#1137)

Co-authored-by: Geoff Romer <gromer@google.com>
Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
Jon Meow 4 лет назад
Родитель
Сommit
c546c81d07

+ 20 - 0
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",
     ],
 )

+ 105 - 0
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 <string>
+
+#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<Success>` 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 <typename T>
+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<T>(val_); }
+
+  // Returns the contained error.
+  // REQUIRES: `ok()` is false.
+  auto error() const -> const Error& {
+    CHECK(!ok());
+    return std::get<Error>(val_);
+  }
+
+  // Returns the contained value.
+  // REQUIRES: `ok()` is true.
+  auto operator*() -> T& {
+    CHECK(ok());
+    return std::get<T>(val_);
+  }
+
+  // Returns the contained value.
+  // REQUIRES: `ok()` is true.
+  auto operator*() const -> const T& {
+    CHECK(ok());
+    return std::get<T>(val_);
+  }
+
+  // Returns the contained value.
+  // REQUIRES: `ok()` is true.
+  auto operator->() -> T* {
+    CHECK(ok());
+    return &std::get<T>(val_);
+  }
+
+  // Returns the contained value.
+  // REQUIRES: `ok()` is true.
+  auto operator->() const -> const T* {
+    CHECK(ok());
+    return &std::get<T>(val_);
+  }
+
+ private:
+  // Either an error message or
+  std::variant<Error, T> val_;
+};
+
+}  // namespace Carbon
+
+#endif  // COMMON_ERROR_H_

+ 47 - 0
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 <gtest/gtest.h>
+
+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<int> err(Error("test"));
+  EXPECT_FALSE(err.ok());
+  EXPECT_EQ(err.error().message(), "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);
+}
+
+}  // namespace
+}  // namespace Carbon::Testing

+ 8 - 14
common/string_helpers.cpp

@@ -27,11 +27,6 @@ static auto FromHex(char c) -> std::optional<char> {
   return std::nullopt;
 }
 
-// Creates an error instance with the specified `message`.
-static auto MakeError(llvm::Twine message) -> llvm::Expected<std::string> {
-  return llvm::createStringError(llvm::inconvertibleErrorCode(), message);
-}
-
 auto UnescapeStringLiteral(llvm::StringRef source, bool is_block_string)
     -> std::optional<std::string> {
   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<std::string> {
+auto ParseBlockStringLiteral(llvm::StringRef source) -> ErrorOr<std::string> {
   llvm::SmallVector<llvm::StringRef> 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<std::string> 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 \<newline> string collapses into nothing.
     if (!unescaped->empty()) {

+ 2 - 3
common/string_helpers.h

@@ -8,8 +8,8 @@
 #include <optional>
 #include <string>
 
+#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<std::string>;
 
 // Parses a block string literal in `source`.
-auto ParseBlockStringLiteral(llvm::StringRef source)
-    -> llvm::Expected<std::string>;
+auto ParseBlockStringLiteral(llvm::StringRef source) -> ErrorOr<std::string>;
 
 // Returns true if the pointer is in the string ref (including equality with
 // `ref.end()`). This should be used instead of `<=` comparisons for

+ 17 - 18
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

+ 4 - 3
executable_semantics/syntax/lexer.lpp

@@ -314,10 +314,11 @@ string_literal        \"([^\\\"\n\v\f\r]|\\.)*\"
       break;
     }
   }
-  llvm::Expected<std::string> block_string = Carbon::ParseBlockStringLiteral(s);
-  if (!block_string) {
+  Carbon::ErrorOr<std::string> 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);
 }