summaryrefslogtreecommitdiffstats
path: root/clangd/unittests/TypeHierarchyTests.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clangd/unittests/TypeHierarchyTests.cpp')
-rw-r--r--clangd/unittests/TypeHierarchyTests.cpp455
1 files changed, 455 insertions, 0 deletions
diff --git a/clangd/unittests/TypeHierarchyTests.cpp b/clangd/unittests/TypeHierarchyTests.cpp
new file mode 100644
index 00000000..9fcb94a9
--- /dev/null
+++ b/clangd/unittests/TypeHierarchyTests.cpp
@@ -0,0 +1,455 @@
+//===-- TypeHierarchyTests.cpp ---------------------------*- C++ -*-------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#include "Annotations.h"
+#include "ClangdUnit.h"
+#include "Compiler.h"
+#include "Matchers.h"
+#include "SyncAPI.h"
+#include "TestFS.h"
+#include "TestTU.h"
+#include "XRefs.h"
+#include "index/FileIndex.h"
+#include "index/SymbolCollector.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/Index/IndexingAction.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/ScopedPrinter.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace clangd {
+namespace {
+
+using ::testing::AllOf;
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::Field;
+using ::testing::IsEmpty;
+using ::testing::Matcher;
+using ::testing::Pointee;
+using ::testing::UnorderedElementsAreArray;
+
+// GMock helpers for matching TypeHierarchyItem.
+MATCHER_P(WithName, N, "") { return arg.name == N; }
+MATCHER_P(WithKind, Kind, "") { return arg.kind == Kind; }
+MATCHER_P(SelectionRangeIs, R, "") { return arg.selectionRange == R; }
+template <class... ParentMatchers>
+::testing::Matcher<TypeHierarchyItem> Parents(ParentMatchers... ParentsM) {
+ return Field(&TypeHierarchyItem::parents, HasValue(ElementsAre(ParentsM...)));
+}
+
+TEST(FindRecordTypeAt, TypeOrVariable) {
+ Annotations Source(R"cpp(
+struct Ch^ild2 {
+ int c;
+};
+
+int main() {
+ Ch^ild2 ch^ild2;
+ ch^ild2.c = 1;
+}
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ for (Position Pt : Source.points()) {
+ const CXXRecordDecl *RD = findRecordTypeAt(AST, Pt);
+ EXPECT_EQ(&findDecl(AST, "Child2"), static_cast<const NamedDecl *>(RD));
+ }
+}
+
+TEST(FindRecordTypeAt, Method) {
+ Annotations Source(R"cpp(
+struct Child2 {
+ void met^hod ();
+ void met^hod (int x);
+};
+
+int main() {
+ Child2 child2;
+ child2.met^hod(5);
+}
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ for (Position Pt : Source.points()) {
+ const CXXRecordDecl *RD = findRecordTypeAt(AST, Pt);
+ EXPECT_EQ(&findDecl(AST, "Child2"), static_cast<const NamedDecl *>(RD));
+ }
+}
+
+TEST(FindRecordTypeAt, Field) {
+ Annotations Source(R"cpp(
+struct Child2 {
+ int fi^eld;
+};
+
+int main() {
+ Child2 child2;
+ child2.fi^eld = 5;
+}
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ for (Position Pt : Source.points()) {
+ const CXXRecordDecl *RD = findRecordTypeAt(AST, Pt);
+ // A field does not unambiguously specify a record type
+ // (possible associated reocrd types could be the field's type,
+ // or the type of the record that the field is a member of).
+ EXPECT_EQ(nullptr, RD);
+ }
+}
+
+TEST(TypeParents, SimpleInheritance) {
+ Annotations Source(R"cpp(
+struct Parent {
+ int a;
+};
+
+struct Child1 : Parent {
+ int b;
+};
+
+struct Child2 : Child1 {
+ int c;
+};
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ const CXXRecordDecl *Parent =
+ dyn_cast<CXXRecordDecl>(&findDecl(AST, "Parent"));
+ const CXXRecordDecl *Child1 =
+ dyn_cast<CXXRecordDecl>(&findDecl(AST, "Child1"));
+ const CXXRecordDecl *Child2 =
+ dyn_cast<CXXRecordDecl>(&findDecl(AST, "Child2"));
+
+ EXPECT_THAT(typeParents(Parent), ElementsAre());
+ EXPECT_THAT(typeParents(Child1), ElementsAre(Parent));
+ EXPECT_THAT(typeParents(Child2), ElementsAre(Child1));
+}
+
+TEST(TypeParents, MultipleInheritance) {
+ Annotations Source(R"cpp(
+struct Parent1 {
+ int a;
+};
+
+struct Parent2 {
+ int b;
+};
+
+struct Parent3 : Parent2 {
+ int c;
+};
+
+struct Child : Parent1, Parent3 {
+ int d;
+};
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ const CXXRecordDecl *Parent1 =
+ dyn_cast<CXXRecordDecl>(&findDecl(AST, "Parent1"));
+ const CXXRecordDecl *Parent2 =
+ dyn_cast<CXXRecordDecl>(&findDecl(AST, "Parent2"));
+ const CXXRecordDecl *Parent3 =
+ dyn_cast<CXXRecordDecl>(&findDecl(AST, "Parent3"));
+ const CXXRecordDecl *Child = dyn_cast<CXXRecordDecl>(&findDecl(AST, "Child"));
+
+ EXPECT_THAT(typeParents(Parent1), ElementsAre());
+ EXPECT_THAT(typeParents(Parent2), ElementsAre());
+ EXPECT_THAT(typeParents(Parent3), ElementsAre(Parent2));
+ EXPECT_THAT(typeParents(Child), ElementsAre(Parent1, Parent3));
+}
+
+TEST(TypeParents, ClassTemplate) {
+ Annotations Source(R"cpp(
+struct Parent {};
+
+template <typename T>
+struct Child : Parent {};
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ const CXXRecordDecl *Parent =
+ dyn_cast<CXXRecordDecl>(&findDecl(AST, "Parent"));
+ const CXXRecordDecl *Child =
+ dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Child"))->getTemplatedDecl();
+
+ EXPECT_THAT(typeParents(Child), ElementsAre(Parent));
+}
+
+MATCHER_P(ImplicitSpecOf, ClassTemplate, "") {
+ const ClassTemplateSpecializationDecl *CTS =
+ dyn_cast<ClassTemplateSpecializationDecl>(arg);
+ return CTS &&
+ CTS->getSpecializedTemplate()->getTemplatedDecl() == ClassTemplate &&
+ CTS->getSpecializationKind() == TSK_ImplicitInstantiation;
+}
+
+// This is similar to findDecl(AST, QName), but supports using
+// a template-id as a query.
+const NamedDecl &findDeclWithTemplateArgs(ParsedAST &AST,
+ llvm::StringRef Query) {
+ return findDecl(AST, [&Query](const NamedDecl &ND) {
+ std::string QName;
+ llvm::raw_string_ostream OS(QName);
+ PrintingPolicy Policy(ND.getASTContext().getLangOpts());
+ // Use getNameForDiagnostic() which includes the template
+ // arguments in the printed name.
+ ND.getNameForDiagnostic(OS, Policy, /*Qualified=*/true);
+ OS.flush();
+ return QName == Query;
+ });
+}
+
+TEST(TypeParents, TemplateSpec1) {
+ Annotations Source(R"cpp(
+template <typename T>
+struct Parent {};
+
+template <>
+struct Parent<int> {};
+
+struct Child1 : Parent<float> {};
+
+struct Child2 : Parent<int> {};
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ const CXXRecordDecl *Parent =
+ dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Parent"))->getTemplatedDecl();
+ const CXXRecordDecl *ParentSpec =
+ dyn_cast<CXXRecordDecl>(&findDeclWithTemplateArgs(AST, "Parent<int>"));
+ const CXXRecordDecl *Child1 =
+ dyn_cast<CXXRecordDecl>(&findDecl(AST, "Child1"));
+ const CXXRecordDecl *Child2 =
+ dyn_cast<CXXRecordDecl>(&findDecl(AST, "Child2"));
+
+ EXPECT_THAT(typeParents(Child1), ElementsAre(ImplicitSpecOf(Parent)));
+ EXPECT_THAT(typeParents(Child2), ElementsAre(ParentSpec));
+}
+
+TEST(TypeParents, TemplateSpec2) {
+ Annotations Source(R"cpp(
+struct Parent {};
+
+template <typename T>
+struct Child {};
+
+template <>
+struct Child<int> : Parent {};
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ const CXXRecordDecl *Parent =
+ dyn_cast<CXXRecordDecl>(&findDecl(AST, "Parent"));
+ const CXXRecordDecl *Child =
+ dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Child"))->getTemplatedDecl();
+ const CXXRecordDecl *ChildSpec =
+ dyn_cast<CXXRecordDecl>(&findDeclWithTemplateArgs(AST, "Child<int>"));
+
+ EXPECT_THAT(typeParents(Child), ElementsAre());
+ EXPECT_THAT(typeParents(ChildSpec), ElementsAre(Parent));
+}
+
+TEST(TypeParents, DependentBase) {
+ Annotations Source(R"cpp(
+template <typename T>
+struct Parent {};
+
+template <typename T>
+struct Child1 : Parent<T> {};
+
+template <typename T>
+struct Child2 : Parent<T>::Type {};
+
+template <typename T>
+struct Child3 : T {};
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ const CXXRecordDecl *Parent =
+ dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Parent"))->getTemplatedDecl();
+ const CXXRecordDecl *Child1 =
+ dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Child1"))->getTemplatedDecl();
+ const CXXRecordDecl *Child2 =
+ dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Child2"))->getTemplatedDecl();
+ const CXXRecordDecl *Child3 =
+ dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Child3"))->getTemplatedDecl();
+
+ // For "Parent<T>", use the primary template as a best-effort guess.
+ EXPECT_THAT(typeParents(Child1), ElementsAre(Parent));
+ // For "Parent<T>::Type", there is nothing we can do.
+ EXPECT_THAT(typeParents(Child2), ElementsAre());
+ // Likewise for "T".
+ EXPECT_THAT(typeParents(Child3), ElementsAre());
+}
+
+// Parts of getTypeHierarchy() are tested in more detail by the
+// FindRecordTypeAt.* and TypeParents.* tests above. This test exercises the
+// entire operation.
+TEST(TypeHierarchy, Parents) {
+ Annotations Source(R"cpp(
+struct $Parent1Def[[Parent1]] {
+ int a;
+};
+
+struct $Parent2Def[[Parent2]] {
+ int b;
+};
+
+struct $Parent3Def[[Parent3]] : Parent2 {
+ int c;
+};
+
+struct Ch^ild : Parent1, Parent3 {
+ int d;
+};
+
+int main() {
+ Ch^ild ch^ild;
+
+ ch^ild.a = 1;
+}
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ for (Position Pt : Source.points()) {
+ // Set ResolveLevels to 0 because it's only used for Children;
+ // for Parents, getTypeHierarchy() always returns all levels.
+ llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
+ AST, Pt, /*ResolveLevels=*/0, TypeHierarchyDirection::Parents);
+ ASSERT_TRUE(bool(Result));
+ EXPECT_THAT(
+ *Result,
+ AllOf(
+ WithName("Child"), WithKind(SymbolKind::Struct),
+ Parents(AllOf(WithName("Parent1"), WithKind(SymbolKind::Struct),
+ SelectionRangeIs(Source.range("Parent1Def")),
+ Parents()),
+ AllOf(WithName("Parent3"), WithKind(SymbolKind::Struct),
+ SelectionRangeIs(Source.range("Parent3Def")),
+ Parents(AllOf(
+ WithName("Parent2"), WithKind(SymbolKind::Struct),
+ SelectionRangeIs(Source.range("Parent2Def")),
+ Parents()))))));
+ }
+}
+
+TEST(TypeHierarchy, RecursiveHierarchyUnbounded) {
+ Annotations Source(R"cpp(
+ template <int N>
+ struct $SDef[[S]] : S<N + 1> {};
+
+ S^<0> s;
+ )cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ // The compiler should produce a diagnostic for hitting the
+ // template instantiation depth.
+ ASSERT_TRUE(!AST.getDiagnostics().empty());
+
+ // Make sure getTypeHierarchy() doesn't get into an infinite recursion.
+ // FIXME(nridge): It would be preferable if the type hierarchy gave us type
+ // names (e.g. "S<0>" for the child and "S<1>" for the parent) rather than
+ // template names (e.g. "S").
+ llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
+ AST, Source.points()[0], 0, TypeHierarchyDirection::Parents);
+ ASSERT_TRUE(bool(Result));
+ EXPECT_THAT(
+ *Result,
+ AllOf(WithName("S"), WithKind(SymbolKind::Struct),
+ Parents(AllOf(WithName("S"), WithKind(SymbolKind::Struct),
+ SelectionRangeIs(Source.range("SDef")), Parents()))));
+}
+
+TEST(TypeHierarchy, RecursiveHierarchyBounded) {
+ Annotations Source(R"cpp(
+ template <int N>
+ struct $SDef[[S]] : S<N - 1> {};
+
+ template <>
+ struct S<0>{};
+
+ S$SRefConcrete^<2> s;
+
+ template <int N>
+ struct Foo {
+ S$SRefDependent^<N> s;
+ };)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ // Make sure getTypeHierarchy() doesn't get into an infinite recursion
+ // for either a concrete starting point or a dependent starting point.
+ llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
+ AST, Source.point("SRefConcrete"), 0, TypeHierarchyDirection::Parents);
+ ASSERT_TRUE(bool(Result));
+ EXPECT_THAT(
+ *Result,
+ AllOf(WithName("S"), WithKind(SymbolKind::Struct),
+ Parents(AllOf(WithName("S"), WithKind(SymbolKind::Struct),
+ SelectionRangeIs(Source.range("SDef")), Parents()))));
+ Result = getTypeHierarchy(AST, Source.point("SRefDependent"), 0,
+ TypeHierarchyDirection::Parents);
+ ASSERT_TRUE(bool(Result));
+ EXPECT_THAT(
+ *Result,
+ AllOf(WithName("S"), WithKind(SymbolKind::Struct),
+ Parents(AllOf(WithName("S"), WithKind(SymbolKind::Struct),
+ SelectionRangeIs(Source.range("SDef")), Parents()))));
+}
+
+} // namespace
+} // namespace clangd
+} // namespace clang