Skip to content

[clang-doc] Update serializer for improved template handling #138065

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang-tools-extra/clang-doc/Representation.h
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,9 @@ struct FunctionInfo : public SymbolInfo {
// specializations.
SmallString<16> FullName;

// Function Prototype
SmallString<256> Prototype;

// When present, this function is a template or specialization.
std::optional<TemplateInfo> Template;
};
Expand Down
218 changes: 210 additions & 8 deletions clang-tools-extra/clang-doc/Serialize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@

#include "Serialize.h"
#include "BitcodeWriter.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Comment.h"
#include "clang/Index/USRGeneration.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/SHA1.h"

Expand All @@ -35,6 +35,184 @@ static void populateMemberTypeInfo(RecordInfo &I, AccessSpecifier &Access,
const DeclaratorDecl *D,
bool IsStatic = false);

static void getTemplateParameters(const TemplateParameterList *TemplateParams,
llvm::raw_ostream &Stream) {
Stream << "template <";

for (unsigned i = 0; i < TemplateParams->size(); ++i) {
if (i > 0)
Stream << ", ";

const NamedDecl *Param = TemplateParams->getParam(i);
if (const auto *TTP = llvm::dyn_cast<TemplateTypeParmDecl>(Param)) {
if (TTP->wasDeclaredWithTypename())
Stream << "typename";
else
Stream << "class";
if (TTP->isParameterPack())
Stream << "...";
Stream << " " << TTP->getNameAsString();

// We need to also handle type constraints for code like:
// template <class T = void>
// class C {};
if (TTP->hasTypeConstraint()) {
Stream << " = ";
TTP->getTypeConstraint()->print(
Stream, TTP->getASTContext().getPrintingPolicy());
}
} else if (const auto *NTTP =
llvm::dyn_cast<NonTypeTemplateParmDecl>(Param)) {
NTTP->getType().print(Stream, NTTP->getASTContext().getPrintingPolicy());
if (NTTP->isParameterPack())
Stream << "...";
Stream << " " << NTTP->getNameAsString();
} else if (const auto *TTPD =
llvm::dyn_cast<TemplateTemplateParmDecl>(Param)) {
Stream << "template <";
getTemplateParameters(TTPD->getTemplateParameters(), Stream);
Stream << "> class " << TTPD->getNameAsString();
}
}

Stream << "> ";
}

// Extract the full function prototype from a FunctionDecl including
// Full Decl
static llvm::SmallString<256>
getFunctionPrototype(const FunctionDecl *FuncDecl) {
llvm::SmallString<256> Result;
llvm::raw_svector_ostream Stream(Result);
const ASTContext &Ctx = FuncDecl->getASTContext();
const auto *Method = llvm::dyn_cast<CXXMethodDecl>(FuncDecl);
// If it's a templated function, handle the template parameters
if (const auto *TmplDecl = FuncDecl->getDescribedTemplate())
getTemplateParameters(TmplDecl->getTemplateParameters(), Stream);

// If it's a virtual method
if (Method && Method->isVirtual())
Stream << "virtual ";

// Print return type
FuncDecl->getReturnType().print(Stream, Ctx.getPrintingPolicy());

// Print function name
Stream << " " << FuncDecl->getNameAsString() << "(";

// Print parameter list with types, names, and default values
for (unsigned I = 0; I < FuncDecl->getNumParams(); ++I) {
if (I > 0)
Stream << ", ";
const ParmVarDecl *ParamDecl = FuncDecl->getParamDecl(I);
QualType ParamType = ParamDecl->getType();
ParamType.print(Stream, Ctx.getPrintingPolicy());

// Print parameter name if it has one
if (!ParamDecl->getName().empty())
Stream << " " << ParamDecl->getNameAsString();

// Print default argument if it exists
if (ParamDecl->hasDefaultArg()) {
const Expr *DefaultArg = ParamDecl->getDefaultArg();
if (DefaultArg) {
Stream << " = ";
DefaultArg->printPretty(Stream, nullptr, Ctx.getPrintingPolicy());
}
}
}

// If it is a variadic function, add '...'
if (FuncDecl->isVariadic()) {
if (FuncDecl->getNumParams() > 0)
Stream << ", ";
Stream << "...";
}

Stream << ")";

// If it's a const method, add 'const' qualifier
if (Method) {
if (Method->isDeleted())
Stream << " = delete";
if (Method->size_overridden_methods())
Stream << " override";
if (Method->hasAttr<clang::FinalAttr>())
Stream << " final";
if (Method->isConst())
Stream << " const";
if (Method->isPureVirtual())
Stream << " = 0";
}

if (auto ExceptionSpecType = FuncDecl->getExceptionSpecType())
Stream << " " << ExceptionSpecType;

return Result; // Convert SmallString to std::string for return
}

