Index: clang-tools-extra/clang-doc/Mapper.cpp =================================================================== --- clang-tools-extra/clang-doc/Mapper.cpp +++ clang-tools-extra/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/clang-doc/Serialize.h =================================================================== --- clang-tools-extra/clang-doc/Serialize.h +++ clang-tools-extra/clang-doc/Serialize.h @@ -27,16 +27,21 @@ 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); +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/clang-doc/Serialize.cpp =================================================================== --- clang-tools-extra/clang-doc/Serialize.cpp +++ clang-tools-extra/clang-doc/Serialize.cpp @@ -323,36 +323,67 @@ 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) { if (PublicOnly && ((D->isAnonymousNamespace()) || !isPublic(D->getAccess(), D->getLinkageInternal()))) - return nullptr; + return {nullptr, nullptr}; auto I = llvm::make_unique(); populateInfo(*I, D, FC); - 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) { if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal())) - return nullptr; + return {nullptr, nullptr}; auto I = llvm::make_unique(); populateSymbolInfo(*I, D, FC, LineNumber, File); I->TagType = D->getTagKind(); parseFields(*I, D, PublicOnly); if (const auto *C = dyn_cast(D)) 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)}}; + } + + if (I->Namespace[0].RefType == 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)}}; + } + + // At this point any parent will be a RecordInfo + 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)}}; } -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) { if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal())) - return nullptr; + return {nullptr, nullptr}; FunctionInfo Func; populateFunctionInfo(Func, D, FC, LineNumber, File); Func.Access = clang::AccessSpecifier::AS_none; @@ -364,14 +395,14 @@ else I->USR = SymbolID(); I->ChildFunctions.emplace_back(std::move(Func)); - return std::unique_ptr{std::move(I)}; + return {std::unique_ptr{std::move(I)}, nullptr}; } -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) { if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal())) - return nullptr; + return {nullptr, nullptr}; FunctionInfo Func; populateFunctionInfo(Func, D, FC, LineNumber, File); Func.IsMethod = true; @@ -385,14 +416,14 @@ auto I = llvm::make_unique(); I->USR = ParentUSR; I->ChildFunctions.emplace_back(std::move(Func)); - return std::unique_ptr{std::move(I)}; + return {std::unique_ptr{std::move(I)}, nullptr}; } -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) { if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal())) - return nullptr; + return {nullptr, nullptr}; EnumInfo Enum; populateSymbolInfo(Enum, D, FC, LineNumber, File); Enum.Scoped = D->isScoped(); @@ -405,13 +436,13 @@ 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)}; + return {std::unique_ptr{std::move(I)}, nullptr}; } 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)}; + return {std::unique_ptr{std::move(I)}, nullptr}; } default: break; @@ -422,7 +453,7 @@ auto I = llvm::make_unique(); I->USR = SymbolID(); I->ChildEnums.emplace_back(std::move(Enum)); - return std::unique_ptr{std::move(I)}; + return {std::unique_ptr{std::move(I)}, nullptr}; } } // namespace serialize Index: clang-tools-extra/unittests/clang-doc/SerializeTest.cpp =================================================================== --- clang-tools-extra/unittests/clang-doc/SerializeTest.cpp +++ clang-tools-extra/unittests/clang-doc/SerializeTest.cpp @@ -38,8 +38,10 @@ bool VisitNamespaceDecl(const NamespaceDecl *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; } @@ -49,32 +51,40 @@ return true; 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 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)); + if (I.first) + EmittedInfos.emplace_back(std::move(I.first)); + if (I.second) + EmittedInfos.emplace_back(std::move(I.second)); return true; } 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)); + if (I.first) + EmittedInfos.emplace_back(std::move(I.first)); + if (I.second) + EmittedInfos.emplace_back(std::move(I.second)); return true; } 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)); + if (I.first) + EmittedInfos.emplace_back(std::move(I.first)); + if (I.second) + EmittedInfos.emplace_back(std::move(I.second)); return true; } }; @@ -101,19 +111,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 +137,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); @@ -142,7 +152,8 @@ E() {} protected: void ProtectedMethod(); -};)raw", 3, /*Public=*/false, Infos); +};)raw", + 4, /*Public=*/false, Infos); RecordInfo *E = InfoAsRecord(Infos[0].get()); RecordInfo ExpectedE(EmptySID, "E"); @@ -150,7 +161,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"; @@ -164,7 +175,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"; @@ -208,7 +219,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"); @@ -219,7 +230,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"); @@ -231,7 +242,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"); @@ -239,7 +250,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; @@ -285,7 +296,7 @@ TEST(SerializeTest, emitInheritedRecordInfo) { EmittedInfoList Infos; ExtractInfosFromCode( - "class F {}; class G{} ; class E : public F, virtual private G {};", 3, + "class F {}; class G{} ; class E : public F, virtual private G {};", 6, /*Public=*/false, Infos); RecordInfo *F = InfoAsRecord(Infos[0].get()); @@ -294,13 +305,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); @@ -341,5 +352,49 @@ 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(Infos[0]->USR, "A", + InfoType::IT_record); + CheckNamespaceInfo(&ExpectedParentA, ParentA); + + RecordInfo *ParentB = InfoAsRecord(Infos[3].get()); + RecordInfo ExpectedParentB(Infos[0]->USR); + ExpectedParentB.ChildRecords.emplace_back(Infos[2]->USR, "B", + InfoType::IT_record); + CheckRecordInfo(&ExpectedParentB, ParentB); + + NamespaceInfo *ParentC = InfoAsNamespace(Infos[7].get()); + NamespaceInfo ExpectedParentC(Infos[4]->USR); + ExpectedParentC.ChildRecords.emplace_back(Infos[6]->USR, "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(Infos[0]->USR, "A", + InfoType::IT_namespace); + CheckNamespaceInfo(&ExpectedParentA, ParentA); + + NamespaceInfo *ParentB = InfoAsNamespace(Infos[3].get()); + NamespaceInfo ExpectedParentB(Infos[0]->USR); + ExpectedParentB.ChildNamespaces.emplace_back(Infos[2]->USR, "B", + InfoType::IT_namespace); + CheckNamespaceInfo(&ExpectedParentB, ParentB); +} + } // namespace doc } // end namespace clang