[clang-tools-extra] [clang-doc] Handle static members and functions (PR #135457)

Paul Kirth via cfe-commits cfe-commits at lists.llvm.org
Sat Apr 12 16:08:58 PDT 2025


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

>From ca38d210bd3058575752ff9d21232e87a550a943 Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Mon, 7 Apr 2025 08:37:40 -0700
Subject: [PATCH 01/12] [clang-doc] Handle static members and functions

clang-doc didn't visit VarDecl, and hence never collected info
from class statics members and functions.

Fixes #59813.
---
 clang-tools-extra/clang-doc/Mapper.cpp        |  4 ++
 clang-tools-extra/clang-doc/Mapper.h          |  1 +
 clang-tools-extra/clang-doc/Serialize.cpp     | 56 +++++++++++++++++++
 clang-tools-extra/clang-doc/Serialize.h       |  4 ++
 .../test/clang-doc/basic-project.test         | 13 ++++-
 5 files changed, 77 insertions(+), 1 deletion(-)

diff --git a/clang-tools-extra/clang-doc/Mapper.cpp b/clang-tools-extra/clang-doc/Mapper.cpp
index 6c90db03424c6..98698f9151280 100644
--- a/clang-tools-extra/clang-doc/Mapper.cpp
+++ b/clang-tools-extra/clang-doc/Mapper.cpp
@@ -82,6 +82,10 @@ bool MapASTVisitor::VisitRecordDecl(const RecordDecl *D) {
   return mapDecl(D, D->isThisDeclarationADefinition());
 }
 
+bool MapASTVisitor::VisitVarDecl(const VarDecl *D) {
+  return mapDecl(D, D->isThisDeclarationADefinition());
+}
+
 bool MapASTVisitor::VisitEnumDecl(const EnumDecl *D) {
   return mapDecl(D, D->isThisDeclarationADefinition());
 }
diff --git a/clang-tools-extra/clang-doc/Mapper.h b/clang-tools-extra/clang-doc/Mapper.h
index 75c8e947c8f90..62fc5fbbdf3da 100644
--- a/clang-tools-extra/clang-doc/Mapper.h
+++ b/clang-tools-extra/clang-doc/Mapper.h
@@ -36,6 +36,7 @@ class MapASTVisitor : public clang::RecursiveASTVisitor<MapASTVisitor>,
   void HandleTranslationUnit(ASTContext &Context) override;
   bool VisitNamespaceDecl(const NamespaceDecl *D);
   bool VisitRecordDecl(const RecordDecl *D);
+  bool VisitVarDecl(const VarDecl *D);
   bool VisitEnumDecl(const EnumDecl *D);
   bool VisitCXXMethodDecl(const CXXMethodDecl *D);
   bool VisitFunctionDecl(const FunctionDecl *D);
diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp
index f737fc75135a1..d34451cd10484 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -729,6 +729,62 @@ emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber,
   return {std::move(I), std::move(Parent)};
 }
 
+std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
+emitInfo(const VarDecl *D, const FullComment *FC, int LineNumber,
+         llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) {
+  auto I = std::make_unique<RecordInfo>();
+  bool IsInAnonymousNamespace = false;
+  populateSymbolInfo(*I, D, FC, LineNumber, File, IsFileInRootDir,
+                     IsInAnonymousNamespace);
+  if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D))
+    return {};
+
+  I->Path = getInfoRelativePath(I->Namespace);
+
+  PopulateTemplateParameters(I->Template, D);
+
+  // Full and partial specializations.
+  if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(D)) {
+    if (!I->Template)
+      I->Template.emplace();
+    I->Template->Specialization.emplace();
+    auto &Specialization = *I->Template->Specialization;
+
+    // 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 =
+                 dyn_cast<ClassTemplatePartialSpecializationDecl *>(SpecOf))
+      Specialization.SpecializationOf = getUSRForDecl(CTPSD);
+
+    // Parameters to the specilization. For partial specializations, get the
+    // parameters "as written" from the ClassTemplatePartialSpecializationDecl
+    // because the non-explicit template parameters will have generated internal
+    // placeholder names rather than the names the user typed that match the
+    // template parameters.
+    if (const ClassTemplatePartialSpecializationDecl *CTPSD =
+            dyn_cast<ClassTemplatePartialSpecializationDecl>(D)) {
+      if (const ASTTemplateArgumentListInfo *AsWritten =
+              CTPSD->getTemplateArgsAsWritten()) {
+        for (unsigned i = 0; i < AsWritten->getNumTemplateArgs(); i++) {
+          Specialization.Params.emplace_back(
+              getSourceCode(D, (*AsWritten)[i].getSourceRange()));
+        }
+      }
+    } else {
+      for (const TemplateArgument &Arg : CTSD->getTemplateArgs().asArray()) {
+        Specialization.Params.push_back(TemplateArgumentToInfo(D, Arg));
+      }
+    }
+  }
+
+  // Records are inserted into the parent by reference, so we need to return
+  // both the parent and the record itself.
+  auto Parent = MakeAndInsertIntoParent<const RecordInfo &>(*I);
+  return {std::move(I), std::move(Parent)};
+}
+
 std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
 emitInfo(const FunctionDecl *D, const FullComment *FC, int LineNumber,
          llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) {
diff --git a/clang-tools-extra/clang-doc/Serialize.h b/clang-tools-extra/clang-doc/Serialize.h
index 4e203ca7891ac..41946796f39f6 100644
--- a/clang-tools-extra/clang-doc/Serialize.h
+++ b/clang-tools-extra/clang-doc/Serialize.h
@@ -52,6 +52,10 @@ std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
 emitInfo(const FunctionDecl *D, const FullComment *FC, int LineNumber,
          StringRef File, bool IsFileInRootDir, bool PublicOnly);
 
+std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
+emitInfo(const VarDecl *D, const FullComment *FC, int LineNumber,
+         StringRef File, bool IsFileInRootDir, bool PublicOnly);
+
 std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
 emitInfo(const CXXMethodDecl *D, const FullComment *FC, int LineNumber,
          StringRef File, bool IsFileInRootDir, bool PublicOnly);
diff --git a/clang-tools-extra/test/clang-doc/basic-project.test b/clang-tools-extra/test/clang-doc/basic-project.test
index 94484b393df59..fb389c62e18a6 100644
--- a/clang-tools-extra/test/clang-doc/basic-project.test
+++ b/clang-tools-extra/test/clang-doc/basic-project.test
@@ -52,7 +52,15 @@
 // JSON-INDEX-NEXT:           "Name": "Calculator",
 // JSON-INDEX-NEXT:           "RefType": "record",
 // JSON-INDEX-NEXT:           "Path": "GlobalNamespace",
-// JSON-INDEX-NEXT:           "Children": []
+// JSON-INDEX-NEXT:           "Children": [
+// JSON-INDEX-NEXT:             {
+// JSON-INDEX-NEXT:               "USR": "{{([0-9A-F]{40})}}",
+// JSON-INDEX-NEXT:               "Name": "static_val",
+// JSON-INDEX-NEXT:               "RefType": "record",
+// JSON-INDEX-NEXT:               "Path": "GlobalNamespace/Calculator",
+// JSON-INDEX-NEXT:               "Children": []
+// JSON-INDEX-NEXT:             }
+// JSON-INDEX-NEXT:           ]
 // JSON-INDEX-NEXT:         },
 // JSON-INDEX-NEXT:         {
 // JSON-INDEX-NEXT:           "USR": "{{([0-9A-F]{40})}}",
@@ -135,6 +143,9 @@
 //      HTML-CALC: <p> Holds a public value.</p>
 //      HTML-CALC: <div>public int public_val</div>
 
+//      HTML-CALC: <h2 id="Records">Records</h2>
+//      HTML-CALC: <a href="../GlobalNamespace/Calculator/static_val.html">static_val</a>
+
 //      HTML-CALC: <h2 id="Functions">Functions</h2>
 //      HTML-CALC: <h3 id="{{([0-9A-F]{40})}}">add</h3>
 //      HTML-CALC: <p>public int add(int a, int b)</p>

>From 7fb1bd717de19c29478302239e013a524dfa6d88 Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Fri, 11 Apr 2025 22:05:42 -0700
Subject: [PATCH 02/12] Fix typo

---
 clang-tools-extra/clang-doc/Serialize.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp
index d34451cd10484..34aaa60c4db85 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -702,7 +702,7 @@ emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber,
                  dyn_cast<ClassTemplatePartialSpecializationDecl *>(SpecOf))
       Specialization.SpecializationOf = getUSRForDecl(CTPSD);
 
