Index: clang-tools-extra/trunk/clang-doc/Mapper.cpp =================================================================== --- clang-tools-extra/trunk/clang-doc/Mapper.cpp +++ clang-tools-extra/trunk/clang-doc/Mapper.cpp @@ -43,9 +43,12 @@ // A null in place of I indicates that the serializer is skipping this decl // for some reason (e.g. we're only reporting public decls). - if (I) - CDCtx.ECtx->reportResult(llvm::toHex(llvm::toStringRef(I->USR)), - serialize::serialize(I)); + if (I.first) + CDCtx.ECtx->reportResult(llvm::toHex(llvm::toStringRef(I.first->USR)), + serialize::serialize(I.first)); + if (I.second) + CDCtx.ECtx->reportResult(llvm::toHex(llvm::toStringRef(I.second->USR)), + serialize::serialize(I.second)); return true; } Index: clang-tools-extra/trunk/clang-doc/Serialize.h =================================================================== --- clang-tools-extra/trunk/clang-doc/Serialize.h +++ clang-tools-extra/trunk/clang-doc/Serialize.h @@ -27,16 +27,30 @@ namespace doc { namespace serialize { -std::unique_ptr emitInfo(const NamespaceDecl *D, const FullComment *FC, - int LineNumber, StringRef File, bool PublicOnly); -std::unique_ptr emitInfo(const RecordDecl *D, const FullComment *FC, - int LineNumber, StringRef File, bool PublicOnly); -std::unique_ptr emitInfo(const EnumDecl *D, const FullComment *FC, - int LineNumber, StringRef File, bool PublicOnly); -std::unique_ptr emitInfo(const FunctionDecl *D, const FullComment *FC, - int LineNumber, StringRef File, bool PublicOnly); -std::unique_ptr emitInfo(const CXXMethodDecl *D, const FullComment *FC, - int LineNumber, StringRef File, bool PublicOnly); +// The first element will contain the relevant information about the declaration +// passed as parameter. +// The second element will contain the relevant information about the +// declaration's parent, it can be a NamespaceInfo or RecordInfo. +// Both elements can be nullptrs if the declaration shouldn't be handled. +// When the declaration is handled, the first element will be a nullptr for +// EnumDecl, FunctionDecl and CXXMethodDecl; they are only returned wrapped in +// its parent scope. For NamespaceDecl and RecordDecl both elements are not +// nullptr. +std::pair, std::unique_ptr> +emitInfo(const NamespaceDecl *D, const FullComment *FC, int LineNumber, + StringRef File, bool PublicOnly); +std::pair, std::unique_ptr> +emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber, + StringRef File, bool PublicOnly); +std::pair, std::unique_ptr> +emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber, + StringRef File, bool PublicOnly); +std::pair, std::unique_ptr> +emitInfo(const FunctionDecl *D, const FullComment *FC, int LineNumber, + StringRef File, bool PublicOnly); +std::pair, std::unique_ptr> +emitInfo(const CXXMethodDecl *D, const FullComment *FC, int LineNumber, + StringRef File, bool PublicOnly); // Function to hash a given USR value for storage. // As USRs (Unified Symbol Resolution) could be large, especially for functions Index: clang-tools-extra/trunk/clang-doc/Serialize.cpp =================================================================== --- clang-tools-extra/trunk/clang-doc/Serialize.cpp +++ clang-tools-extra/trunk/clang-doc/Serialize.cpp @@ -335,30 +335,39 @@ parseParameters(I, D); } -std::unique_ptr emitInfo(const NamespaceDecl *D, const FullComment *FC, - int LineNumber, llvm::StringRef File, - bool PublicOnly) { +std::pair, std::unique_ptr> +emitInfo(const NamespaceDecl *D, const FullComment *FC, int LineNumber, + llvm::StringRef File, bool PublicOnly) { auto I = llvm::make_unique(); bool IsInAnonymousNamespace = false; populateInfo(*I, D, FC, IsInAnonymousNamespace); if (PublicOnly && ((IsInAnonymousNamespace || D->isAnonymousNamespace()) || !isPublic(D->getAccess(), D->getLinkageInternal()))) - return nullptr; + return {}; I->Name = D->isAnonymousNamespace() ? llvm::SmallString<16>("@nonymous_namespace") : I->Name; - return std::unique_ptr{std::move(I)}; + if (I->Namespace.empty() && I->USR == SymbolID()) + return {std::unique_ptr{std::move(I)}, nullptr}; + + SymbolID ParentUSR = I->Namespace.empty() ? SymbolID() : I->Namespace[0].USR; + + auto Parent = llvm::make_unique(); + Parent->USR = ParentUSR; + Parent->ChildNamespaces.emplace_back(I->USR, I->Name, InfoType::IT_namespace); + return {std::unique_ptr{std::move(I)}, + std::unique_ptr{std::move(Parent)}}; } -std::unique_ptr emitInfo(const RecordDecl *D, const FullComment *FC, - int LineNumber, llvm::StringRef File, - bool PublicOnly) { +std::pair, std::unique_ptr> +emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber, + llvm::StringRef File, bool PublicOnly) { auto I = llvm::make_unique(); bool IsInAnonymousNamespace = false; populateSymbolInfo(*I, D, FC, LineNumber, File, IsInAnonymousNamespace); if (PublicOnly && ((IsInAnonymousNamespace || !isPublic(D->getAccess(), D->getLinkageInternal())))) - return nullptr; + return {}; I->TagType = D->getTagKind(); parseFields(*I, D, PublicOnly); @@ -369,18 +378,44 @@ } parseBases(*I, C); } - return std::unique_ptr{std::move(I)}; + + if (I->Namespace.empty()) { + auto Parent = llvm::make_unique(); + Parent->USR = SymbolID(); + Parent->ChildRecords.emplace_back(I->USR, I->Name, InfoType::IT_record); + return {std::unique_ptr{std::move(I)}, + std::unique_ptr{std::move(Parent)}}; + } + + switch (I->Namespace[0].RefType) { + case InfoType::IT_namespace: { + auto Parent = llvm::make_unique(); + Parent->USR = I->Namespace[0].USR; + Parent->ChildRecords.emplace_back(I->USR, I->Name, InfoType::IT_record); + return {std::unique_ptr{std::move(I)}, + std::unique_ptr{std::move(Parent)}}; + } + case InfoType::IT_record: { + auto Parent = llvm::make_unique(); + Parent->USR = I->Namespace[0].USR; + Parent->ChildRecords.emplace_back(I->USR, I->Name, InfoType::IT_record); + return {std::unique_ptr{std::move(I)}, + std::unique_ptr{std::move(Parent)}}; + } + default: + llvm_unreachable("Invalid reference type"); + } } -std::unique_ptr emitInfo(const FunctionDecl *D, const FullComment *FC, - int LineNumber, llvm::StringRef File, - bool PublicOnly) { +std::pair, std::unique_ptr> +emitInfo(const FunctionDecl *D, const FullComment *FC, int LineNumber, + llvm::StringRef File, bool PublicOnly) { FunctionInfo Func; bool IsInAnonymousNamespace = false; populateFunctionInfo(Func, D, FC, LineNumber, File, IsInAnonymousNamespace); if (PublicOnly && ((IsInAnonymousNamespace || !isPublic(D->getAccess(), D->getLinkageInternal())))) - return nullptr; + return {}; Func.Access = clang::AccessSpecifier::AS_none; @@ -391,18 +426,19 @@ else I->USR = SymbolID(); I->ChildFunctions.emplace_back(std::move(Func)); - return std::unique_ptr{std::move(I)}; + // Info es wrapped in its parent scope so it's returned in the second position + return {nullptr, std::unique_ptr{std::move(I)}}; } -std::unique_ptr emitInfo(const CXXMethodDecl *D, const FullComment *FC, - int LineNumber, llvm::StringRef File, - bool PublicOnly) { +std::pair, std::unique_ptr> +emitInfo(const CXXMethodDecl *D, const FullComment *FC, int LineNumber, + llvm::StringRef File, bool PublicOnly) { FunctionInfo Func; bool IsInAnonymousNamespace = false; populateFunctionInfo(Func, D, FC, LineNumber, File, IsInAnonymousNamespace); if (PublicOnly && ((IsInAnonymousNamespace || !isPublic(D->getAccess(), D->getLinkageInternal())))) - return nullptr; + return {}; Func.IsMethod = true; @@ -422,18 +458,19 @@ auto I = llvm::make_unique(); I->USR = ParentUSR; I->ChildFunctions.emplace_back(std::move(Func)); - return std::unique_ptr{std::move(I)}; + // Info is wrapped in its parent scope so it's returned in the second position + return {nullptr, std::unique_ptr{std::move(I)}}; } -std::unique_ptr emitInfo(const EnumDecl *D, const FullComment *FC, - int LineNumber, llvm::StringRef File, - bool PublicOnly) { +std::pair, std::unique_ptr> +emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber, + llvm::StringRef File, bool PublicOnly) { EnumInfo Enum; bool IsInAnonymousNamespace = false; populateSymbolInfo(Enum, D, FC, LineNumber, File, IsInAnonymousNamespace); if (PublicOnly && ((IsInAnonymousNamespace || !isPublic(D->getAccess(), D->getLinkageInternal())))) - return nullptr; + return {}; Enum.Scoped = D->isScoped(); parseEnumerators(Enum, D); @@ -445,13 +482,17 @@ auto I = llvm::make_unique(); I->USR = Enum.Namespace[0].USR; I->ChildEnums.emplace_back(std::move(Enum)); - return std::unique_ptr{std::move(I)}; + // Info is wrapped in its parent scope so it's returned in the second + // position + return {nullptr, std::unique_ptr{std::move(I)}}; } case InfoType::IT_record: { auto I = llvm::make_unique(); I->USR = Enum.Namespace[0].USR; I->ChildEnums.emplace_back(std::move(Enum)); - return std::unique_ptr{std::move(I)}; + // Info is wrapped in its parent scope so it's returned in the second + // position + return {nullptr, std::unique_ptr{std::move(I)}}; } default: break; @@ -462,7 +503,8 @@ auto I = llvm::make_unique(); I->USR = SymbolID(); I->ChildEnums.emplace_back(std::move(Enum)); - return std::unique_ptr{std::move(I)}; + // Info is wrapped in its parent scope so it's returned in the second position + return {nullptr, std::unique_ptr{std::move(I)}}; } } // namespace serialize Index: clang-tools-extra/trunk/unittests/clang-doc/ClangDocTest.cpp =================================================================== --- clang-tools-extra/trunk/unittests/clang-doc/ClangDocTest.cpp +++ clang-tools-extra/trunk/unittests/clang-doc/ClangDocTest.cpp @@ -130,11 +130,12 @@ ASSERT_EQ(Expected->ChildNamespaces.size(), Actual->ChildNamespaces.size()); for (size_t Idx = 0; Idx < Actual->ChildNamespaces.size(); ++Idx) - EXPECT_EQ(Expected->ChildNamespaces[Idx], Actual->ChildNamespaces[Idx]); + CheckReference(Expected->ChildNamespaces[Idx], + Actual->ChildNamespaces[Idx]); ASSERT_EQ(Expected->ChildRecords.size(), Actual->ChildRecords.size()); for (size_t Idx = 0; Idx < Actual->ChildRecords.size(); ++Idx) - EXPECT_EQ(Expected->ChildRecords[Idx], Actual->ChildRecords[Idx]); + CheckReference(Expected->ChildRecords[Idx], Actual->ChildRecords[Idx]); ASSERT_EQ(Expected->ChildFunctions.size(), Actual->ChildFunctions.size()); for (size_t Idx = 0; Idx < Actual->ChildFunctions.size(); ++Idx) @@ -167,7 +168,7 @@ ASSERT_EQ(Expected->ChildRecords.size(), Actual->ChildRecords.size()); for (size_t Idx = 0; Idx < Actual->ChildRecords.size(); ++Idx) - EXPECT_EQ(Expected->ChildRecords[Idx], Actual->ChildRecords[Idx]); + CheckReference(Expected->ChildRecords[Idx], Actual->ChildRecords[Idx]); ASSERT_EQ(Expected->ChildFunctions.size(), Actual->ChildFunctions.size()); for (size_t Idx = 0; Idx < Actual->ChildFunctions.size(); ++Idx) Index: clang-tools-extra/trunk/unittests/clang-doc/SerializeTest.cpp =================================================================== --- clang-tools-extra/trunk/unittests/clang-doc/SerializeTest.cpp +++ clang-tools-extra/trunk/unittests/clang-doc/SerializeTest.cpp @@ -35,48 +35,30 @@ ClangDocSerializeTestVisitor(EmittedInfoList &EmittedInfos, bool Public) : EmittedInfos(EmittedInfos), Public(Public) {} - bool VisitNamespaceDecl(const NamespaceDecl *D) { + template bool mapDecl(const T *D) { auto I = serialize::emitInfo(D, getComment(D), /*Line=*/0, /*File=*/"test.cpp", Public); - if (I) - EmittedInfos.emplace_back(std::move(I)); + if (I.first) + EmittedInfos.emplace_back(std::move(I.first)); + if (I.second) + EmittedInfos.emplace_back(std::move(I.second)); return true; } + bool VisitNamespaceDecl(const NamespaceDecl *D) { return mapDecl(D); } + bool VisitFunctionDecl(const FunctionDecl *D) { // Don't visit CXXMethodDecls twice if (dyn_cast(D)) return true; - auto I = serialize::emitInfo(D, getComment(D), /*Line=*/0, - /*File=*/"test.cpp", Public); - if (I) - EmittedInfos.emplace_back(std::move(I)); - return true; + return mapDecl(D); } - bool VisitCXXMethodDecl(const CXXMethodDecl *D) { - auto I = serialize::emitInfo(D, getComment(D), /*Line=*/0, - /*File=*/"test.cpp", Public); - if (I) - EmittedInfos.emplace_back(std::move(I)); - return true; - } + bool VisitCXXMethodDecl(const CXXMethodDecl *D) { return mapDecl(D); } - bool VisitRecordDecl(const RecordDecl *D) { - auto I = serialize::emitInfo(D, getComment(D), /*Line=*/0, - /*File=*/"test.cpp", Public); - if (I) - EmittedInfos.emplace_back(std::move(I)); - return true; - } + bool VisitRecordDecl(const RecordDecl *D) { return mapDecl(D); } - bool VisitEnumDecl(const EnumDecl *D) { - auto I = serialize::emitInfo(D, getComment(D), /*Line=*/0, - /*File=*/"test.cpp", Public); - if (I) - EmittedInfos.emplace_back(std::move(I)); - return true; - } + bool VisitEnumDecl(const EnumDecl *D) { return mapDecl(D); } }; void ExtractInfosFromCode(StringRef Code, size_t NumExpectedInfos, bool Public, @@ -101,19 +83,19 @@ // Test serialization of namespace declarations. TEST(SerializeTest, emitNamespaceInfo) { EmittedInfoList Infos; - ExtractInfosFromCode("namespace A { namespace B { void f() {} } }", 3, + ExtractInfosFromCode("namespace A { namespace B { void f() {} } }", 5, /*Public=*/false, Infos); NamespaceInfo *A = InfoAsNamespace(Infos[0].get()); NamespaceInfo ExpectedA(EmptySID, "A"); CheckNamespaceInfo(&ExpectedA, A); - NamespaceInfo *B = InfoAsNamespace(Infos[1].get()); + NamespaceInfo *B = InfoAsNamespace(Infos[2].get()); NamespaceInfo ExpectedB(EmptySID, "B"); ExpectedB.Namespace.emplace_back(EmptySID, "A", InfoType::IT_namespace); CheckNamespaceInfo(&ExpectedB, B); - NamespaceInfo *BWithFunction = InfoAsNamespace(Infos[2].get()); + NamespaceInfo *BWithFunction = InfoAsNamespace(Infos[4].get()); NamespaceInfo ExpectedBWithFunction(EmptySID); FunctionInfo F; F.Name = "f"; @@ -127,7 +109,7 @@ TEST(SerializeTest, emitAnonymousNamespaceInfo) { EmittedInfoList Infos; - ExtractInfosFromCode("namespace { }", 1, /*Public=*/false, Infos); + ExtractInfosFromCode("namespace { }", 2, /*Public=*/false, Infos); NamespaceInfo *A = InfoAsNamespace(Infos[0].get()); NamespaceInfo ExpectedA(EmptySID); @@ -151,7 +133,7 @@ template <> void F::TemplateMethod(); typedef struct {} G;)raw", - 7, /*Public=*/false, Infos); + 10, /*Public=*/false, Infos); RecordInfo *E = InfoAsRecord(Infos[0].get()); RecordInfo ExpectedE(EmptySID, "E"); @@ -159,7 +141,7 @@ ExpectedE.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); CheckRecordInfo(&ExpectedE, E); - RecordInfo *RecordWithEConstructor = InfoAsRecord(Infos[1].get()); + RecordInfo *RecordWithEConstructor = InfoAsRecord(Infos[2].get()); RecordInfo ExpectedRecordWithEConstructor(EmptySID); FunctionInfo EConstructor; EConstructor.Name = "E"; @@ -173,7 +155,7 @@ std::move(EConstructor)); CheckRecordInfo(&ExpectedRecordWithEConstructor, RecordWithEConstructor); - RecordInfo *RecordWithMethod = InfoAsRecord(Infos[2].get()); + RecordInfo *RecordWithMethod = InfoAsRecord(Infos[3].get()); RecordInfo ExpectedRecordWithMethod(EmptySID); FunctionInfo Method; Method.Name = "ProtectedMethod"; @@ -186,13 +168,13 @@ ExpectedRecordWithMethod.ChildFunctions.emplace_back(std::move(Method)); CheckRecordInfo(&ExpectedRecordWithMethod, RecordWithMethod); - RecordInfo *F = InfoAsRecord(Infos[3].get()); + RecordInfo *F = InfoAsRecord(Infos[4].get()); RecordInfo ExpectedF(EmptySID, "F"); ExpectedF.TagType = TagTypeKind::TTK_Struct; ExpectedF.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); CheckRecordInfo(&ExpectedF, F); - RecordInfo *RecordWithTemplateMethod = InfoAsRecord(Infos[4].get()); + RecordInfo *RecordWithTemplateMethod = InfoAsRecord(Infos[6].get()); RecordInfo ExpectedRecordWithTemplateMethod(EmptySID); FunctionInfo TemplateMethod; TemplateMethod.Name = "TemplateMethod"; @@ -206,7 +188,7 @@ std::move(TemplateMethod)); CheckRecordInfo(&ExpectedRecordWithTemplateMethod, RecordWithTemplateMethod); - RecordInfo *TemplatedRecord = InfoAsRecord(Infos[5].get()); + RecordInfo *TemplatedRecord = InfoAsRecord(Infos[7].get()); RecordInfo ExpectedTemplatedRecord(EmptySID); FunctionInfo SpecializedTemplateMethod; SpecializedTemplateMethod.Name = "TemplateMethod"; @@ -224,7 +206,7 @@ std::move(SpecializedTemplateMethod)); CheckRecordInfo(&ExpectedTemplatedRecord, TemplatedRecord); - RecordInfo *G = InfoAsRecord(Infos[6].get()); + RecordInfo *G = InfoAsRecord(Infos[8].get()); RecordInfo ExpectedG(EmptySID, "G"); ExpectedG.TagType = TagTypeKind::TTK_Struct; ExpectedG.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); @@ -262,7 +244,7 @@ TEST(SerializeTest, emitUndefinedRecordInfo) { EmittedInfoList Infos; - ExtractInfosFromCode("class E;", 1, /*Public=*/false, Infos); + ExtractInfosFromCode("class E;", 2, /*Public=*/false, Infos); RecordInfo *E = InfoAsRecord(Infos[0].get()); RecordInfo ExpectedE(EmptySID, "E"); @@ -273,7 +255,7 @@ TEST(SerializeTest, emitRecordMemberInfo) { EmittedInfoList Infos; - ExtractInfosFromCode("struct E { int I; };", 1, /*Public=*/false, Infos); + ExtractInfosFromCode("struct E { int I; };", 2, /*Public=*/false, Infos); RecordInfo *E = InfoAsRecord(Infos[0].get()); RecordInfo ExpectedE(EmptySID, "E"); @@ -285,7 +267,7 @@ TEST(SerializeTest, emitInternalRecordInfo) { EmittedInfoList Infos; - ExtractInfosFromCode("class E { class G {}; };", 2, /*Public=*/false, Infos); + ExtractInfosFromCode("class E { class G {}; };", 4, /*Public=*/false, Infos); RecordInfo *E = InfoAsRecord(Infos[0].get()); RecordInfo ExpectedE(EmptySID, "E"); @@ -293,7 +275,7 @@ ExpectedE.TagType = TagTypeKind::TTK_Class; CheckRecordInfo(&ExpectedE, E); - RecordInfo *G = InfoAsRecord(Infos[1].get()); + RecordInfo *G = InfoAsRecord(Infos[2].get()); RecordInfo ExpectedG(EmptySID, "G"); ExpectedG.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); ExpectedG.TagType = TagTypeKind::TTK_Class; @@ -336,7 +318,7 @@ CheckNamespaceInfo(&ExpectedBWithFunction, BWithFunction); } -TEST(SerializeTest, emitInheritedRecordInfo) { +TEST(SerializeTest, ) { EmittedInfoList Infos; ExtractInfosFromCode(R"raw(class F {}; class G {} ; @@ -344,7 +326,7 @@ template class H {} ; class I : public H {} ;)raw", - 5, /*Public=*/false, Infos); + 10, /*Public=*/false, Infos); RecordInfo *F = InfoAsRecord(Infos[0].get()); RecordInfo ExpectedF(EmptySID, "F"); @@ -352,13 +334,13 @@ ExpectedF.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); CheckRecordInfo(&ExpectedF, F); - RecordInfo *G = InfoAsRecord(Infos[1].get()); + RecordInfo *G = InfoAsRecord(Infos[2].get()); RecordInfo ExpectedG(EmptySID, "G"); ExpectedG.TagType = TagTypeKind::TTK_Class; ExpectedG.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); CheckRecordInfo(&ExpectedG, G); - RecordInfo *E = InfoAsRecord(Infos[2].get()); + RecordInfo *E = InfoAsRecord(Infos[4].get()); RecordInfo ExpectedE(EmptySID, "E"); ExpectedE.Parents.emplace_back(EmptySID, "F", InfoType::IT_record); ExpectedE.VirtualParents.emplace_back(EmptySID, "G", InfoType::IT_record); @@ -366,13 +348,13 @@ ExpectedE.TagType = TagTypeKind::TTK_Class; CheckRecordInfo(&ExpectedE, E); - RecordInfo *H = InfoAsRecord(Infos[3].get()); + RecordInfo *H = InfoAsRecord(Infos[6].get()); RecordInfo ExpectedH(EmptySID, "H"); ExpectedH.TagType = TagTypeKind::TTK_Class; ExpectedH.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); CheckRecordInfo(&ExpectedH, H); - RecordInfo *I = InfoAsRecord(Infos[4].get()); + RecordInfo *I = InfoAsRecord(Infos[8].get()); RecordInfo ExpectedI(EmptySID, "I"); ExpectedI.Parents.emplace_back(EmptySID, "H", InfoType::IT_record); ExpectedI.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); @@ -412,5 +394,46 @@ CheckNamespaceInfo(&ExpectedBWithExportedFunction, BWithExportedFunction); } +// Test serialization of child records in namespaces and other records +TEST(SerializeTest, emitChildRecords) { + EmittedInfoList Infos; + ExtractInfosFromCode("class A { class B {}; }; namespace { class C {}; } ", 8, + /*Public=*/false, Infos); + + NamespaceInfo *ParentA = InfoAsNamespace(Infos[1].get()); + NamespaceInfo ExpectedParentA(EmptySID); + ExpectedParentA.ChildRecords.emplace_back(EmptySID, "A", InfoType::IT_record); + CheckNamespaceInfo(&ExpectedParentA, ParentA); + + RecordInfo *ParentB = InfoAsRecord(Infos[3].get()); + RecordInfo ExpectedParentB(EmptySID); + ExpectedParentB.ChildRecords.emplace_back(EmptySID, "B", InfoType::IT_record); + CheckRecordInfo(&ExpectedParentB, ParentB); + + NamespaceInfo *ParentC = InfoAsNamespace(Infos[7].get()); + NamespaceInfo ExpectedParentC(EmptySID); + ExpectedParentC.ChildRecords.emplace_back(EmptySID, "C", InfoType::IT_record); + CheckNamespaceInfo(&ExpectedParentC, ParentC); +} + +// Test serialization of child namespaces +TEST(SerializeTest, emitChildNamespaces) { + EmittedInfoList Infos; + ExtractInfosFromCode("namespace A { namespace B { } }", 4, /*Public=*/false, + Infos); + + NamespaceInfo *ParentA = InfoAsNamespace(Infos[1].get()); + NamespaceInfo ExpectedParentA(EmptySID); + ExpectedParentA.ChildNamespaces.emplace_back(EmptySID, "A", + InfoType::IT_namespace); + CheckNamespaceInfo(&ExpectedParentA, ParentA); + + NamespaceInfo *ParentB = InfoAsNamespace(Infos[3].get()); + NamespaceInfo ExpectedParentB(EmptySID); + ExpectedParentB.ChildNamespaces.emplace_back(EmptySID, "B", + InfoType::IT_namespace); + CheckNamespaceInfo(&ExpectedParentB, ParentB); +} + } // namespace doc } // end namespace clang