Add structured error information for errors from `lifetime_analysis`.

PiperOrigin-RevId: 521986503
diff --git a/lifetime_annotations/BUILD b/lifetime_annotations/BUILD
index d8d0364..ca96518 100644
--- a/lifetime_annotations/BUILD
+++ b/lifetime_annotations/BUILD
@@ -14,6 +14,14 @@
     ],
 )
 
+cc_library(
+    name = "lifetime_error",
+    hdrs = ["lifetime_error.h"],
+    deps = [
+        "@llvm-project//llvm:Support",
+    ],
+)
+
 cc_test(
     name = "lifetime_test",
     srcs = ["lifetime_test.cc"],
@@ -35,6 +43,7 @@
     ],
     deps = [
         ":lifetime",
+        ":lifetime_error",
         ":lifetime_substitutions",
         ":lifetime_symbol_table",
         ":pointee_type",
@@ -51,6 +60,7 @@
     srcs = ["lifetime_annotations.cc"],
     hdrs = ["lifetime_annotations.h"],
     deps = [
+        ":lifetime_error",
         ":lifetime_symbol_table",
         ":pointee_type",
         ":type_lifetimes",
@@ -68,6 +78,7 @@
     srcs = ["lifetime_annotations_test.cc"],
     deps = [
         ":lifetime_annotations",
+        ":lifetime_error",
         ":type_lifetimes",
         "@com_google_googletest//:gtest_main",
         "@absl//absl/status",
diff --git a/lifetime_annotations/function_lifetimes.cc b/lifetime_annotations/function_lifetimes.cc
index dcf4861..0221e66 100644
--- a/lifetime_annotations/function_lifetimes.cc
+++ b/lifetime_annotations/function_lifetimes.cc
@@ -9,6 +9,7 @@
 #include "absl/strings/str_cat.h"
 #include "absl/strings/str_join.h"
 #include "lifetime_annotations/lifetime.h"
+#include "lifetime_annotations/lifetime_error.h"
 #include "lifetime_annotations/type_lifetimes.h"
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/Type.h"
@@ -120,8 +121,8 @@
   }
 
   if (this_type.isNull() && !lifetime_names.empty()) {
-    return llvm::createStringError(
-        llvm::inconvertibleErrorCode(),
+    return llvm::make_error<LifetimeError>(
+        LifetimeError::Type::Other,
         absl::StrCat("Encountered a `this` lifetime on a function with no "
                      "`this` parameter"));
   }
@@ -131,8 +132,8 @@
     const clang::Expr* lifetime_name = nullptr;
     if (!lifetime_names.empty()) {
       if (lifetime_names.size() != 1) {
-        return llvm::createStringError(
-            llvm::inconvertibleErrorCode(),
+        return llvm::make_error<LifetimeError>(
+            LifetimeError::Type::Other,
             absl::StrCat("Expected a single lifetime but ",
                          lifetime_names.size(), " were given"));
       }
diff --git a/lifetime_annotations/lifetime_annotations.cc b/lifetime_annotations/lifetime_annotations.cc
index c4db67f..e228161 100644
--- a/lifetime_annotations/lifetime_annotations.cc
+++ b/lifetime_annotations/lifetime_annotations.cc
@@ -12,6 +12,7 @@
 
 #include "absl/strings/str_cat.h"
 #include "lifetime_annotations/function_lifetimes.h"
+#include "lifetime_annotations/lifetime_error.h"
 #include "lifetime_annotations/lifetime_symbol_table.h"
 #include "lifetime_annotations/pointee_type.h"
 #include "lifetime_annotations/type_lifetimes.h"
@@ -74,8 +75,8 @@
        &next_lifetime](const clang::Expr*) -> llvm::Expected<Lifetime> {
         llvm::StringRef next = next_lifetime();
         if (next.empty()) {
-          return llvm::createStringError(
-              llvm::inconvertibleErrorCode(),
+          return llvm::make_error<LifetimeError>(
+              LifetimeError::Type::Other,
               "Invalid lifetime annotation: too few lifetimes");
         }
         return symbol_table.LookupNameAndMaybeDeclare(next);
@@ -84,8 +85,8 @@
   auto ret = FunctionLifetimes::CreateForDecl(func, factory);
 
   if (!next_lifetime().empty()) {
-    return llvm::createStringError(
-        llvm::inconvertibleErrorCode(),
+    return llvm::make_error<LifetimeError>(
+        LifetimeError::Type::Other,
         "Invalid lifetime annotation: too many lifetimes");
   }
   return ret;
@@ -103,7 +104,7 @@
     if (!detail.empty()) {
       absl::StrAppend(&msg, ": ", detail);
     }
-    return llvm::createStringError(llvm::inconvertibleErrorCode(), msg);
+    return llvm::make_error<LifetimeError>(LifetimeError::Type::Other, msg);
   };
 
   if (attr->args_size() != 1) {
@@ -129,8 +130,8 @@
     if (auto annotate = clang::dyn_cast<clang::AnnotateAttr>(attr)) {
       if (annotate->getAnnotation() == "lifetimes") {
         if (lifetime_annotation != nullptr) {
-          return llvm::createStringError(
-              llvm::inconvertibleErrorCode(),
+          return llvm::make_error<LifetimeError>(
+              LifetimeError::Type::Other,
               absl::StrCat("Can't extract lifetimes as '",
                            func->getNameAsString(),
                            "' has multiple lifetime annotations"));
@@ -178,8 +179,8 @@
         // safely even if elision is disabled.
         if (!elision_enabled && func->getDeclName().getNameKind() !=
                                     clang::DeclarationName::CXXDestructorName) {
-          return llvm::createStringError(
-              llvm::inconvertibleErrorCode(),
+          return llvm::make_error<LifetimeError>(
+              LifetimeError::Type::ElisionNotEnabled,
               absl::StrCat("Lifetime elision not enabled for '",
                            func->getNameAsString(), "'"));
         }
@@ -279,8 +280,8 @@
             }
 
             if (!elision_enabled) {
-              return llvm::createStringError(
-                  llvm::inconvertibleErrorCode(),
+              return llvm::make_error<LifetimeError>(
+                  LifetimeError::Type::ElisionNotEnabled,
                   absl::StrCat("Lifetime elision not enabled for '",
                                func->getNameAsString(), "'"));
             }
@@ -291,8 +292,8 @@
               return *input_lifetime;
             } else {
               // Otherwise, we don't know how to elide the output lifetime.
-              return llvm::createStringError(
-                  llvm::inconvertibleErrorCode(),
+              return llvm::make_error<LifetimeError>(
+                  LifetimeError::Type::CannotElideOutputLifetimes,
                   absl::StrCat("Cannot elide output lifetimes for '",
                                func->getNameAsString(),
                                "' because it is a non-member function that "
@@ -312,6 +313,8 @@
 }
 }  // namespace
 
+char LifetimeError::ID;
+
 llvm::Expected<FunctionLifetimes> GetLifetimeAnnotations(
     const clang::FunctionDecl* func, const LifetimeAnnotationContext& context,
     LifetimeSymbolTable* symbol_table) {
diff --git a/lifetime_annotations/lifetime_annotations.h b/lifetime_annotations/lifetime_annotations.h
index 65392d8..f260291 100644
--- a/lifetime_annotations/lifetime_annotations.h
+++ b/lifetime_annotations/lifetime_annotations.h
@@ -33,6 +33,8 @@
 // rules were not applicable.
 // The names of annotated function lifetimes as well as autogenerated names for
 // elided lifetimes are added to `symbol_table`.
+//
+// Returns structured error information as a `LifetimeError`.
 llvm::Expected<FunctionLifetimes> GetLifetimeAnnotations(
     const clang::FunctionDecl* func, const LifetimeAnnotationContext& context,
     LifetimeSymbolTable* symbol_table = nullptr);
@@ -40,6 +42,8 @@
 // Parses "a: b, a -> b"-style lifetime annotations from `lifetimes_str` for the
 // function declaration `func`. Lifetimes are inserted into the given
 // `symbol_table`, or used from there if already known.
+//
+// Returns structured error information as a `LifetimeError`.
 llvm::Expected<FunctionLifetimes> ParseLifetimeAnnotations(
     const clang::FunctionDecl* func, const std::string& lifetimes_str,
     LifetimeSymbolTable* symbol_table = nullptr);
diff --git a/lifetime_annotations/lifetime_annotations_test.cc b/lifetime_annotations/lifetime_annotations_test.cc
index 316eb30..97d04a1 100644
--- a/lifetime_annotations/lifetime_annotations_test.cc
+++ b/lifetime_annotations/lifetime_annotations_test.cc
@@ -11,6 +11,7 @@
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
 #include "common/status_test_matchers.h"
+#include "lifetime_annotations/lifetime_error.h"
 #include "lifetime_annotations/test/named_func_lifetimes.h"
 #include "lifetime_annotations/test/run_on_code.h"
 #include "lifetime_annotations/type_lifetimes.h"
@@ -70,6 +71,29 @@
   return result;
 }
 
+std::string FormatErrorString(llvm::Error err) {
+  std::string result;
+  err = llvm::handleErrors(
+      std::move(err), [&result](const LifetimeError& lifetime_err) {
+        switch (lifetime_err.type()) {
+          case LifetimeError::Type::ElisionNotEnabled:
+            result = "ERROR(ElisionNotEnabled): ";
+            break;
+          case LifetimeError::Type::CannotElideOutputLifetimes:
+            result = "ERROR(CannotElideOutputLifetimes): ";
+            break;
+          case LifetimeError::Type::Other:
+            result = "ERROR(Other): ";
+            break;
+        }
+        absl::StrAppend(&result, lifetime_err.message());
+      });
+  if (err) {
+    result = absl::StrCat("ERROR: ", llvm::toString(std::move(err)));
+  }
+  return result;
+}
+
 class LifetimeAnnotationsTest : public testing::Test {
  protected:
   absl::StatusOr<NamedFuncLifetimes> GetNamedLifetimeAnnotations(
@@ -115,8 +139,7 @@
               if (func_lifetimes) {
                 new_entry = NameLifetimes(*func_lifetimes, symbol_table);
               } else {
-                new_entry = absl::StrCat(
-                    "ERROR: ", llvm::toString(func_lifetimes.takeError()));
+                new_entry = FormatErrorString(func_lifetimes.takeError());
               }
 
               std::string func_name = QualifiedName(func);
@@ -172,8 +195,9 @@
   EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
         int** f(int*);
   )"),
-              IsOkAndHolds(LifetimesAre(
-                  {{"f", "ERROR: Lifetime elision not enabled for 'f'"}})));
+              IsOkAndHolds(LifetimesAre({{"f",
+                                          "ERROR(ElisionNotEnabled): Lifetime "
+                                          "elision not enabled for 'f'"}})));
 }
 
 TEST_F(LifetimeAnnotationsTest, Failure_NoOutputAnnotationNoLifetimeElision) {
@@ -182,8 +206,9 @@
   )"),
               // We specifically want to see this error message rather than
               // "Cannot elide output lifetimes".
-              IsOkAndHolds(LifetimesAre(
-                  {{"f", "ERROR: Lifetime elision not enabled for 'f'"}})));
+              IsOkAndHolds(LifetimesAre({{"f",
+                                          "ERROR(ElisionNotEnabled): Lifetime "
+                                          "elision not enabled for 'f'"}})));
 }
 
 TEST_F(LifetimeAnnotationsTest, Failure_NoAnnotationsElisionPragmaInWrongFile) {
@@ -194,8 +219,9 @@
                                           {std::make_pair("header.h", R"(
         int** f(int*);
   )")}),
-              IsOkAndHolds(LifetimesAre(
-                  {{"f", "ERROR: Lifetime elision not enabled for 'f'"}})));
+              IsOkAndHolds(LifetimesAre({{"f",
+                                          "ERROR(ElisionNotEnabled): Lifetime "
+                                          "elision not enabled for 'f'"}})));
 }
 
 TEST_F(LifetimeAnnotationsTest, LifetimeElision_OneInputLifetime) {
@@ -351,9 +377,9 @@
   )"),
               IsOkAndHolds(LifetimesAre(
                   {{"f",
-                    "ERROR: Cannot elide output lifetimes for 'f' because it "
-                    "is a non-member function that does not have exactly one "
-                    "input lifetime"}})));
+                    "ERROR(CannotElideOutputLifetimes): Cannot elide output "
+                    "lifetimes for 'f' because it is a non-member function "
+                    "that does not have exactly one input lifetime"}})));
 }
 
 TEST_F(LifetimeAnnotationsTest, LifetimeElision_FailureTooManyInputLifetimes) {
@@ -363,9 +389,9 @@
   )"),
               IsOkAndHolds(LifetimesAre(
                   {{"f",
-                    "ERROR: Cannot elide output lifetimes for 'f' because it "
-                    "is a non-member function that does not have exactly one "
-                    "input lifetime"}})));
+                    "ERROR(CannotElideOutputLifetimes): Cannot elide output "
+                    "lifetimes for 'f' because it is a non-member function "
+                    "that does not have exactly one input lifetime"}})));
 }
 
 TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_NoLifetimes) {
@@ -406,7 +432,8 @@
         void f(int* [[clang::annotate_type("lifetime", 1)]]);
   )")),
       IsOkAndHolds(LifetimesAre(
-          {{"f", "ERROR: cannot evaluate argument as a string literal"}})));
+          {{"f",
+            "ERROR(Other): cannot evaluate argument as a string literal"}})));
 }
 
 TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Simple) {
@@ -429,38 +456,42 @@
 
 TEST_F(LifetimeAnnotationsTest,
        LifetimeAnnotation_Invalid_LifetimeOnNonReferenceLikeType) {
-  EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
+  EXPECT_THAT(
+      GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
         void f(int $a);
   )")),
-              IsOkAndHolds(LifetimesAre(
-                  {{"f", "ERROR: Type may not be annotated with lifetimes"}})));
+      IsOkAndHolds(LifetimesAre(
+          {{"f", "ERROR(Other): Type may not be annotated with lifetimes"}})));
 }
 
 TEST_F(LifetimeAnnotationsTest,
        LifetimeAnnotation_Invalid_LifetimeOnFunctionPointer) {
-  EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
+  EXPECT_THAT(
+      GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
         void f(void (* $a)());
   )")),
-              IsOkAndHolds(LifetimesAre(
-                  {{"f", "ERROR: Type may not be annotated with lifetimes"}})));
+      IsOkAndHolds(LifetimesAre(
+          {{"f", "ERROR(Other): Type may not be annotated with lifetimes"}})));
 }
 
 TEST_F(LifetimeAnnotationsTest,
        LifetimeAnnotation_Invalid_LifetimeOnFunctionPointerReturnType) {
-  EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
+  EXPECT_THAT(
+      GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
         int (* $a f())(float, double);
   )")),
-              IsOkAndHolds(LifetimesAre(
-                  {{"f", "ERROR: Type may not be annotated with lifetimes"}})));
+      IsOkAndHolds(LifetimesAre(
+          {{"f", "ERROR(Other): Type may not be annotated with lifetimes"}})));
 }
 
 TEST_F(LifetimeAnnotationsTest,
        LifetimeAnnotation_Invalid_LifetimeOnFunctionReference) {
-  EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
+  EXPECT_THAT(
+      GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
         void f(void (& $a)());
   )")),
-              IsOkAndHolds(LifetimesAre(
-                  {{"f", "ERROR: Type may not be annotated with lifetimes"}})));
+      IsOkAndHolds(LifetimesAre(
+          {{"f", "ERROR(Other): Type may not be annotated with lifetimes"}})));
 }
 
 TEST_F(LifetimeAnnotationsTest,
@@ -470,7 +501,8 @@
         void f(int* $2(a, b));
   )")),
       IsOkAndHolds(LifetimesAre(
-          {{"f", "ERROR: Expected a single lifetime but 2 were given"}})));
+          {{"f",
+            "ERROR(Other): Expected a single lifetime but 2 were given"}})));
 }
 
 TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Static) {
@@ -548,29 +580,30 @@
 TEST_F(
     LifetimeAnnotationsTest,
     LifetimeAnnotation_LifetimeParameterizedType_Invalid_WrongNumberOfLifetimes) {
-  EXPECT_THAT(
-      GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
+  EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
     struct [[clang::annotate("lifetime_params", "a", "b")]] S_param {};
 
     void f(S_param $3(a, b, c) s);
   )")),
-      IsOkAndHolds(LifetimesAre({{"f",
-                                  "ERROR: Type has 2 lifetime parameters but 3 "
-                                  "lifetime arguments were given"}})));
+              IsOkAndHolds(LifetimesAre(
+                  {{"f",
+                    "ERROR(Other): Type has 2 lifetime parameters but 3 "
+                    "lifetime arguments were given"}})));
 }
 
 TEST_F(
     LifetimeAnnotationsTest,
     LifetimeAnnotation_LifetimeParameterizedType_Invalid_MultipleAnnotateAttributes) {
-  EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
+  EXPECT_THAT(
+      GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
     struct [[clang::annotate("lifetime_params", "a", "b")]] S_param {};
 
     void f(S_param $a $b s);
   )")),
-              IsOkAndHolds(LifetimesAre(
-                  {{"f",
-                    "ERROR: Only one `[[annotate_type(\"lifetime\", ...)]]` "
-                    "attribute may be placed on a type"}})));
+      IsOkAndHolds(LifetimesAre(
+          {{"f",
+            "ERROR(Other): Only one `[[annotate_type(\"lifetime\", ...)]]` "
+            "attribute may be placed on a type"}})));
 }
 
 TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Template) {
@@ -700,31 +733,32 @@
   )")),
       IsOkAndHolds(LifetimesAre(
           {{"S::f",
-            "ERROR: Invalid lifetime annotation: too few lifetimes"}})));
+            "ERROR(Other): Invalid lifetime annotation: too few lifetimes"}})));
   EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
         struct S {
           int* $a f();
         };
   )")),
