[llvm-branch-commits] [clang-tools-extra] [clang-doc] Update serializer for improved template handling (PR #138065)

Paul Kirth via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue May 20 14:05:15 PDT 2025


https://github.com/ilovepi updated https://github.com/llvm/llvm-project/pull/138065

>From 6d69f6400edaf79895938c1db3ad2e7e96a284e2 Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at 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.chou at mail.utoronto.ca>
---
 clang-tools-extra/clang-doc/Representation.h |   3 +
 clang-tools-extra/clang-doc/Serialize.cpp    | 203 ++++++++++++++++++-
 2 files changed, 198 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..0a59724a0d75a 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,169 @@ 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> 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 +571,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 +596,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 +712,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 +844,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 +878,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 +954,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 +989,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 +1001,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))};
 }



More information about the llvm-branch-commits mailing list