1 | //===-- TypeHierarchyTests.cpp ---------------------------*- C++ -*-------===// |
2 | // |
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 |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | #include "AST.h" |
9 | #include "Annotations.h" |
10 | #include "Matchers.h" |
11 | #include "ParsedAST.h" |
12 | #include "TestFS.h" |
13 | #include "TestTU.h" |
14 | #include "XRefs.h" |
15 | #include "clang/AST/DeclCXX.h" |
16 | #include "clang/AST/DeclTemplate.h" |
17 | #include "llvm/Support/Path.h" |
18 | #include "gmock/gmock.h" |
19 | #include "gtest/gtest.h" |
20 | #include <vector> |
21 | |
22 | namespace clang { |
23 | namespace clangd { |
24 | namespace { |
25 | |
26 | using ::testing::AllOf; |
27 | using ::testing::ElementsAre; |
28 | using ::testing::Field; |
29 | using ::testing::IsEmpty; |
30 | using ::testing::Matcher; |
31 | using ::testing::SizeIs; |
32 | using ::testing::UnorderedElementsAre; |
33 | |
34 | // GMock helpers for matching TypeHierarchyItem. |
35 | MATCHER_P(withName, N, "" ) { return arg.name == N; } |
36 | MATCHER_P(withKind, Kind, "" ) { return arg.kind == Kind; } |
37 | MATCHER_P(selectionRangeIs, R, "" ) { return arg.selectionRange == R; } |
38 | template <class... ParentMatchers> |
39 | ::testing::Matcher<TypeHierarchyItem> parents(ParentMatchers... ParentsM) { |
40 | return Field(&TypeHierarchyItem::parents, |
41 | HasValue(UnorderedElementsAre(ParentsM...))); |
42 | } |
43 | template <class... ChildMatchers> |
44 | ::testing::Matcher<TypeHierarchyItem> children(ChildMatchers... ChildrenM) { |
45 | return Field(&TypeHierarchyItem::children, |
46 | HasValue(UnorderedElementsAre(ChildrenM...))); |
47 | } |
48 | // Note: "not resolved" is different from "resolved but empty"! |
49 | MATCHER(parentsNotResolved, "" ) { return !arg.parents; } |
50 | MATCHER(childrenNotResolved, "" ) { return !arg.children; } |
51 | MATCHER_P(withResolveID, SID, "" ) { return arg.symbolID.str() == SID; } |
52 | MATCHER_P(withResolveParents, M, "" ) { |
53 | return testing::ExplainMatchResult(M, arg.data.parents, result_listener); |
54 | } |
55 | |
56 | TEST(FindRecordTypeAt, TypeOrVariable) { |
57 | Annotations Source(R"cpp( |
58 | struct Ch^ild2 { |
59 | int c; |
60 | }; |
61 | |
62 | using A^lias = Child2; |
63 | |
64 | int main() { |
65 | Ch^ild2 ch^ild2; |
66 | ch^ild2.c = 1; |
67 | } |
68 | )cpp" ); |
69 | |
70 | TestTU TU = TestTU::withCode(Code: Source.code()); |
71 | auto AST = TU.build(); |
72 | |
73 | for (Position Pt : Source.points()) { |
74 | auto Records = findRecordTypeAt(AST, Pos: Pt); |
75 | ASSERT_THAT(Records, SizeIs(1)); |
76 | EXPECT_EQ(&findDecl(AST, "Child2" ), |
77 | static_cast<const NamedDecl *>(Records.front())); |
78 | } |
79 | } |
80 | |
81 | TEST(FindRecordTypeAt, Nonexistent) { |
82 | Annotations Source(R"cpp( |
83 | int *wa^ldo; |
84 | )cpp" ); |
85 | TestTU TU = TestTU::withCode(Code: Source.code()); |
86 | auto AST = TU.build(); |
87 | |
88 | for (Position Pt : Source.points()) { |
89 | auto Records = findRecordTypeAt(AST, Pos: Pt); |
90 | ASSERT_THAT(Records, SizeIs(0)); |
91 | } |
92 | } |
93 | |
94 | TEST(FindRecordTypeAt, Method) { |
95 | Annotations Source(R"cpp( |
96 | struct Child2 { |
97 | void met^hod (); |
98 | void met^hod (int x); |
99 | }; |
100 | |
101 | int main() { |
102 | Child2 child2; |
103 | child2.met^hod(5); |
104 | } |
105 | )cpp" ); |
106 | |
107 | TestTU TU = TestTU::withCode(Code: Source.code()); |
108 | auto AST = TU.build(); |
109 | |
110 | for (Position Pt : Source.points()) { |
111 | auto Records = findRecordTypeAt(AST, Pos: Pt); |
112 | ASSERT_THAT(Records, SizeIs(1)); |
113 | EXPECT_EQ(&findDecl(AST, "Child2" ), |
114 | static_cast<const NamedDecl *>(Records.front())); |
115 | } |
116 | } |
117 | |
118 | TEST(FindRecordTypeAt, Field) { |
119 | Annotations Source(R"cpp( |
120 | struct Child2 { |
121 | int fi^eld; |
122 | }; |
123 | |
124 | int main() { |
125 | Child2 child2; |
126 | child2.fi^eld = 5; |
127 | } |
128 | )cpp" ); |
129 | |
130 | TestTU TU = TestTU::withCode(Code: Source.code()); |
131 | auto AST = TU.build(); |
132 | |
133 | for (Position Pt : Source.points()) { |
134 | // A field does not unambiguously specify a record type |
135 | // (possible associated record types could be the field's type, |
136 | // or the type of the record that the field is a member of). |
137 | EXPECT_THAT(findRecordTypeAt(AST, Pt), SizeIs(0)); |
138 | } |
139 | } |
140 | |
141 | TEST(TypeParents, SimpleInheritance) { |
142 | Annotations Source(R"cpp( |
143 | struct Parent { |
144 | int a; |
145 | }; |
146 | |
147 | struct Child1 : Parent { |
148 | int b; |
149 | }; |
150 | |
151 | struct Child2 : Child1 { |
152 | int c; |
153 | }; |
154 | )cpp" ); |
155 | |
156 | TestTU TU = TestTU::withCode(Code: Source.code()); |
157 | auto AST = TU.build(); |
158 | |
159 | const CXXRecordDecl *Parent = |
160 | dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Parent" )); |
161 | const CXXRecordDecl *Child1 = |
162 | dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Child1" )); |
163 | const CXXRecordDecl *Child2 = |
164 | dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Child2" )); |
165 | |
166 | EXPECT_THAT(typeParents(Parent), ElementsAre()); |
167 | EXPECT_THAT(typeParents(Child1), ElementsAre(Parent)); |
168 | EXPECT_THAT(typeParents(Child2), ElementsAre(Child1)); |
169 | } |
170 | |
171 | TEST(TypeParents, MultipleInheritance) { |
172 | Annotations Source(R"cpp( |
173 | struct Parent1 { |
174 | int a; |
175 | }; |
176 | |
177 | struct Parent2 { |
178 | int b; |
179 | }; |
180 | |
181 | struct Parent3 : Parent2 { |
182 | int c; |
183 | }; |
184 | |
185 | struct Child : Parent1, Parent3 { |
186 | int d; |
187 | }; |
188 | )cpp" ); |
189 | |
190 | TestTU TU = TestTU::withCode(Code: Source.code()); |
191 | auto AST = TU.build(); |
192 | |
193 | const CXXRecordDecl *Parent1 = |
194 | dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Parent1" )); |
195 | const CXXRecordDecl *Parent2 = |
196 | dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Parent2" )); |
197 | const CXXRecordDecl *Parent3 = |
198 | dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Parent3" )); |
199 | const CXXRecordDecl *Child = dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Child" )); |
200 | |
201 | EXPECT_THAT(typeParents(Parent1), ElementsAre()); |
202 | EXPECT_THAT(typeParents(Parent2), ElementsAre()); |
203 | EXPECT_THAT(typeParents(Parent3), ElementsAre(Parent2)); |
204 | EXPECT_THAT(typeParents(Child), ElementsAre(Parent1, Parent3)); |
205 | } |
206 | |
207 | TEST(TypeParents, ClassTemplate) { |
208 | Annotations Source(R"cpp( |
209 | struct Parent {}; |
210 | |
211 | template <typename T> |
212 | struct Child : Parent {}; |
213 | )cpp" ); |
214 | |
215 | TestTU TU = TestTU::withCode(Code: Source.code()); |
216 | auto AST = TU.build(); |
217 | |
218 | const CXXRecordDecl *Parent = |
219 | dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Parent" )); |
220 | const CXXRecordDecl *Child = |
221 | dyn_cast<ClassTemplateDecl>(Val: &findDecl(AST, QName: "Child" ))->getTemplatedDecl(); |
222 | |
223 | EXPECT_THAT(typeParents(Child), ElementsAre(Parent)); |
224 | } |
225 | |
226 | MATCHER_P(implicitSpecOf, ClassTemplate, "" ) { |
227 | const ClassTemplateSpecializationDecl *CTS = |
228 | dyn_cast<ClassTemplateSpecializationDecl>(arg); |
229 | return CTS && |
230 | CTS->getSpecializedTemplate()->getTemplatedDecl() == ClassTemplate && |
231 | CTS->getSpecializationKind() == TSK_ImplicitInstantiation; |
232 | } |
233 | |
234 | // This is similar to findDecl(AST, QName), but supports using |
235 | // a template-id as a query. |
236 | const NamedDecl &findDeclWithTemplateArgs(ParsedAST &AST, |
237 | llvm::StringRef Query) { |
238 | return findDecl(AST, Filter: [&Query](const NamedDecl &ND) { |
239 | std::string QName; |
240 | llvm::raw_string_ostream OS(QName); |
241 | PrintingPolicy Policy(ND.getASTContext().getLangOpts()); |
242 | // Use getNameForDiagnostic() which includes the template |
243 | // arguments in the printed name. |
244 | ND.getNameForDiagnostic(OS, Policy, /*Qualified=*/true); |
245 | OS.flush(); |
246 | return QName == Query; |
247 | }); |
248 | } |
249 | |
250 | TEST(TypeParents, TemplateSpec1) { |
251 | Annotations Source(R"cpp( |
252 | template <typename T> |
253 | struct Parent {}; |
254 | |
255 | template <> |
256 | struct Parent<int> {}; |
257 | |
258 | struct Child1 : Parent<float> {}; |
259 | |
260 | struct Child2 : Parent<int> {}; |
261 | )cpp" ); |
262 | |
263 | TestTU TU = TestTU::withCode(Code: Source.code()); |
264 | auto AST = TU.build(); |
265 | |
266 | const CXXRecordDecl *Parent = |
267 | dyn_cast<ClassTemplateDecl>(Val: &findDecl(AST, QName: "Parent" ))->getTemplatedDecl(); |
268 | const CXXRecordDecl *ParentSpec = |
269 | dyn_cast<CXXRecordDecl>(Val: &findDeclWithTemplateArgs(AST, Query: "Parent<int>" )); |
270 | const CXXRecordDecl *Child1 = |
271 | dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Child1" )); |
272 | const CXXRecordDecl *Child2 = |
273 | dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Child2" )); |
274 | |
275 | EXPECT_THAT(typeParents(Child1), ElementsAre(implicitSpecOf(Parent))); |
276 | EXPECT_THAT(typeParents(Child2), ElementsAre(ParentSpec)); |
277 | } |
278 | |
279 | TEST(TypeParents, TemplateSpec2) { |
280 | Annotations Source(R"cpp( |
281 | struct Parent {}; |
282 | |
283 | template <typename T> |
284 | struct Child {}; |
285 | |
286 | template <> |
287 | struct Child<int> : Parent {}; |
288 | )cpp" ); |
289 | |
290 | TestTU TU = TestTU::withCode(Code: Source.code()); |
291 | auto AST = TU.build(); |
292 | |
293 | const CXXRecordDecl *Parent = |
294 | dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Parent" )); |
295 | const CXXRecordDecl *Child = |
296 | dyn_cast<ClassTemplateDecl>(Val: &findDecl(AST, QName: "Child" ))->getTemplatedDecl(); |
297 | const CXXRecordDecl *ChildSpec = |
298 | dyn_cast<CXXRecordDecl>(Val: &findDeclWithTemplateArgs(AST, Query: "Child<int>" )); |
299 | |
300 | EXPECT_THAT(typeParents(Child), ElementsAre()); |
301 | EXPECT_THAT(typeParents(ChildSpec), ElementsAre(Parent)); |
302 | } |
303 | |
304 | TEST(TypeParents, DependentBase) { |
305 | Annotations Source(R"cpp( |
306 | template <typename T> |
307 | struct Parent {}; |
308 | |
309 | template <typename T> |
310 | struct Child1 : Parent<T> {}; |
311 | |
312 | template <typename T> |
313 | struct Child2 : Parent<T>::Type {}; |
314 | |
315 | template <typename T> |
316 | struct Child3 : T {}; |
317 | )cpp" ); |
318 | |
319 | TestTU TU = TestTU::withCode(Code: Source.code()); |
320 | auto AST = TU.build(); |
321 | |
322 | const CXXRecordDecl *Parent = |
323 | dyn_cast<ClassTemplateDecl>(Val: &findDecl(AST, QName: "Parent" ))->getTemplatedDecl(); |
324 | const CXXRecordDecl *Child1 = |
325 | dyn_cast<ClassTemplateDecl>(Val: &findDecl(AST, QName: "Child1" ))->getTemplatedDecl(); |
326 | const CXXRecordDecl *Child2 = |
327 | dyn_cast<ClassTemplateDecl>(Val: &findDecl(AST, QName: "Child2" ))->getTemplatedDecl(); |
328 | const CXXRecordDecl *Child3 = |
329 | dyn_cast<ClassTemplateDecl>(Val: &findDecl(AST, QName: "Child3" ))->getTemplatedDecl(); |
330 | |
331 | // For "Parent<T>", use the primary template as a best-effort guess. |
332 | EXPECT_THAT(typeParents(Child1), ElementsAre(Parent)); |
333 | // For "Parent<T>::Type", there is nothing we can do. |
334 | EXPECT_THAT(typeParents(Child2), ElementsAre()); |
335 | // Likewise for "T". |
336 | EXPECT_THAT(typeParents(Child3), ElementsAre()); |
337 | } |
338 | |
339 | TEST(TypeParents, IncompleteClass) { |
340 | Annotations Source(R"cpp( |
341 | class Incomplete; |
342 | )cpp" ); |
343 | TestTU TU = TestTU::withCode(Code: Source.code()); |
344 | auto AST = TU.build(); |
345 | |
346 | const CXXRecordDecl *Incomplete = |
347 | dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Incomplete" )); |
348 | EXPECT_THAT(typeParents(Incomplete), IsEmpty()); |
349 | } |
350 | |
351 | // Parts of getTypeHierarchy() are tested in more detail by the |
352 | // FindRecordTypeAt.* and TypeParents.* tests above. This test exercises the |
353 | // entire operation. |
354 | TEST(TypeHierarchy, Parents) { |
355 | Annotations Source(R"cpp( |
356 | struct $Parent1Def[[Parent1]] { |
357 | int a; |
358 | }; |
359 | |
360 | struct $Parent2Def[[Parent2]] { |
361 | int b; |
362 | }; |
363 | |
364 | struct $Parent3Def[[Parent3]] : Parent2 { |
365 | int c; |
366 | }; |
367 | |
368 | struct Ch^ild : Parent1, Parent3 { |
369 | int d; |
370 | }; |
371 | |
372 | int main() { |
373 | Ch^ild ch^ild; |
374 | |
375 | ch^ild.a = 1; |
376 | } |
377 | )cpp" ); |
378 | |
379 | TestTU TU = TestTU::withCode(Code: Source.code()); |
380 | auto AST = TU.build(); |
381 | |
382 | for (Position Pt : Source.points()) { |
383 | // Set ResolveLevels to 0 because it's only used for Children; |
384 | // for Parents, getTypeHierarchy() always returns all levels. |
385 | auto Result = getTypeHierarchy(AST, Pos: Pt, /*ResolveLevels=*/Resolve: 0, |
386 | Direction: TypeHierarchyDirection::Parents); |
387 | ASSERT_THAT(Result, SizeIs(1)); |
388 | EXPECT_THAT( |
389 | Result.front(), |
390 | AllOf( |
391 | withName("Child" ), withKind(SymbolKind::Struct), |
392 | parents(AllOf(withName("Parent1" ), withKind(SymbolKind::Struct), |
393 | selectionRangeIs(Source.range("Parent1Def" )), |
394 | parents()), |
395 | AllOf(withName("Parent3" ), withKind(SymbolKind::Struct), |
396 | selectionRangeIs(Source.range("Parent3Def" )), |
397 | parents(AllOf( |
398 | withName("Parent2" ), withKind(SymbolKind::Struct), |
399 | selectionRangeIs(Source.range("Parent2Def" )), |
400 | parents())))))); |
401 | } |
402 | } |
403 | |
404 | TEST(TypeHierarchy, RecursiveHierarchyUnbounded) { |
405 | Annotations Source(R"cpp( |
406 | template <int N> |
407 | struct $SDef[[S]] : S<N + 1> {}; |
408 | |
409 | S^<0> s; // error-ok |
410 | )cpp" ); |
411 | |
412 | TestTU TU = TestTU::withCode(Code: Source.code()); |
413 | TU.ExtraArgs.push_back(x: "-ftemplate-depth=10" ); |
414 | auto AST = TU.build(); |
415 | |
416 | // The compiler should produce a diagnostic for hitting the |
417 | // template instantiation depth. |
418 | ASSERT_FALSE(AST.getDiagnostics().empty()); |
419 | |
420 | // Make sure getTypeHierarchy() doesn't get into an infinite recursion. |
421 | // The parent is reported as "S" because "S<0>" is an invalid instantiation. |
422 | // We then iterate once more and find "S" again before detecting the |
423 | // recursion. |
424 | auto Result = getTypeHierarchy(AST, Pos: Source.points()[0], Resolve: 0, |
425 | Direction: TypeHierarchyDirection::Parents); |
426 | ASSERT_THAT(Result, SizeIs(1)); |
427 | EXPECT_THAT( |
428 | Result.front(), |
429 | AllOf(withName("S<0>" ), withKind(SymbolKind::Struct), |
430 | parents( |
431 | AllOf(withName("S" ), withKind(SymbolKind::Struct), |
432 | selectionRangeIs(Source.range("SDef" )), |
433 | parents(AllOf(withName("S" ), withKind(SymbolKind::Struct), |
434 | selectionRangeIs(Source.range("SDef" )), |
435 | parents())))))); |
436 | } |
437 | |
438 | TEST(TypeHierarchy, RecursiveHierarchyBounded) { |
439 | Annotations Source(R"cpp( |
440 | template <int N> |
441 | struct $SDef[[S]] : S<N - 1> {}; |
442 | |
443 | template <> |
444 | struct S<0>{}; |
445 | |
446 | S$SRefConcrete^<2> s; |
447 | |
448 | template <int N> |
449 | struct Foo { |
450 | S$SRefDependent^<N> s; |
451 | };)cpp" ); |
452 | |
453 | TestTU TU = TestTU::withCode(Code: Source.code()); |
454 | auto AST = TU.build(); |
455 | |
456 | // Make sure getTypeHierarchy() doesn't get into an infinite recursion |
457 | // for either a concrete starting point or a dependent starting point. |
458 | auto Result = getTypeHierarchy(AST, Pos: Source.point(Name: "SRefConcrete" ), Resolve: 0, |
459 | Direction: TypeHierarchyDirection::Parents); |
460 | ASSERT_THAT(Result, SizeIs(1)); |
461 | EXPECT_THAT( |
462 | Result.front(), |
463 | AllOf(withName("S<2>" ), withKind(SymbolKind::Struct), |
464 | parents(AllOf( |
465 | withName("S<1>" ), withKind(SymbolKind::Struct), |
466 | selectionRangeIs(Source.range("SDef" )), |
467 | parents(AllOf(withName("S<0>" ), withKind(SymbolKind::Struct), |
468 | parents())))))); |
469 | Result = getTypeHierarchy(AST, Pos: Source.point(Name: "SRefDependent" ), Resolve: 0, |
470 | Direction: TypeHierarchyDirection::Parents); |
471 | ASSERT_THAT(Result, SizeIs(1)); |
472 | EXPECT_THAT( |
473 | Result.front(), |
474 | AllOf(withName("S" ), withKind(SymbolKind::Struct), |
475 | parents(AllOf(withName("S" ), withKind(SymbolKind::Struct), |
476 | selectionRangeIs(Source.range("SDef" )), parents())))); |
477 | } |
478 | |
479 | TEST(TypeHierarchy, DeriveFromImplicitSpec) { |
480 | Annotations Source(R"cpp( |
481 | template <typename T> |
482 | struct Parent {}; |
483 | |
484 | struct Child1 : Parent<int> {}; |
485 | |
486 | struct Child2 : Parent<char> {}; |
487 | |
488 | Parent<int> Fo^o; |
489 | )cpp" ); |
490 | |
491 | TestTU TU = TestTU::withCode(Code: Source.code()); |
492 | auto AST = TU.build(); |
493 | auto Index = TU.index(); |
494 | |
495 | auto Result = getTypeHierarchy(AST, Pos: Source.points()[0], Resolve: 2, |
496 | Direction: TypeHierarchyDirection::Children, Index: Index.get(), |
497 | TUPath: testPath(File: TU.Filename)); |
498 | ASSERT_THAT(Result, SizeIs(1)); |
499 | EXPECT_THAT(Result.front(), |
500 | AllOf(withName("Parent" ), withKind(SymbolKind::Struct), |
501 | children(AllOf(withName("Child1" ), |
502 | withKind(SymbolKind::Struct), children()), |
503 | AllOf(withName("Child2" ), |
504 | withKind(SymbolKind::Struct), children())))); |
505 | } |
506 | |
507 | TEST(TypeHierarchy, DeriveFromPartialSpec) { |
508 | Annotations Source(R"cpp( |
509 | template <typename T> struct Parent {}; |
510 | template <typename T> struct Parent<T*> {}; |
511 | |
512 | struct Child : Parent<int*> {}; |
513 | |
514 | Parent<int> Fo^o; |
515 | )cpp" ); |
516 | |
517 | TestTU TU = TestTU::withCode(Code: Source.code()); |
518 | auto AST = TU.build(); |
519 | auto Index = TU.index(); |
520 | |
521 | auto Result = getTypeHierarchy(AST, Pos: Source.points()[0], Resolve: 2, |
522 | Direction: TypeHierarchyDirection::Children, Index: Index.get(), |
523 | TUPath: testPath(File: TU.Filename)); |
524 | ASSERT_THAT(Result, SizeIs(1)); |
525 | EXPECT_THAT(Result.front(), AllOf(withName("Parent" ), |
526 | withKind(SymbolKind::Struct), children())); |
527 | } |
528 | |
529 | TEST(TypeHierarchy, DeriveFromTemplate) { |
530 | Annotations Source(R"cpp( |
531 | template <typename T> |
532 | struct Parent {}; |
533 | |
534 | template <typename T> |
535 | struct Child : Parent<T> {}; |
536 | |
537 | Parent<int> Fo^o; |
538 | )cpp" ); |
539 | |
540 | TestTU TU = TestTU::withCode(Code: Source.code()); |
541 | auto AST = TU.build(); |
542 | auto Index = TU.index(); |
543 | |
544 | // FIXME: We'd like this to show the implicit specializations Parent<int> |
545 | // and Child<int>, but currently libIndex does not expose relationships |
546 | // between implicit specializations. |
547 | auto Result = getTypeHierarchy(AST, Pos: Source.points()[0], Resolve: 2, |
548 | Direction: TypeHierarchyDirection::Children, Index: Index.get(), |
549 | TUPath: testPath(File: TU.Filename)); |
550 | ASSERT_THAT(Result, SizeIs(1)); |
551 | EXPECT_THAT(Result.front(), |
552 | AllOf(withName("Parent" ), withKind(SymbolKind::Struct), |
553 | children(AllOf(withName("Child" ), |
554 | withKind(SymbolKind::Struct), children())))); |
555 | } |
556 | |
557 | TEST(TypeHierarchy, Preamble) { |
558 | Annotations SourceAnnotations(R"cpp( |
559 | struct Ch^ild : Parent { |
560 | int b; |
561 | };)cpp" ); |
562 | |
563 | Annotations (R"cpp( |
564 | struct [[Parent]] { |
565 | int a; |
566 | };)cpp" ); |
567 | |
568 | TestTU TU = TestTU::withCode(Code: SourceAnnotations.code()); |
569 | TU.HeaderCode = HeaderInPreambleAnnotations.code().str(); |
570 | auto AST = TU.build(); |
571 | |
572 | std::vector<TypeHierarchyItem> Result = getTypeHierarchy( |
573 | AST, Pos: SourceAnnotations.point(), Resolve: 1, Direction: TypeHierarchyDirection::Parents); |
574 | |
575 | ASSERT_THAT(Result, SizeIs(1)); |
576 | EXPECT_THAT( |
577 | Result.front(), |
578 | AllOf(withName("Child" ), |
579 | parents(AllOf(withName("Parent" ), |
580 | selectionRangeIs(HeaderInPreambleAnnotations.range()), |
581 | parents())))); |
582 | } |
583 | |
584 | SymbolID findSymbolIDByName(SymbolIndex *Index, llvm::StringRef Name, |
585 | llvm::StringRef TemplateArgs = "" ) { |
586 | SymbolID Result; |
587 | FuzzyFindRequest Request; |
588 | Request.Query = std::string(Name); |
589 | Request.AnyScope = true; |
590 | bool GotResult = false; |
591 | Index->fuzzyFind(Req: Request, Callback: [&](const Symbol &S) { |
592 | if (TemplateArgs == S.TemplateSpecializationArgs) { |
593 | EXPECT_FALSE(GotResult); |
594 | Result = S.ID; |
595 | GotResult = true; |
596 | } |
597 | }); |
598 | EXPECT_TRUE(GotResult); |
599 | return Result; |
600 | } |
601 | |
602 | std::vector<SymbolID> collectSubtypes(SymbolID Subject, SymbolIndex *Index) { |
603 | std::vector<SymbolID> Result; |
604 | RelationsRequest Req; |
605 | Req.Subjects.insert(V: Subject); |
606 | Req.Predicate = RelationKind::BaseOf; |
607 | Index->relations(Req, |
608 | Callback: [&Result](const SymbolID &Subject, const Symbol &Object) { |
609 | Result.push_back(x: Object.ID); |
610 | }); |
611 | return Result; |
612 | } |
613 | |
614 | TEST(Subtypes, SimpleInheritance) { |
615 | Annotations Source(R"cpp( |
616 | struct Parent {}; |
617 | struct Child1a : Parent {}; |
618 | struct Child1b : Parent {}; |
619 | struct Child2 : Child1a {}; |
620 | )cpp" ); |
621 | |
622 | TestTU TU = TestTU::withCode(Code: Source.code()); |
623 | auto Index = TU.index(); |
624 | |
625 | SymbolID Parent = findSymbolIDByName(Index: Index.get(), Name: "Parent" ); |
626 | SymbolID Child1a = findSymbolIDByName(Index: Index.get(), Name: "Child1a" ); |
627 | SymbolID Child1b = findSymbolIDByName(Index: Index.get(), Name: "Child1b" ); |
628 | SymbolID Child2 = findSymbolIDByName(Index: Index.get(), Name: "Child2" ); |
629 | |
630 | EXPECT_THAT(collectSubtypes(Parent, Index.get()), |
631 | UnorderedElementsAre(Child1a, Child1b)); |
632 | EXPECT_THAT(collectSubtypes(Child1a, Index.get()), ElementsAre(Child2)); |
633 | } |
634 | |
635 | TEST(Subtypes, MultipleInheritance) { |
636 | Annotations Source(R"cpp( |
637 | struct Parent1 {}; |
638 | struct Parent2 {}; |
639 | struct Parent3 : Parent2 {}; |
640 | struct Child : Parent1, Parent3 {}; |
641 | )cpp" ); |
642 | |
643 | TestTU TU = TestTU::withCode(Code: Source.code()); |
644 | auto Index = TU.index(); |
645 | |
646 | SymbolID Parent1 = findSymbolIDByName(Index: Index.get(), Name: "Parent1" ); |
647 | SymbolID Parent2 = findSymbolIDByName(Index: Index.get(), Name: "Parent2" ); |
648 | SymbolID Parent3 = findSymbolIDByName(Index: Index.get(), Name: "Parent3" ); |
649 | SymbolID Child = findSymbolIDByName(Index: Index.get(), Name: "Child" ); |
650 | |
651 | EXPECT_THAT(collectSubtypes(Parent1, Index.get()), ElementsAre(Child)); |
652 | EXPECT_THAT(collectSubtypes(Parent2, Index.get()), ElementsAre(Parent3)); |
653 | EXPECT_THAT(collectSubtypes(Parent3, Index.get()), ElementsAre(Child)); |
654 | } |
655 | |
656 | TEST(Subtypes, ClassTemplate) { |
657 | Annotations Source(R"cpp( |
658 | struct Parent {}; |
659 | |
660 | template <typename T> |
661 | struct Child : Parent {}; |
662 | )cpp" ); |
663 | |
664 | TestTU TU = TestTU::withCode(Code: Source.code()); |
665 | auto Index = TU.index(); |
666 | |
667 | SymbolID Parent = findSymbolIDByName(Index: Index.get(), Name: "Parent" ); |
668 | SymbolID Child = findSymbolIDByName(Index: Index.get(), Name: "Child" ); |
669 | |
670 | EXPECT_THAT(collectSubtypes(Parent, Index.get()), ElementsAre(Child)); |
671 | } |
672 | |
673 | TEST(Subtypes, TemplateSpec1) { |
674 | Annotations Source(R"cpp( |
675 | template <typename T> |
676 | struct Parent {}; |
677 | |
678 | template <> |
679 | struct Parent<int> {}; |
680 | |
681 | struct Child1 : Parent<float> {}; |
682 | |
683 | struct Child2 : Parent<int> {}; |
684 | )cpp" ); |
685 | |
686 | TestTU TU = TestTU::withCode(Code: Source.code()); |
687 | auto Index = TU.index(); |
688 | |
689 | SymbolID Parent = findSymbolIDByName(Index: Index.get(), Name: "Parent" ); |
690 | SymbolID ParentSpec = findSymbolIDByName(Index: Index.get(), Name: "Parent" , TemplateArgs: "<int>" ); |
691 | SymbolID Child1 = findSymbolIDByName(Index: Index.get(), Name: "Child1" ); |
692 | SymbolID Child2 = findSymbolIDByName(Index: Index.get(), Name: "Child2" ); |
693 | |
694 | EXPECT_THAT(collectSubtypes(Parent, Index.get()), ElementsAre(Child1)); |
695 | EXPECT_THAT(collectSubtypes(ParentSpec, Index.get()), ElementsAre(Child2)); |
696 | } |
697 | |
698 | TEST(Subtypes, TemplateSpec2) { |
699 | Annotations Source(R"cpp( |
700 | struct Parent {}; |
701 | |
702 | template <typename T> |
703 | struct Child {}; |
704 | |
705 | template <> |
706 | struct Child<int> : Parent {}; |
707 | )cpp" ); |
708 | |
709 | TestTU TU = TestTU::withCode(Code: Source.code()); |
710 | auto Index = TU.index(); |
711 | |
712 | SymbolID Parent = findSymbolIDByName(Index: Index.get(), Name: "Parent" ); |
713 | SymbolID ChildSpec = findSymbolIDByName(Index: Index.get(), Name: "Child" , TemplateArgs: "<int>" ); |
714 | |
715 | EXPECT_THAT(collectSubtypes(Parent, Index.get()), ElementsAre(ChildSpec)); |
716 | } |
717 | |
718 | TEST(Subtypes, DependentBase) { |
719 | Annotations Source(R"cpp( |
720 | template <typename T> |
721 | struct Parent {}; |
722 | |
723 | template <typename T> |
724 | struct Child : Parent<T> {}; |
725 | )cpp" ); |
726 | |
727 | TestTU TU = TestTU::withCode(Code: Source.code()); |
728 | auto Index = TU.index(); |
729 | |
730 | SymbolID Parent = findSymbolIDByName(Index: Index.get(), Name: "Parent" ); |
731 | SymbolID Child = findSymbolIDByName(Index: Index.get(), Name: "Child" ); |
732 | |
733 | EXPECT_THAT(collectSubtypes(Parent, Index.get()), ElementsAre(Child)); |
734 | } |
735 | |
736 | TEST(Subtypes, LazyResolution) { |
737 | Annotations Source(R"cpp( |
738 | struct P^arent {}; |
739 | struct Child1 : Parent {}; |
740 | struct Child2a : Child1 {}; |
741 | struct Child2b : Child1 {}; |
742 | )cpp" ); |
743 | |
744 | TestTU TU = TestTU::withCode(Code: Source.code()); |
745 | auto AST = TU.build(); |
746 | auto Index = TU.index(); |
747 | |
748 | auto Result = getTypeHierarchy(AST, Pos: Source.point(), /*ResolveLevels=*/Resolve: 1, |
749 | Direction: TypeHierarchyDirection::Children, Index: Index.get(), |
750 | TUPath: testPath(File: TU.Filename)); |
751 | ASSERT_THAT(Result, SizeIs(1)); |
752 | EXPECT_THAT( |
753 | Result.front(), |
754 | AllOf(withName("Parent" ), withKind(SymbolKind::Struct), parents(), |
755 | children(AllOf(withName("Child1" ), withKind(SymbolKind::Struct), |
756 | parentsNotResolved(), childrenNotResolved())))); |
757 | |
758 | resolveTypeHierarchy(Item&: (*Result.front().children)[0], /*ResolveLevels=*/1, |
759 | Direction: TypeHierarchyDirection::Children, Index: Index.get()); |
760 | |
761 | EXPECT_THAT( |
762 | (*Result.front().children)[0], |
763 | AllOf(withName("Child1" ), withKind(SymbolKind::Struct), |
764 | parentsNotResolved(), |
765 | children(AllOf(withName("Child2a" ), withKind(SymbolKind::Struct), |
766 | parentsNotResolved(), childrenNotResolved()), |
767 | AllOf(withName("Child2b" ), withKind(SymbolKind::Struct), |
768 | parentsNotResolved(), childrenNotResolved())))); |
769 | } |
770 | |
771 | TEST(Standard, SubTypes) { |
772 | Annotations Source(R"cpp( |
773 | struct Pare^nt1 {}; |
774 | struct Parent2 {}; |
775 | struct Child : Parent1, Parent2 {}; |
776 | )cpp" ); |
777 | |
778 | TestTU TU = TestTU::withCode(Code: Source.code()); |
779 | auto AST = TU.build(); |
780 | auto Index = TU.index(); |
781 | |
782 | auto Result = getTypeHierarchy(AST, Pos: Source.point(), /*ResolveLevels=*/Resolve: 1, |
783 | Direction: TypeHierarchyDirection::Children, Index: Index.get(), |
784 | TUPath: testPath(File: TU.Filename)); |
785 | ASSERT_THAT(Result, SizeIs(1)); |
786 | auto Children = subTypes(Item: Result.front(), Index: Index.get()); |
787 | |
788 | // Make sure parents are populated when getting children. |
789 | // FIXME: This is partial. |
790 | EXPECT_THAT( |
791 | Children, |
792 | UnorderedElementsAre( |
793 | AllOf(withName("Child" ), |
794 | withResolveParents(HasValue(UnorderedElementsAre(withResolveID( |
795 | getSymbolID(&findDecl(AST, "Parent1" )).str()))))))); |
796 | } |
797 | |
798 | TEST(Standard, SuperTypes) { |
799 | Annotations Source(R"cpp( |
800 | struct Parent {}; |
801 | struct Chil^d : Parent {}; |
802 | )cpp" ); |
803 | |
804 | TestTU TU = TestTU::withCode(Code: Source.code()); |
805 | auto AST = TU.build(); |
806 | auto Index = TU.index(); |
807 | |
808 | auto Result = getTypeHierarchy(AST, Pos: Source.point(), /*ResolveLevels=*/Resolve: 1, |
809 | Direction: TypeHierarchyDirection::Children, Index: Index.get(), |
810 | TUPath: testPath(File: TU.Filename)); |
811 | ASSERT_THAT(Result, SizeIs(1)); |
812 | auto Parents = superTypes(Item: Result.front(), Index: Index.get()); |
813 | |
814 | EXPECT_THAT(Parents, HasValue(UnorderedElementsAre( |
815 | AllOf(withName("Parent" ), |
816 | withResolveParents(HasValue(IsEmpty())))))); |
817 | } |
818 | } // namespace |
819 | } // namespace clangd |
820 | } // namespace clang |
821 | |