[clang-doc] Update serializer for improved template handling (#138065)
This patch updates Serialize.cpp to serialize more data about C++ templates, which are supported by the new mustache HTML template. Split from #133161. Co-authored-by: Peter Chou <peter.chou@mail.utoronto.ca>
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -792,18 +975,34 @@ 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
|
||||
@@ -811,6 +1010,7 @@ emitInfo(const TypedefDecl *D, const FullComment *FC, Location Loc,
|
||||
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))};
|
||||
@@ -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))};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user