[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.
This commit is contained in:
Erick Velez 2025-07-11 13:39:41 -07:00 committed by GitHub
parent 136558bab2
commit 94bb9e12ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 92 additions and 14 deletions

View File

@ -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");

View File

@ -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)

View File

@ -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,

View File

@ -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);
}

View File

@ -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)

View File

@ -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;
};

View File

@ -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

View File

@ -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) {

View File

@ -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: },

View File

@ -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;

View File

@ -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 @@ protected:
// 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"

View File

@ -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++;

View File

@ -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>

View File

@ -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) {

View File

@ -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() {}

View File

@ -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) {

View File

@ -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 {};

View File

@ -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;

View File

@ -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"