[clang-tools-extra] 94bb9e1 - [clang-doc] Serialize record files with mangled name (#148021)

via cfe-commits cfe-commits at lists.llvm.org
Fri Jul 11 13:39:46 PDT 2025


Author: Erick Velez
Date: 2025-07-11T13:39:41-07:00
New Revision: 94bb9e12ec4ec243aac747910c5ae6359f354642

URL: https://github.com/llvm/llvm-project/commit/94bb9e12ec4ec243aac747910c5ae6359f354642
DIFF: https://github.com/llvm/llvm-project/commit/94bb9e12ec4ec243aac747910c5ae6359f354642.diff

LOG: [clang-doc] Serialize record files with mangled name (#148021)

This patch changes JSON file serialization. Now, files are serialized
to a single directory instead of nesting them based on namespaces. The
global namespace retains the "index.json" name.

This solves the problem of class template specializations being serialized to the
same file as its base template. This is also planned as part of
future integration with the Mustache generator which will consume the JSON files.

Added: 
    clang-tools-extra/test/clang-doc/json/class-specialization.cpp

Modified: 
    clang-tools-extra/clang-doc/BitcodeReader.cpp
    clang-tools-extra/clang-doc/BitcodeWriter.cpp
    clang-tools-extra/clang-doc/BitcodeWriter.h
    clang-tools-extra/clang-doc/JSONGenerator.cpp
    clang-tools-extra/clang-doc/Representation.cpp
    clang-tools-extra/clang-doc/Representation.h
    clang-tools-extra/clang-doc/Serialize.cpp
    clang-tools-extra/test/clang-doc/json/class-requires.cpp
    clang-tools-extra/test/clang-doc/json/class-template.cpp
    clang-tools-extra/test/clang-doc/json/class.cpp
    clang-tools-extra/test/clang-doc/json/compound-constraints.cpp
    clang-tools-extra/test/clang-doc/json/concept.cpp
    clang-tools-extra/test/clang-doc/json/function-requires.cpp
    clang-tools-extra/test/clang-doc/json/function-specifiers.cpp
    clang-tools-extra/test/clang-doc/json/method-template.cpp
    clang-tools-extra/test/clang-doc/json/namespace.cpp
    clang-tools-extra/test/clang-doc/json/nested-namespace.cpp
    clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clang-doc/BitcodeReader.cpp b/clang-tools-extra/clang-doc/BitcodeReader.cpp
index f756ae6d897c8..dce34a8434ff8 100644
--- a/clang-tools-extra/clang-doc/BitcodeReader.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeReader.cpp
@@ -180,6 +180,8 @@ static llvm::Error parseRecord(const Record &R, unsigned ID,
     return decodeRecord(R, I->TagType, Blob);
   case RECORD_IS_TYPE_DEF:
     return decodeRecord(R, I->IsTypeDef, Blob);
+  case RECORD_MANGLED_NAME:
+    return decodeRecord(R, I->MangledName, Blob);
   default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                    "invalid field for RecordInfo");

diff  --git a/clang-tools-extra/clang-doc/BitcodeWriter.cpp b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
index 3cc0d4ad332f0..eed23726e17bf 100644
--- a/clang-tools-extra/clang-doc/BitcodeWriter.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
@@ -189,6 +189,7 @@ static const llvm::IndexedMap<RecordIdDsc, RecordIdToIndexFunctor>
           {RECORD_LOCATION, {"Location", &genLocationAbbrev}},
           {RECORD_TAG_TYPE, {"TagType", &genIntAbbrev}},
           {RECORD_IS_TYPE_DEF, {"IsTypeDef", &genBoolAbbrev}},
+          {RECORD_MANGLED_NAME, {"MangledName", &genStringAbbrev}},
           {BASE_RECORD_USR, {"USR", &genSymbolIdAbbrev}},
           {BASE_RECORD_NAME, {"Name", &genStringAbbrev}},
           {BASE_RECORD_PATH, {"Path", &genStringAbbrev}},
@@ -271,7 +272,8 @@ static const std::vector<std::pair<BlockId, std::vector<RecordId>>>
         // Record Block
         {BI_RECORD_BLOCK_ID,
          {RECORD_USR, RECORD_NAME, RECORD_PATH, RECORD_DEFLOCATION,
-          RECORD_LOCATION, RECORD_TAG_TYPE, RECORD_IS_TYPE_DEF}},
+          RECORD_LOCATION, RECORD_TAG_TYPE, RECORD_IS_TYPE_DEF,
+          RECORD_MANGLED_NAME}},
         // BaseRecord Block
         {BI_BASE_RECORD_BLOCK_ID,
          {BASE_RECORD_USR, BASE_RECORD_NAME, BASE_RECORD_PATH,
@@ -616,6 +618,7 @@ void ClangDocBitcodeWriter::emitBlock(const RecordInfo &I) {
   emitRecord(I.USR, RECORD_USR);
   emitRecord(I.Name, RECORD_NAME);
   emitRecord(I.Path, RECORD_PATH);
+  emitRecord(I.MangledName, RECORD_MANGLED_NAME);
   for (const auto &N : I.Namespace)
     emitBlock(N, FieldId::F_namespace);
   for (const auto &CI : I.Description)

diff  --git a/clang-tools-extra/clang-doc/BitcodeWriter.h b/clang-tools-extra/clang-doc/BitcodeWriter.h
index d09ec4ca34006..501af12582a8e 100644
--- a/clang-tools-extra/clang-doc/BitcodeWriter.h
+++ b/clang-tools-extra/clang-doc/BitcodeWriter.h
@@ -126,6 +126,7 @@ enum RecordId {
   RECORD_LOCATION,
   RECORD_TAG_TYPE,
   RECORD_IS_TYPE_DEF,
+  RECORD_MANGLED_NAME,
   BASE_RECORD_USR,
   BASE_RECORD_NAME,
   BASE_RECORD_PATH,

diff  --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp b/clang-tools-extra/clang-doc/JSONGenerator.cpp
index 0e1a0cc347e45..6fdc7196e9095 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -386,6 +386,7 @@ static void serializeInfo(const RecordInfo &I, json::Object &Obj,
   Obj["FullName"] = I.FullName;
   Obj["TagType"] = getTagType(I.TagType);
   Obj["IsTypedef"] = I.IsTypeDef;
+  Obj["MangledName"] = I.MangledName;
 
   if (!I.Children.Functions.empty()) {
     json::Value PubFunctionsArray = Array();
@@ -491,6 +492,23 @@ static void serializeInfo(const NamespaceInfo &I, json::Object &Obj,
   serializeCommonChildren(I.Children, Obj, RepositoryUrl);
 }
 
+static SmallString<16> determineFileName(Info *I, SmallString<128> &Path) {
+  SmallString<16> FileName;
+  if (I->IT == InfoType::IT_record) {
+    auto *RecordSymbolInfo = static_cast<SymbolInfo *>(I);
+    if (RecordSymbolInfo->MangledName.size() < 255)
+      FileName = RecordSymbolInfo->MangledName;
+    else
+      FileName = toStringRef(toHex(RecordSymbolInfo->USR));
+  } else if (I->IT == InfoType::IT_namespace && I->Name != "")
+    // Serialize the global namespace as index.json
+    FileName = I->Name;
+  else
+    FileName = I->getFileBaseName();
+  sys::path::append(Path, FileName + ".json");
+  return FileName;
+}
+
 Error JSONGenerator::generateDocs(
     StringRef RootDir, llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
     const ClangDocContext &CDCtx) {
@@ -501,7 +519,6 @@ Error JSONGenerator::generateDocs(
 
     SmallString<128> Path;
     sys::path::native(RootDir, Path);
-    sys::path::append(Path, Info->getRelativeFilePath(""));
     if (!CreatedDirs.contains(Path)) {
       if (std::error_code Err = sys::fs::create_directories(Path);
           Err != std::error_code())
@@ -509,7 +526,7 @@ Error JSONGenerator::generateDocs(
       CreatedDirs.insert(Path);
     }
 
-    sys::path::append(Path, Info->getFileBaseName() + ".json");
+    SmallString<16> FileName = determineFileName(Info, Path);
     FileToInfos[Path].push_back(Info);
   }
 

diff  --git a/clang-tools-extra/clang-doc/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp
index 422a76d99e5b3..beaf314a04ae1 100644
--- a/clang-tools-extra/clang-doc/Representation.cpp
+++ b/clang-tools-extra/clang-doc/Representation.cpp
@@ -290,6 +290,8 @@ void SymbolInfo::merge(SymbolInfo &&Other) {
   auto *Last = llvm::unique(Loc);
   Loc.erase(Last, Loc.end());
   mergeBase(std::move(Other));
+  if (MangledName.empty())
+    MangledName = std::move(Other.MangledName);
 }
 
 NamespaceInfo::NamespaceInfo(SymbolID USR, StringRef Name, StringRef Path)

diff  --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h
index fe5cc48069d58..23f0e90daa27f 100644
--- a/clang-tools-extra/clang-doc/Representation.h
+++ b/clang-tools-extra/clang-doc/Representation.h
@@ -377,6 +377,7 @@ struct SymbolInfo : public Info {
 
   std::optional<Location> DefLoc;     // Location where this decl is defined.
   llvm::SmallVector<Location, 2> Loc; // Locations where this decl is declared.
+  SmallString<16> MangledName;
   bool IsStatic = false;
 };
 

diff  --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp
index 6cc372ce98a6d..7a0e00c6d9c2d 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -12,6 +12,7 @@
 #include "clang/AST/Attr.h"
 #include "clang/AST/Comment.h"
 #include "clang/AST/DeclFriend.h"
+#include "clang/AST/Mangle.h"
 #include "clang/Index/USRGeneration.h"
 #include "clang/Lex/Lexer.h"
 #include "llvm/ADT/StringExtras.h"
@@ -767,6 +768,17 @@ static void populateSymbolInfo(SymbolInfo &I, const T *D, const FullComment *C,
     I.DefLoc = Loc;
   else
     I.Loc.emplace_back(Loc);
+
+  auto *Mangler = ItaniumMangleContext::create(
+      D->getASTContext(), D->getASTContext().getDiagnostics());
+  std::string MangledName;
+  llvm::raw_string_ostream MangledStream(MangledName);
+  if (auto *CXXD = dyn_cast<CXXRecordDecl>(D))
+    Mangler->mangleCXXVTable(CXXD, MangledStream);
+  else
+    MangledStream << D->getNameAsString();
+  I.MangledName = MangledName;
+  delete Mangler;
 }
 
 static void

diff  --git a/clang-tools-extra/test/clang-doc/json/class-requires.cpp b/clang-tools-extra/test/clang-doc/json/class-requires.cpp
index 2dd25771d6d8b..213da93a1adfa 100644
--- a/clang-tools-extra/test/clang-doc/json/class-requires.cpp
+++ b/clang-tools-extra/test/clang-doc/json/class-requires.cpp
@@ -1,6 +1,6 @@
 // RUN: rm -rf %t && mkdir -p %t
 // RUN: clang-doc --extra-arg -std=c++20 --output=%t --format=json --executor=standalone %s
-// RUN: FileCheck %s < %t/GlobalNamespace/MyClass.json
+// RUN: FileCheck %s < %t/_ZTV7MyClass.json
 
 template<typename T>
 concept Addable = requires(T a, T b) {

diff  --git a/clang-tools-extra/test/clang-doc/json/class-specialization.cpp b/clang-tools-extra/test/clang-doc/json/class-specialization.cpp
new file mode 100644
index 0000000000000..e9259edad5cb8
--- /dev/null
+++ b/clang-tools-extra/test/clang-doc/json/class-specialization.cpp
@@ -0,0 +1,37 @@
+// RUN: rm -rf %t && mkdir -p %t
+// RUN: clang-doc --output=%t --format=json --executor=standalone %s
+// RUN: FileCheck %s < %t/_ZTV7MyClass.json --check-prefix=BASE
+// RUN: FileCheck %s < %t/_ZTV7MyClassIiE.json --check-prefix=SPECIALIZATION
+
+template<typename T> struct MyClass {};
+
+template<> struct MyClass<int> {};
+
+// BASE:       "MangledName": "_ZTV7MyClass",
+// BASE-NEXT:  "Name": "MyClass",
+// BASE-NEXT:  "Namespace": [
+// BASE-NEXT:    "GlobalNamespace"
+// BASE-NEXT:  ],
+// BASE-NEXT:  "Path": "GlobalNamespace",
+// BASE-NEXT:  "TagType": "struct",
+// BASE-NEXT:  "Template": {
+// BASE-NEXT:    "Parameters": [
+// BASE-NEXT:      "typename T"
+// BASE-NEXT:    ]
+// BASE-NEXT:  },
+
+// SPECIALIZATION:       "MangledName": "_ZTV7MyClassIiE",
+// SPECIALIZATION-NEXT:  "Name": "MyClass",
+// SPECIALIZATION-NEXT:  "Namespace": [
+// SPECIALIZATION-NEXT:    "GlobalNamespace"
+// SPECIALIZATION-NEXT:  ],
+// SPECIALIZATION-NEXT:  "Path": "GlobalNamespace",
+// SPECIALIZATION-NEXT:  "TagType": "struct",
+// SPECIALIZATION-NEXT:  "Template": {
+// SPECIALIZATION-NEXT:    "Specialization": {
+// SPECIALIZATION-NEXT:      "Parameters": [
+// SPECIALIZATION-NEXT:        "int"
+// SPECIALIZATION-NEXT:      ],
+// SPECIALIZATION-NEXT:      "SpecializationOf": "{{[0-9A-F]*}}"
+// SPECIALIZATION-NEXT:    }
+// SPECIALIZATION-NEXT:  },

diff  --git a/clang-tools-extra/test/clang-doc/json/class-template.cpp b/clang-tools-extra/test/clang-doc/json/class-template.cpp
index fb9c4c2f21c2e..6cdc3e9175278 100644
--- a/clang-tools-extra/test/clang-doc/json/class-template.cpp
+++ b/clang-tools-extra/test/clang-doc/json/class-template.cpp
@@ -1,6 +1,6 @@
 // RUN: rm -rf %t && mkdir -p %t
 // RUN: clang-doc --output=%t --format=json --executor=standalone %s
-// RUN: FileCheck %s < %t/GlobalNamespace/MyClass.json
+// RUN: FileCheck %s < %t/_ZTV7MyClass.json
 
 template<typename T> struct MyClass {
   T MemberTemplate;

diff  --git a/clang-tools-extra/test/clang-doc/json/class.cpp b/clang-tools-extra/test/clang-doc/json/class.cpp
index ae47da75edccb..d8317eafea91a 100644
--- a/clang-tools-extra/test/clang-doc/json/class.cpp
+++ b/clang-tools-extra/test/clang-doc/json/class.cpp
@@ -1,6 +1,6 @@
 // RUN: rm -rf %t && mkdir -p %t
 // RUN: clang-doc --output=%t --format=json --executor=standalone %s
-// RUN: FileCheck %s < %t/GlobalNamespace/MyClass.json
+// RUN: FileCheck %s < %t/_ZTV7MyClass.json
 
 struct Foo;
 
@@ -134,6 +134,7 @@ struct MyClass {
 // CHECK-NEXT:      "Filename": "{{.*}}class.cpp",
 // CHECK-NEXT:      "LineNumber": 10
 // CHECK-NEXT:    },
+// CHECK-NEXT:    "MangledName": "_ZTV7MyClass",
 // CHECK-NEXT:    "Name": "MyClass",
 // CHECK-NEXT:    "Namespace": [
 // CHECK-NEXT:      "GlobalNamespace"

diff  --git a/clang-tools-extra/test/clang-doc/json/compound-constraints.cpp b/clang-tools-extra/test/clang-doc/json/compound-constraints.cpp
index b49dec5cc78c5..34acb6808409d 100644
--- a/clang-tools-extra/test/clang-doc/json/compound-constraints.cpp
+++ b/clang-tools-extra/test/clang-doc/json/compound-constraints.cpp
@@ -1,6 +1,6 @@
 // RUN: rm -rf %t && mkdir -p %t
 // RUN: clang-doc --extra-arg -std=c++20 --output=%t --format=json --executor=standalone %s
-// RUN: FileCheck %s < %t/GlobalNamespace/index.json
+// RUN: FileCheck %s < %t/index.json
 
 template<typename T> concept Incrementable = requires (T a) {
   a++;

diff  --git a/clang-tools-extra/test/clang-doc/json/concept.cpp b/clang-tools-extra/test/clang-doc/json/concept.cpp
index 887c9d79146a0..b946393274c85 100644
--- a/clang-tools-extra/test/clang-doc/json/concept.cpp
+++ b/clang-tools-extra/test/clang-doc/json/concept.cpp
@@ -1,6 +1,6 @@
 // RUN: rm -rf %t && mkdir -p %t
 // RUN: clang-doc --extra-arg -std=c++20 --output=%t --format=json --executor=standalone %s
-// RUN: FileCheck %s < %t/GlobalNamespace/index.json
+// RUN: FileCheck %s < %t/index.json
 
 // Requires that T suports post and pre-incrementing.
 template<typename T>

diff  --git a/clang-tools-extra/test/clang-doc/json/function-requires.cpp b/clang-tools-extra/test/clang-doc/json/function-requires.cpp
index 4e8432e088c4f..08ac4c7ed2ca3 100644
--- a/clang-tools-extra/test/clang-doc/json/function-requires.cpp
+++ b/clang-tools-extra/test/clang-doc/json/function-requires.cpp
@@ -1,6 +1,6 @@
 // RUN: rm -rf %t && mkdir -p %t
 // RUN: clang-doc --extra-arg -std=c++20 --output=%t --format=json --executor=standalone %s
-// RUN: FileCheck %s < %t/GlobalNamespace/index.json
+// RUN: FileCheck %s < %t/index.json
 
 template<typename T>
 concept Incrementable = requires(T x) {

diff  --git a/clang-tools-extra/test/clang-doc/json/function-specifiers.cpp b/clang-tools-extra/test/clang-doc/json/function-specifiers.cpp
index 7005fb7b3e66e..b194e3371bf76 100644
--- a/clang-tools-extra/test/clang-doc/json/function-specifiers.cpp
+++ b/clang-tools-extra/test/clang-doc/json/function-specifiers.cpp
@@ -1,6 +1,6 @@
 // RUN: rm -rf %t && mkdir -p %t
 // RUN: clang-doc --output=%t --format=json --executor=standalone %s
-// RUN: FileCheck %s < %t/GlobalNamespace/index.json
+// RUN: FileCheck %s < %t/index.json
 
 static void myFunction() {}
 

diff  --git a/clang-tools-extra/test/clang-doc/json/method-template.cpp b/clang-tools-extra/test/clang-doc/json/method-template.cpp
index ea9110d6c2d1c..ac8450a72d3a7 100644
--- a/clang-tools-extra/test/clang-doc/json/method-template.cpp
+++ b/clang-tools-extra/test/clang-doc/json/method-template.cpp
@@ -1,6 +1,6 @@
 // RUN: rm -rf %t && mkdir -p %t
 // RUN: clang-doc --output=%t --format=json --executor=standalone %s
-// RUN: FileCheck %s < %t/GlobalNamespace/MyClass.json
+// RUN: FileCheck %s < %t/_ZTV7MyClass.json
 
 struct MyClass {
   template<class T> T methodTemplate(T param) {

diff  --git a/clang-tools-extra/test/clang-doc/json/namespace.cpp b/clang-tools-extra/test/clang-doc/json/namespace.cpp
index 6e4fc6938d856..779d7b49f5581 100644
--- a/clang-tools-extra/test/clang-doc/json/namespace.cpp
+++ b/clang-tools-extra/test/clang-doc/json/namespace.cpp
@@ -1,6 +1,6 @@
 // RUN: rm -rf %t && mkdir -p %t
 // RUN: clang-doc --output=%t --format=json --executor=standalone %s
-// RUN: FileCheck %s < %t/GlobalNamespace/index.json
+// RUN: FileCheck %s < %t/index.json
 
 class MyClass {};
 

diff  --git a/clang-tools-extra/test/clang-doc/json/nested-namespace.cpp b/clang-tools-extra/test/clang-doc/json/nested-namespace.cpp
index 9b176feb67a00..54f95c4d041ca 100644
--- a/clang-tools-extra/test/clang-doc/json/nested-namespace.cpp
+++ b/clang-tools-extra/test/clang-doc/json/nested-namespace.cpp
@@ -1,7 +1,7 @@
 // RUN: rm -rf %t && mkdir -p %t
 // RUN: clang-doc --output=%t --format=json --executor=standalone %s
-// RUN: FileCheck %s < %t/nested/index.json --check-prefix=NESTED
-// RUN: FileCheck %s < %t/nested/inner/index.json --check-prefix=INNER
+// RUN: FileCheck %s < %t/nested.json --check-prefix=NESTED
+// RUN: FileCheck %s < %t/inner.json --check-prefix=INNER
 
 namespace nested {
   int Global;

diff  --git a/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp b/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp
index 09e522133d832..5927235b3bd93 100644
--- a/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp
+++ b/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp
@@ -67,6 +67,7 @@ TEST(JSONGeneratorTest, emitRecordJSON) {
       "IsParent": true,
       "IsTypedef": false,
       "IsVirtual": true,
+      "MangledName": "",
       "Name": "F",
       "Path": "path/to/F",
       "PublicFunctions": [
@@ -112,6 +113,7 @@ TEST(JSONGeneratorTest, emitRecordJSON) {
     "Filename": "main.cpp",
     "LineNumber": 1
   },
+  "MangledName": "",
   "Name": "Foo",
   "Namespace": [
     "GlobalNamespace"


        


More information about the cfe-commits mailing list