[clang-doc] Serialize child namespaces and records
authorJulie Hockett <juliehockett@google.com>
Tue, 2 Jul 2019 19:59:56 +0000 (19:59 +0000)
committerJulie Hockett <juliehockett@google.com>
Tue, 2 Jul 2019 19:59:56 +0000 (19:59 +0000)
Serialization of child namespaces and records is now handled.
Namespaces can have child records and child namespaces.
Records can only have child records.

Committed on behalf of Diego Astiazarán (diegoaat97@gmail.com).

Differential Revision: https://reviews.llvm.org/D63911

llvm-svn: 364963

clang-tools-extra/clang-doc/Mapper.cpp
clang-tools-extra/clang-doc/Serialize.cpp
clang-tools-extra/clang-doc/Serialize.h
clang-tools-extra/unittests/clang-doc/ClangDocTest.cpp
clang-tools-extra/unittests/clang-doc/SerializeTest.cpp

index 654096d..9bf2015 100644 (file)
@@ -43,9 +43,12 @@ template <typename T> bool MapASTVisitor::mapDecl(const T *D) {
 
   // 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 462918b..4962fa0 100644 (file)
@@ -335,30 +335,39 @@ static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D,
   parseParameters(I, D);
 }
 
-std::unique_ptr<Info> emitInfo(const NamespaceDecl *D, const FullComment *FC,
-                               int LineNumber, llvm::StringRef File,
-                               bool PublicOnly) {
+std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
+emitInfo(const NamespaceDecl *D, const FullComment *FC, int LineNumber,
+         llvm::StringRef File, bool PublicOnly) {
   auto I = llvm::make_unique<NamespaceInfo>();
   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<Info>{std::move(I)};
+  if (I->Namespace.empty() && I->USR == SymbolID())
+    return {std::unique_ptr<Info>{std::move(I)}, nullptr};
+
+  SymbolID ParentUSR = I->Namespace.empty() ? SymbolID() : I->Namespace[0].USR;
+
+  auto Parent = llvm::make_unique<NamespaceInfo>();
+  Parent->USR = ParentUSR;
+  Parent->ChildNamespaces.emplace_back(I->USR, I->Name, InfoType::IT_namespace);
+  return {std::unique_ptr<Info>{std::move(I)},
+          std::unique_ptr<Info>{std::move(Parent)}};
 }
 
-std::unique_ptr<Info> emitInfo(const RecordDecl *D, const FullComment *FC,
-                               int LineNumber, llvm::StringRef File,
-                               bool PublicOnly) {
+std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
+emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber,
+         llvm::StringRef File, bool PublicOnly) {
   auto I = llvm::make_unique<RecordInfo>();
   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 @@ std::unique_ptr<Info> emitInfo(const RecordDecl *D, const FullComment *FC,
     }
     parseBases(*I, C);
   }
-  return std::unique_ptr<Info>{std::move(I)};
+
+  if (I->Namespace.empty()) {
+    auto Parent = llvm::make_unique<NamespaceInfo>();
+    Parent->USR = SymbolID();
+    Parent->ChildRecords.emplace_back(I->USR, I->Name, InfoType::IT_record);
+    return {std::unique_ptr<Info>{std::move(I)},
+            std::unique_ptr<Info>{std::move(Parent)}};
+  }
+
+  switch (I->Namespace[0].RefType) {
+  case InfoType::IT_namespace: {
+    auto Parent = llvm::make_unique<NamespaceInfo>();
+    Parent->USR = I->Namespace[0].USR;
+    Parent->ChildRecords.emplace_back(I->USR, I->Name, InfoType::IT_record);
+    return {std::unique_ptr<Info>{std::move(I)},
+            std::unique_ptr<Info>{std::move(Parent)}};
+  }
+  case InfoType::IT_record: {
+    auto Parent = llvm::make_unique<RecordInfo>();
+    Parent->USR = I->Namespace[0].USR;
+    Parent->ChildRecords.emplace_back(I->USR, I->Name, InfoType::IT_record);
+    return {std::unique_ptr<Info>{std::move(I)},
+            std::unique_ptr<Info>{std::move(Parent)}};
+  }
+  default:
+    llvm_unreachable("Invalid reference type");
+  }
 }
 
