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(withSelectionRange
, R
, "") { return arg
.selectionRange
== R
; }
49 template <class ItemMatcher
>
50 ::testing::Matcher
<CallHierarchyIncomingCall
> from(ItemMatcher M
) {
51 return Field(&CallHierarchyIncomingCall::from
, M
);
53 template <class... RangeMatchers
>
54 ::testing::Matcher
<CallHierarchyIncomingCall
> fromRanges(RangeMatchers
... M
) {
55 return Field(&CallHierarchyIncomingCall::fromRanges
,
56 UnorderedElementsAre(M
...));
59 TEST(CallHierarchy
, IncomingOneFileCpp
) {
60 Annotations
Source(R
"cpp(
63 $Callee[[callee]](42);
66 $Caller1A[[caller1]]();
67 $Caller1B[[caller1]]();
70 $Caller1C[[caller1]]();
71 $Caller2[[caller2]]();
74 TestTU TU
= TestTU::withCode(Source
.code());
75 auto AST
= TU
.build();
76 auto Index
= TU
.index();
78 std::vector
<CallHierarchyItem
> Items
=
79 prepareCallHierarchy(AST
, Source
.point(), testPath(TU
.Filename
));
80 ASSERT_THAT(Items
, ElementsAre(withName("callee")));
81 auto IncomingLevel1
= incomingCalls(Items
[0], Index
.get());
82 ASSERT_THAT(IncomingLevel1
,
83 ElementsAre(AllOf(from(withName("caller1")),
84 fromRanges(Source
.range("Callee")))));
85 auto IncomingLevel2
= incomingCalls(IncomingLevel1
[0].from
, Index
.get());
86 ASSERT_THAT(IncomingLevel2
,
87 ElementsAre(AllOf(from(withName("caller2")),
88 fromRanges(Source
.range("Caller1A"),
89 Source
.range("Caller1B"))),
90 AllOf(from(withName("caller3")),
91 fromRanges(Source
.range("Caller1C")))));
93 auto IncomingLevel3
= incomingCalls(IncomingLevel2
[0].from
, Index
.get());
94 ASSERT_THAT(IncomingLevel3
,
95 ElementsAre(AllOf(from(withName("caller3")),
96 fromRanges(Source
.range("Caller2")))));
98 auto IncomingLevel4
= incomingCalls(IncomingLevel3
[0].from
, Index
.get());
99 EXPECT_THAT(IncomingLevel4
, IsEmpty());
102 TEST(CallHierarchy
, IncomingOneFileObjC
) {
103 Annotations
Source(R
"objc(
104 @implementation MyClass {}
107 [MyClass $Callee[[callee]]];
110 [MyClass $Caller1A[[caller1]]];
111 [MyClass $Caller1B[[caller1]]];
114 [MyClass $Caller1C[[caller1]]];
115 [MyClass $Caller2[[caller2]]];
119 TestTU TU
= TestTU::withCode(Source
.code());
120 TU
.Filename
= "TestTU.m";
121 auto AST
= TU
.build();
122 auto Index
= TU
.index();
123 std::vector
<CallHierarchyItem
> Items
=
124 prepareCallHierarchy(AST
, Source
.point(), testPath(TU
.Filename
));
125 ASSERT_THAT(Items
, ElementsAre(withName("callee")));
126 auto IncomingLevel1
= incomingCalls(Items
[0], Index
.get());
127 ASSERT_THAT(IncomingLevel1
,
128 ElementsAre(AllOf(from(withName("caller1")),
129 fromRanges(Source
.range("Callee")))));
130 auto IncomingLevel2
= incomingCalls(IncomingLevel1
[0].from
, Index
.get());
131 ASSERT_THAT(IncomingLevel2
,
132 ElementsAre(AllOf(from(withName("caller2")),
133 fromRanges(Source
.range("Caller1A"),
134 Source
.range("Caller1B"))),
135 AllOf(from(withName("caller3")),
136 fromRanges(Source
.range("Caller1C")))));
138 auto IncomingLevel3
= incomingCalls(IncomingLevel2
[0].from
, Index
.get());
139 ASSERT_THAT(IncomingLevel3
,
140 ElementsAre(AllOf(from(withName("caller3")),
141 fromRanges(Source
.range("Caller2")))));
143 auto IncomingLevel4
= incomingCalls(IncomingLevel3
[0].from
, Index
.get());
144 EXPECT_THAT(IncomingLevel4
, IsEmpty());
147 TEST(CallHierarchy
, MainFileOnlyRef
) {
148 // In addition to testing that we store refs to main-file only symbols,
149 // this tests that anonymous namespaces do not interfere with the
150 // symbol re-identification process in callHierarchyItemToSymbo().
151 Annotations
Source(R
"cpp(
155 $Callee[[callee]](42);
159 $Caller1[[caller1]]();
162 TestTU TU
= TestTU::withCode(Source
.code());
163 auto AST
= TU
.build();
164 auto Index
= TU
.index();
166 std::vector
<CallHierarchyItem
> Items
=
167 prepareCallHierarchy(AST
, Source
.point(), testPath(TU
.Filename
));
168 ASSERT_THAT(Items
, ElementsAre(withName("callee")));
169 auto IncomingLevel1
= incomingCalls(Items
[0], Index
.get());
170 ASSERT_THAT(IncomingLevel1
,
171 ElementsAre(AllOf(from(withName("caller1")),
172 fromRanges(Source
.range("Callee")))));
174 auto IncomingLevel2
= incomingCalls(IncomingLevel1
[0].from
, Index
.get());
175 EXPECT_THAT(IncomingLevel2
,
176 ElementsAre(AllOf(from(withName("caller2")),
177 fromRanges(Source
.range("Caller1")))));
180 TEST(CallHierarchy
, IncomingQualified
) {
181 Annotations
Source(R
"cpp(
186 void Waldo::find() {}
187 void caller1(Waldo &W) {
188 W.$Caller1[[f^ind]]();
190 void caller2(Waldo &W) {
191 W.$Caller2[[find]]();
195 TestTU TU
= TestTU::withCode(Source
.code());
196 auto AST
= TU
.build();
197 auto Index
= TU
.index();
199 std::vector
<CallHierarchyItem
> Items
=
200 prepareCallHierarchy(AST
, Source
.point(), testPath(TU
.Filename
));
201 ASSERT_THAT(Items
, ElementsAre(withName("Waldo::find")));
202 auto Incoming
= incomingCalls(Items
[0], Index
.get());
203 EXPECT_THAT(Incoming
,
204 ElementsAre(AllOf(from(withName("caller1")),
205 fromRanges(Source
.range("Caller1"))),
206 AllOf(from(withName("caller2")),
207 fromRanges(Source
.range("Caller2")))));
210 TEST(CallHierarchy
, IncomingMultiFileCpp
) {
211 // The test uses a .hh suffix for header files to get clang
212 // to parse them in C++ mode. .h files are parsed in C mode
213 // by default, which causes problems because e.g. symbol
214 // USRs are different in C mode (do not include function signatures).
216 Annotations
CalleeH(R
"cpp(
219 Annotations
CalleeC(R
"cpp(
223 Annotations
Caller1H(R
"cpp(
226 Annotations
Caller1C(R
"cpp(
228 #include "caller1
.hh
"
233 Annotations
Caller2H(R
"cpp(
236 Annotations
Caller2C(R
"cpp(
237 #include "caller1
.hh
"
238 #include "caller2
.hh
"
244 Annotations
Caller3C(R
"cpp(
245 #include "caller1
.hh
"
246 #include "caller2
.hh
"
248 $Caller1[[caller1]]();
249 $Caller2[[caller2]]();
253 TestWorkspace Workspace
;
254 Workspace
.addSource("callee.hh", CalleeH
.code());
255 Workspace
.addSource("caller1.hh", Caller1H
.code());
256 Workspace
.addSource("caller2.hh", Caller2H
.code());
257 Workspace
.addMainFile("callee.cc", CalleeC
.code());
258 Workspace
.addMainFile("caller1.cc", Caller1C
.code());
259 Workspace
.addMainFile("caller2.cc", Caller2C
.code());
260 Workspace
.addMainFile("caller3.cc", Caller3C
.code());
262 auto Index
= Workspace
.index();
264 auto CheckCallHierarchy
= [&](ParsedAST
&AST
, Position Pos
, PathRef TUPath
) {
265 std::vector
<CallHierarchyItem
> Items
=
266 prepareCallHierarchy(AST
, Pos
, TUPath
);
267 ASSERT_THAT(Items
, ElementsAre(withName("callee")));
268 auto IncomingLevel1
= incomingCalls(Items
[0], Index
.get());
269 ASSERT_THAT(IncomingLevel1
,
270 ElementsAre(AllOf(from(withName("caller1")),
271 fromRanges(Caller1C
.range()))));
273 auto IncomingLevel2
= incomingCalls(IncomingLevel1
[0].from
, Index
.get());
276 ElementsAre(AllOf(from(withName("caller2")),
277 fromRanges(Caller2C
.range("A"), Caller2C
.range("B"))),
278 AllOf(from(withName("caller3")),
279 fromRanges(Caller3C
.range("Caller1")))));
281 auto IncomingLevel3
= incomingCalls(IncomingLevel2
[0].from
, Index
.get());
282 ASSERT_THAT(IncomingLevel3
,
283 ElementsAre(AllOf(from(withName("caller3")),
284 fromRanges(Caller3C
.range("Caller2")))));
286 auto IncomingLevel4
= incomingCalls(IncomingLevel3
[0].from
, Index
.get());
287 EXPECT_THAT(IncomingLevel4
, IsEmpty());
290 // Check that invoking from a call site works.
291 auto AST
= Workspace
.openFile("caller1.cc");
292 ASSERT_TRUE(bool(AST
));
293 CheckCallHierarchy(*AST
, Caller1C
.point(), testPath("caller1.cc"));
295 // Check that invoking from the declaration site works.
296 AST
= Workspace
.openFile("callee.hh");
297 ASSERT_TRUE(bool(AST
));
298 CheckCallHierarchy(*AST
, CalleeH
.point(), testPath("callee.hh"));
300 // Check that invoking from the definition site works.
301 AST
= Workspace
.openFile("callee.cc");
302 ASSERT_TRUE(bool(AST
));
303 CheckCallHierarchy(*AST
, CalleeC
.point(), testPath("callee.cc"));
306 TEST(CallHierarchy
, IncomingMultiFileObjC
) {
307 // The test uses a .mi suffix for header files to get clang
308 // to parse them in ObjC mode. .h files are parsed in C mode
309 // by default, which causes problems because e.g. symbol
310 // USRs are different in C mode (do not include function signatures).
312 Annotations
CalleeH(R
"objc(
313 @interface CalleeClass
317 Annotations
CalleeC(R
"objc(
319 @implementation CalleeClass {}
323 Annotations
Caller1H(R
"objc(
324 @interface Caller1Class
328 Annotations
Caller1C(R
"objc(
331 @implementation Caller1Class {}
333 [CalleeClass [[calle^e]]];
337 Annotations
Caller2H(R
"objc(
338 @interface Caller2Class
342 Annotations
Caller2C(R
"objc(
345 @implementation Caller2Class {}
347 [Caller1Class $A[[caller1]]];
348 [Caller1Class $B[[caller1]]];
352 Annotations
Caller3C(R
"objc(
355 @implementation Caller3Class {}
357 [Caller1Class $Caller1[[caller1]]];
358 [Caller2Class $Caller2[[caller2]]];
363 TestWorkspace Workspace
;
364 Workspace
.addSource("callee.mi", CalleeH
.code());
365 Workspace
.addSource("caller1.mi", Caller1H
.code());
366 Workspace
.addSource("caller2.mi", Caller2H
.code());
367 Workspace
.addMainFile("callee.m", CalleeC
.code());
368 Workspace
.addMainFile("caller1.m", Caller1C
.code());
369 Workspace
.addMainFile("caller2.m", Caller2C
.code());
370 Workspace
.addMainFile("caller3.m", Caller3C
.code());
371 auto Index
= Workspace
.index();
373 auto CheckCallHierarchy
= [&](ParsedAST
&AST
, Position Pos
, PathRef TUPath
) {
374 std::vector
<CallHierarchyItem
> Items
=
375 prepareCallHierarchy(AST
, Pos
, TUPath
);
376 ASSERT_THAT(Items
, ElementsAre(withName("callee")));
377 auto IncomingLevel1
= incomingCalls(Items
[0], Index
.get());
378 ASSERT_THAT(IncomingLevel1
,
379 ElementsAre(AllOf(from(withName("caller1")),
380 fromRanges(Caller1C
.range()))));
382 auto IncomingLevel2
= incomingCalls(IncomingLevel1
[0].from
, Index
.get());
385 ElementsAre(AllOf(from(withName("caller2")),
386 fromRanges(Caller2C
.range("A"), Caller2C
.range("B"))),
387 AllOf(from(withName("caller3")),
388 fromRanges(Caller3C
.range("Caller1")))));
390 auto IncomingLevel3
= incomingCalls(IncomingLevel2
[0].from
, Index
.get());
391 ASSERT_THAT(IncomingLevel3
,
392 ElementsAre(AllOf(from(withName("caller3")),
393 fromRanges(Caller3C
.range("Caller2")))));
395 auto IncomingLevel4
= incomingCalls(IncomingLevel3
[0].from
, Index
.get());
396 EXPECT_THAT(IncomingLevel4
, IsEmpty());
399 // Check that invoking from a call site works.
400 auto AST
= Workspace
.openFile("caller1.m");
401 ASSERT_TRUE(bool(AST
));
402 CheckCallHierarchy(*AST
, Caller1C
.point(), testPath("caller1.m"));
404 // Check that invoking from the declaration site works.
405 AST
= Workspace
.openFile("callee.mi");
406 ASSERT_TRUE(bool(AST
));
407 CheckCallHierarchy(*AST
, CalleeH
.point(), testPath("callee.mi"));
409 // Check that invoking from the definition site works.
410 AST
= Workspace
.openFile("callee.m");
411 ASSERT_TRUE(bool(AST
));
412 CheckCallHierarchy(*AST
, CalleeC
.point(), testPath("callee.m"));
415 TEST(CallHierarchy
, CallInLocalVarDecl
) {
416 // Tests that local variable declarations are not treated as callers
417 // (they're not indexed, so they can't be represented as call hierarchy
418 // items); instead, the caller should be the containing function.
419 // However, namespace-scope variable declarations should be treated as
420 // callers because those are indexed and there is no enclosing entity
421 // that would be a useful caller.
422 Annotations
Source(R
"cpp(
428 int localVar = $call2[[callee]]();
430 int caller3 = $call3[[callee]]();
432 TestTU TU
= TestTU::withCode(Source
.code());
433 auto AST
= TU
.build();
434 auto Index
= TU
.index();
436 std::vector
<CallHierarchyItem
> Items
=
437 prepareCallHierarchy(AST
, Source
.point(), testPath(TU
.Filename
));
438 ASSERT_THAT(Items
, ElementsAre(withName("callee")));
440 auto Incoming
= incomingCalls(Items
[0], Index
.get());
444 AllOf(from(withName("caller1")), fromRanges(Source
.range("call1"))),
445 AllOf(from(withName("caller2")), fromRanges(Source
.range("call2"))),
446 AllOf(from(withName("caller3")), fromRanges(Source
.range("call3")))));
450 } // namespace clangd