https://github.com/ilovepi updated https://github.com/llvm/llvm-project/pull/138065
>From b7e8fc4a4d18860f320d87ac4c3eb9b32e1e45a6 Mon Sep 17 00:00:00 2001 From: Paul Kirth <paulki...@google.com> Date: Tue, 29 Apr 2025 18:31:54 -0700 Subject: [PATCH] [clang-doc] Update serializer for improved template handling 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.c...@mail.utoronto.ca> --- clang-tools-extra/clang-doc/Representation.h | 3 + clang-tools-extra/clang-doc/Serialize.cpp | 214 ++++++++++++++++++- 2 files changed, 209 insertions(+), 8 deletions(-) diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h index a2e01719eb59e..1673be496b7b2 100644 --- a/clang-tools-extra/clang-doc/Representation.h +++ b/clang-tools-extra/clang-doc/Representation.h @@ -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; }; diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp index 18db427b5239e..b7c0d95c3be39 100644 --- a/clang-tools-extra/clang-doc/Serialize.cpp +++ b/clang-tools-extra/clang-doc/Serialize.cpp @@ -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,180 @@ 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(); + } 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->size_overridden_methods()) + Stream << " override"; + if (Method->hasAttr<clang::FinalAttr>()) + Stream << " final"; + if (Method->isConst()) + Stream << " const"; + if (Method->isPureVirtual()) + Stream << " = 0"; + } + return Result; // Convert SmallString to std::string for return +} + +static llvm::SmallString<16> getTypeDefDecl(const TypedefDecl *TypeDef) { + llvm::SmallString<16> Result; + llvm::raw_svector_ostream Stream(Result); + const ASTContext &Ctx = TypeDef->getASTContext(); + Stream << "typedef "; + QualType Q = TypeDef->getUnderlyingType(); + Q.print(Stream, Ctx.getPrintingPolicy()); + Stream << " " << TypeDef->getNameAsString(); + return Result; +} + +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 +582,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 +607,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; @@ -549,6 +723,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); @@ -680,15 +855,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; @@ -710,11 +889,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 @@ -786,18 +965,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 @@ -805,6 +1000,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))}; @@ -816,17 +1012,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))}; } _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits