forked from OSchip/llvm-project
[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
This commit is contained in:
parent
3a700cabdc
commit
e2cad4df22
|
@ -1906,7 +1906,9 @@ prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath) {
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
for (const NamedDecl *Decl : getDeclAtPosition(AST, *Loc, {})) {
|
for (const NamedDecl *Decl : getDeclAtPosition(AST, *Loc, {})) {
|
||||||
if (!Decl->isFunctionOrFunctionTemplate())
|
if (!(isa<DeclContext>(Decl) &&
|
||||||
|
cast<DeclContext>(Decl)->isFunctionOrMethod()) &&
|
||||||
|
Decl->getKind() != Decl::Kind::FunctionTemplate)
|
||||||
continue;
|
continue;
|
||||||
if (auto CHI = declToCallHierarchyItem(*Decl))
|
if (auto CHI = declToCallHierarchyItem(*Decl))
|
||||||
Result.emplace_back(std::move(*CHI));
|
Result.emplace_back(std::move(*CHI));
|
||||||
|
|
|
@ -65,7 +65,7 @@ template <class... RangeMatchers>
|
||||||
UnorderedElementsAre(M...));
|
UnorderedElementsAre(M...));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(CallHierarchy, IncomingOneFile) {
|
TEST(CallHierarchy, IncomingOneFileCpp) {
|
||||||
Annotations Source(R"cpp(
|
Annotations Source(R"cpp(
|
||||||
void call^ee(int);
|
void call^ee(int);
|
||||||
void caller1() {
|
void caller1() {
|
||||||
|
@ -91,7 +91,51 @@ TEST(CallHierarchy, IncomingOneFile) {
|
||||||
ASSERT_THAT(IncomingLevel1,
|
ASSERT_THAT(IncomingLevel1,
|
||||||
ElementsAre(AllOf(From(WithName("caller1")),
|
ElementsAre(AllOf(From(WithName("caller1")),
|
||||||
FromRanges(Source.range("Callee")))));
|
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());
|
auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
|
||||||
ASSERT_THAT(IncomingLevel2,
|
ASSERT_THAT(IncomingLevel2,
|
||||||
ElementsAre(AllOf(From(WithName("caller2")),
|
ElementsAre(AllOf(From(WithName("caller2")),
|
||||||
|
@ -172,7 +216,7 @@ TEST(CallHierarchy, IncomingQualified) {
|
||||||
FromRanges(Source.range("Caller2")))));
|
FromRanges(Source.range("Caller2")))));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(CallHierarchy, IncomingMultiFile) {
|
TEST(CallHierarchy, IncomingMultiFileCpp) {
|
||||||
// The test uses a .hh suffix for header files to get clang
|
// 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
|
// to parse them in C++ mode. .h files are parsed in C mode
|
||||||
// by default, which causes problems because e.g. symbol
|
// by default, which causes problems because e.g. symbol
|
||||||
|
@ -268,6 +312,115 @@ TEST(CallHierarchy, IncomingMultiFile) {
|
||||||
CheckCallHierarchy(*AST, CalleeC.point(), testPath("callee.cc"));
|
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) {
|
TEST(CallHierarchy, CallInLocalVarDecl) {
|
||||||
// Tests that local variable declarations are not treated as callers
|
// Tests that local variable declarations are not treated as callers
|
||||||
// (they're not indexed, so they can't be represented as call hierarchy
|
// (they're not indexed, so they can't be represented as call hierarchy
|
||||||
|
|
Loading…
Reference in New Issue