-    // Parameters to the specilization. For partial specializations, get the
+    // Parameters to the specialization. For partial specializations, get the
     // parameters "as written" from the ClassTemplatePartialSpecializationDecl
     // because the non-explicit template parameters will have generated internal
     // placeholder names rather than the names the user typed that match the

>From 3c37677d51213753d63f191acfc8ddef52b9b1ea Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Sat, 12 Apr 2025 09:42:04 -0700
Subject: [PATCH 03/12] Fix file separator in test for Windows

---
 clang-tools-extra/test/clang-doc/basic-project.test | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang-tools-extra/test/clang-doc/basic-project.test b/clang-tools-extra/test/clang-doc/basic-project.test
index fb389c62e18a6..0bd9f0227bd44 100644
--- a/clang-tools-extra/test/clang-doc/basic-project.test
+++ b/clang-tools-extra/test/clang-doc/basic-project.test
@@ -57,7 +57,7 @@
 // JSON-INDEX-NEXT:               "USR": "{{([0-9A-F]{40})}}",
 // JSON-INDEX-NEXT:               "Name": "static_val",
 // JSON-INDEX-NEXT:               "RefType": "record",
-// JSON-INDEX-NEXT:               "Path": "GlobalNamespace/Calculator",
+// JSON-INDEX-NEXT:               "Path": "GlobalNamespace{{[\/]}}Calculator",
 // JSON-INDEX-NEXT:               "Children": []
 // JSON-INDEX-NEXT:             }
 // JSON-INDEX-NEXT:           ]

>From 9333d967052ae7d8c69368f1b600416748a59b7a Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Sat, 12 Apr 2025 10:06:31 -0700
Subject: [PATCH 04/12] Allow for escaped forward slash in Windows paths

---
 clang-tools-extra/test/clang-doc/basic-project.test | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang-tools-extra/test/clang-doc/basic-project.test b/clang-tools-extra/test/clang-doc/basic-project.test
index 0bd9f0227bd44..0262f6d2a01d6 100644
--- a/clang-tools-extra/test/clang-doc/basic-project.test
+++ b/clang-tools-extra/test/clang-doc/basic-project.test
@@ -57,7 +57,7 @@
 // JSON-INDEX-NEXT:               "USR": "{{([0-9A-F]{40})}}",
 // JSON-INDEX-NEXT:               "Name": "static_val",
 // JSON-INDEX-NEXT:               "RefType": "record",
-// JSON-INDEX-NEXT:               "Path": "GlobalNamespace{{[\/]}}Calculator",
+// JSON-INDEX-NEXT:               "Path": "GlobalNamespace{{[\/]+}}Calculator",
 // JSON-INDEX-NEXT:               "Children": []
 // JSON-INDEX-NEXT:             }
 // JSON-INDEX-NEXT:           ]

>From 487782f63b66d10cb79c73d516f2bcf354f1ab56 Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Sat, 12 Apr 2025 10:20:35 -0700
Subject: [PATCH 05/12] Fix path check in MD-CALC

---
 clang-tools-extra/test/clang-doc/basic-project.test | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/clang-tools-extra/test/clang-doc/basic-project.test b/clang-tools-extra/test/clang-doc/basic-project.test
index 0262f6d2a01d6..b14624a2b49f7 100644
--- a/clang-tools-extra/test/clang-doc/basic-project.test
+++ b/clang-tools-extra/test/clang-doc/basic-project.test
@@ -337,6 +337,8 @@
 // MD-CALC:  Provides basic arithmetic operations.
 // MD-CALC: ## Members
 // MD-CALC: public int public_val
+// MD-CALC: ## Records
+// MD-CALC: static_val
 // MD-CALC: ## Functions
 // MD-CALC: ### add
 // MD-CALC: *public int add(int a, int b)*

>From b7daaedac99186dac54560fb401ad159932d5a3e Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Sat, 12 Apr 2025 12:54:52 -0700
Subject: [PATCH 06/12] Mark static functions and members in normal processing

Visiting VarDecls looses context. Instead, track additional information
about statics within the Info* classes, and update serialization to
handle it. We can visit VarDecl's as a normal part of visiting Records,
and keep the necessary context.
---
 clang-tools-extra/clang-doc/BitcodeReader.cpp |  4 +
 clang-tools-extra/clang-doc/BitcodeWriter.cpp |  8 +-
 clang-tools-extra/clang-doc/BitcodeWriter.h   |  2 +
 clang-tools-extra/clang-doc/HTMLGenerator.cpp |  8 ++
 clang-tools-extra/clang-doc/Mapper.cpp        |  4 -
 clang-tools-extra/clang-doc/Mapper.h          |  1 -
 clang-tools-extra/clang-doc/Representation.h  |  8 +-
 clang-tools-extra/clang-doc/Serialize.cpp     | 76 +++++--------------
 .../test/clang-doc/basic-project.test         | 20 ++---
 9 files changed, 49 insertions(+), 82 deletions(-)

