1 //===-- CallHierarchyTests.cpp ---------------------------*- C++ -*-------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
8 #include "Annotations.h"
12 #include "TestWorkspace.h"
14 #include "llvm/Support/Path.h"
15 #include "gmock/gmock.h"
16 #include "gtest/gtest.h"
21 llvm::raw_ostream
&operator<<(llvm::raw_ostream
&Stream
,
22 const CallHierarchyItem
&Item
) {
23 return Stream
<< Item
.name
<< "@" << Item
.selectionRange
;
26 llvm::raw_ostream
&operator<<(llvm::raw_ostream
&Stream
,
27 const CallHierarchyIncomingCall
&Call
) {
28 Stream
<< "{ from: " << Call
.from
<< ", ranges: [";
29 for (const auto &R
: Call
.fromRanges
) {
33 return Stream
<< "] }";
38 using ::testing::AllOf
;
39 using ::testing::ElementsAre
;
40 using ::testing::Field
;
41 using ::testing::IsEmpty
;
42 using ::testing::Matcher
;
43 using ::testing::UnorderedElementsAre
;
45 // Helpers for matching call hierarchy data structures.
46 MATCHER_P(withName
, N
, "") { return arg
.name
== N
; }
47 MATCHER_P(withDetail
, N
, "") { return arg
.detail
== N
; }
48 MATCHER_P(withSelectionRange
, R
, "") { return arg
.selectionRange
== R
; }
50 template <class ItemMatcher
>
51 ::testing::Matcher
<CallHierarchyIncomingCall
> from(ItemMatcher M
) {
52 return Field(&CallHierarchyIncomingCall::from
, M
);
54 template <class ItemMatcher
>
55 ::testing::Matcher
<CallHierarchyOutgoingCall
> to(ItemMatcher M
) {
56 return Field(&CallHierarchyOutgoingCall::to
, M
);
58 template <class... RangeMatchers
>
59 ::testing::Matcher
<CallHierarchyIncomingCall
> iFromRanges(RangeMatchers
... M
) {
60 return Field(&CallHierarchyIncomingCall::fromRanges
,
61 UnorderedElementsAre(M
...));
63 template <class... RangeMatchers
>
64 ::testing::Matcher
<CallHierarchyOutgoingCall
> oFromRanges(RangeMatchers
... M
) {
65 return Field(&CallHierarchyOutgoingCall::fromRanges
,
66 UnorderedElementsAre(M
...));
69 TEST(CallHierarchy
, IncomingOneFileCpp
) {
70 Annotations
Source(R
"cpp(
73 $Callee[[callee]](42);
76 $Caller1A[[caller1]]();
77 $Caller1B[[caller1]]();
80 $Caller1C[[caller1]]();
81 $Caller2[[caller2]]();
84 TestTU TU
= TestTU::withCode(Source
.code());
85 auto AST
= TU
.build();
86 auto Index
= TU
.index();
88 std::vector
<CallHierarchyItem
> Items
=
89 prepareCallHierarchy(AST
, Source
.point(), testPath(TU
.Filename
));
90 ASSERT_THAT(Items
, ElementsAre(withName("callee")));
91 auto IncomingLevel1
= incomingCalls(Items
[0], Index
.get());
94 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("caller1"))),
95 iFromRanges(Source
.range("Callee")))));
96 auto IncomingLevel2
= incomingCalls(IncomingLevel1
[0].from
, Index
.get());
99 ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail("caller2"))),
100 iFromRanges(Source
.range("Caller1A"),
101 Source
.range("Caller1B"))),
102 AllOf(from(AllOf(withName("caller3"), withDetail("caller3"))),
103 iFromRanges(Source
.range("Caller1C")))));
105 auto IncomingLevel3
= incomingCalls(IncomingLevel2
[0].from
, Index
.get());
108 ElementsAre(AllOf(from(AllOf(withName("caller3"), withDetail("caller3"))),
109 iFromRanges(Source
.range("Caller2")))));
111 auto IncomingLevel4
= incomingCalls(IncomingLevel3
[0].from
, Index
.get());
112 EXPECT_THAT(IncomingLevel4
, IsEmpty());
115 TEST(CallHierarchy
, IncomingOneFileObjC
) {
116 Annotations
Source(R
"objc(
117 @implementation MyClass {}
120 [MyClass $Callee[[callee]]];
123 [MyClass $Caller1A[[caller1]]];
124 [MyClass $Caller1B[[caller1]]];
127 [MyClass $Caller1C[[caller1]]];
128 [MyClass $Caller2[[caller2]]];
132 TestTU TU
= TestTU::withCode(Source
.code());
133 TU
.Filename
= "TestTU.m";
134 auto AST
= TU
.build();
135 auto Index
= TU
.index();
136 std::vector
<CallHierarchyItem
> Items
=
137 prepareCallHierarchy(AST
, Source
.point(), testPath(TU
.Filename
));
138 ASSERT_THAT(Items
, ElementsAre(withName("callee")));
139 auto IncomingLevel1
= incomingCalls(Items
[0], Index
.get());
140 ASSERT_THAT(IncomingLevel1
,
141 ElementsAre(AllOf(from(AllOf(withName("caller1"),
142 withDetail("MyClass::caller1"))),
143 iFromRanges(Source
.range("Callee")))));
144 auto IncomingLevel2
= incomingCalls(IncomingLevel1
[0].from
, Index
.get());
145 ASSERT_THAT(IncomingLevel2
,
146 ElementsAre(AllOf(from(AllOf(withName("caller2"),
147 withDetail("MyClass::caller2"))),
148 iFromRanges(Source
.range("Caller1A"),
149 Source
.range("Caller1B"))),
150 AllOf(from(AllOf(withName("caller3"),
151 withDetail("MyClass::caller3"))),
152 iFromRanges(Source
.range("Caller1C")))));
154 auto IncomingLevel3
= incomingCalls(IncomingLevel2
[0].from
, Index
.get());
155 ASSERT_THAT(IncomingLevel3
,
156 ElementsAre(AllOf(from(AllOf(withName("caller3"),
157 withDetail("MyClass::caller3"))),
158 iFromRanges(Source
.range("Caller2")))));
160 auto IncomingLevel4
= incomingCalls(IncomingLevel3
[0].from
, Index
.get());
161 EXPECT_THAT(IncomingLevel4
, IsEmpty());
164 TEST(CallHierarchy
, MainFileOnlyRef
) {
165 // In addition to testing that we store refs to main-file only symbols,
166 // this tests that anonymous namespaces do not interfere with the
167 // symbol re-identification process in callHierarchyItemToSymbo().
168 Annotations
Source(R
"cpp(
172 $Callee[[callee]](42);
176 $Caller1[[caller1]]();
179 TestTU TU
= TestTU::withCode(Source
.code());
180 auto AST
= TU
.build();
181 auto Index
= TU
.index();
183 std::vector
<CallHierarchyItem
> Items
=
184 prepareCallHierarchy(AST
, Source
.point(), testPath(TU
.Filename
));
185 ASSERT_THAT(Items
, ElementsAre(withName("callee")));
186 auto IncomingLevel1
= incomingCalls(Items
[0], Index
.get());
189 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("caller1"))),
190 iFromRanges(Source
.range("Callee")))));
192 auto IncomingLevel2
= incomingCalls(IncomingLevel1
[0].from
, Index
.get());
195 ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail("caller2"))),
196 iFromRanges(Source
.range("Caller1")))));
199 TEST(CallHierarchy
, IncomingQualified
) {
200 Annotations
Source(R
"cpp(
205 void Waldo::find() {}
206 void caller1(Waldo &W) {
207 W.$Caller1[[f^ind]]();
209 void caller2(Waldo &W) {
210 W.$Caller2[[find]]();
214 TestTU TU
= TestTU::withCode(Source
.code());
215 auto AST
= TU
.build();
216 auto Index
= TU
.index();
218 std::vector
<CallHierarchyItem
> Items
=
219 prepareCallHierarchy(AST
, Source
.point(), testPath(TU
.Filename
));
220 ASSERT_THAT(Items
, ElementsAre(withName("Waldo::find")));
221 auto Incoming
= incomingCalls(Items
[0], Index
.get());
225 AllOf(from(AllOf(withName("caller1"), withDetail("ns::caller1"))),
226 iFromRanges(Source
.range("Caller1"))),
227 AllOf(from(AllOf(withName("caller2"), withDetail("ns::caller2"))),
228 iFromRanges(Source
.range("Caller2")))));
231 TEST(CallHierarchy
, OutgoingOneFile
) {
232 // Test outgoing call on the main file, with namespaces and methods
233 Annotations
Source(R
"cpp(
239 void Foo::caller1() {
240 $Callee[[callee]](42);
244 void caller2(ns::Foo& F) {
245 F.$Caller1A[[caller1]]();
246 F.$Caller1B[[caller1]]();
249 void call^er3(ns::Foo& F) {
250 F.$Caller1C[[caller1]]();
251 $Caller2[[caller2]](F);
254 TestTU TU
= TestTU::withCode(Source
.code());
255 auto AST
= TU
.build();
256 auto Index
= TU
.index();
258 std::vector
<CallHierarchyItem
> Items
=
259 prepareCallHierarchy(AST
, Source
.point(), testPath(TU
.Filename
));
260 ASSERT_THAT(Items
, ElementsAre(withName("caller3")));
261 auto OugoingLevel1
= outgoingCalls(Items
[0], Index
.get());
265 AllOf(to(AllOf(withName("caller1"), withDetail("ns::Foo::caller1"))),
266 oFromRanges(Source
.range("Caller1C"))),
267 AllOf(to(AllOf(withName("caller2"), withDetail("caller2"))),
268 oFromRanges(Source
.range("Caller2")))));
270 auto OutgoingLevel2
= outgoingCalls(OugoingLevel1
[1].to
, Index
.get());
274 to(AllOf(withName("caller1"), withDetail("ns::Foo::caller1"))),
275 oFromRanges(Source
.range("Caller1A"), Source
.range("Caller1B")))));
277 auto OutgoingLevel3
= outgoingCalls(OutgoingLevel2
[0].to
, Index
.get());
280 ElementsAre(AllOf(to(AllOf(withName("callee"), withDetail("callee"))),
281 oFromRanges(Source
.range("Callee")))));
283 auto OutgoingLevel4
= outgoingCalls(OutgoingLevel3
[0].to
, Index
.get());
284 EXPECT_THAT(OutgoingLevel4
, IsEmpty());
287 TEST(CallHierarchy
, MultiFileCpp
) {
288 // The test uses a .hh suffix for header files to get clang
289 // to parse them in C++ mode. .h files are parsed in C mode
290 // by default, which causes problems because e.g. symbol
291 // USRs are different in C mode (do not include function signatures).
293 Annotations
CalleeH(R
"cpp(
296 Annotations
CalleeC(R
"cpp(
300 Annotations
Caller1H(R
"cpp(
305 Annotations
Caller1C(R
"cpp(
307 #include "caller1
.hh
"
314 Annotations
Caller2H(R
"cpp(
319 Annotations
Caller2C(R
"cpp(
320 #include "caller1
.hh
"
321 #include "caller2
.hh
"
324 nsa::$A[[caller1]]();
325 nsa::$B[[caller1]]();
329 Annotations
Caller3H(R
"cpp(
334 Annotations
Caller3C(R
"cpp(
335 #include "caller1
.hh
"
336 #include "caller2
.hh
"
339 $Caller1[[caller1]]();
340 nsb::$Caller2[[caller2]]();
345 TestWorkspace Workspace
;
346 Workspace
.addSource("callee.hh", CalleeH
.code());
347 Workspace
.addSource("caller1.hh", Caller1H
.code());
348 Workspace
.addSource("caller2.hh", Caller2H
.code());
349 Workspace
.addSource("caller3.hh", Caller3H
.code());
350 Workspace
.addMainFile("callee.cc", CalleeC
.code());
351 Workspace
.addMainFile("caller1.cc", Caller1C
.code());
352 Workspace
.addMainFile("caller2.cc", Caller2C
.code());
353 Workspace
.addMainFile("caller3.cc", Caller3C
.code());
355 auto Index
= Workspace
.index();
357 auto CheckIncomingCalls
= [&](ParsedAST
&AST
, Position Pos
, PathRef TUPath
) {
358 std::vector
<CallHierarchyItem
> Items
=
359 prepareCallHierarchy(AST
, Pos
, TUPath
);
360 ASSERT_THAT(Items
, ElementsAre(withName("callee")));
361 auto IncomingLevel1
= incomingCalls(Items
[0], Index
.get());
362 ASSERT_THAT(IncomingLevel1
,
363 ElementsAre(AllOf(from(AllOf(withName("caller1"),
364 withDetail("nsa::caller1"))),
365 iFromRanges(Caller1C
.range()))));
367 auto IncomingLevel2
= incomingCalls(IncomingLevel1
[0].from
, Index
.get());
371 AllOf(from(AllOf(withName("caller2"), withDetail("nsb::caller2"))),
372 iFromRanges(Caller2C
.range("A"), Caller2C
.range("B"))),
373 AllOf(from(AllOf(withName("caller3"), withDetail("nsa::caller3"))),
374 iFromRanges(Caller3C
.range("Caller1")))));
376 auto IncomingLevel3
= incomingCalls(IncomingLevel2
[0].from
, Index
.get());
377 ASSERT_THAT(IncomingLevel3
,
378 ElementsAre(AllOf(from(AllOf(withName("caller3"),
379 withDetail("nsa::caller3"))),
380 iFromRanges(Caller3C
.range("Caller2")))));
382 auto IncomingLevel4
= incomingCalls(IncomingLevel3
[0].from
, Index
.get());
383 EXPECT_THAT(IncomingLevel4
, IsEmpty());
386 auto CheckOutgoingCalls
= [&](ParsedAST
&AST
, Position Pos
, PathRef TUPath
) {
387 std::vector
<CallHierarchyItem
> Items
=
388 prepareCallHierarchy(AST
, Pos
, TUPath
);
389 ASSERT_THAT(Items
, ElementsAre(withName("caller3")));
390 auto OutgoingLevel1
= outgoingCalls(Items
[0], Index
.get());
394 AllOf(to(AllOf(withName("caller1"), withDetail("nsa::caller1"))),
395 oFromRanges(Caller3C
.range("Caller1"))),
396 AllOf(to(AllOf(withName("caller2"), withDetail("nsb::caller2"))),
397 oFromRanges(Caller3C
.range("Caller2")))));
399 auto OutgoingLevel2
= outgoingCalls(OutgoingLevel1
[1].to
, Index
.get());
400 ASSERT_THAT(OutgoingLevel2
,
402 to(AllOf(withName("caller1"), withDetail("nsa::caller1"))),
403 oFromRanges(Caller2C
.range("A"), Caller2C
.range("B")))));
405 auto OutgoingLevel3
= outgoingCalls(OutgoingLevel2
[0].to
, Index
.get());
408 ElementsAre(AllOf(to(AllOf(withName("callee"), withDetail("callee"))),
409 oFromRanges(Caller1C
.range()))));
411 auto OutgoingLevel4
= outgoingCalls(OutgoingLevel3
[0].to
, Index
.get());
412 EXPECT_THAT(OutgoingLevel4
, IsEmpty());
415 // Check that invoking from a call site works.
416 auto AST
= Workspace
.openFile("caller1.cc");
417 ASSERT_TRUE(bool(AST
));
418 CheckIncomingCalls(*AST
, Caller1C
.point(), testPath("caller1.cc"));
420 // Check that invoking from the declaration site works.
421 AST
= Workspace
.openFile("callee.hh");
422 ASSERT_TRUE(bool(AST
));
423 CheckIncomingCalls(*AST
, CalleeH
.point(), testPath("callee.hh"));
424 AST
= Workspace
.openFile("caller3.hh");
425 ASSERT_TRUE(bool(AST
));
426 CheckOutgoingCalls(*AST
, Caller3H
.point(), testPath("caller3.hh"));
428 // Check that invoking from the definition site works.
429 AST
= Workspace
.openFile("callee.cc");
430 ASSERT_TRUE(bool(AST
));
431 CheckIncomingCalls(*AST
, CalleeC
.point(), testPath("callee.cc"));
432 AST
= Workspace
.openFile("caller3.cc");
433 ASSERT_TRUE(bool(AST
));
434 CheckOutgoingCalls(*AST
, Caller3C
.point(), testPath("caller3.cc"));
437 TEST(CallHierarchy
, IncomingMultiFileObjC
) {
438 // The test uses a .mi suffix for header files to get clang
439 // to parse them in ObjC mode. .h files are parsed in C mode
440 // by default, which causes problems because e.g. symbol
441 // USRs are different in C mode (do not include function signatures).
443 Annotations
CalleeH(R
"objc(
444 @interface CalleeClass
448 Annotations
CalleeC(R
"objc(
450 @implementation CalleeClass {}
454 Annotations
Caller1H(R
"objc(
455 @interface Caller1Class
459 Annotations
Caller1C(R
"objc(
462 @implementation Caller1Class {}
464 [CalleeClass [[calle^e]]];
468 Annotations
Caller2H(R
"objc(
469 @interface Caller2Class
473 Annotations
Caller2C(R
"objc(
476 @implementation Caller2Class {}
478 [Caller1Class $A[[caller1]]];
479 [Caller1Class $B[[caller1]]];
483 Annotations
Caller3C(R
"objc(
486 @implementation Caller3Class {}
488 [Caller1Class $Caller1[[caller1]]];
489 [Caller2Class $Caller2[[caller2]]];
494 TestWorkspace Workspace
;
495 Workspace
.addSource("callee.mi", CalleeH
.code());
496 Workspace
.addSource("caller1.mi", Caller1H
.code());
497 Workspace
.addSource("caller2.mi", Caller2H
.code());
498 Workspace
.addMainFile("callee.m", CalleeC
.code());
499 Workspace
.addMainFile("caller1.m", Caller1C
.code());
500 Workspace
.addMainFile("caller2.m", Caller2C
.code());
501 Workspace
.addMainFile("caller3.m", Caller3C
.code());
502 auto Index
= Workspace
.index();
504 auto CheckCallHierarchy
= [&](ParsedAST
&AST
, Position Pos
, PathRef TUPath
) {
505 std::vector
<CallHierarchyItem
> Items
=
506 prepareCallHierarchy(AST
, Pos
, TUPath
);
507 ASSERT_THAT(Items
, ElementsAre(withName("callee")));
508 auto IncomingLevel1
= incomingCalls(Items
[0], Index
.get());
509 ASSERT_THAT(IncomingLevel1
,
510 ElementsAre(AllOf(from(withName("caller1")),
511 iFromRanges(Caller1C
.range()))));
513 auto IncomingLevel2
= incomingCalls(IncomingLevel1
[0].from
, Index
.get());
514 ASSERT_THAT(IncomingLevel2
,
515 ElementsAre(AllOf(from(withName("caller2")),
516 iFromRanges(Caller2C
.range("A"),
517 Caller2C
.range("B"))),
518 AllOf(from(withName("caller3")),
519 iFromRanges(Caller3C
.range("Caller1")))));
521 auto IncomingLevel3
= incomingCalls(IncomingLevel2
[0].from
, Index
.get());
522 ASSERT_THAT(IncomingLevel3
,
523 ElementsAre(AllOf(from(withName("caller3")),
524 iFromRanges(Caller3C
.range("Caller2")))));
526 auto IncomingLevel4
= incomingCalls(IncomingLevel3
[0].from
, Index
.get());
527 EXPECT_THAT(IncomingLevel4
, IsEmpty());
530 // Check that invoking from a call site works.
531 auto AST
= Workspace
.openFile("caller1.m");
532 ASSERT_TRUE(bool(AST
));
533 CheckCallHierarchy(*AST
, Caller1C
.point(), testPath("caller1.m"));
535 // Check that invoking from the declaration site works.
536 AST
= Workspace
.openFile("callee.mi");
537 ASSERT_TRUE(bool(AST
));
538 CheckCallHierarchy(*AST
, CalleeH
.point(), testPath("callee.mi"));
540 // Check that invoking from the definition site works.
541 AST
= Workspace
.openFile("callee.m");
542 ASSERT_TRUE(bool(AST
));
543 CheckCallHierarchy(*AST
, CalleeC
.point(), testPath("callee.m"));
546 TEST(CallHierarchy
, CallInLocalVarDecl
) {
547 // Tests that local variable declarations are not treated as callers
548 // (they're not indexed, so they can't be represented as call hierarchy
549 // items); instead, the caller should be the containing function.
550 // However, namespace-scope variable declarations should be treated as
551 // callers because those are indexed and there is no enclosing entity
552 // that would be a useful caller.
553 Annotations
Source(R
"cpp(
559 int localVar = $call2[[callee]]();
561 int caller3 = $call3[[callee]]();
563 TestTU TU
= TestTU::withCode(Source
.code());
564 auto AST
= TU
.build();
565 auto Index
= TU
.index();
567 std::vector
<CallHierarchyItem
> Items
=
568 prepareCallHierarchy(AST
, Source
.point(), testPath(TU
.Filename
));
569 ASSERT_THAT(Items
, ElementsAre(withName("callee")));
571 auto Incoming
= incomingCalls(Items
[0], Index
.get());
572 ASSERT_THAT(Incoming
, ElementsAre(AllOf(from(withName("caller1")),
573 iFromRanges(Source
.range("call1"))),
574 AllOf(from(withName("caller2")),
575 iFromRanges(Source
.range("call2"))),
576 AllOf(from(withName("caller3")),
577 iFromRanges(Source
.range("call3")))));
580 TEST(CallHierarchy
, HierarchyOnField
) {
581 // Tests that the call hierarchy works on fields.
582 Annotations
Source(R
"cpp(
588 values.$Callee[[var1]];
591 TestTU TU
= TestTU::withCode(Source
.code());
592 auto AST
= TU
.build();
593 auto Index
= TU
.index();
595 std::vector
<CallHierarchyItem
> Items
=
596 prepareCallHierarchy(AST
, Source
.point(), testPath(TU
.Filename
));
597 ASSERT_THAT(Items
, ElementsAre(withName("var1")));
598 auto IncomingLevel1
= incomingCalls(Items
[0], Index
.get());
599 ASSERT_THAT(IncomingLevel1
,
600 ElementsAre(AllOf(from(withName("caller")),
601 iFromRanges(Source
.range("Callee")))));
604 TEST(CallHierarchy
, HierarchyOnVar
) {
605 // Tests that the call hierarchy works on non-local variables.
606 Annotations
Source(R
"cpp(
612 TestTU TU
= TestTU::withCode(Source
.code());
613 auto AST
= TU
.build();
614 auto Index
= TU
.index();
616 std::vector
<CallHierarchyItem
> Items
=
617 prepareCallHierarchy(AST
, Source
.point(), testPath(TU
.Filename
));
618 ASSERT_THAT(Items
, ElementsAre(withName("var")));
619 auto IncomingLevel1
= incomingCalls(Items
[0], Index
.get());
620 ASSERT_THAT(IncomingLevel1
,
621 ElementsAre(AllOf(from(withName("caller")),
622 iFromRanges(Source
.range("Callee")))));
625 TEST(CallHierarchy
, CallInDifferentFileThanCaller
) {
626 Annotations
Header(R
"cpp(
627 #define WALDO void caller() {
629 Annotations
Source(R
"cpp(
635 auto TU
= TestTU::withCode(Source
.code());
636 TU
.HeaderCode
= Header
.code();
637 auto AST
= TU
.build();
638 auto Index
= TU
.index();
640 std::vector
<CallHierarchyItem
> Items
=
641 prepareCallHierarchy(AST
, Source
.point(), testPath(TU
.Filename
));
642 ASSERT_THAT(Items
, ElementsAre(withName("callee")));
644 auto Incoming
= incomingCalls(Items
[0], Index
.get());
646 // The only call site is in the source file, which is a different file from
647 // the declaration of the function containing the call, which is in the
648 // header. The protocol does not allow us to represent such calls, so we drop
649 // them. (The call hierarchy item itself is kept.)
650 EXPECT_THAT(Incoming
,
651 ElementsAre(AllOf(from(withName("caller")), iFromRanges())));
655 } // namespace clangd