Move lifetime_annotations.h/.cc./_test.cc to lifetime_annotations/.

PiperOrigin-RevId: 412439810
diff --git a/lifetime_annotations/lifetime_annotations.cc b/lifetime_annotations/lifetime_annotations.cc
new file mode 100644
index 0000000..dc5281a
--- /dev/null
+++ b/lifetime_annotations/lifetime_annotations.cc
@@ -0,0 +1,128 @@
+// 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
+
+#include "lifetime_annotations/lifetime_annotations.h"
+
+#include <optional>
+#include <utility>
+
+#include "third_party/absl/strings/str_cat.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/AST/ASTContext.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/AST/DeclCXX.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"
+
+namespace devtools_rust {
+
+static std::optional<FunctionLifetimes> ElidedLifetimes(
+    const clang::FunctionDecl* func) {
+  FunctionLifetimes result;
+
+  // Every input lifetime is assigned a distinct lifetime.
+  result.param_lifetimes.resize(func->getNumParams());
+  llvm::SmallVector<Lifetime> all_input_lifetimes;
+  for (unsigned i = 0; i < func->getNumParams(); ++i) {
+    const clang::ParmVarDecl* param = func->getParamDecl(i);
+
+    result.param_lifetimes[i] =
+        CreateLifetimesForType(param->getType(), Lifetime::CreateVariable);
+    all_input_lifetimes.append(result.param_lifetimes[i]);
+  }
+
+  if (clang::isa<clang::CXXMethodDecl>(func)) {
+    Lifetime this_lifetime = Lifetime::CreateVariable();
+    result.this_lifetimes.push_back(this_lifetime);
+
+    // If we have an implicit `this` parameter, its lifetime is assigned to all
+    // output lifetimes.
+    result.return_lifetimes = CreateLifetimesForType(
+        func->getReturnType(), [this_lifetime]() { return this_lifetime; });
+    return result;
+  }
+
+  // If we have no output lifetimes, there's nothing left to do.
+  if (CreateLifetimesForType(func->getReturnType(), Lifetime::Static).empty()) {
+    return result;
+  }
+
+  // If we have a single input lifetime, its lifetime is assigned to all output
+  // lifetimes.
+  if (all_input_lifetimes.size() == 1) {
+    result.return_lifetimes = CreateLifetimesForType(
+        func->getReturnType(),
+        [&all_input_lifetimes]() { return all_input_lifetimes[0]; });
+    return result;
+  }
+
+  return std::nullopt;
+}
+
+llvm::Expected<FunctionLifetimes> GetLifetimeAnnotations(
+    const clang::FunctionDecl* func, const LifetimeAnnotationContext& context) {
+  // 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.
+
+  // For the time being, we only return elided lifetimes.
+  std::optional<FunctionLifetimes> elided_lifetimes = ElidedLifetimes(func);
+
+  if (!elided_lifetimes.has_value()) {
+    return llvm::createStringError(
+        llvm::inconvertibleErrorCode(),
+        absl::StrCat("Cannot determine output lifetimes for '",
+                     func->getNameAsString(),
+                     "' because it does not have exactly one input lifetime"));
+  }
+
+  // If the function has any elided lifetimes, we need to check if lifetime
+  // elision is enabled.
+  if (elided_lifetimes->ContainsLifetimes()) {
+    clang::SourceManager& source_manager =
+        func->getASTContext().getSourceManager();
+    clang::FileID file_id =
+        source_manager.getFileID(func->getSourceRange().getBegin());
+    if (!context.lifetime_elision_files.contains(file_id)) {
+      return llvm::createStringError(
+          llvm::inconvertibleErrorCode(),
+          absl::StrCat("Lifetime elision not enabled for '",
+                       func->getNameAsString(), "'"));
+    }
+  }
+
+  return *std::move(elided_lifetimes);
+}
+
+namespace {
+
+class LifetimeElisionPragmaHandler : public clang::PragmaHandler {
+ public:
+  explicit LifetimeElisionPragmaHandler(
+      std::shared_ptr<LifetimeAnnotationContext> context)
+      : clang::PragmaHandler("lifetime_elision"), context_(context) {}
+
+  void HandlePragma(clang::Preprocessor& preprocessor,
+                    clang::PragmaIntroducer introducer,
+                    clang::Token&) override {
+    clang::SourceManager& source_manager = preprocessor.getSourceManager();
+    clang::FileID file_id = source_manager.getFileID(introducer.Loc);
+    context_->lifetime_elision_files.insert(file_id);
+  }
+
+ private:
+  std::shared_ptr<LifetimeAnnotationContext> context_;
+};
+
+}  // namespace
+
+void AddLifetimeAnnotationHandlers(
+    clang::CompilerInstance& compiler,
+    std::shared_ptr<LifetimeAnnotationContext> context) {
+  // Preprocessor takes ownership of the handler.
+  compiler.getPreprocessor().AddPragmaHandler(
+      new LifetimeElisionPragmaHandler(context));
+}
+
+}  // namespace devtools_rust
diff --git a/lifetime_annotations/lifetime_annotations.h b/lifetime_annotations/lifetime_annotations.h
new file mode 100644
index 0000000..8e34228
--- /dev/null
+++ b/lifetime_annotations/lifetime_annotations.h
@@ -0,0 +1,44 @@
+// 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 CRUBIT_LIFETIME_ANNOTATIONS_LIFETIME_ANNOTATIONS_H_
+#define CRUBIT_LIFETIME_ANNOTATIONS_LIFETIME_ANNOTATIONS_H_
+
+#include <memory>
+
+#include "lifetime_annotations/function_lifetimes.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/AST/Decl.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/Frontend/CompilerInstance.h"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/Error.h"
+
+namespace devtools_rust {
+
+// Context that is required to obtain lifetime annotations for a function.
+struct LifetimeAnnotationContext {
+  // Files in which the `lifetime_elision` pragma was specified.
+  llvm::DenseSet<clang::FileID> lifetime_elision_files;
+};
+
+// Returns the lifetimes annotated on `func`.
+// If the file containing the function definition specifies the
+// `lifetime_elision` pragma, lifetime elision rules are used to determine
+// any unannotated lifetimes.
+// Returns an error if the function contains unannotated lifetimes that could
+// not be determined through lifetime elision, either because the
+// `lifetime_elision`pragma was not specified or because the lifetime elision
+// rules were not applicable.
+// TODO(mboehme): This function has only been partially implemented.
+llvm::Expected<FunctionLifetimes> GetLifetimeAnnotations(
+    const clang::FunctionDecl* func, const LifetimeAnnotationContext& context);
+
+// Adds handlers to `compiler` to populate `context`.
+// To be able to use GetLifetimeAnnotations(), call this function to add the
+// necessary handlers before compiling any code.
+void AddLifetimeAnnotationHandlers(
+    clang::CompilerInstance& compiler,
+    std::shared_ptr<LifetimeAnnotationContext> context);
+
+}  // namespace devtools_rust
+
+#endif  // CRUBIT_LIFETIME_ANNOTATIONS_LIFETIME_ANNOTATIONS_H_
diff --git a/lifetime_annotations/lifetime_annotations_test.cc b/lifetime_annotations/lifetime_annotations_test.cc
new file mode 100644
index 0000000..8b60e37
--- /dev/null
+++ b/lifetime_annotations/lifetime_annotations_test.cc
@@ -0,0 +1,155 @@
+// 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
+
+#include "lifetime_annotations/lifetime_annotations.h"
+
+#include <string>
+#include <utility>
+
+#include "devtools/cymbal/data_flow/testing_support.h"
+#include "lifetime_analysis/test/named_func_lifetimes.h"
+#include "lifetime_analysis/test/run_on_code.h"
+#include "testing/base/public/gunit.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/ASTMatchers/ASTMatchFinder.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/ASTMatchers/ASTMatchers.h"
+
+namespace devtools_rust {
+namespace {
+
+using testing::StartsWith;
+using testing::status::IsOkAndHolds;
+using testing::status::StatusIs;
+
+std::string QualifiedName(const clang::FunctionDecl* func) {
+  std::string str;
+  llvm::raw_string_ostream ostream(str);
+  func->printQualifiedName(ostream);
+  ostream.flush();
+  return str;
+}
+
+class LifetimeAnnotationsTest : public testing::Test {
+ protected:
+  absl::StatusOr<NamedFuncLifetimes> GetNamedLifetimeAnnotations(
+      absl::string_view code,
+      const clang::tooling::FileContentMappings& file_contents =
+          clang::tooling::FileContentMappings()) {
+    absl::StatusOr<NamedFuncLifetimes> result;
+    runOnCodeWithLifetimeHandlers(
+        code,
+        [&result](clang::ASTContext& ast_context,
+                  const LifetimeAnnotationContext& lifetime_context) {
+          using clang::ast_matchers::findAll;
+          using clang::ast_matchers::functionDecl;
+          using clang::ast_matchers::match;
+
+          NamedFuncLifetimes named_func_lifetimes;
+          for (const auto& node :
+               match(findAll(functionDecl().bind("func")), ast_context)) {
+            if (const auto* func =
+                    node.getNodeAs<clang::FunctionDecl>("func")) {
+              llvm::Expected<FunctionLifetimes> func_lifetimes =
+                  GetLifetimeAnnotations(func, lifetime_context);
+
+              if (!func_lifetimes) {
+                result = absl::UnknownError(
+                    llvm::toString(func_lifetimes.takeError()));
+                return;
+              }
+              named_func_lifetimes.Add(QualifiedName(func),
+                                       NameLifetimes(*func_lifetimes));
+            }
+          }
+
+          result = std::move(named_func_lifetimes);
+        },
+        {}, file_contents);
+
+    return result;
+  }
+};
+
+TEST_F(LifetimeAnnotationsTest, NoLifetimes) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        int f(int);
+  )"),
+              IsOkAndHolds(LifetimesAre({{"f", "()"}})));
+}
+
+TEST_F(LifetimeAnnotationsTest, Failure_NoAnnotationsNoLifetimeElision) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        int** f(int*);
+  )"),
+              StatusIs(absl::StatusCode::kUnknown,
+                       StartsWith("Lifetime elision not enabled")));
+}
+
+TEST_F(LifetimeAnnotationsTest, Failure_NoAnnotationsElisionPragmaInWrongFile) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        #pragma lifetime_elision
+        #include "header.h"
+  )",
+                                          {std::make_pair("header.h", R"(
+        int** f(int*);
+  )")}),
+              StatusIs(absl::StatusCode::kUnknown,
+                       StartsWith("Lifetime elision not enabled")));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeElision_OneInputLifetime) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        #pragma lifetime_elision
+        int** f(int*);
+  )"),
+              IsOkAndHolds(LifetimesAre({{"f", "a -> (a, a)"}})));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeElision_NoOutputLifetimes) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        #pragma lifetime_elision
+        void f(int**, int *);
+  )"),
+              IsOkAndHolds(LifetimesAre({{"f", "(a, b), c"}})));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeElision_Templates) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        #pragma lifetime_elision
+        template <class T> class vector {};
+        int* f(vector<int *>);
+        vector<int*> g(int *);
+  )"),
+              IsOkAndHolds(LifetimesAre({{"f", "a -> a"}, {"g", "a -> a"}})));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeElision_Method) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        #pragma lifetime_elision
+        struct S {
+          int** method(int *, int *);
+        };
+  )"),
+              IsOkAndHolds(LifetimesAre({{"S::method", "c: a, b -> (c, c)"}})));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeElision_FailureTooFewInputLifetimes) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        #pragma lifetime_elision
+        int* f();
+  )"),
+              StatusIs(absl::StatusCode::kUnknown,
+                       StartsWith("Cannot determine output lifetimes")));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeElision_FailureTooManyInputLifetimes) {
+  EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+        #pragma lifetime_elision
+        int* f(int**);
+  )"),
+              StatusIs(absl::StatusCode::kUnknown,
+                       StartsWith("Cannot determine output lifetimes")));
+}
+
+}  // namespace
+}  // namespace devtools_rust