diff --git a/clang-tools-extra/clang-doc/BitcodeReader.cpp b/clang-tools-extra/clang-doc/BitcodeReader.cpp
index 1f2fb0a8b2b85..d6c9da81a05c7 100644
--- a/clang-tools-extra/clang-doc/BitcodeReader.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeReader.cpp
@@ -274,6 +274,8 @@ llvm::Error parseRecord(const Record &R, unsigned ID, llvm::StringRef Blob,
     return decodeRecord(R, I->Access, Blob);
   case FUNCTION_IS_METHOD:
     return decodeRecord(R, I->IsMethod, Blob);
+  case FUNCTION_IS_STATIC:
+    return decodeRecord(R, I->IsStatic, Blob);
   default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                    "invalid field for FunctionInfo");
@@ -305,6 +307,8 @@ llvm::Error parseRecord(const Record &R, unsigned ID, llvm::StringRef Blob,
     return decodeRecord(R, I->Name, Blob);
   case MEMBER_TYPE_ACCESS:
     return decodeRecord(R, I->Access, Blob);
+  case MEMBER_TYPE_IS_STATIC:
+    return decodeRecord(R, I->IsStatic, Blob);
   default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                    "invalid field for MemberTypeInfo");
diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.cpp b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
index 06f30f76e33d8..fbc3f9969dbed 100644
--- a/clang-tools-extra/clang-doc/BitcodeWriter.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
@@ -156,6 +156,7 @@ static const llvm::IndexedMap<RecordIdDsc, RecordIdToIndexFunctor>
           {FIELD_DEFAULT_VALUE, {"DefaultValue", &StringAbbrev}},
           {MEMBER_TYPE_NAME, {"Name", &StringAbbrev}},
           {MEMBER_TYPE_ACCESS, {"Access", &IntAbbrev}},
+          {MEMBER_TYPE_IS_STATIC, {"IsStatic", &BoolAbbrev}},
           {NAMESPACE_USR, {"USR", &SymbolIDAbbrev}},
           {NAMESPACE_NAME, {"Name", &StringAbbrev}},
           {NAMESPACE_PATH, {"Path", &StringAbbrev}},
@@ -187,6 +188,7 @@ static const llvm::IndexedMap<RecordIdDsc, RecordIdToIndexFunctor>
           {FUNCTION_LOCATION, {"Location", &LocationAbbrev}},
           {FUNCTION_ACCESS, {"Access", &IntAbbrev}},
           {FUNCTION_IS_METHOD, {"IsMethod", &BoolAbbrev}},
+          {FUNCTION_IS_STATIC, {"IsStatic", &BoolAbbrev}},
           {REFERENCE_USR, {"USR", &SymbolIDAbbrev}},
           {REFERENCE_NAME, {"Name", &StringAbbrev}},
           {REFERENCE_QUAL_NAME, {"QualName", &StringAbbrev}},
@@ -222,7 +224,7 @@ static const std::vector<std::pair<BlockId, std::vector<RecordId>>>
         // FieldType Block
         {BI_FIELD_TYPE_BLOCK_ID, {FIELD_TYPE_NAME, FIELD_DEFAULT_VALUE}},
         // MemberType Block
-        {BI_MEMBER_TYPE_BLOCK_ID, {MEMBER_TYPE_NAME, MEMBER_TYPE_ACCESS}},
+        {BI_MEMBER_TYPE_BLOCK_ID, {MEMBER_TYPE_NAME, MEMBER_TYPE_ACCESS, MEMBER_TYPE_IS_STATIC}},
         // Enum Block
         {BI_ENUM_BLOCK_ID,
          {ENUM_USR, ENUM_NAME, ENUM_DEFLOCATION, ENUM_LOCATION, ENUM_SCOPED}},