-std::unique_ptr<Info> emitInfo(const FunctionDecl *D, const FullComment *FC,
-                               int LineNumber, llvm::StringRef File,
-                               bool PublicOnly) {
+std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
+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 @@ std::unique_ptr<Info> emitInfo(const FunctionDecl *D, const FullComment *FC,
   else
     I->USR = SymbolID();
   I->ChildFunctions.emplace_back(std::move(Func));
-  return std::unique_ptr<Info>{std::move(I)};
+  // Info es wrapped in its parent scope so it's returned in the second position
+  return {nullptr, std::unique_ptr<Info>{std::move(I)}};
 }
 
-std::unique_ptr<Info> emitInfo(const CXXMethodDecl *D, const FullComment *FC,
-                               int LineNumber, llvm::StringRef File,
-                               bool PublicOnly) {
+std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
+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 @@ std::unique_ptr<Info> emitInfo(const CXXMethodDecl *D, const FullComment *FC,
   auto I = llvm::make_unique<RecordInfo>();
   I->USR = ParentUSR;
   I->ChildFunctions.emplace_back(std::move(Func));
-  return std::unique_ptr<Info>{std::move(I)};
+  // Info is wrapped in its parent scope so it's returned in the second position
+  return {nullptr, std::unique_ptr<Info>{std::move(I)}};
 }
 
-std::unique_ptr<Info> emitInfo(const EnumDecl *D, const FullComment *FC,
-                               int LineNumber, llvm::StringRef File,
-                               bool PublicOnly) {
+std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
+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 @@ std::unique_ptr<Info> emitInfo(const EnumDecl *D, const FullComment *FC,
       auto I = llvm::make_unique<NamespaceInfo>();
       I->USR = Enum.Namespace[0].USR;
       I->ChildEnums.emplace_back(std::move(Enum));
-      return std::unique_ptr<Info>{std::move(I)};
+      // Info is wrapped in its parent scope so it's returned in the second
+      // position
+      return {nullptr, std::unique_ptr<Info>{std::move(I)}};
     }
     case InfoType::IT_record: {
       auto I = llvm::make_unique<RecordInfo>();
       I->USR = Enum.Namespace[0].USR;
       I->ChildEnums.emplace_back(std::move(Enum));
-      return std::unique_ptr<Info>{std::move(I)};
+      // Info is wrapped in its parent scope so it's returned in the second
+      // position
+      return {nullptr, std::unique_ptr<Info>{std::move(I)}};
     }
     default:
       break;
@@ -462,7 +503,8 @@ std::unique_ptr<Info> emitInfo(const EnumDecl *D, const FullComment *FC,
   auto I = llvm::make_unique<NamespaceInfo>();
   I->USR = SymbolID();
   I->ChildEnums.emplace_back(std::move(Enum));
-  return std::unique_ptr<Info>{std::move(I)};
+  // Info is wrapped in its parent scope so it's returned in the second position
+  return {nullptr, std::unique_ptr<Info>{std::move(I)}};
 }
 
 } // namespace serialize
index 3a2c93d..1301e43 100644 (file)
@@ -27,16 +27,30 @@ namespace clang {
 namespace doc {
 namespace serialize {
 
-std::unique_ptr<Info> emitInfo(const NamespaceDecl *D, const FullComment *FC,
-                               int LineNumber, StringRef File, bool PublicOnly);
-std::unique_ptr<Info> emitInfo(const RecordDecl *D, const FullComment *FC,
-                               int LineNumber, StringRef File, bool PublicOnly);
-std::unique_ptr<Info> emitInfo(const EnumDecl *D, const FullComment *FC,
-                               int LineNumber, StringRef File, bool PublicOnly);
-std::unique_ptr<Info> emitInfo(const FunctionDecl *D, const FullComment *FC,
-                               int LineNumber, StringRef File, bool PublicOnly);
-std::unique_ptr<Info> 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<Info>, std::unique_ptr<Info>>
+emitInfo(const NamespaceDecl *D, const FullComment *FC, int LineNumber,
+         StringRef File, bool PublicOnly);
+std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
+emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber,
+         StringRef File, bool PublicOnly);
+std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
+emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber,
+         StringRef File, bool PublicOnly);
+std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
+emitInfo(const FunctionDecl *D, const FullComment *FC, int LineNumber,
+         StringRef File, bool PublicOnly);
+std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
+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 7576798..064ec28 100644 (file)
@@ -130,11 +130,12 @@ void CheckNamespaceInfo(NamespaceInfo *Expected, NamespaceInfo *Actual) {
 
   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 @@ void CheckRecordInfo(RecordInfo *Expected, RecordInfo *Actual) {
 
   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 44cc912..9384fab 100644 (file)
@@ -35,48 +35,30 @@ public:
   ClangDocSerializeTestVisitor(EmittedInfoList &EmittedInfos, bool Public)
       : EmittedInfos(EmittedInfos), Public(Public) {}
 
-  bool VisitNamespaceDecl(const NamespaceDecl *D) {
+  template <typename T> 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<CXXMethodDecl>(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 @@ void ExtractInfosFromCodeWithArgs(StringRef Code, size_t NumExpectedInfos,
 // 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, emitNamespaceInfo) {
 
 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 @@ struct F {
 template <>
 void F<int>::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 @@ typedef struct {} G;)raw",
   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 @@ typedef struct {} G;)raw",
       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 @@ typedef struct {} G;)raw",
   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 @@ typedef struct {} G;)raw",
       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 @@ typedef struct {} G;)raw",
       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, emitEnumInfo) {
 
 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, emitUndefinedRecordInfo) {
 
 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, emitRecordMemberInfo) {
 
 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 @@ TEST(SerializeTest, emitInternalRecordInfo) {
   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 @@ TEST(SerializeTest, emitInlinedFunctionInfo) {
   CheckNamespaceInfo(&ExpectedBWithFunction, BWithFunction);
 }
 
-TEST(SerializeTest, emitInheritedRecordInfo) {
+TEST(SerializeTest, ) {
   EmittedInfoList Infos;
   ExtractInfosFromCode(R"raw(class F {};
 class G {} ;
@@ -344,7 +326,7 @@ class E : public F, virtual private G {};
 template <typename T>
 class H {} ;
 class I : public H<int> {} ;)raw",
-                       5, /*Public=*/false, Infos);
+                       10, /*Public=*/false, Infos);
 
   RecordInfo *F = InfoAsRecord(Infos[0].get());
   RecordInfo ExpectedF(EmptySID, "F");
@@ -352,13 +334,13 @@ class I : public H<int> {} ;)raw",
   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 @@ class I : public H<int> {} ;)raw",
   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<int>", InfoType::IT_record);
   ExpectedI.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"});
@@ -412,5 +394,46 @@ export double exportedModuleFunction(double y);)raw",
   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