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,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/clang-doc/Serialize.cpp =================================================================== --- clang-tools-extra/clang-doc/Serialize.cpp +++ clang-tools-extra/clang-doc/Serialize.cpp @@ -323,36 +323,71 @@ 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 {}; 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 {}; 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)}}; + } + + 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) { if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal())) - return nullptr; + return {}; FunctionInfo Func; populateFunctionInfo(Func, D, FC, LineNumber, File); Func.Access = clang::AccessSpecifier::AS_none; @@ -364,14 +399,15 @@ 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) { if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal())) - return nullptr; + return {}; FunctionInfo Func; populateFunctionInfo(Func, D, FC, LineNumber, File); Func.IsMethod = true; @@ -385,14 +421,15 @@ 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) { if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal())) - return nullptr; + return {}; EnumInfo Enum; populateSymbolInfo(Enum, D, FC, LineNumber, File); Enum.Scoped = D->isScoped(); @@ -405,13 +442,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; @@ -422,7 +463,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/unittests/clang-doc/ClangDocTest.cpp =================================================================== --- clang-tools-extra/unittests/clang-doc/ClangDocTest.cpp +++ clang-tools-extra/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) @@ -165,7 +166,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/unittests/clang-doc/SerializeTest.cpp =================================================================== --- clang-tools-extra/unittests/clang-doc/SerializeTest.cpp +++ clang-tools-extra/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); @@ -142,7 +124,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 +133,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 +147,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 +191,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 +202,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 +214,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 +222,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 +268,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 +277,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 +324,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