[clang-tools-extra] [clangd] Support callHierarchy/outgoingCalls (PR #91191)

via cfe-commits cfe-commits at lists.llvm.org
Mon May 6 04:47:42 PDT 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-tools-extra

@llvm/pr-subscribers-clangd

Author: Christian Kandeler (ckandeler)

<details>
<summary>Changes</summary>



---

Patch is 23.27 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/91191.diff


7 Files Affected:

- (modified) clang-tools-extra/clangd/ClangdLSPServer.cpp (+7) 
- (modified) clang-tools-extra/clangd/ClangdLSPServer.h (+3) 
- (modified) clang-tools-extra/clangd/ClangdServer.cpp (+13) 
- (modified) clang-tools-extra/clangd/ClangdServer.h (+4) 
- (modified) clang-tools-extra/clangd/XRefs.cpp (+71) 
- (modified) clang-tools-extra/clangd/XRefs.h (+3) 
- (modified) clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp (+192-34) 


``````````diff
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 7fd599d4e1a0b0..5820a644088e3e 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -1368,6 +1368,12 @@ void ClangdLSPServer::onCallHierarchyIncomingCalls(
   Server->incomingCalls(Params.item, std::move(Reply));
 }
 
+void ClangdLSPServer::onCallHierarchyOutgoingCalls(
+    const CallHierarchyOutgoingCallsParams &Params,
+    Callback<std::vector<CallHierarchyOutgoingCall>> Reply) {
+  Server->outgoingCalls(Params.item, std::move(Reply));
+}
+
 void ClangdLSPServer::onClangdInlayHints(const InlayHintsParams &Params,
                                          Callback<llvm::json::Value> Reply) {
   // Our extension has a different representation on the wire than the standard.
@@ -1688,6 +1694,7 @@ void ClangdLSPServer::bindMethods(LSPBinder &Bind,
   Bind.method("typeHierarchy/subtypes", this, &ClangdLSPServer::onSubTypes);
   Bind.method("textDocument/prepareCallHierarchy", this, &ClangdLSPServer::onPrepareCallHierarchy);
   Bind.method("callHierarchy/incomingCalls", this, &ClangdLSPServer::onCallHierarchyIncomingCalls);
+  Bind.method("callHierarchy/outgoingCalls", this, &ClangdLSPServer::onCallHierarchyOutgoingCalls);
   Bind.method("textDocument/selectionRange", this, &ClangdLSPServer::onSelectionRange);
   Bind.method("textDocument/documentLink", this, &ClangdLSPServer::onDocumentLink);
   Bind.method("textDocument/semanticTokens/full", this, &ClangdLSPServer::onSemanticTokens);
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h
index 8bcb29522509b7..4981027372cb57 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -153,6 +153,9 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
   void onCallHierarchyIncomingCalls(
       const CallHierarchyIncomingCallsParams &,
       Callback<std::vector<CallHierarchyIncomingCall>>);
+  void onCallHierarchyOutgoingCalls(
+      const CallHierarchyOutgoingCallsParams &,
+      Callback<std::vector<CallHierarchyOutgoingCall>>);
   void onClangdInlayHints(const InlayHintsParams &,
                           Callback<llvm::json::Value>);
   void onInlayHint(const InlayHintsParams &, Callback<std::vector<InlayHint>>);
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 1c4c2a79b5c051..19d01dfbd873e2 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -898,6 +898,19 @@ void ClangdServer::incomingCalls(
                      });
 }
 
+void ClangdServer::outgoingCalls(
+    const CallHierarchyItem &Item,
+    Callback<std::vector<CallHierarchyOutgoingCall>> CB) {
+  auto Action = [Item,
+                 CB = std::move(CB)](Expected<InputsAndAST> InpAST) mutable {
+    if (!InpAST)
+      return CB(InpAST.takeError());
+    CB(clangd::outgoingCalls(InpAST->AST, Item));
+  };
+  WorkScheduler->runWithAST("Outgoing Calls", Item.uri.file(),
+                            std::move(Action));
+}
+
 void ClangdServer::inlayHints(PathRef File, std::optional<Range> RestrictRange,
                               Callback<std::vector<InlayHint>> CB) {
   auto Action = [RestrictRange(std::move(RestrictRange)),
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 1661028be88b4e..4caef917c1ec16 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -288,6 +288,10 @@ class ClangdServer {
   void incomingCalls(const CallHierarchyItem &Item,
                      Callback<std::vector<CallHierarchyIncomingCall>>);
 
+  /// Resolve outgoing calls for a given call hierarchy item.
+  void outgoingCalls(const CallHierarchyItem &Item,
+                     Callback<std::vector<CallHierarchyOutgoingCall>>);
+
   /// Resolve inlay hints for a given document.
   void inlayHints(PathRef File, std::optional<Range> RestrictRange,
                   Callback<std::vector<InlayHint>>);
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index cd909266489a85..b9bf944a7bba98 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -42,6 +42,7 @@
 #include "clang/AST/StmtCXX.h"
 #include "clang/AST/StmtVisitor.h"
 #include "clang/AST/Type.h"
+#include "clang/Analysis/CallGraph.h"
 #include "clang/Basic/LLVM.h"
 #include "clang/Basic/LangOptions.h"
 #include "clang/Basic/SourceLocation.h"
@@ -2303,6 +2304,76 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
   return Results;
 }
 
+std::vector<CallHierarchyOutgoingCall>
+outgoingCalls(ParsedAST &AST, const CallHierarchyItem &Item) {
+  if (AST.tuPath() != Item.uri.file())
+    return {};
+
+  const auto &SM = AST.getSourceManager();
+  auto Loc = sourceLocationInMainFile(SM, Item.selectionRange.start);
+  if (!Loc) {
+    elog("outgoingCalls failed to convert position to source location: "
+         "{0}",
+         Loc.takeError());
+    return {};
+  }
+
+  // For user convenience, we allow cursors on declarations, in which case
+  // we will try to find the definition.
+  std::optional<std::pair<const NamedDecl *, const NamedDecl *>> DeclAndDef;
+  for (const NamedDecl *Decl : getDeclAtPosition(AST, *Loc, {})) {
+    if (getSymbolID(Decl).str() != Item.data)
+      continue;
+    DeclAndDef = std::make_pair(Decl, getDefinition(Decl));
+    break;
+  }
+  if (!DeclAndDef || !DeclAndDef->second)
+    return {};
+
+  // Collect calls.
+  CallGraph callGraph;
+  callGraph.addToCallGraph(const_cast<NamedDecl *>(DeclAndDef->second));
+  llvm::DenseMap<NamedDecl *, std::vector<Range>> CallsOut;
+  for (const CallGraphNode::CallRecord &CallRecord :
+       callGraph.getRoot()->callees()) {
+    if (CallRecord.Callee->getDecl() != DeclAndDef->first)
+      continue;
+    for (const CallGraphNode::CallRecord &CallRecord :
+         CallRecord.Callee->callees()) {
+      SourceLocation BeginLoc = CallRecord.CallExpr->getBeginLoc();
+      if (auto *M = dyn_cast_if_present<ObjCMessageExpr>(CallRecord.CallExpr)) {
+        BeginLoc = M->getSelectorStartLoc();
+      } else if (auto *M = dyn_cast_if_present<CXXMemberCallExpr>(
+                     CallRecord.CallExpr)) {
+        if (auto ME = dyn_cast_if_present<MemberExpr>(M->getCallee()))
+          BeginLoc = ME->getMemberLoc();
+      }
+      BeginLoc = SM.getFileLoc(BeginLoc);
+      Position NameBegin = sourceLocToPosition(SM, BeginLoc);
+      Position NameEnd = sourceLocToPosition(
+          SM, Lexer::getLocForEndOfToken(BeginLoc, 0, SM,
+                                         AST.getASTContext().getLangOpts()));
+      CallsOut[static_cast<NamedDecl *>(CallRecord.Callee->getDecl())]
+          .push_back(Range{NameBegin, NameEnd});
+    }
+    break;
+  }
+
+  // Create and sort items.
+  std::vector<CallHierarchyOutgoingCall> Results;
+  for (auto It = CallsOut.begin(); It != CallsOut.end(); ++It) {
+    if (auto CHI = declToCallHierarchyItem(*It->first, Item.uri.file())) {
+      Results.push_back(CallHierarchyOutgoingCall{std::move(*CHI), It->second});
+    }
+  }
+  llvm::sort(Results, [](const CallHierarchyOutgoingCall &A,
+                         const CallHierarchyOutgoingCall &B) {
+    return A.to.name < B.to.name;
+  });
+
+  return Results;
+}
+
 llvm::DenseSet<const Decl *> getNonLocalDeclRefs(ParsedAST &AST,
                                                  const FunctionDecl *FD) {
   if (!FD->hasBody())
diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h
index df91dd15303c18..73c22ab7673ad0 100644
--- a/clang-tools-extra/clangd/XRefs.h
+++ b/clang-tools-extra/clangd/XRefs.h
@@ -150,6 +150,9 @@ prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath);
 std::vector<CallHierarchyIncomingCall>
 incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index);
 
+std::vector<CallHierarchyOutgoingCall>
+outgoingCalls(ParsedAST &AST, const CallHierarchyItem &Item);
+
 /// Returns all decls that are referenced in the \p FD except local symbols.
 llvm::DenseSet<const Decl *> getNonLocalDeclRefs(ParsedAST &AST,
                                                  const FunctionDecl *FD);
diff --git a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
index 6fa76aa6094bf2..0ec96599ccc43f 100644
--- a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
@@ -50,11 +50,21 @@ template <class ItemMatcher>
 ::testing::Matcher<CallHierarchyIncomingCall> from(ItemMatcher M) {
   return Field(&CallHierarchyIncomingCall::from, M);
 }
+template <class ItemMatcher>
+::testing::Matcher<CallHierarchyOutgoingCall> to(ItemMatcher M) {
+  return Field(&CallHierarchyOutgoingCall::to, M);
+}
 template <class... RangeMatchers>
-::testing::Matcher<CallHierarchyIncomingCall> fromRanges(RangeMatchers... M) {
+::testing::Matcher<CallHierarchyIncomingCall> fromRangesIn(RangeMatchers... M) {
   return Field(&CallHierarchyIncomingCall::fromRanges,
                UnorderedElementsAre(M...));
 }
+template <class... RangeMatchers>
+::testing::Matcher<CallHierarchyOutgoingCall>
+fromRangesOut(RangeMatchers... M) {
+  return Field(&CallHierarchyOutgoingCall::fromRanges,
+               UnorderedElementsAre(M...));
+}
 
 TEST(CallHierarchy, IncomingOneFileCpp) {
   Annotations Source(R"cpp(
@@ -81,24 +91,61 @@ TEST(CallHierarchy, IncomingOneFileCpp) {
   auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
   ASSERT_THAT(IncomingLevel1,
               ElementsAre(AllOf(from(withName("caller1")),
-                                fromRanges(Source.range("Callee")))));
+                                fromRangesIn(Source.range("Callee")))));
   auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
   ASSERT_THAT(IncomingLevel2,
               ElementsAre(AllOf(from(withName("caller2")),
-                                fromRanges(Source.range("Caller1A"),
-                                           Source.range("Caller1B"))),
+                                fromRangesIn(Source.range("Caller1A"),
+                                             Source.range("Caller1B"))),
                           AllOf(from(withName("caller3")),
-                                fromRanges(Source.range("Caller1C")))));
+                                fromRangesIn(Source.range("Caller1C")))));
 
   auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
   ASSERT_THAT(IncomingLevel3,
               ElementsAre(AllOf(from(withName("caller3")),
-                                fromRanges(Source.range("Caller2")))));
+                                fromRangesIn(Source.range("Caller2")))));
 
   auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
   EXPECT_THAT(IncomingLevel4, IsEmpty());
 }
 
