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::Optional; |
32 | using ::testing::SizeIs; |
33 | using ::testing::UnorderedElementsAre; |
34 | |
35 | // GMock helpers for matching TypeHierarchyItem. |
36 | MATCHER_P(withName, N, "" ) { return arg.name == N; } |
37 | MATCHER_P(withKind, Kind, "" ) { return arg.kind == Kind; } |
38 | MATCHER_P(selectionRangeIs, R, "" ) { return arg.selectionRange == R; } |
39 | template <class... ParentMatchers> |
40 | ::testing::Matcher<TypeHierarchyItem> parents(ParentMatchers... ParentsM) { |
41 | return Field(&TypeHierarchyItem::parents, |
42 | Optional(UnorderedElementsAre(ParentsM...))); |
43 | } |
44 | template <class... ChildMatchers> |
45 | ::testing::Matcher<TypeHierarchyItem> children(ChildMatchers... ChildrenM) { |
46 | return Field(&TypeHierarchyItem::children, |
47 | Optional(UnorderedElementsAre(ChildrenM...))); |
48 | } |
49 | // Note: "not resolved" is different from "resolved but empty"! |
50 | MATCHER(parentsNotResolved, "" ) { return !arg.parents; } |
51 | MATCHER(childrenNotResolved, "" ) { return !arg.children; } |
52 | MATCHER_P(withResolveID, SID, "" ) { return arg.symbolID.str() == SID; } |
53 | MATCHER_P(withResolveParents, M, "" ) { |
54 | return testing::ExplainMatchResult(M, arg.data.parents, result_listener); |
55 | } |
56 | |
57 | TEST(FindRecordTypeAt, TypeOrVariable) { |
58 | Annotations Source(R"cpp( |
59 | struct Ch^ild2 { |
60 | int c; |
61 | }; |
62 | |
63 | using A^lias = Child2; |
64 | |
65 | int main() { |
66 | Ch^ild2 ch^ild2; |
67 | ch^ild2.c = 1; |
68 | } |
69 | )cpp" ); |
70 | |
71 | TestTU TU = TestTU::withCode(Code: Source.code()); |
72 | auto AST = TU.build(); |
73 | |
74 | for (Position Pt : Source.points()) { |
75 | auto Records = findRecordTypeAt(AST, Pos: Pt); |
76 | ASSERT_THAT(Records, SizeIs(1)); |
77 | EXPECT_EQ(&findDecl(AST, "Child2" ), |
78 | static_cast<const NamedDecl *>(Records.front())); |
79 | } |
80 | } |
81 | |
82 | TEST(FindRecordTypeAt, Nonexistent) { |
83 | Annotations Source(R"cpp( |
84 | int *wa^ldo; |
85 | )cpp" ); |
86 | TestTU TU = TestTU::withCode(Code: Source.code()); |
87 | auto AST = TU.build(); |
88 | |
89 | for (Position Pt : Source.points()) { |
90 | auto Records = findRecordTypeAt(AST, Pos: Pt); |
91 | ASSERT_THAT(Records, SizeIs(0)); |
92 | } |
93 | } |
94 | |
95 | TEST(FindRecordTypeAt, Method) { |
96 | Annotations Source(R"cpp( |
97 | struct Child2 { |
98 | void met^hod (); |
99 | void met^hod (int x); |
100 | }; |
101 | |
102 | int main() { |
103 | Child2 child2; |
104 | child2.met^hod(5); |
105 | } |
106 | )cpp" ); |
107 | |
108 | TestTU TU = TestTU::withCode(Code: Source.code()); |
109 | auto AST = TU.build(); |
110 | |
111 | for (Position Pt : Source.points()) { |
112 | auto Records = findRecordTypeAt(AST, Pos: Pt); |
113 | ASSERT_THAT(Records, SizeIs(1)); |
114 | EXPECT_EQ(&findDecl(AST, "Child2" ), |
115 | static_cast<const NamedDecl *>(Records.front())); |
116 | } |
117 | } |
118 | |
119 | TEST(FindRecordTypeAt, Field) { |
120 | Annotations Source(R"cpp( |
121 | struct Child2 { |
122 | int fi^eld; |
123 | }; |
124 | |
125 | int main() { |
126 | Child2 child2; |
127 | child2.fi^eld = 5; |
128 | } |
129 | )cpp" ); |
130 | |
131 | TestTU TU = TestTU::withCode(Code: Source.code()); |
132 | auto AST = TU.build(); |
133 | |
134 | for (Position Pt : Source.points()) { |
135 | // A field does not unambiguously specify a record type |
136 | // (possible associated record types could be the field's type, |
137 | // or the type of the record that the field is a member of). |
138 | EXPECT_THAT(findRecordTypeAt(AST, Pt), SizeIs(0)); |
139 | } |
140 | } |
141 | |
142 | TEST(TypeParents, SimpleInheritance) { |
143 | Annotations Source(R"cpp( |
144 | struct Parent { |
145 | int a; |
146 | }; |
147 | |
148 | struct Child1 : Parent { |
149 | int b; |
150 | }; |
151 | |
152 | struct Child2 : Child1 { |
153 | int c; |
154 | }; |
155 | )cpp" ); |
156 | |
157 | TestTU TU = TestTU::withCode(Code: Source.code()); |
158 | auto AST = TU.build(); |
159 | |
160 | const CXXRecordDecl *Parent = |
161 | dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Parent" )); |
162 | const CXXRecordDecl *Child1 = |
163 | dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Child1" )); |
164 | const CXXRecordDecl *Child2 = |
165 | dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Child2" )); |
166 | |
167 | EXPECT_THAT(typeParents(Parent), ElementsAre()); |
168 | EXPECT_THAT(typeParents(Child1), ElementsAre(Parent)); |
169 | EXPECT_THAT(typeParents(Child2), ElementsAre(Child1)); |
170 | } |
171 | |
172 | TEST(TypeParents, MultipleInheritance) { |
173 | Annotations Source(R"cpp( |
174 | struct Parent1 { |
175 | int a; |
176 | }; |
177 | |
178 | struct Parent2 { |
179 | int b; |
180 | }; |
181 | |
182 | struct Parent3 : Parent2 { |
183 | int c; |
184 | }; |
185 | |
186 | struct Child : Parent1, Parent3 { |
187 | int d; |
188 | }; |
189 | )cpp" ); |
190 | |
191 | TestTU TU = TestTU::withCode(Code: Source.code()); |
192 | auto AST = TU.build(); |
193 | |
194 | const CXXRecordDecl *Parent1 = |
195 | dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Parent1" )); |
196 | const CXXRecordDecl *Parent2 = |
197 | dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Parent2" )); |
198 | const CXXRecordDecl *Parent3 = |
199 | dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Parent3" )); |
200 | const CXXRecordDecl *Child = dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Child" )); |
201 | |
202 | EXPECT_THAT(typeParents(Parent1), ElementsAre()); |
203 | EXPECT_THAT(typeParents(Parent2), ElementsAre()); |
204 | EXPECT_THAT(typeParents(Parent3), ElementsAre(Parent2)); |
205 | EXPECT_THAT(typeParents(Child), ElementsAre(Parent1, Parent3)); |
206 | } |
207 | |
208 | TEST(TypeParents, ClassTemplate) { |
209 | Annotations Source(R"cpp( |
210 | struct Parent {}; |
211 | |
212 | template <typename T> |
213 | struct Child : Parent {}; |
214 | )cpp" ); |
215 | |
216 | TestTU TU = TestTU::withCode(Code: Source.code()); |
217 | auto AST = TU.build(); |
218 | |
219 | const CXXRecordDecl *Parent = |
220 | dyn_cast<CXXRecordDecl>(Val: &findDecl(AST, QName: "Parent" )); |
221 | const CXXRecordDecl *Child = |
222 | dyn_cast<ClassTemplateDecl>(Val: &findDecl(AST, QName: "Child" ))->getTemplatedDecl(); |
223 | |
224 | EXPECT_THAT(typeParents(Child), ElementsAre(Parent)); |
225 | } |
226 | |
227 | MATCHER_P(implicitSpecOf, ClassTemplate, "" ) { |
228 | const ClassTemplateSpecializationDecl *CTS = |
229 | dyn_cast<ClassTemplateSpecializationDecl>(arg); |
230 | return CTS && |
231 | CTS->getSpecializedTemplate()->getTemplatedDecl() == ClassTemplate && |
232 | CTS->getSpecializationKind() == TSK_ImplicitInstantiation; |
233 | } |
234 | |
235 | // This is similar to findDecl(AST, QName), but supports using |
236 | // a template-id as a query. |
237 | const NamedDecl &findDeclWithTemplateArgs(ParsedAST &AST, |
238 | llvm::StringRef Query) { |
239 | return findDecl(AST, Filter: [&Query](const NamedDecl &ND) { |
240 | std::string QName; |
241 | llvm::raw_string_ostream OS(QName); |
242 | PrintingPolicy Policy(ND.getASTContext().getLangOpts()); |
243 | // Use getNameForDiagnostic() which includes the template |
244 | // arguments in the printed name. |
245 | ND.getNameForDiagnostic(OS, Policy, /*Qualified=*/true); |
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(Optional(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, Optional(UnorderedElementsAre( |
815 | AllOf(withName("Parent" ), |
816 | withResolveParents(Optional(IsEmpty())))))); |
817 | } |
818 | } // namespace |
819 | } // namespace clangd |
820 | } // namespace clang |
821 | |