static llvm::SmallString<16> getTypeAlias(const TypeAliasDecl *Alias) {
llvm::SmallString<16> Result;
llvm::raw_svector_ostream Stream(Result);
const ASTContext &Ctx = Alias->getASTContext();
if (const auto *TmplDecl = Alias->getDescribedTemplate())
getTemplateParameters(TmplDecl->getTemplateParameters(), Stream);
Stream << "using " << Alias->getNameAsString() << " = ";
QualType Q = Alias->getUnderlyingType();
Q.print(Stream, Ctx.getPrintingPolicy());

return Result;
}

// extract full syntax for record declaration
static llvm::SmallString<16> getRecordPrototype(const CXXRecordDecl *CXXRD) {
llvm::SmallString<16> Result;
LangOptions LangOpts;
PrintingPolicy Policy(LangOpts);
Policy.SuppressTagKeyword = false;
Policy.FullyQualifiedName = true;
Policy.IncludeNewlines = false;
llvm::raw_svector_ostream OS(Result);
if (const auto *TD = CXXRD->getDescribedClassTemplate()) {
OS << "template <";
bool FirstParam = true;
for (const auto *Param : *TD->getTemplateParameters()) {
if (!FirstParam)
OS << ", ";
Param->print(OS, Policy);
FirstParam = false;
}
OS << ">\n";
}

if (CXXRD->isStruct())
OS << "struct ";
else if (CXXRD->isClass())
OS << "class ";
else if (CXXRD->isUnion())
OS << "union ";

OS << CXXRD->getNameAsString();

// We need to make sure we have a good enough declaration to check. In the
// case where the class is a forward declaration, we'll fail assertions in
// DeclCXX.
if (CXXRD->isCompleteDefinition() && CXXRD->getNumBases() > 0) {
OS << " : ";
bool FirstBase = true;
for (const auto &Base : CXXRD->bases()) {
if (!FirstBase)
OS << ", ";
if (Base.isVirtual())
OS << "virtual ";
OS << getAccessSpelling(Base.getAccessSpecifier()) << " ";
OS << Base.getType().getAsString(Policy);
FirstBase = false;
}
}
return Result;
}