+TEST(CallHierarchy, OutgoingOneFileCpp) {
+  Annotations Source(R"cpp(
+    void calle^r1();
+    void terminator1() {}
+    void terminator2() { struct S { S() { terminator1(); } }; }
+    void caller2() {
+      $Terminator1A[[terminator1]]();
+      $Terminator1B[[terminator1]]();
+      $Terminator2[[terminator2]]();
+    }
+    void caller1() {
+      $Caller2[[caller2]]();
+    }
+  )cpp");
+  TestTU TU = TestTU::withCode(Source.code());
+  auto AST = TU.build();
+
+  std::vector<CallHierarchyItem> Items =
+      prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
+  ASSERT_THAT(Items, ElementsAre(withName("caller1")));
+  auto OutgoingLevel1 = outgoingCalls(AST, Items[0]);
+  ASSERT_THAT(OutgoingLevel1,
+              ElementsAre(AllOf(to(withName("caller2")),
+                                fromRangesOut(Source.range("Caller2")))));
+  auto OutgoingLevel2 = outgoingCalls(AST, OutgoingLevel1[0].to);
+  ASSERT_THAT(OutgoingLevel2,
+              ElementsAre(AllOf(to(withName("terminator1")),
+                                fromRangesOut(Source.range("Terminator1A"),
+                                              Source.range("Terminator1B"))),
+                          AllOf(to(withName("terminator2")),
+                                fromRangesOut(Source.range("Terminator2")))));
+  auto OutgoingLevel3a = outgoingCalls(AST, OutgoingLevel2[0].to);
+  EXPECT_THAT(OutgoingLevel3a, IsEmpty());
+  auto OutgoingLevel3b = outgoingCalls(AST, OutgoingLevel2[1].to);
+  EXPECT_THAT(OutgoingLevel3b, IsEmpty());
+}
+
 TEST(CallHierarchy, IncomingOneFileObjC) {
   Annotations Source(R"objc(
     @implementation MyClass {}
@@ -126,24 +173,66 @@ TEST(CallHierarchy, IncomingOneFileObjC) {
   auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
   ASSERT_THAT(IncomingLevel1,
               ElementsAre(AllOf(from(withName("caller1")),
-                                fromRanges(Source.range("Callee")))));
+                                fromRangesIn(Source.range("Callee")))));
   auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
   ASSERT_THAT(IncomingLevel2,
               ElementsAre(AllOf(from(withName("caller2")),
-                                fromRanges(Source.range("Caller1A"),
-                                           Source.range("Caller1B"))),
+                                fromRangesIn(Source.range("Caller1A"),
+                                             Source.range("Caller1B"))),
                           AllOf(from(withName("caller3")),
-                                fromRanges(Source.range("Caller1C")))));
+                                fromRangesIn(Source.range("Caller1C")))));
 
   auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
   ASSERT_THAT(IncomingLevel3,
               ElementsAre(AllOf(from(withName("caller3")),
-                                fromRanges(Source.range("Caller2")))));
+                                fromRangesIn(Source.range("Caller2")))));
 
   auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
   EXPECT_THAT(IncomingLevel4, IsEmpty());
 }
 
