[clangd] Add ObjC method support to prepareCallHierarchy

This fixes "textDocument/prepareCallHierarchy" in clangd for ObjC methods. Details at https://github.com/clangd/vscode-clangd/issues/247.

clangd uses Decl::isFunctionOrFunctionTemplate to check if the decl given in a prepareCallHierarchy request is eligible for prepareCallHierarchy. We change to use isFunctionOrMethod which includes functions and ObjC methods.

Reviewed By: kadircet

Differential Revision: https://reviews.llvm.org/D114058

GitOrigin-RevId: e2cad4df22a6a411e7f7fcbc9bff0bd789545136
diff --git a/clangd/XRefs.cpp b/clangd/XRefs.cpp
index 452067a..b46d2c0 100644
--- a/clangd/XRefs.cpp
+++ b/clangd/XRefs.cpp
@@ -1906,7 +1906,9 @@
     return Result;
   }
   for (const NamedDecl *Decl : getDeclAtPosition(AST, *Loc, {})) {
-    if (!Decl->isFunctionOrFunctionTemplate())
+    if (!(isa<DeclContext>(Decl) &&
+          cast<DeclContext>(Decl)->isFunctionOrMethod()) &&
+        Decl->getKind() != Decl::Kind::FunctionTemplate)
       continue;
     if (auto CHI = declToCallHierarchyItem(*Decl))
       Result.emplace_back(std::move(*CHI));
diff --git a/clangd/unittests/CallHierarchyTests.cpp b/clangd/unittests/CallHierarchyTests.cpp
index 6f63ea2..a9d0385 100644
--- a/clangd/unittests/CallHierarchyTests.cpp
+++ b/clangd/unittests/CallHierarchyTests.cpp
@@ -65,7 +65,7 @@
                UnorderedElementsAre(M...));
 }
 
