[rust-interop] Add a temporary way to add lifetime annotations to functions.

This exploits the clang::annotate attribute to attach a function lifetime
annotation to the function declaration. Eventually, this should be replaced by
a more convenient syntax, but in the meantime this allows to start working with
lifetime annotations.

A future CL will add similar temporary support for lifetime parameters on classes/structs.

PiperOrigin-RevId: 424328341
diff --git a/lifetime_annotations/function_lifetimes.cc b/lifetime_annotations/function_lifetimes.cc
index 42bdd38..7b373ed 100644
--- a/lifetime_annotations/function_lifetimes.cc
+++ b/lifetime_annotations/function_lifetimes.cc
@@ -6,9 +6,12 @@
 
 #include <string>
 
+#include "lifetime_annotations/lifetime.h"
 #include "lifetime_annotations/type_lifetimes.h"
 #include "third_party/absl/strings/str_cat.h"
 #include "third_party/absl/strings/str_join.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/AST/DeclCXX.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/Basic/LLVM.h"
 
 namespace devtools_rust {
 namespace {
@@ -114,6 +117,32 @@
   return result;
 }
 
+bool FunctionLifetimes::Validate(const clang::FunctionDecl* func) const {
+  if (auto method = clang::dyn_cast<clang::CXXMethodDecl>(func)) {
+    if (CreateLifetimesForType(method->getThisType(), Lifetime::CreateLocal)
+            .size() != this_lifetimes.size()) {
+      return false;
+    }
+  } else if (!this_lifetimes.empty()) {
+    return false;
+  }
+  if (CreateLifetimesForType(func->getReturnType(), Lifetime::CreateLocal)
+          .size() != return_lifetimes.size()) {
+    return false;
+  }
+  if (param_lifetimes.size() != func->getNumParams()) {
+    return false;
+  }
+  for (size_t i = 0; i < param_lifetimes.size(); i++) {
+    if (CreateLifetimesForType(func->getParamDecl(i)->getType(),
+                               Lifetime::CreateLocal)
+            .size() != param_lifetimes[i].size()) {
+      return false;
+    }
+  }
+  return true;
+}
+
 std::ostream& operator<<(std::ostream& os,
                          const FunctionLifetimes& func_lifetimes) {
   return os << func_lifetimes.DebugString();
diff --git a/lifetime_annotations/function_lifetimes.h b/lifetime_annotations/function_lifetimes.h
index 30d5a85..d1971ef 100644
--- a/lifetime_annotations/function_lifetimes.h
+++ b/lifetime_annotations/function_lifetimes.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "lifetime_annotations/type_lifetimes.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/AST/Decl.h"
 #include "third_party/llvm/llvm-project/llvm/include/llvm/ADT/SmallVector.h"
 
 namespace devtools_rust {
@@ -36,6 +37,10 @@
   // in the same positions.
   bool IsIsomorphic(const FunctionLifetimes& other) const;
 
+  // Returns true if this FunctionLifetimes object is valid for the given
+  // function.
+  bool Validate(const clang::FunctionDecl* func) const;
+
   // Returns a human-readable representation of `func_lifetimes`. Formats
   // lifetimes using `formatter`, or Lifetime::DebugString() if `formatter` is
   // null.
diff --git a/lifetime_annotations/lifetime_annotations.cc b/lifetime_annotations/lifetime_annotations.cc
index 760c8ca..a9a1bac 100644
--- a/lifetime_annotations/lifetime_annotations.cc
+++ b/lifetime_annotations/lifetime_annotations.cc
@@ -6,13 +6,23 @@
 
 #include <functional>
 #include <optional>
+#include <string>
 #include <utility>
 
+#include "lifetime_annotations/function_lifetimes.h"
+#include "lifetime_annotations/type_lifetimes.h"
 #include "third_party/absl/strings/str_cat.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/AST/APValue.h"
 #include "third_party/llvm/llvm-project/clang/include/clang/AST/ASTContext.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/AST/Attr.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/AST/Attrs.inc"
 #include "third_party/llvm/llvm-project/clang/include/clang/AST/DeclCXX.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/AST/PrettyPrinter.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/Basic/LangOptions.h"
 #include "third_party/llvm/llvm-project/clang/include/clang/Lex/Pragma.h"
 #include "third_party/llvm/llvm-project/clang/include/clang/Lex/Preprocessor.h"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/ADT/SmallVector.h"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/ADT/StringRef.h"
 
 namespace devtools_rust {
 
@@ -30,7 +40,7 @@
 
   // TODO(mboehme): Extract lifetime annotations from `attrs` if present.
 
-  // No lifetime annoations: Use elided lifetimes.
+  // No lifetime annotations: Use elided lifetimes.
   for (int i = 0; i < num_expected; ++i) {
     llvm::Expected<Lifetime> maybe_lifetime = elided_lifetime_factory();
     if (maybe_lifetime) {
@@ -98,16 +108,133 @@
       attrs, 1, symbol_table, elided_lifetime_factory, method->getASTContext());
 }
 
+// Parse a "(a, b): (a, b), (), a -> b"-style annotation into a
+// FunctionLifetimes.
+// TODO(veluca): this is a temporary solution.
+llvm::Expected<FunctionLifetimes> ParseLifetimeAnnotations(
+    const clang::FunctionDecl* func, LifetimeSymbolTable& symbol_table,
+    llvm::StringRef annotation, clang::SourceLocation source_loc) {
+  // The lexer requires a null character at the end of the string.
+  std::string annotation_str(annotation.data(), annotation.size());
+  clang::Lexer lexer(source_loc, clang::LangOptions(), annotation_str.data(),
+                     annotation_str.data(),
+                     annotation_str.data() + annotation_str.size());
+
+  const char* end = annotation_str.data() + annotation_str.size();
+
+  auto error = [func]() {
+    return llvm::createStringError(
+        llvm::inconvertibleErrorCode(),
+        absl::StrCat("Invalid lifetime annotation for function ",
+                     func->getNameAsString()));
+  };
+
+  auto tok = [&]() -> llvm::StringRef {
+    clang::Token token;
+    if (lexer.getBufferLocation() != end) {
+      lexer.LexFromRawLexer(token);
+      return llvm::StringRef(annotation.data() +
+                                 token.getLocation().getRawEncoding() -
+                                 source_loc.getRawEncoding(),
+                             token.getLength());
+    }
+    return "";
+  };
+
+  // Consume the "lifetimes =" initial part.
+  if (tok() != "lifetimes" || tok() != "=") {
+    return error();
+  }
+
+  llvm::SmallVector<TypeLifetimes> fn_lifetimes;
+  bool has_this_lifetimes = false;
+  bool has_return_lifetimes = false;
+
+  for (llvm::StringRef token; !(token = tok()).empty();) {
+    if (token == ",") {
+      continue;
+    }
+    if (has_return_lifetimes) {
+      return error();
+    }
+    if (token == ":") {
+      if (has_this_lifetimes || fn_lifetimes.size() != 1) {
+        return error();
+      }
+      has_this_lifetimes = true;
+      continue;
+    }
+    // Skip the -> and parse return lifetimes. No more lifetimes should be
+    // parsed afterwards.
+    if (token == "->") {
+      has_return_lifetimes = true;
+      token = tok();
+    }
+    fn_lifetimes.emplace_back();
+    if (token == "(") {
+      for (; (token = tok()) != ")" && !token.empty();) {
+        if (token == ",") continue;
+        fn_lifetimes.back().push_back(
+            symbol_table.LookupNameAndMaybeDeclare(token));
+      }
+    } else {
+      fn_lifetimes.back().push_back(
+          symbol_table.LookupNameAndMaybeDeclare(token));
+    }
+  }
+
+  FunctionLifetimes function_lifetimes;
+  size_t param_start = 0;
+  if (has_this_lifetimes) {
+    function_lifetimes.this_lifetimes = fn_lifetimes[0];
+    param_start = 1;
+  }
+  size_t param_end = fn_lifetimes.size();
+  if (has_return_lifetimes) {
+    function_lifetimes.return_lifetimes = fn_lifetimes.back();
+    param_end -= 1;
+  }
+  function_lifetimes.param_lifetimes.assign(fn_lifetimes.begin() + param_start,
+                                            fn_lifetimes.begin() + param_end);
+
+  if (function_lifetimes.Validate(func)) {
+    return function_lifetimes;
+  }
+  return error();
+}
+
 llvm::Expected<FunctionLifetimes> GetLifetimeAnnotationsInternal(
     const clang::FunctionDecl* func, LifetimeSymbolTable& symbol_table,
     bool elision_enabled) {
   FunctionLifetimes result;
 
+  const clang::AnnotateAttr* lifetime_annotation = nullptr;
+  for (const clang::Attr* attr : func->attrs()) {
+    if (auto annotate = clang::dyn_cast<clang::AnnotateAttr>(attr)) {
+      if (annotate->getAnnotation().startswith("lifetimes")) {
+        if (lifetime_annotation != nullptr) {
+          return llvm::createStringError(
+              llvm::inconvertibleErrorCode(),
+              absl::StrCat("Can't extract lifetimes as '",
+                           func->getNameAsString(),
+                           "' has multiple lifetime annotations"));
+        }
+        lifetime_annotation = annotate;
+      }
+    }
+  }
+  if (lifetime_annotation) {
+    return ParseLifetimeAnnotations(func, symbol_table,
+                                    lifetime_annotation->getAnnotation(),
+                                    lifetime_annotation->getLoc());
+  }
+
   if (!func->getTypeSourceInfo()) {
     // TODO(mboehme): At least try to do lifetime elision.
     return llvm::createStringError(
         llvm::inconvertibleErrorCode(),
-        absl::StrCat("Can't extract lifetimes as '", func->getNameAsString(),
+        absl::StrCat("Can't extract lifetimes because '",
+                     func->getNameAsString(),
                      "' appears to be a generated function"));
   }
 
@@ -205,11 +332,11 @@
 llvm::Expected<FunctionLifetimes> GetLifetimeAnnotations(
     const clang::FunctionDecl* func, const LifetimeAnnotationContext& context,
     LifetimeSymbolTable* symbol_table) {
-  // TODO(mboehme):
-  // - Add support for retrieving actual lifetime annotations (not just
-  //   lifetimes implied by elision).
-  // - If we have multiple declarations of a function, make sure they are all
-  //   annotated with the same lifetimes.
+  // TODO(mboehme): if we have multiple declarations of a function, make sure
+  // they are all annotated with the same lifetimes.
+  // TODO(veluca): the syntax we are using for lifetime annotations here is just
+  // a placeholder. Adapt this to the actual syntax once the clang-side support
+  // is there.
 
   clang::SourceManager& source_manager =
       func->getASTContext().getSourceManager();
diff --git a/lifetime_annotations/lifetime_annotations_test.cc b/lifetime_annotations/lifetime_annotations_test.cc
index 5c9a785..e590c6a 100644
--- a/lifetime_annotations/lifetime_annotations_test.cc
+++ b/lifetime_annotations/lifetime_annotations_test.cc
@@ -163,5 +163,102 @@
                        StartsWith("Cannot elide output lifetimes")));
 }
 
+TEST_F(LifetimeAnnotationsTest, LifetimeAnnotationsNoLifetimes) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"_(
+        [[clang::annotate("lifetimes = ()")]]
+        void f(int);
+  )_"),
+              IsOkAndHolds(LifetimesAre({{"f", "()"}})));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeAnnotationsSimple) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        [[clang::annotate("lifetimes = a -> a")]]
+        int* f(int*);
+  )"),
+              IsOkAndHolds(LifetimesAre({{"f", "a -> a"}})));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeAnnotationsMultiplePtr) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        [[clang::annotate("lifetimes = (a, b) -> a")]]
+        int* f(int**);
+  )"),
+              IsOkAndHolds(LifetimesAre({{"f", "(a, b) -> a"}})));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeAnnotationsMultipleArguments) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        [[clang::annotate("lifetimes = a, b -> a")]]
+        int* f(int*, int*);
+  )"),
+              IsOkAndHolds(LifetimesAre({{"f", "a, b -> a"}})));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeAnnotationsNoReturn) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        [[clang::annotate("lifetimes = a, b")]]
+        void f(int*, int*);
+  )"),
+              IsOkAndHolds(LifetimesAre({{"f", "a, b"}})));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeAnnotationsNoLifetimeParam) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        [[clang::annotate("lifetimes = a, (), a -> a")]]
+        int* f(int*, int, int*);
+  )"),
+              IsOkAndHolds(LifetimesAre({{"f", "a, (), a -> a"}})));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeAnnotationsMethod) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        struct S {
+          [[clang::annotate("lifetimes = a: -> a")]]
+          int* f();
+        };
+  )"),
+              IsOkAndHolds(LifetimesAre({{"S::f", "a: -> a"}})));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeAnnotationsMethodWithParam) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        struct S {
+          [[clang::annotate("lifetimes = a: b -> a")]]
+          int* f(int*);
+        };
+  )"),
+              IsOkAndHolds(LifetimesAre({{"S::f", "a: b -> a"}})));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeAnnotationsInvalid_MissingThis) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        struct S {
+          [[clang::annotate("lifetimes = -> a")]]
+          int* f();
+        };
+  )"),
+              StatusIs(absl::StatusCode::kUnknown,
+                       StartsWith("Invalid lifetime annotation")));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeAnnotationsInvalid_ThisOnFreeFunction) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        [[clang::annotate("lifetimes = a: a -> a")]]
+        int* f(int*);
+  )"),
+              StatusIs(absl::StatusCode::kUnknown,
+                       StartsWith("Invalid lifetime annotation")));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeAnnotationsInvalid_WrongNumber) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        [[clang::annotate("lifetimes = a -> a")]]
+        int* f(int**);
+  )"),
+              StatusIs(absl::StatusCode::kUnknown,
+                       StartsWith("Invalid lifetime annotation")));
+}
+
 }  // namespace
 }  // namespace devtools_rust