+TEST(CallHierarchy, OutgoingOneFileObjC) {
+  Annotations Source(R"objc(
+    @implementation MyClass {}
+      +(void)callee {}
+      +(void) caller1 {
+        [MyClass $Callee[[callee]]];
+      }
+      +(void) caller2 {
+        [MyClass $Caller1A[[caller1]]];
+        [MyClass $Caller1B[[caller1]]];
+      }
+      +(void) calle^r3 {
+        [MyClass $Caller1C[[caller1]]];
+        [MyClass $Caller2[[caller2]]];
+      }
+    @end
+  )objc");
+  TestTU TU = TestTU::withCode(Source.code());
+  TU.Filename = "TestTU.m";
+  auto AST = TU.build();
+  std::vector<CallHierarchyItem> Items =
+      prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
+  ASSERT_THAT(Items, ElementsAre(withName("caller3")));
+  auto OutgoingLevel1 = outgoingCalls(AST, Items[0]);
+  ASSERT_THAT(OutgoingLevel1,
+              ElementsAre(AllOf(to(withName("caller1")),
+                                fromRangesOut(Source.range("Caller1C"))),
+                          AllOf(to(withName("caller2")),
+                                fromRangesOut(Source.range("Caller2")))));
+  auto OutgoingLevel2a = outgoingCalls(AST, OutgoingLevel1[0].to);
+  ASSERT_THAT(OutgoingLevel2a,
+              ElementsAre(AllOf(to(withName("callee")),
+                                fromRangesOut(Source.range("Callee")))));
+  auto OutgoingLevel2b = outgoingCalls(AST, OutgoingLevel1[1].to);
+  ASSERT_THAT(OutgoingLevel2b,
+              ElementsAre(AllOf(to(withName("caller1")),
+                                fromRangesOut(Source.range("Caller1A"),
+                                              Source.range("Caller1B")))));
+  auto OutgoingLevel3 = outgoingCalls(AST, OutgoingLevel2a[0].to);
+  EXPECT_THAT(OutgoingLevel3, IsEmpty());
+}
+
 TEST(CallHierarchy, MainFileOnlyRef) {
   // In addition to testing that we store refs to main-file only symbols,
   // this tests that anonymous namespaces do not interfere with the
@@ -169,12 +258,12 @@ TEST(CallHierarchy, MainFileOnlyRef) {
   auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
   ASSERT_THAT(IncomingLevel1,
               ElementsAre(AllOf(from(withName("caller1")),
-                                fromRanges(Source.range("Callee")))));
+                                fromRangesIn(Source.range("Callee")))));
 
   auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
   EXPECT_THAT(IncomingLevel2,
               ElementsAre(AllOf(from(withName("caller2")),
-                                fromRanges(Source.range("Caller1")))));
+                                fromRangesIn(Source.range("Caller1")))));
 }
 
 TEST(CallHierarchy, IncomingQualified) {
@@ -202,9 +291,39 @@ TEST(CallHierarchy, IncomingQualified) {
   auto Incoming = incomingCalls(Items[0], Index.get());
   EXPECT_THAT(Incoming,
               ElementsAre(AllOf(from(withName("caller1")),
-                                fromRanges(Source.range("Caller1"))),
+                                fromRangesIn(Source.range("Caller1"))),
                           AllOf(from(withName("caller2")),
-                                fromRanges(Source.range("Caller2")))));
+                                fromRangesIn(Source.range("Caller2")))));
+}
+
+TEST(CallHierarchy, OutgoingQualified) {
+  Annotations Source(R"cpp(
+    namespace ns {
+    class Waldo {
+    public:
+      void find() { $Find[[locate]](); }
+    private:
+      void locate() {}
+    };
+    void c^aller(Waldo &W) {
+      W.$Caller[[find]]();
+    }
+    }
+  )cpp");
+  TestTU TU = TestTU::withCode(Source.code());
+  auto AST = TU.build();
+
+  std::vector<CallHierarchyItem> Items =
+      prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
+  ASSERT_THAT(Items, ElementsAre(withName("caller")));
+  auto OutgoingLevel1 = outgoingCalls(AST, Items[0]);
+  EXPECT_THAT(OutgoingLevel1,
+              ElementsAre(AllOf(to(withName("find")),
+                                fromRangesOut(Source.range("Caller")))));
+  auto OutgoingLevel2 = outgoingCalls(AST, OutgoingLevel1[0].to);
+  EXPECT_THAT(OutgoingLevel2,
+              ElementsAre(AllOf(to(withName("locate")),
+                                fromRangesOut(Source.range("Find")))));
 }
 
 TEST(CallHierarchy, IncomingMultiFileCpp) {
@@ -268,20 +387,20 @@ TEST(CallHierarchy, IncomingMultiFileCpp) {
     auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
     ASSERT_THAT(IncomingLevel1,
                 ElementsAre(AllOf(from(withName("caller1")),
-                                  fromRanges(Caller1C.range()))));
+                                  fromRangesIn(Caller1C.range()))));
 
     auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