-TEST(CallHierarchy, IncomingOneFile) {
+TEST(CallHierarchy, IncomingOneFileCpp) {
   Annotations Source(R"cpp(
     void call^ee(int);
     void caller1() {
@@ -91,7 +91,51 @@
   ASSERT_THAT(IncomingLevel1,
               ElementsAre(AllOf(From(WithName("caller1")),
                                 FromRanges(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"))),
+                          AllOf(From(WithName("caller3")),
+                                FromRanges(Source.range("Caller1C")))));
 
+  auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
+  ASSERT_THAT(IncomingLevel3,
+              ElementsAre(AllOf(From(WithName("caller3")),
+                                FromRanges(Source.range("Caller2")))));
+
+  auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
+  EXPECT_THAT(IncomingLevel4, IsEmpty());
+}
+
+TEST(CallHierarchy, IncomingOneFileObjC) {
+  Annotations Source(R"objc(
+    @implementation MyClass {}
+      +(void)call^ee {}
+      +(void) caller1 {
+        [MyClass $Callee[[callee]]];
+      }
+      +(void) caller2 {
+        [MyClass $Caller1A[[caller1]]];
+        [MyClass $Caller1B[[caller1]]];
+      }
+      +(void) caller3 {
+        [MyClass $Caller1C[[caller1]]];
+        [MyClass $Caller2[[caller2]]];
+      }
+    @end
+  )objc");
+  TestTU TU = TestTU::withCode(Source.code());
+  TU.Filename = "TestTU.m";
+  auto AST = TU.build();
+  auto Index = TU.index();
+  std::vector<CallHierarchyItem> Items =
+      prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
+  ASSERT_THAT(Items, ElementsAre(WithName("callee")));
+  auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+  ASSERT_THAT(IncomingLevel1,
+              ElementsAre(AllOf(From(WithName("caller1")),
+                                FromRanges(Source.range("Callee")))));
   auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
   ASSERT_THAT(IncomingLevel2,
               ElementsAre(AllOf(From(WithName("caller2")),
@@ -172,7 +216,7 @@
                                 FromRanges(Source.range("Caller2")))));
 }
 
-TEST(CallHierarchy, IncomingMultiFile) {
+TEST(CallHierarchy, IncomingMultiFileCpp) {
   // The test uses a .hh suffix for header files to get clang
   // to parse them in C++ mode. .h files are parsed in C mode
   // by default, which causes problems because e.g. symbol
@@ -268,6 +312,115 @@
   CheckCallHierarchy(*AST, CalleeC.point(), testPath("callee.cc"));
 }
 
+TEST(CallHierarchy, IncomingMultiFileObjC) {
+  // The test uses a .mi suffix for header files to get clang
+  // to parse them in ObjC mode. .h files are parsed in C mode
+  // by default, which causes problems because e.g. symbol
+  // USRs are different in C mode (do not include function signatures).
+
+  Annotations CalleeH(R"objc(
+    @interface CalleeClass
+      +(void)call^ee;
+    @end
+  )objc");
+  Annotations CalleeC(R"objc(
+    #import "callee.mi"
+    @implementation CalleeClass {}
+      +(void)call^ee {}
+    @end
+  )objc");
+  Annotations Caller1H(R"objc(
+    @interface Caller1Class
+      +(void)caller1;
+    @end
+  )objc");
+  Annotations Caller1C(R"objc(
+    #import "callee.mi"
+    #import "caller1.mi"
+    @implementation Caller1Class {}
+      +(void)caller1 {
+        [CalleeClass [[calle^e]]];
+      }
+    @end
+  )objc");
+  Annotations Caller2H(R"objc(
+    @interface Caller2Class
+      +(void)caller2;
+    @end
+  )objc");
+  Annotations Caller2C(R"objc(
+    #import "caller1.mi"
+    #import "caller2.mi"
+    @implementation Caller2Class {}
+      +(void)caller2 {
+        [Caller1Class $A[[caller1]]];
+        [Caller1Class $B[[caller1]]];
+      }
+    @end
+  )objc");
+  Annotations Caller3C(R"objc(
+    #import "caller1.mi"
+    #import "caller2.mi"
+    @implementation Caller3Class {}
+      +(void)caller3 {
+        [Caller1Class $Caller1[[caller1]]];
+        [Caller2Class $Caller2[[caller2]]];
+      }
+    @end
+  )objc");
+
+  TestWorkspace Workspace;
+  Workspace.addSource("callee.mi", CalleeH.code());
+  Workspace.addSource("caller1.mi", Caller1H.code());
+  Workspace.addSource("caller2.mi", Caller2H.code());
+  Workspace.addMainFile("callee.m", CalleeC.code());
+  Workspace.addMainFile("caller1.m", Caller1C.code());
+  Workspace.addMainFile("caller2.m", Caller2C.code());
+  Workspace.addMainFile("caller3.m", Caller3C.code());
+  auto Index = Workspace.index();
+
+  auto CheckCallHierarchy = [&](ParsedAST &AST, Position Pos, PathRef TUPath) {
+    std::vector<CallHierarchyItem> Items =
+        prepareCallHierarchy(AST, Pos, TUPath);
+    ASSERT_THAT(Items, ElementsAre(WithName("callee")));
+    auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+    ASSERT_THAT(IncomingLevel1,
+                ElementsAre(AllOf(From(WithName("caller1")),
+                                  FromRanges(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")))));
+
+    auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
+    ASSERT_THAT(IncomingLevel3,
+                ElementsAre(AllOf(From(WithName("caller3")),
+                                  FromRanges(Caller3C.range("Caller2")))));
+
+    auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
+    EXPECT_THAT(IncomingLevel4, IsEmpty());
+  };
+
+  // Check that invoking from a call site works.
+  auto AST = Workspace.openFile("caller1.m");
+  ASSERT_TRUE(bool(AST));
+  CheckCallHierarchy(*AST, Caller1C.point(), testPath("caller1.m"));
+
+  // Check that invoking from the declaration site works.
+  AST = Workspace.openFile("callee.mi");
+  ASSERT_TRUE(bool(AST));
+  CheckCallHierarchy(*AST, CalleeH.point(), testPath("callee.mi"));
+
+  // Check that invoking from the definition site works.
+  AST = Workspace.openFile("callee.m");
+  ASSERT_TRUE(bool(AST));
+  CheckCallHierarchy(*AST, CalleeC.point(), testPath("callee.m"));
+}
+
 TEST(CallHierarchy, CallInLocalVarDecl) {
   // Tests that local variable declarations are not treated as callers
   // (they're not indexed, so they can't be represented as call hierarchy