@@ -247,7 +249,7 @@ static const std::vector<std::pair<BlockId, std::vector<RecordId>>>
         // Function Block
         {BI_FUNCTION_BLOCK_ID,
          {FUNCTION_USR, FUNCTION_NAME, FUNCTION_DEFLOCATION, FUNCTION_LOCATION,
-          FUNCTION_ACCESS, FUNCTION_IS_METHOD}},
+          FUNCTION_ACCESS, FUNCTION_IS_METHOD, FUNCTION_IS_STATIC}},
         // Reference Block
         {BI_REFERENCE_BLOCK_ID,
          {REFERENCE_USR, REFERENCE_NAME, REFERENCE_QUAL_NAME, REFERENCE_TYPE,
@@ -465,6 +467,7 @@ void ClangDocBitcodeWriter::emitBlock(const MemberTypeInfo &T) {
   emitBlock(T.Type, FieldId::F_type);
   emitRecord(T.Name, MEMBER_TYPE_NAME);
   emitRecord(T.Access, MEMBER_TYPE_ACCESS);
+  emitRecord(T.IsStatic, MEMBER_TYPE_IS_STATIC);
   for (const auto &CI : T.Description)
     emitBlock(CI);
 }
@@ -600,6 +603,7 @@ void ClangDocBitcodeWriter::emitBlock(const FunctionInfo &I) {
     emitBlock(CI);
   emitRecord(I.Access, FUNCTION_ACCESS);
   emitRecord(I.IsMethod, FUNCTION_IS_METHOD);
+  emitRecord(I.IsStatic, FUNCTION_IS_STATIC);
   if (I.DefLoc)
     emitRecord(*I.DefLoc, FUNCTION_DEFLOCATION);
   for (const auto &L : I.Loc)
diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.h b/clang-tools-extra/clang-doc/BitcodeWriter.h
index 9a572e40e352f..e33a1aece883c 100644
--- a/clang-tools-extra/clang-doc/BitcodeWriter.h
+++ b/clang-tools-extra/clang-doc/BitcodeWriter.h
@@ -81,6 +81,7 @@ enum RecordId {
   FUNCTION_LOCATION,
   FUNCTION_ACCESS,
   FUNCTION_IS_METHOD,
+  FUNCTION_IS_STATIC,
   COMMENT_KIND,
   COMMENT_TEXT,
   COMMENT_NAME,
@@ -96,6 +97,7 @@ enum RecordId {
   FIELD_DEFAULT_VALUE,
   MEMBER_TYPE_NAME,
   MEMBER_TYPE_ACCESS,
+  MEMBER_TYPE_IS_STATIC,
   NAMESPACE_USR,
   NAMESPACE_NAME,
   NAMESPACE_PATH,
diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
index cb10f16804024..1bc3265c1fe89 100644
--- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
@@ -419,6 +419,8 @@ genRecordMembersBlock(const llvm::SmallVector<MemberTypeInfo, 4> &Members,
     std::string Access = getAccessSpelling(M.Access).str();
     if (Access != "")
       Access = Access + " ";
+    if(M.IsStatic)
+      Access += "static ";
     auto LIBody = std::make_unique<TagNode>(HTMLTag::TAG_LI);
     auto MemberDecl = std::make_unique<TagNode>(HTMLTag::TAG_DIV);
     MemberDecl->Children.emplace_back(std::make_unique<TextNode>(Access));
@@ -740,6 +742,12 @@ genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx,
   if (Access != "")
     FunctionHeader->Children.emplace_back(
         std::make_unique<TextNode>(Access + " "));
+  if(I.IsStatic)
+    FunctionHeader->Children.emplace_back(
+        std::make_unique<TextNode>("static "));
+  else{
+    llvm::errs() << I.Name << " is not static\n";
+  }
   if (I.ReturnType.Type.Name != "") {
     FunctionHeader->Children.emplace_back(
         genReference(I.ReturnType.Type, ParentInfoDir));
diff --git a/clang-tools-extra/clang-doc/Mapper.cpp b/clang-tools-extra/clang-doc/Mapper.cpp
index 98698f9151280..6c90db03424c6 100644
--- a/clang-tools-extra/clang-doc/Mapper.cpp
+++ b/clang-tools-extra/clang-doc/Mapper.cpp
@@ -82,10 +82,6 @@ bool MapASTVisitor::VisitRecordDecl(const RecordDecl *D) {
   return mapDecl(D, D->isThisDeclarationADefinition());
 }
 
-bool MapASTVisitor::VisitVarDecl(const VarDecl *D) {
-  return mapDecl(D, D->isThisDeclarationADefinition());
-}
-
 bool MapASTVisitor::VisitEnumDecl(const EnumDecl *D) {
   return mapDecl(D, D->isThisDeclarationADefinition());
 }
diff --git a/clang-tools-extra/clang-doc/Mapper.h b/clang-tools-extra/clang-doc/Mapper.h
index 62fc5fbbdf3da..75c8e947c8f90 100644
--- a/clang-tools-extra/clang-doc/Mapper.h
+++ b/clang-tools-extra/clang-doc/Mapper.h
@@ -36,7 +36,6 @@ class MapASTVisitor : public clang::RecursiveASTVisitor<MapASTVisitor>,
   void HandleTranslationUnit(ASTContext &Context) override;
   bool VisitNamespaceDecl(const NamespaceDecl *D);
   bool VisitRecordDecl(const RecordDecl *D);
-  bool VisitVarDecl(const VarDecl *D);
   bool VisitEnumDecl(const EnumDecl *D);
   bool VisitCXXMethodDecl(const CXXMethodDecl *D);
   bool VisitFunctionDecl(const FunctionDecl *D);
diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h
index 2153b62864ee3..dc95c6295f900 100644
--- a/clang-tools-extra/clang-doc/Representation.h
+++ b/clang-tools-extra/clang-doc/Representation.h
@@ -224,8 +224,9 @@ struct MemberTypeInfo : public FieldTypeInfo {
       : FieldTypeInfo(TI, Name), Access(Access) {}
 
   bool operator==(const MemberTypeInfo &Other) const {
-    return std::tie(Type, Name, Access, Description) ==
-           std::tie(Other.Type, Other.Name, Other.Access, Other.Description);
+    return std::tie(Type, Name, Access, IsStatic, Description) ==
+           std::tie(Other.Type, Other.Name, Other.Access, Other.IsStatic,
+                    Other.Description);
   }
 
   // Access level associated with this info (public, protected, private, none).
@@ -235,6 +236,7 @@ struct MemberTypeInfo : public FieldTypeInfo {
   AccessSpecifier Access = AccessSpecifier::AS_public;
 
   std::vector<CommentInfo> Description; // Comment description of this field.
+  bool IsStatic = false;
 };
 
 struct Location {
@@ -314,6 +316,8 @@ struct NamespaceInfo : public Info {
 
 // Info for symbols.
 struct SymbolInfo : public Info {
+  bool IsStatic = false;
+
   SymbolInfo(InfoType IT, SymbolID USR = SymbolID(),
              StringRef Name = StringRef(), StringRef Path = StringRef())
       : Info(IT, USR, Name, Path) {}
diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp
index 34aaa60c4db85..429f7de5a7202 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -30,7 +30,7 @@ static void
 populateParentNamespaces(llvm::SmallVector<Reference, 4> &Namespaces,
                          const T *D, bool &IsAnonymousNamespace);
 
-static void populateMemberTypeInfo(MemberTypeInfo &I, const FieldDecl *D);
+static void populateMemberTypeInfo(MemberTypeInfo &I, const Decl *D);
 
 // A function to extract the appropriate relative path for a given info's
 // documentation. The path returned is a composite of the parent namespaces.
@@ -375,11 +375,11 @@ static AccessSpecifier getFinalAccessSpecifier(AccessSpecifier FirstAS,
 // record, the access specification of the field depends on the inheritance mode
 static void parseFields(RecordInfo &I, const RecordDecl *D, bool PublicOnly,
                         AccessSpecifier Access = AccessSpecifier::AS_public) {
+  auto &LO = D->getLangOpts();
   for (const FieldDecl *F : D->fields()) {
     if (!shouldSerializeInfo(PublicOnly, /*IsInAnonymousNamespace=*/false, F))
       continue;
 
-    auto &LO = F->getLangOpts();
     // Use getAccessUnsafe so that we just get the default AS_none if it's not
     // valid, as opposed to an assert.
     MemberTypeInfo &NewMember = I.Members.emplace_back(
@@ -388,6 +388,19 @@ static void parseFields(RecordInfo &I, const RecordDecl *D, bool PublicOnly,
         getFinalAccessSpecifier(Access, F->getAccessUnsafe()));
     populateMemberTypeInfo(NewMember, F);
   }
+  const CXXRecordDecl *CPP = dyn_cast<CXXRecordDecl>(D);
+  for (Decl *F : CPP->decls()) {
+    if (auto *VD = dyn_cast<VarDecl>(F)) {
+      if (VD->isStaticDataMember()) {
+        MemberTypeInfo &NewMember = I.Members.emplace_back(
+            getTypeInfoForType(VD->getTypeSourceInfo()->getType(), LO),
+            VD->getNameAsString(),
+            getFinalAccessSpecifier(Access, F->getAccessUnsafe()));
+        NewMember.IsStatic = true;
+        populateMemberTypeInfo(NewMember, VD);
+      }
+    }
+  }
 }
 
 static void parseEnumerators(EnumInfo &I, const EnumDecl *D) {
@@ -568,7 +581,7 @@ static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D,
   }
 }
 
-static void populateMemberTypeInfo(MemberTypeInfo &I, const FieldDecl *D) {
+static void populateMemberTypeInfo(MemberTypeInfo &I, const Decl *D) {
   assert(D && "Expect non-null FieldDecl in populateMemberTypeInfo");
 
   ASTContext& Context = D->getASTContext();
@@ -619,6 +632,7 @@ parseBases(RecordInfo &I, const CXXRecordDecl *D, bool IsFileInRootDir,
               continue;
             FunctionInfo FI;
             FI.IsMethod = true;
+            FI.IsStatic = MD->isStatic();
             // The seventh arg in populateFunctionInfo is a boolean passed by
             // reference, its value is not relevant in here so it's not used
             // anywhere besides the function call.
@@ -729,61 +743,6 @@ emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber,
   return {std::move(I), std::move(Parent)};
 }
 
-std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
-emitInfo(const VarDecl *D, const FullComment *FC, int LineNumber,
-         llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) {
-  auto I = std::make_unique<RecordInfo>();
-  bool IsInAnonymousNamespace = false;
-  populateSymbolInfo(*I, D, FC, LineNumber, File, IsFileInRootDir,
-                     IsInAnonymousNamespace);
-  if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D))
-    return {};
-
-  I->Path = getInfoRelativePath(I->Namespace);
-
-  PopulateTemplateParameters(I->Template, D);
-
-  // Full and partial specializations.
-  if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(D)) {
-    if (!I->Template)
-      I->Template.emplace();
-    I->Template->Specialization.emplace();
-    auto &Specialization = *I->Template->Specialization;
-
-    // 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 =
-                 dyn_cast<ClassTemplatePartialSpecializationDecl *>(SpecOf))
-      Specialization.SpecializationOf = getUSRForDecl(CTPSD);
-
-    // Parameters to the specilization. For partial specializations, get the
-    // parameters "as written" from the ClassTemplatePartialSpecializationDecl
-    // because the non-explicit template parameters will have generated internal
-    // placeholder names rather than the names the user typed that match the
-    // template parameters.
-    if (const ClassTemplatePartialSpecializationDecl *CTPSD =
-            dyn_cast<ClassTemplatePartialSpecializationDecl>(D)) {
-      if (const ASTTemplateArgumentListInfo *AsWritten =
-              CTPSD->getTemplateArgsAsWritten()) {
-        for (unsigned i = 0; i < AsWritten->getNumTemplateArgs(); i++) {
-          Specialization.Params.emplace_back(
-              getSourceCode(D, (*AsWritten)[i].getSourceRange()));
-        }
-      }
-    } else {
-      for (const TemplateArgument &Arg : CTSD->getTemplateArgs().asArray()) {
-        Specialization.Params.push_back(TemplateArgumentToInfo(D, Arg));
-      }
-    }
-  }
-
-  // Records are inserted into the parent by reference, so we need to return
-  // both the parent and the record itself.
-  auto Parent = MakeAndInsertIntoParent<const RecordInfo &>(*I);
-  return {std::move(I), std::move(Parent)};
-}
 
 std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
 emitInfo(const FunctionDecl *D, const FullComment *FC, int LineNumber,
@@ -811,6 +770,7 @@ emitInfo(const CXXMethodDecl *D, const FullComment *FC, int LineNumber,
     return {};
 
   Func.IsMethod = true;
+  Func.IsStatic = D->isStatic();
 
   const NamedDecl *Parent = nullptr;
   if (const auto *SD =
diff --git a/clang-tools-extra/test/clang-doc/basic-project.test b/clang-tools-extra/test/clang-doc/basic-project.test
index b14624a2b49f7..e14fa81a58b88 100644
--- a/clang-tools-extra/test/clang-doc/basic-project.test
+++ b/clang-tools-extra/test/clang-doc/basic-project.test
@@ -52,15 +52,7 @@
 // JSON-INDEX-NEXT:           "Name": "Calculator",
 // JSON-INDEX-NEXT:           "RefType": "record",
 // JSON-INDEX-NEXT:           "Path": "GlobalNamespace",
-// JSON-INDEX-NEXT:           "Children": [
-// JSON-INDEX-NEXT:             {
-// JSON-INDEX-NEXT:               "USR": "{{([0-9A-F]{40})}}",
-// JSON-INDEX-NEXT:               "Name": "static_val",
-// JSON-INDEX-NEXT:               "RefType": "record",
-// JSON-INDEX-NEXT:               "Path": "GlobalNamespace{{[\/]+}}Calculator",
-// JSON-INDEX-NEXT:               "Children": []
-// JSON-INDEX-NEXT:             }
-// JSON-INDEX-NEXT:           ]
+// JSON-INDEX-NEXT:           "Children": []
 // JSON-INDEX-NEXT:         },
 // JSON-INDEX-NEXT:         {
 // JSON-INDEX-NEXT:           "USR": "{{([0-9A-F]{40})}}",
@@ -142,9 +134,9 @@
 //      HTML-CALC: <div>brief</div>
 //      HTML-CALC: <p> Holds a public value.</p>
 //      HTML-CALC: <div>public int public_val</div>
-
-//      HTML-CALC: <h2 id="Records">Records</h2>
-//      HTML-CALC: <a href="../GlobalNamespace/Calculator/static_val.html">static_val</a>
+//      HTML-CALC: <div>brief</div>
+//      HTML-CALC: <p> A static value.</p>
+//      HTML-CALC: <div>public static const int static_val</div>
 
 //      HTML-CALC: <h2 id="Functions">Functions</h2>
 //      HTML-CALC: <h3 id="{{([0-9A-F]{40})}}">add</h3>
@@ -202,7 +194,7 @@
 //      HTML-CALC: <div>throw</div>
 //      HTML-CALC: <p>if b is zero.</p>
 
-//      HTML-CALC: <p>public int mod(int a, int b)</p>
+//      HTML-CALC: <p>public static int mod(int a, int b)</p>
 //   CALC-NO-REPOSITORY: Defined at line 54 of file .{{.}}include{{.}}Calculator.h
 //      CALC-REPOSITORY: Defined at line 
 // CALC-REPOSITORY-NEXT: <a href="https://repository.com/./include/Calculator.h#54">54</a>
@@ -337,8 +329,6 @@
 // MD-CALC:  Provides basic arithmetic operations.
 // MD-CALC: ## Members
 // MD-CALC: public int public_val
-// MD-CALC: ## Records
-// MD-CALC: static_val
 // MD-CALC: ## Functions
 // MD-CALC: ### add
 // MD-CALC: *public int add(int a, int b)*

>From fc820045ae33e2a86eebb2f0e513dd11803b5d02 Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Sat, 12 Apr 2025 13:03:20 -0700
Subject: [PATCH 07/12] fix formatting

---
 clang-tools-extra/clang-doc/BitcodeWriter.cpp | 3 ++-
 clang-tools-extra/clang-doc/HTMLGenerator.cpp | 6 +++---
 clang-tools-extra/clang-doc/Serialize.cpp     | 1 -
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.cpp b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
index fbc3f9969dbed..621af4e51e443 100644
--- a/clang-tools-extra/clang-doc/BitcodeWriter.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
@@ -224,7 +224,8 @@ static const std::vector<std::pair<BlockId, std::vector<RecordId>>>
         // FieldType Block
         {BI_FIELD_TYPE_BLOCK_ID, {FIELD_TYPE_NAME, FIELD_DEFAULT_VALUE}},
         // MemberType Block
-        {BI_MEMBER_TYPE_BLOCK_ID, {MEMBER_TYPE_NAME, MEMBER_TYPE_ACCESS, MEMBER_TYPE_IS_STATIC}},
+        {BI_MEMBER_TYPE_BLOCK_ID,
+         {MEMBER_TYPE_NAME, MEMBER_TYPE_ACCESS, MEMBER_TYPE_IS_STATIC}},
         // Enum Block
         {BI_ENUM_BLOCK_ID,
          {ENUM_USR, ENUM_NAME, ENUM_DEFLOCATION, ENUM_LOCATION, ENUM_SCOPED}},
diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
index 1bc3265c1fe89..e54c3f8a4ed42 100644
--- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
@@ -419,7 +419,7 @@ genRecordMembersBlock(const llvm::SmallVector<MemberTypeInfo, 4> &Members,
     std::string Access = getAccessSpelling(M.Access).str();
     if (Access != "")
       Access = Access + " ";
-    if(M.IsStatic)
+    if (M.IsStatic)
       Access += "static ";
     auto LIBody = std::make_unique<TagNode>(HTMLTag::TAG_LI);
     auto MemberDecl = std::make_unique<TagNode>(HTMLTag::TAG_DIV);
@@ -742,10 +742,10 @@ genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx,
   if (Access != "")
     FunctionHeader->Children.emplace_back(
         std::make_unique<TextNode>(Access + " "));
-  if(I.IsStatic)
+  if (I.IsStatic)
     FunctionHeader->Children.emplace_back(
         std::make_unique<TextNode>("static "));
-  else{
+  else {
     llvm::errs() << I.Name << " is not static\n";
   }
   if (I.ReturnType.Type.Name != "") {
diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp
index 429f7de5a7202..727e2ed6e93d9 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -743,7 +743,6 @@ emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber,
   return {std::move(I), std::move(Parent)};
 }
 
-
 std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
 emitInfo(const FunctionDecl *D, const FullComment *FC, int LineNumber,
          llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) {

>From e97e6f2c6bae585f642749e23cf581f14758bd18 Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Sat, 12 Apr 2025 13:59:19 -0700
Subject: [PATCH 08/12] Introduce helper function and remove debugging code

---
 clang-tools-extra/clang-doc/HTMLGenerator.cpp |  3 --
 clang-tools-extra/clang-doc/Representation.h  |  5 +-
 clang-tools-extra/clang-doc/Serialize.cpp     | 53 +++++++++++--------
 3 files changed, 34 insertions(+), 27 deletions(-)

diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
index e54c3f8a4ed42..0fc1d74d806d5 100644
--- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
@@ -745,9 +745,6 @@ genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx,
   if (I.IsStatic)
     FunctionHeader->Children.emplace_back(
         std::make_unique<TextNode>("static "));
-  else {
-    llvm::errs() << I.Name << " is not static\n";
-  }
   if (I.ReturnType.Type.Name != "") {
     FunctionHeader->Children.emplace_back(
         genReference(I.ReturnType.Type, ParentInfoDir));
diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h
index dc95c6295f900..d87a13f8ff119 100644
--- a/clang-tools-extra/clang-doc/Representation.h
+++ b/clang-tools-extra/clang-doc/Representation.h
@@ -220,8 +220,9 @@ struct FieldTypeInfo : public TypeInfo {
 // Info for member types.
 struct MemberTypeInfo : public FieldTypeInfo {
   MemberTypeInfo() = default;
-  MemberTypeInfo(const TypeInfo &TI, StringRef Name, AccessSpecifier Access)
-      : FieldTypeInfo(TI, Name), Access(Access) {}
+  MemberTypeInfo(const TypeInfo &TI, StringRef Name, AccessSpecifier Access,
+                 bool IsStatic = false)
+      : FieldTypeInfo(TI, Name), Access(Access), IsStatic(IsStatic) {}
 
   bool operator==(const MemberTypeInfo &Other) const {
     return std::tie(Type, Name, Access, IsStatic, Description) ==
diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp
index 727e2ed6e93d9..496c8674c1ae2 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -31,6 +31,10 @@ populateParentNamespaces(llvm::SmallVector<Reference, 4> &Namespaces,
                          const T *D, bool &IsAnonymousNamespace);
 
 static void populateMemberTypeInfo(MemberTypeInfo &I, const Decl *D);
+static void populateMemberTypeInfo(RecordInfo &I, AccessSpecifier &Access,
+                                   const LangOptions &LO,
+                                   const DeclaratorDecl *D,
+                                   bool IsStatic = false);
 
 // A function to extract the appropriate relative path for a given info's
 // documentation. The path returned is a composite of the parent namespaces.
@@ -371,35 +375,25 @@ static AccessSpecifier getFinalAccessSpecifier(AccessSpecifier FirstAS,
   return AccessSpecifier::AS_public;
 }
 
-// The Access parameter is only provided when parsing the field of an inherited
-// record, the access specification of the field depends on the inheritance mode
 static void parseFields(RecordInfo &I, const RecordDecl *D, bool PublicOnly,
                         AccessSpecifier Access = AccessSpecifier::AS_public) {
   auto &LO = D->getLangOpts();
   for (const FieldDecl *F : D->fields()) {
     if (!shouldSerializeInfo(PublicOnly, /*IsInAnonymousNamespace=*/false, F))
       continue;
-
-    // Use getAccessUnsafe so that we just get the default AS_none if it's not
-    // valid, as opposed to an assert.
-    MemberTypeInfo &NewMember = I.Members.emplace_back(
-        getTypeInfoForType(F->getTypeSourceInfo()->getType(), LO),
-        F->getNameAsString(),
-        getFinalAccessSpecifier(Access, F->getAccessUnsafe()));
-    populateMemberTypeInfo(NewMember, F);
+    populateMemberTypeInfo(I, Access, LO, F);
   }
-  const CXXRecordDecl *CPP = dyn_cast<CXXRecordDecl>(D);
-  for (Decl *F : CPP->decls()) {
-    if (auto *VD = dyn_cast<VarDecl>(F)) {
-      if (VD->isStaticDataMember()) {
-        MemberTypeInfo &NewMember = I.Members.emplace_back(
-            getTypeInfoForType(VD->getTypeSourceInfo()->getType(), LO),
-            VD->getNameAsString(),
-            getFinalAccessSpecifier(Access, F->getAccessUnsafe()));
-        NewMember.IsStatic = true;
-        populateMemberTypeInfo(NewMember, VD);
-      }
-    }
+  const auto *CxxRD = dyn_cast<CXXRecordDecl>(D);
+  if (!CxxRD)
+    return;
+  for (Decl *CxxDecl : CxxRD->decls()) {
+    auto *VD = dyn_cast<VarDecl>(CxxDecl);
+    if (!VD ||
+        !shouldSerializeInfo(PublicOnly, /*IsInAnonymousNamespace=*/false, VD))
+      continue;
+
+    if (VD->isStaticDataMember())
+      populateMemberTypeInfo(I, Access, LO, VD, /*IsStatic=*/true);
   }
 }
 
@@ -598,6 +592,21 @@ static void populateMemberTypeInfo(MemberTypeInfo &I, const Decl *D) {
   }
 }
 
+// The Access parameter is only provided when parsing the field of an inherited
+// record, the access specification of the field depends on the inheritance mode
+static void populateMemberTypeInfo(RecordInfo &I, AccessSpecifier &Access,
+                                   const LangOptions &LO,
+                                   const DeclaratorDecl *D,
+                                   bool IsStatic) {
+  // Use getAccessUnsafe so that we just get the default AS_none if it's not
+  // valid, as opposed to an assert.
+  MemberTypeInfo &NewMember = I.Members.emplace_back(
+      getTypeInfoForType(D->getTypeSourceInfo()->getType(), LO),
+      D->getNameAsString(),
+      getFinalAccessSpecifier(Access, D->getAccessUnsafe()), IsStatic);
+  populateMemberTypeInfo(NewMember, D);
+}
+
 static void
 parseBases(RecordInfo &I, const CXXRecordDecl *D, bool IsFileInRootDir,
            bool PublicOnly, bool IsParent,

>From 0a2e3fa4da9c7c38a894657afeec454060c629d8 Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Sat, 12 Apr 2025 15:29:08 -0700
Subject: [PATCH 09/12] Handle missing MD case

---
 clang-tools-extra/clang-doc/MDGenerator.cpp   | 24 +++++++++----------
 clang-tools-extra/clang-doc/Serialize.cpp     | 12 ++++------
 .../test/clang-doc/basic-project.test         |  3 ++-
 3 files changed, 18 insertions(+), 21 deletions(-)

diff --git a/clang-tools-extra/clang-doc/MDGenerator.cpp b/clang-tools-extra/clang-doc/MDGenerator.cpp
index 5c782fcc10da5..641119ba1d2bd 100644
--- a/clang-tools-extra/clang-doc/MDGenerator.cpp
+++ b/clang-tools-extra/clang-doc/MDGenerator.cpp
@@ -170,14 +170,13 @@ static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I,
   }
   writeHeader(I.Name, 3, OS);
   std::string Access = getAccessSpelling(I.Access).str();
-  if (Access != "")
-    writeLine(genItalic(Access + " " + I.ReturnType.Type.QualName + " " +
-                        I.Name + "(" + Stream.str() + ")"),
-              OS);
-  else
-    writeLine(genItalic(I.ReturnType.Type.QualName + " " + I.Name + "(" +
-                        Stream.str() + ")"),
-              OS);
+  if (!Access.empty())
+    Access += " ";
+  if (I.IsStatic)
+    Access += "static ";
+  writeLine(genItalic(Access + I.ReturnType.Type.QualName + " " + I.Name + "(" +
+                      Stream.str() + ")"),
+            OS);
 
   maybeWriteSourceFileRef(OS, CDCtx, I.DefLoc);
 
@@ -263,10 +262,11 @@ static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I,
     writeHeader("Members", 2, OS);
     for (const auto &Member : I.Members) {
       std::string Access = getAccessSpelling(Member.Access).str();
-      if (Access != "")
-        writeLine(Access + " " + Member.Type.Name + " " + Member.Name, OS);
-      else
-        writeLine(Member.Type.Name + " " + Member.Name, OS);
+      if (!Access.empty())
+        Access += " ";
+      if (Member.IsStatic)
+        Access += "static ";
+      writeLine(Access + Member.Type.Name + " " + Member.Name, OS);
     }
     writeNewLine(OS);
   }
diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp
index 496c8674c1ae2..4ee5416cd0ef5 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -32,7 +32,6 @@ populateParentNamespaces(llvm::SmallVector<Reference, 4> &Namespaces,
 
 static void populateMemberTypeInfo(MemberTypeInfo &I, const Decl *D);
 static void populateMemberTypeInfo(RecordInfo &I, AccessSpecifier &Access,
-                                   const LangOptions &LO,
                                    const DeclaratorDecl *D,
                                    bool IsStatic = false);
 
@@ -377,11 +376,10 @@ static AccessSpecifier getFinalAccessSpecifier(AccessSpecifier FirstAS,
 
 static void parseFields(RecordInfo &I, const RecordDecl *D, bool PublicOnly,
                         AccessSpecifier Access = AccessSpecifier::AS_public) {
-  auto &LO = D->getLangOpts();
   for (const FieldDecl *F : D->fields()) {
     if (!shouldSerializeInfo(PublicOnly, /*IsInAnonymousNamespace=*/false, F))
       continue;
-    populateMemberTypeInfo(I, Access, LO, F);
+    populateMemberTypeInfo(I, Access, F);
   }
   const auto *CxxRD = dyn_cast<CXXRecordDecl>(D);
   if (!CxxRD)
@@ -393,7 +391,7 @@ static void parseFields(RecordInfo &I, const RecordDecl *D, bool PublicOnly,
       continue;
 
     if (VD->isStaticDataMember())
-      populateMemberTypeInfo(I, Access, LO, VD, /*IsStatic=*/true);
+      populateMemberTypeInfo(I, Access, VD, /*IsStatic=*/true);
   }
 }
 
@@ -595,13 +593,11 @@ static void populateMemberTypeInfo(MemberTypeInfo &I, const Decl *D) {
 // The Access parameter is only provided when parsing the field of an inherited
 // record, the access specification of the field depends on the inheritance mode
 static void populateMemberTypeInfo(RecordInfo &I, AccessSpecifier &Access,
-                                   const LangOptions &LO,
-                                   const DeclaratorDecl *D,
-                                   bool IsStatic) {
+                                   const DeclaratorDecl *D, bool IsStatic) {
   // Use getAccessUnsafe so that we just get the default AS_none if it's not
   // valid, as opposed to an assert.
   MemberTypeInfo &NewMember = I.Members.emplace_back(
-      getTypeInfoForType(D->getTypeSourceInfo()->getType(), LO),
+      getTypeInfoForType(D->getTypeSourceInfo()->getType(), D->getLangOpts()),
       D->getNameAsString(),
       getFinalAccessSpecifier(Access, D->getAccessUnsafe()), IsStatic);
   populateMemberTypeInfo(NewMember, D);
diff --git a/clang-tools-extra/test/clang-doc/basic-project.test b/clang-tools-extra/test/clang-doc/basic-project.test
index e14fa81a58b88..9c1ed29973d79 100644
--- a/clang-tools-extra/test/clang-doc/basic-project.test
+++ b/clang-tools-extra/test/clang-doc/basic-project.test
@@ -329,6 +329,7 @@
 // MD-CALC:  Provides basic arithmetic operations.
 // MD-CALC: ## Members
 // MD-CALC: public int public_val
+// MD-CALC: public static const int static_val
 // MD-CALC: ## Functions
 // MD-CALC: ### add
 // MD-CALC: *public int add(int a, int b)*
@@ -360,7 +361,7 @@
 // MD-CALC: **return** double The result of a / b.
 // MD-CALC: **throw**if b is zero.
 // MD-CALC: ### mod
-// MD-CALC: *public int mod(int a, int b)*
+// MD-CALC: *public static int mod(int a, int b)*
 // MD-CALC: *Defined at ./include{{[\/]}}Calculator.h#54*
 // MD-CALC: **brief** Performs the mod operation on integers.
 // MD-CALC: **a** First integer.

>From c56abae6b22a47ec7582e6fc316fb7c504202268 Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Sat, 12 Apr 2025 15:41:48 -0700
Subject: [PATCH 10/12] Put comment back in original place

---
 clang-tools-extra/clang-doc/Serialize.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp
index 4ee5416cd0ef5..837ecdd9f1278 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -374,6 +374,8 @@ static AccessSpecifier getFinalAccessSpecifier(AccessSpecifier FirstAS,
   return AccessSpecifier::AS_public;
 }
 
+// The Access parameter is only provided when parsing the field of an inherited
+// record, the access specification of the field depends on the inheritance mode
 static void parseFields(RecordInfo &I, const RecordDecl *D, bool PublicOnly,
                         AccessSpecifier Access = AccessSpecifier::AS_public) {
   for (const FieldDecl *F : D->fields()) {
@@ -590,8 +592,6 @@ static void populateMemberTypeInfo(MemberTypeInfo &I, const Decl *D) {
   }
 }
 
-// The Access parameter is only provided when parsing the field of an inherited
-// record, the access specification of the field depends on the inheritance mode
 static void populateMemberTypeInfo(RecordInfo &I, AccessSpecifier &Access,
                                    const DeclaratorDecl *D, bool IsStatic) {
   // Use getAccessUnsafe so that we just get the default AS_none if it's not

>From 8586b7739e2e7d03b9afa11fd20822f34fcc05d0 Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Sat, 12 Apr 2025 15:47:16 -0700
Subject: [PATCH 11/12] Move fields to end of struct definition

---
 clang-tools-extra/clang-doc/Representation.h | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h
index d87a13f8ff119..d7f2453fe11b2 100644
--- a/clang-tools-extra/clang-doc/Representation.h
+++ b/clang-tools-extra/clang-doc/Representation.h
@@ -317,17 +317,12 @@ struct NamespaceInfo : public Info {
 
 // Info for symbols.
 struct SymbolInfo : public Info {
-  bool IsStatic = false;
-
   SymbolInfo(InfoType IT, SymbolID USR = SymbolID(),
              StringRef Name = StringRef(), StringRef Path = StringRef())
       : Info(IT, USR, Name, Path) {}
 
   void merge(SymbolInfo &&I);
 
-  std::optional<Location> DefLoc;     // Location where this decl is defined.
-  llvm::SmallVector<Location, 2> Loc; // Locations where this decl is declared.
-
   bool operator<(const SymbolInfo &Other) const {
     // Sort by declaration location since we want the doc to be
     // generated in the order of the source code.
@@ -341,6 +336,10 @@ struct SymbolInfo : public Info {
 
     return extractName() < Other.extractName();
   }
+
+  std::optional<Location> DefLoc;     // Location where this decl is defined.
+  llvm::SmallVector<Location, 2> Loc; // Locations where this decl is declared.
+  bool IsStatic = false;
 };
 
 // TODO: Expand to allow for documenting templating and default args.

>From 2079253053fab7af4b69c941c1fc7a9531a51929 Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Sat, 12 Apr 2025 16:07:39 -0700
Subject: [PATCH 12/12] Avoid concatenation by using StringRef + Twine in calls

---
 clang-tools-extra/clang-doc/MDGenerator.cpp | 23 +++++++++------------
 1 file changed, 10 insertions(+), 13 deletions(-)

diff --git a/clang-tools-extra/clang-doc/MDGenerator.cpp b/clang-tools-extra/clang-doc/MDGenerator.cpp
index 641119ba1d2bd..71e61c2852718 100644
--- a/clang-tools-extra/clang-doc/MDGenerator.cpp
+++ b/clang-tools-extra/clang-doc/MDGenerator.cpp
@@ -169,13 +169,11 @@ static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I,
     First = false;
   }
   writeHeader(I.Name, 3, OS);
-  std::string Access = getAccessSpelling(I.Access).str();
-  if (!Access.empty())
-    Access += " ";
-  if (I.IsStatic)
-    Access += "static ";
-  writeLine(genItalic(Access + I.ReturnType.Type.QualName + " " + I.Name + "(" +
-                      Stream.str() + ")"),
+  StringRef Access = getAccessSpelling(I.Access);
+  writeLine(genItalic(Twine(Access) + (!Access.empty() ? " " : "") +
+                      (I.IsStatic ? "static " : "") +
+                      I.ReturnType.Type.QualName.str() + " " + I.Name.str() + "(" +
+                      Twine(Stream.str()) + ")"),
             OS);
 
   maybeWriteSourceFileRef(OS, CDCtx, I.DefLoc);
@@ -261,12 +259,11 @@ static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I,
   if (!I.Members.empty()) {
     writeHeader("Members", 2, OS);
     for (const auto &Member : I.Members) {
-      std::string Access = getAccessSpelling(Member.Access).str();
-      if (!Access.empty())
-        Access += " ";
-      if (Member.IsStatic)
-        Access += "static ";
-      writeLine(Access + Member.Type.Name + " " + Member.Name, OS);
+      StringRef Access = getAccessSpelling(Member.Access);
+      writeLine(Twine(Access) + (Access.empty() ? "" : " ") +
+                    (Member.IsStatic ? "static " : "") + Member.Type.Name.str() +
+                    " " + Member.Name.str(),
+                OS);
     }
     writeNewLine(OS);
   }



More information about the cfe-commits mailing list