-    ASSERT_THAT(
-        IncomingLevel2,
-        ElementsAre(AllOf(from(withName("caller2")),
-                          fromRanges(Caller2C.range("A"), Caller2C.range("B"))),
-                    AllOf(from(withName("caller3")),
-                          fromRanges(Caller3C.range("Caller1")))));
+    ASSERT_THAT(IncomingLevel2,
+                ElementsAre(AllOf(from(withName("caller2")),
+                                  fromRangesIn(Caller2C.range("A"),
+                                               Caller2C.range("B"))),
+                            AllOf(from(withName("caller3")),
+                                  fromRangesIn(Caller3C.range("Caller1")))));
 
     auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
     ASSERT_THAT(IncomingLevel3,
                 ElementsAre(AllOf(from(withName("caller3")),
-                                  fromRanges(Caller3C.range("Caller2")))));
+                                  fromRangesIn(Caller3C.range("Caller2")))));
 
     auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
     EXPECT_THAT(IncomingLevel4, IsEmpty());
@@ -303,6 +422,44 @@ TEST(CallHierarchy, IncomingMultiFileCpp) {
   CheckCallHierarchy(*AST, CalleeC.point(), testPath("callee.cc"));
 }
 
+TEST(CallHierarchy, OutgoingMultiFileCpp) {
+  Annotations CalleeH(R"cpp(
+    void calleeFwd() { $CalleeFwd[[callee]](0); }
+    void callee(int);
+  )cpp");
+  Annotations CalleeC(R"cpp(
+    #include "callee.hh"
+    void callee(int) {}
+  )cpp");
+  Annotations Caller(R"cpp(
+    #include "callee.hh"
+ ...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/91191


More information about the cfe-commits mailing list