// A function to extract the appropriate relative path for a given info's
// documentation. The path returned is a composite of the parent namespaces.
//
Expand Down Expand Up @@ -408,7 +586,6 @@ static void parseEnumerators(EnumInfo &I, const EnumDecl *D) {
ASTContext &Context = E->getASTContext();
if (RawComment *Comment =
E->getASTContext().getRawCommentForDeclNoCache(E)) {
CommentInfo CInfo;
Comment->setAttached();
if (comments::FullComment *Fc = Comment->parse(Context, nullptr, E)) {
EnumValueInfo &Member = I.Members.back();
Expand All @@ -434,6 +611,7 @@ static void parseBases(RecordInfo &I, const CXXRecordDecl *D) {
// Don't parse bases if this isn't a definition.
if (!D->isThisDeclarationADefinition())
return;

for (const CXXBaseSpecifier &B : D->bases()) {
if (B.isVirtual())
continue;
Expand Down Expand Up @@ -555,6 +733,7 @@ static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D,
populateSymbolInfo(I, D, FC, Loc, IsInAnonymousNamespace);
auto &LO = D->getLangOpts();
I.ReturnType = getTypeInfoForType(D->getReturnType(), LO);
I.Prototype = getFunctionPrototype(D);
parseParameters(I, D);

populateTemplateParameters(I.Template, D);
Expand Down Expand Up @@ -686,15 +865,19 @@ emitInfo(const NamespaceDecl *D, const FullComment *FC, Location Loc,
std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
emitInfo(const RecordDecl *D, const FullComment *FC, Location Loc,
bool PublicOnly) {

auto RI = std::make_unique<RecordInfo>();
bool IsInAnonymousNamespace = false;

populateSymbolInfo(*RI, D, FC, Loc, IsInAnonymousNamespace);
if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D))
return {};

RI->TagType = D->getTagKind();
parseFields(*RI, D, PublicOnly);

if (const auto *C = dyn_cast<CXXRecordDecl>(D)) {
RI->FullName = getRecordPrototype(C);
if (const TypedefNameDecl *TD = C->getTypedefNameForAnonDecl()) {
RI->Name = TD->getNameAsString();
RI->IsTypeDef = true;
Expand All @@ -716,11 +899,11 @@ emitInfo(const RecordDecl *D, const FullComment *FC, Location Loc,

// What this is a specialization of.
auto SpecOf = CTSD->getSpecializedTemplateOrPartial();
if (auto *CTD = dyn_cast<ClassTemplateDecl *>(SpecOf))
Specialization.SpecializationOf = getUSRForDecl(CTD);
else if (auto *CTPSD =
if (auto *SpecTD = dyn_cast<ClassTemplateDecl *>(SpecOf))
Specialization.SpecializationOf = getUSRForDecl(SpecTD);
else if (auto *SpecTD =
dyn_cast<ClassTemplatePartialSpecializationDecl *>(SpecOf))
Specialization.SpecializationOf = getUSRForDecl(CTPSD);
Specialization.SpecializationOf = getUSRForDecl(SpecTD);

// Parameters to the specialization. For partial specializations, get the
// parameters "as written" from the ClassTemplatePartialSpecializationDecl
Expand Down Expand Up @@ -792,25 +975,42 @@ emitInfo(const CXXMethodDecl *D, const FullComment *FC, Location Loc,
return {nullptr, makeAndInsertIntoParent<FunctionInfo &&>(std::move(Func))};
}

static void extractCommentFromDecl(const Decl *D, TypedefInfo &Info) {
assert(D && "Invalid Decl when extracting comment");
ASTContext &Context = D->getASTContext();
RawComment *Comment = Context.getRawCommentForDeclNoCache(D);
if (!Comment)
return;

Comment->setAttached();
if (comments::FullComment *Fc = Comment->parse(Context, nullptr, D)) {
Info.Description.emplace_back();
parseFullComment(Fc, Info.Description.back());
}
}

std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
emitInfo(const TypedefDecl *D, const FullComment *FC, Location Loc,
bool PublicOnly) {
TypedefInfo Info;
bool IsInAnonymousNamespace = false;
populateInfo(Info, D, FC, IsInAnonymousNamespace);

if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D))
return {};

Info.DefLoc = Loc;
auto &LO = D->getLangOpts();
Info.Underlying = getTypeInfoForType(D->getUnderlyingType(), LO);

if (Info.Underlying.Type.Name.empty()) {
// Typedef for an unnamed type. This is like "typedef struct { } Foo;"
// The record serializer explicitly checks for this syntax and constructs
// a record with that name, so we don't want to emit a duplicate here.
return {};
}
Info.IsUsing = false;
extractCommentFromDecl(D, Info);

// Info is wrapped in its parent scope so is returned in the second position.
return {nullptr, makeAndInsertIntoParent<TypedefInfo &&>(std::move(Info))};
Expand All @@ -822,17 +1022,19 @@ std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
emitInfo(const TypeAliasDecl *D, const FullComment *FC, Location Loc,
bool PublicOnly) {
TypedefInfo Info;

bool IsInAnonymousNamespace = false;
populateInfo(Info, D, FC, IsInAnonymousNamespace);
if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D))
return {};

Info.DefLoc = Loc;
auto &LO = D->getLangOpts();
const LangOptions &LO = D->getLangOpts();
Info.Underlying = getTypeInfoForType(D->getUnderlyingType(), LO);
Info.TypeDeclaration = getTypeAlias(D);
Info.IsUsing = true;

extractCommentFromDecl(D, Info);

// Info is wrapped in its parent scope so is returned in the second position.
return {nullptr, makeAndInsertIntoParent<TypedefInfo &&>(std::move(Info))};
}
Expand Down
Loading