blob: 09f90fd6e6b58367b287969a84bb821b7e3b6b02 [file] [log] [blame]
//===-- 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 "Compiler.h"
#include "Matchers.h"
#include "ParsedAST.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::Field;
using ::testing::IsEmpty;
using ::testing::Matcher;
using ::testing::UnorderedElementsAre;
// 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(UnorderedElementsAre(ParentsM...)));
}
template <class... ChildMatchers>
::testing::Matcher<TypeHierarchyItem> Children(ChildMatchers... ChildrenM) {
return Field(&TypeHierarchyItem::children,
HasValue(UnorderedElementsAre(ChildrenM...)));
}
// Note: "not resolved" is different from "resolved but empty"!
MATCHER(ParentsNotResolved, "") { return !arg.parents; }
MATCHER(ChildrenNotResolved, "") { return !arg.children; }
TEST(FindRecordTypeAt, TypeOrVariable) {
Annotations Source(R"cpp(
struct Ch^ild2 {
int c;
};
using A^lias = Child2;
int main() {
Ch^ild2 ch^ild2;
ch^ild2.c = 1;
}
)cpp");
TestTU TU = TestTU::withCode(Source.code());
auto AST = TU.build();
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();
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();
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();
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();
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();
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();
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();
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();
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());
}
TEST(TypeParents, IncompleteClass) {
Annotations Source(R"cpp(
class Incomplete;
)cpp");
TestTU TU = TestTU::withCode(Source.code());
auto AST = TU.build();
const CXXRecordDecl *Incomplete =
dyn_cast<CXXRecordDecl>(&findDecl(AST, "Incomplete"));
EXPECT_THAT(typeParents(Incomplete), IsEmpty());
}
// 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; // error-ok
)cpp");
TestTU TU = TestTU::withCode(Source.code());
TU.ExtraArgs.push_back("-ftemplate-depth=10");
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.
// The parent is reported as "S" because "S<0>" is an invalid instantiation.
// We then iterate once more and find "S" again before detecting the
// recursion.
llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
AST, Source.points()[0], 0, TypeHierarchyDirection::Parents);
ASSERT_TRUE(bool(Result));
EXPECT_THAT(
*Result,
AllOf(WithName("S<0>"), WithKind(SymbolKind::Struct),
Parents(
AllOf(WithName("S"), WithKind(SymbolKind::Struct),
SelectionRangeIs(Source.range("SDef")),
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();
// 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<2>"), WithKind(SymbolKind::Struct),
Parents(AllOf(
WithName("S<1>"), WithKind(SymbolKind::Struct),
SelectionRangeIs(Source.range("SDef")),
Parents(AllOf(WithName("S<0>"), WithKind(SymbolKind::Struct),
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()))));
}
TEST(TypeHierarchy, DeriveFromImplicitSpec) {
Annotations Source(R"cpp(
template <typename T>
struct Parent {};
struct Child1 : Parent<int> {};
struct Child2 : Parent<char> {};
Parent<int> Fo^o;
)cpp");
TestTU TU = TestTU::withCode(Source.code());
auto AST = TU.build();
auto Index = TU.index();
llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
AST, Source.points()[0], 2, TypeHierarchyDirection::Children, Index.get(),
testPath(TU.Filename));
ASSERT_TRUE(bool(Result));
EXPECT_THAT(*Result,
AllOf(WithName("Parent"), WithKind(SymbolKind::Struct),
Children(AllOf(WithName("Child1"),
WithKind(SymbolKind::Struct), Children()),
AllOf(WithName("Child2"),
WithKind(SymbolKind::Struct), Children()))));
}
TEST(TypeHierarchy, DeriveFromPartialSpec) {
Annotations Source(R"cpp(
template <typename T> struct Parent {};
template <typename T> struct Parent<T*> {};
struct Child : Parent<int*> {};
Parent<int> Fo^o;
)cpp");
TestTU TU = TestTU::withCode(Source.code());
auto AST = TU.build();
auto Index = TU.index();
llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
AST, Source.points()[0], 2, TypeHierarchyDirection::Children, Index.get(),
testPath(TU.Filename));
ASSERT_TRUE(bool(Result));
EXPECT_THAT(*Result, AllOf(WithName("Parent"), WithKind(SymbolKind::Struct),
Children()));
}
TEST(TypeHierarchy, DeriveFromTemplate) {
Annotations Source(R"cpp(
template <typename T>
struct Parent {};
template <typename T>
struct Child : Parent<T> {};
Parent<int> Fo^o;
)cpp");
TestTU TU = TestTU::withCode(Source.code());
auto AST = TU.build();
auto Index = TU.index();
// FIXME: We'd like this to show the implicit specializations Parent<int>
// and Child<int>, but currently libIndex does not expose relationships
// between implicit specializations.
llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
AST, Source.points()[0], 2, TypeHierarchyDirection::Children, Index.get(),
testPath(TU.Filename));
ASSERT_TRUE(bool(Result));
EXPECT_THAT(*Result,
AllOf(WithName("Parent"), WithKind(SymbolKind::Struct),
Children(AllOf(WithName("Child"),
WithKind(SymbolKind::Struct), Children()))));
}
TEST(TypeHierarchy, Preamble) {
Annotations SourceAnnotations(R"cpp(
struct Ch^ild : Parent {
int b;
};)cpp");
Annotations HeaderInPreambleAnnotations(R"cpp(
struct [[Parent]] {
int a;
};)cpp");
TestTU TU = TestTU::withCode(SourceAnnotations.code());
TU.HeaderCode = HeaderInPreambleAnnotations.code().str();
auto AST = TU.build();
llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
AST, SourceAnnotations.point(), 1, TypeHierarchyDirection::Parents);
ASSERT_TRUE(Result);
EXPECT_THAT(
*Result,
AllOf(WithName("Child"),
Parents(AllOf(WithName("Parent"),
SelectionRangeIs(HeaderInPreambleAnnotations.range()),
Parents()))));
}
SymbolID findSymbolIDByName(SymbolIndex *Index, llvm::StringRef Name,
llvm::StringRef TemplateArgs = "") {
SymbolID Result;
FuzzyFindRequest Request;
Request.Query = std::string(Name);
Request.AnyScope = true;
bool GotResult = false;
Index->fuzzyFind(Request, [&](const Symbol &S) {
if (TemplateArgs == S.TemplateSpecializationArgs) {
EXPECT_FALSE(GotResult);
Result = S.ID;
GotResult = true;
}
});
EXPECT_TRUE(GotResult);
return Result;
}
std::vector<SymbolID> collectSubtypes(SymbolID Subject, SymbolIndex *Index) {
std::vector<SymbolID> Result;
RelationsRequest Req;
Req.Subjects.insert(Subject);
Req.Predicate = RelationKind::BaseOf;
Index->relations(Req,
[&Result](const SymbolID &Subject, const Symbol &Object) {
Result.push_back(Object.ID);
});
return Result;
}
TEST(Subtypes, SimpleInheritance) {
Annotations Source(R"cpp(
struct Parent {};
struct Child1a : Parent {};
struct Child1b : Parent {};
struct Child2 : Child1a {};
)cpp");
TestTU TU = TestTU::withCode(Source.code());
auto Index = TU.index();
SymbolID Parent = findSymbolIDByName(Index.get(), "Parent");
SymbolID Child1a = findSymbolIDByName(Index.get(), "Child1a");
SymbolID Child1b = findSymbolIDByName(Index.get(), "Child1b");
SymbolID Child2 = findSymbolIDByName(Index.get(), "Child2");
EXPECT_THAT(collectSubtypes(Parent, Index.get()),
UnorderedElementsAre(Child1a, Child1b));
EXPECT_THAT(collectSubtypes(Child1a, Index.get()), ElementsAre(Child2));
}
TEST(Subtypes, MultipleInheritance) {
Annotations Source(R"cpp(
struct Parent1 {};
struct Parent2 {};
struct Parent3 : Parent2 {};
struct Child : Parent1, Parent3 {};
)cpp");
TestTU TU = TestTU::withCode(Source.code());
auto Index = TU.index();
SymbolID Parent1 = findSymbolIDByName(Index.get(), "Parent1");
SymbolID Parent2 = findSymbolIDByName(Index.get(), "Parent2");
SymbolID Parent3 = findSymbolIDByName(Index.get(), "Parent3");
SymbolID Child = findSymbolIDByName(Index.get(), "Child");
EXPECT_THAT(collectSubtypes(Parent1, Index.get()), ElementsAre(Child));
EXPECT_THAT(collectSubtypes(Parent2, Index.get()), ElementsAre(Parent3));
EXPECT_THAT(collectSubtypes(Parent3, Index.get()), ElementsAre(Child));
}
TEST(Subtypes, ClassTemplate) {
Annotations Source(R"cpp(
struct Parent {};
template <typename T>
struct Child : Parent {};
)cpp");
TestTU TU = TestTU::withCode(Source.code());
auto Index = TU.index();
SymbolID Parent = findSymbolIDByName(Index.get(), "Parent");
SymbolID Child = findSymbolIDByName(Index.get(), "Child");
EXPECT_THAT(collectSubtypes(Parent, Index.get()), ElementsAre(Child));
}
TEST(Subtypes, 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 Index = TU.index();
SymbolID Parent = findSymbolIDByName(Index.get(), "Parent");
SymbolID ParentSpec = findSymbolIDByName(Index.get(), "Parent", "<int>");
SymbolID Child1 = findSymbolIDByName(Index.get(), "Child1");
SymbolID Child2 = findSymbolIDByName(Index.get(), "Child2");
EXPECT_THAT(collectSubtypes(Parent, Index.get()), ElementsAre(Child1));
EXPECT_THAT(collectSubtypes(ParentSpec, Index.get()), ElementsAre(Child2));
}
TEST(Subtypes, 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 Index = TU.index();
SymbolID Parent = findSymbolIDByName(Index.get(), "Parent");
SymbolID ChildSpec = findSymbolIDByName(Index.get(), "Child", "<int>");
EXPECT_THAT(collectSubtypes(Parent, Index.get()), ElementsAre(ChildSpec));
}
TEST(Subtypes, DependentBase) {
Annotations Source(R"cpp(
template <typename T>
struct Parent {};
template <typename T>
struct Child : Parent<T> {};
)cpp");
TestTU TU = TestTU::withCode(Source.code());
auto Index = TU.index();
SymbolID Parent = findSymbolIDByName(Index.get(), "Parent");
SymbolID Child = findSymbolIDByName(Index.get(), "Child");
EXPECT_THAT(collectSubtypes(Parent, Index.get()), ElementsAre(Child));
}
TEST(Subtypes, LazyResolution) {
Annotations Source(R"cpp(
struct P^arent {};
struct Child1 : Parent {};
struct Child2a : Child1 {};
struct Child2b : Child1 {};
)cpp");
TestTU TU = TestTU::withCode(Source.code());
auto AST = TU.build();
auto Index = TU.index();
llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
AST, Source.point(), /*ResolveLevels=*/1,
TypeHierarchyDirection::Children, Index.get(), testPath(TU.Filename));
ASSERT_TRUE(bool(Result));
EXPECT_THAT(
*Result,
AllOf(WithName("Parent"), WithKind(SymbolKind::Struct),
ParentsNotResolved(),
Children(AllOf(WithName("Child1"), WithKind(SymbolKind::Struct),
ParentsNotResolved(), ChildrenNotResolved()))));
resolveTypeHierarchy((*Result->children)[0], /*ResolveLevels=*/1,
TypeHierarchyDirection::Children, Index.get());
EXPECT_THAT(
(*Result->children)[0],
AllOf(WithName("Child1"), WithKind(SymbolKind::Struct),
ParentsNotResolved(),
Children(AllOf(WithName("Child2a"), WithKind(SymbolKind::Struct),
ParentsNotResolved(), ChildrenNotResolved()),
AllOf(WithName("Child2b"), WithKind(SymbolKind::Struct),
ParentsNotResolved(), ChildrenNotResolved()))));
}
} // namespace
} // namespace clangd
} // namespace clang