-              IsOkAndHolds(LifetimesAre(
-                  {{"S::f", "ERROR: Lifetime elision not enabled for 'f'"}})));
+              IsOkAndHolds(LifetimesAre({{"S::f",
+                                          "ERROR(ElisionNotEnabled): Lifetime "
+                                          "elision not enabled for 'f'"}})));
 }
 
 TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Invalid_ThisOnFreeFunction) {
-  EXPECT_THAT(
-      GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
+  EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
         [[clang::annotate("lifetimes", "a: a -> a")]]
         int* f(int*);
   )")),
-      IsOkAndHolds(LifetimesAre(
-          {{"f", "ERROR: Invalid lifetime annotation: too many lifetimes"}})));
-  EXPECT_THAT(
-      GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
+              IsOkAndHolds(LifetimesAre({{"f",
+                                          "ERROR(Other): Invalid lifetime "
+                                          "annotation: too many lifetimes"}})));
+  EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
         int* $a f(int* $a) $a;
   )")),
-      IsOkAndHolds(LifetimesAre({{"f",
-                                  "ERROR: Encountered a `this` lifetime on a "
-                                  "function with no `this` parameter"}})));
+              IsOkAndHolds(LifetimesAre(
+                  {{"f",
+                    "ERROR(Other): Encountered a `this` lifetime on a "
+                    "function with no `this` parameter"}})));
 }
 
 TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Invalid_WrongNumber) {
@@ -734,7 +768,8 @@
         int* f(int**);
   )"),
       IsOkAndHolds(LifetimesAre(
-          {{"f", "ERROR: Invalid lifetime annotation: too few lifetimes"}})));
+          {{"f",
+            "ERROR(Other): Invalid lifetime annotation: too few lifetimes"}})));
 }
 
 TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Callback) {
diff --git a/lifetime_annotations/lifetime_error.h b/lifetime_annotations/lifetime_error.h
new file mode 100644
index 0000000..fde8dd0
--- /dev/null
+++ b/lifetime_annotations/lifetime_error.h
@@ -0,0 +1,46 @@
+// Part of the Crubit 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 THIRD_PARTY_CRUBIT_LIFETIME_ANNOTATIONS_LIFETIME_ERROR_H_
+#define THIRD_PARTY_CRUBIT_LIFETIME_ANNOTATIONS_LIFETIME_ERROR_H_
+
+#include "llvm/Support/Error.h"
+
+namespace clang {
+namespace tidy {
+namespace lifetimes {
+
+// Error information for errors that originate in the `lifetime_analysis`
+// package.
+class LifetimeError : public llvm::ErrorInfo<LifetimeError> {
+ public:
+  enum class Type {
+    ElisionNotEnabled,
+    CannotElideOutputLifetimes,
+    Other,
+  };
+
+  LifetimeError(Type type, std::string message)
+      : type_(type), message_(std::move(message)) {}
+
+  Type type() const { return type_; }
+
+  void log(llvm::raw_ostream& OS) const override { OS << message_; }
+
+  std::error_code convertToErrorCode() const override {
+    return llvm::inconvertibleErrorCode();
+  }
+
+  static char ID;
+
+ private:
+  Type type_;
+  std::string message_;
+};
+
+}  // namespace lifetimes
+}  // namespace tidy
+}  // namespace clang
+
+#endif  // THIRD_PARTY_CRUBIT_LIFETIME_ANNOTATIONS_LIFETIME_ERROR_H_
diff --git a/lifetime_annotations/type_lifetimes.cc b/lifetime_annotations/type_lifetimes.cc
index 7d58505..989a84d 100644
--- a/lifetime_annotations/type_lifetimes.cc
+++ b/lifetime_annotations/type_lifetimes.cc
@@ -15,6 +15,7 @@
 #include "absl/strings/str_join.h"
 #include "lifetime_annotations/function_lifetimes.h"
 #include "lifetime_annotations/lifetime.h"
+#include "lifetime_annotations/lifetime_error.h"
 #include "lifetime_annotations/lifetime_symbol_table.h"
 #include "lifetime_annotations/pointee_type.h"
 #include "clang/AST/Attr.h"
@@ -91,8 +92,8 @@
       continue;
 
     if (saw_annotate_type) {
-      return llvm::createStringError(
-          llvm::inconvertibleErrorCode(),
+      return llvm::make_error<LifetimeError>(
+          LifetimeError::Type::Other,
           "Only one `[[annotate_type(\"lifetime\", ...)]]` attribute may be "
           "placed on a type");
     }
@@ -328,8 +329,8 @@
   llvm::SmallVector<std::string> lifetime_params = GetLifetimeParameters(type);
   if (!lifetime_params.empty() && !lifetime_names.empty() &&
       lifetime_names.size() != lifetime_params.size()) {
-    return llvm::createStringError(
-        llvm::inconvertibleErrorCode(),
+    return llvm::make_error<LifetimeError>(
+        LifetimeError::Type::Other,
         absl::StrCat("Type has ", lifetime_params.size(),
                      " lifetime parameters but ", lifetime_names.size(),
                      " lifetime arguments were given"));
@@ -376,8 +377,8 @@
   if (pointee.isNull() || type->isFunctionPointerType() ||
       type->isFunctionReferenceType()) {
     if (lifetime_params.empty() && !lifetime_names.empty())
-      return llvm::createStringError(
-          llvm::inconvertibleErrorCode(),
+      return llvm::make_error<LifetimeError>(
+          LifetimeError::Type::Other,
           absl::StrCat("Type may not be annotated with lifetimes"));
   }
   if (pointee.isNull()) {
@@ -409,8 +410,8 @@
     const clang::Expr* lifetime_name = nullptr;
     if (!lifetime_names.empty()) {
       if (lifetime_names.size() != 1) {
-        return llvm::createStringError(
-            llvm::inconvertibleErrorCode(),
+        return llvm::make_error<LifetimeError>(
+            LifetimeError::Type::Other,
             absl::StrCat("Expected a single lifetime but ",
                          lifetime_names.size(), " were given"));
       }
@@ -929,8 +930,8 @@
 llvm::Expected<llvm::StringRef> EvaluateAsStringLiteral(
     const clang::Expr* expr, const clang::ASTContext& ast_context) {
   auto error = []() {
-    return llvm::createStringError(
-        llvm::inconvertibleErrorCode(),
+    return llvm::make_error<LifetimeError>(
+        LifetimeError::Type::Other,
         "cannot evaluate argument as a string literal");
   };