https://github.com/evelez7 updated 
https://github.com/llvm/llvm-project/pull/173960

>From 6cbfa25c8ac1372120338859659ec8adb599174e Mon Sep 17 00:00:00 2001
From: Erick Velez <[email protected]>
Date: Wed, 24 Dec 2025 14:14:10 -0800
Subject: [PATCH] [clang-doc] Add friends to class template

This patch also allows comments to be associated with friend
declarations. Currently, it seems like the comments for friend `RecordDecl`
are taken from the actual class declaration, while a friend
function's comments are taken from the actual `friend` declaration.
---
 clang-tools-extra/clang-doc/BitcodeReader.cpp |  4 ++
 clang-tools-extra/clang-doc/BitcodeWriter.cpp |  2 +
 clang-tools-extra/clang-doc/JSONGenerator.cpp |  7 ++-
 .../clang-doc/Representation.cpp              |  1 +
 clang-tools-extra/clang-doc/Serialize.cpp     |  7 ++-
 .../clang-doc/assets/class-template.mustache  | 35 +++++++++++
 .../test/clang-doc/json/class.cpp             | 62 ++++++++++++++++++-
 7 files changed, 111 insertions(+), 7 deletions(-)

diff --git a/clang-tools-extra/clang-doc/BitcodeReader.cpp 
b/clang-tools-extra/clang-doc/BitcodeReader.cpp
index cf2d5dedafb14..76b5c6cefca6b 100644
--- a/clang-tools-extra/clang-doc/BitcodeReader.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeReader.cpp
@@ -515,6 +515,10 @@ template <> Expected<CommentInfo *> getCommentInfo(VarInfo 
*I) {
   return &I->Description.emplace_back();
 }
 
+template <> Expected<CommentInfo *> getCommentInfo(FriendInfo *I) {
+  return &I->Description.emplace_back();
+}
+
 // When readSubBlock encounters a TypeInfo sub-block, it calls addTypeInfo on
 // the parent block to set it. The template specializations define what to do
 // for each supported parent block.
diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.cpp 
b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
index e7537458d6e44..8a7efd82bc75d 100644
--- a/clang-tools-extra/clang-doc/BitcodeWriter.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
@@ -493,6 +493,8 @@ void ClangDocBitcodeWriter::emitBlock(const FriendInfo &R) {
       emitBlock(P);
   if (R.ReturnType)
     emitBlock(*R.ReturnType);
+  for (const auto &CI : R.Description)
+    emitBlock(CI);
 }
 
 void ClangDocBitcodeWriter::emitBlock(const TypeInfo &T) {
diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp 
b/clang-tools-extra/clang-doc/JSONGenerator.cpp
index 791923562932f..15fcbb4a9cff5 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -281,7 +281,7 @@ static Object serializeComment(const CommentInfo &I, Object 
&Description) {
 static void
 serializeCommonAttributes(const Info &I, json::Object &Obj,
                           const std::optional<StringRef> RepositoryUrl) {
-  Obj["Name"] = I.Name;
+  insertNonEmpty("Name", I.Name, Obj);
   Obj["USR"] = toHex(toStringRef(I.USR));
   Obj["InfoType"] = infoTypeToString(I.IT);
   // Conditionally insert fields.
@@ -528,6 +528,7 @@ static void serializeInfo(const FriendInfo &I, Object &Obj) 
{
     serializeInfo(I.ReturnType.value(), ReturnTypeObj);
     Obj["ReturnType"] = std::move(ReturnTypeObj);
   }
+  serializeCommonAttributes(I, Obj, std::nullopt);
 }
 
 static void insertArray(Object &Obj, json::Value &Array, StringRef Key) {
@@ -617,8 +618,10 @@ static void serializeInfo(const RecordInfo &I, 
json::Object &Obj,
   if (I.Template)
     serializeInfo(I.Template.value(), Obj);
 
-  if (!I.Friends.empty())
+  if (!I.Friends.empty()) {
     serializeArray(I.Friends, Obj, "Friends", SerializeInfoLambda);
+    Obj["HasFriends"] = true;
+  }
 
   serializeCommonChildren(I.Children, Obj, RepositoryUrl);
 }
diff --git a/clang-tools-extra/clang-doc/Representation.cpp 
b/clang-tools-extra/clang-doc/Representation.cpp
index d840350ba8426..a0487c69b7378 100644
--- a/clang-tools-extra/clang-doc/Representation.cpp
+++ b/clang-tools-extra/clang-doc/Representation.cpp
@@ -254,6 +254,7 @@ bool FriendInfo::mergeable(const FriendInfo &Other) {
 void FriendInfo::merge(FriendInfo &&Other) {
   assert(mergeable(Other));
   Ref.merge(std::move(Other.Ref));
+  SymbolInfo::merge(std::move(Other));
 }
 
 void Info::mergeBase(Info &&Other) {
diff --git a/clang-tools-extra/clang-doc/Serialize.cpp 
b/clang-tools-extra/clang-doc/Serialize.cpp
index c52106b11f84b..776399d2b5a60 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -46,7 +46,7 @@ static void
 populateParentNamespaces(llvm::SmallVector<Reference, 4> &Namespaces,
                          const T *D, bool &IsAnonymousNamespace);
 
-static void populateMemberTypeInfo(MemberTypeInfo &I, const Decl *D);
+template <typename T> static void populateMemberTypeInfo(T &I, const Decl *D);
 static void populateMemberTypeInfo(RecordInfo &I, AccessSpecifier &Access,
                                    const DeclaratorDecl *D,
                                    bool IsStatic = false);
@@ -819,7 +819,9 @@ static void populateFunctionInfo(FunctionInfo &I, const 
FunctionDecl *D,
   }
 }
 
-static void populateMemberTypeInfo(MemberTypeInfo &I, const Decl *D) {
+// TODO: Rename this, since this doesn't populate anything besides comments and
+// isn't exclusive to members
+template <typename T> static void populateMemberTypeInfo(T &I, const Decl *D) {
   assert(D && "Expect non-null FieldDecl in populateMemberTypeInfo");
 
   ASTContext &Context = D->getASTContext();
@@ -968,6 +970,7 @@ static void parseFriends(RecordInfo &RI, const 
CXXRecordDecl *D) {
                   InfoType::IT_default, ActualDecl->getQualifiedNameAsString(),
                   getInfoRelativePath(ActualDecl));
 
+    populateMemberTypeInfo(F, ActualDecl);
     RI.Friends.push_back(std::move(F));
   }
 }
diff --git a/clang-tools-extra/clang-doc/assets/class-template.mustache 
b/clang-tools-extra/clang-doc/assets/class-template.mustache
index a539671f52e6d..c28655fbb219a 100644
--- a/clang-tools-extra/clang-doc/assets/class-template.mustache
+++ b/clang-tools-extra/clang-doc/assets/class-template.mustache
@@ -113,6 +113,20 @@
                         </ul>
                     </li>
                     {{/HasRecords}}
+                    {{#HasFriends}}
+                    <li class="sidebar-section">
+                        <a class="sidebar-item" href="#Friends">Friends</a>
+                    </li>
+                    <li>
+                        <ul>
+                            {{#Friends}}
+                            <li class="sidebar-item-container">
+                                <a class="sidebar-item" 
href="#{{Reference.USR}}">{{Reference.Name}}</a>
+                            </li>
+                            {{/Friends}}
+                        </ul>
+                    </li>
+                    {{/HasRecords}}
                 </ul>
             </div>
             <div class="resizer" id="resizer"></div>
@@ -217,6 +231,27 @@
                     {{/Typedefs}}
                 </section>
                 {{/HasTypedefs}}
+                {{#HasFriends}}
+                <section id="Friends" class="section-container">
+                    <h2>Friends</h2>
+                    {{#Friends}}
+                    <div id="{{Reference.USR}}" class="delimiter-container">
+                        {{#Template}}
+                        <pre><code class="language-cpp 
code-clang-doc">template &lt;{{#Parameters}}{{Param}}{{^End}}, 
{{/End}}{{/Parameters}}&gt;</code></pre>
+                        {{/Template}}
+                        {{#IsClass}}
+                        <pre><code class="language-cpp code-clang-doc">class 
{{Reference.Name}}</code></pre>
+                        {{/IsClass}}
+                        {{^IsClass}}
+                        <pre><code class="language-cpp 
code-clang-doc">{{ReturnType.Name}} 
{{Name}}{{#Template}}{{#Specialization}}&lt;{{#Parameters}}{{Param}}{{^End}}, 
{{/End}}{{/Parameters}}&gt;{{/Specialization}}{{/Template}} 
({{#Params}}{{^End}}{{Type}} {{Name}}, {{/End}}{{#End}}{{Type}} 
{{Name}}{{/End}}{{/Params}})</code></pre>
+                        {{/IsClass}}
+                        {{#.Description}}
+                        {{>Comments}}
+                        {{/.Description}}
+                    </div>
+                    {{/Friends}}
+                </section>
+                {{/HasFriends}}
             </div>
         </div>
     </main>
diff --git a/clang-tools-extra/test/clang-doc/json/class.cpp 
b/clang-tools-extra/test/clang-doc/json/class.cpp
index 43aa8df187c07..9feb04c792a43 100644
--- a/clang-tools-extra/test/clang-doc/json/class.cpp
+++ b/clang-tools-extra/test/clang-doc/json/class.cpp
@@ -3,6 +3,7 @@
 // RUN: FileCheck %s < %t/json/GlobalNamespace/_ZTV7MyClass.json
 // RUN: FileCheck %s < %t/html/GlobalNamespace/_ZTV7MyClass.html 
-check-prefix=HTML
 
+/// This is a struct friend.
 struct Foo;
 
 // This is a nice class.
@@ -26,6 +27,7 @@ struct MyClass {
   class NestedClass;
   
   friend struct Foo;
+  /// This is a function template friend.
   template<typename T> friend void friendFunction(int);
 protected:
   int protectedMethod();
@@ -58,7 +60,7 @@ struct MyClass {
 // CHECK-NEXT:        "InfoType": "enum",
 // CHECK-NEXT:        "Location": {
 // CHECK-NEXT:          "Filename": "{{.*}}class.cpp",
-// CHECK-NEXT:          "LineNumber": 18
+// CHECK-NEXT:          "LineNumber": 19
 // CHECK-NEXT:        },
 // CHECK-NEXT:        "Members": [
 // CHECK-NEXT:          {
@@ -86,6 +88,17 @@ struct MyClass {
 // CHECK-NEXT:    ],
 // CHECK-NEXT:    "Friends": [
 // CHECK-NEXT:      {
+// CHECK-NEXT:        "Description": {
+// CHECK-NEXT:          "HasParagraphComments": true,
+// CHECK-NEXT:          "ParagraphComments": [
+// CHECK-NEXT:            [
+// CHECK-NEXT:              {
+// CHECK-NEXT:                "TextComment": " This is a function template 
friend."
+// CHECK-NEXT:              }
+// CHECK-NEXT:            ]
+// CHECK-NEXT:          ]
+// CHECK-NEXT:        },
+// CHECK-NEXT:        "InfoType": "friend",
 // CHECK-NEXT:        "IsClass": false,
 // CHECK-NEXT:        "Params": [
 // CHECK-NEXT:          {
@@ -114,9 +127,21 @@ struct MyClass {
 // CHECK-NEXT:            }
 // CHECK-NEXT:          ]
 // CHECK-NEXT:        }
+// CHECK-NEXT:        "USR": "0000000000000000000000000000000000000000"
 // CHECK-NEXT:      },
 // CHECK-NEXT:      {
+// CHECK-NEXT:        "Description": {
+// CHECK-NEXT:          "HasParagraphComments": true,
+// CHECK-NEXT:          "ParagraphComments": [
+// CHECK-NEXT:            [
+// CHECK-NEXT:              {
+// CHECK-NEXT:                "TextComment": " This is a struct friend."
+// CHECK-NEXT:              }
+// CHECK-NEXT:            ]
+// CHECK-NEXT:          ]
+// CHECK-NEXT:        },
 // CHECK-NEXT:        "End": true,
+// CHECK-NEXT:        "InfoType": "friend",
 // CHECK-NEXT:        "IsClass": true,
 // CHECK-NEXT:        "Reference": {
 // CHECK-NEXT:          "Name": "Foo",
@@ -124,9 +149,11 @@ struct MyClass {
 // CHECK-NEXT:          "QualName": "Foo",
 // CHECK-NEXT:          "USR": "{{[0-9A-F]*}}"
 // CHECK-NEXT:        }
+// CHECK-NEXT:        "USR": "0000000000000000000000000000000000000000"
 // CHECK-NEXT:      }
 // CHECK-NEXT:    ],
 // CHECK-NEXT:    "HasEnums": true,
+// CHECK-NEXT:    "HasFriends": true,
 // CHECK-NEXT:    "HasPrivateMembers": true,
 // CHECK-NEXT:    "HasPublicFunctions": true,
 // CHECK-NEXT:    "HasPublicMembers": true,
@@ -136,7 +163,7 @@ struct MyClass {
 // CHECK-NEXT:    "IsTypedef": false,
 // CHECK-NEXT:    "Location": {
 // CHECK-NEXT:      "Filename": "{{.*}}class.cpp",
-// CHECK-NEXT:      "LineNumber": 11
+// CHECK-NEXT:      "LineNumber": 12
 // CHECK-NEXT:    },
 // CHECK-NEXT:    "MangledName": "_ZTV7MyClass",
 // CHECK-NEXT:    "Name": "MyClass",
@@ -237,7 +264,7 @@ struct MyClass {
 // CHECK-NEXT:        "IsUsing": false,
 // CHECK-NEXT:        "Location": {
 // CHECK-NEXT:          "Filename": "{{.*}}class.cpp",
-// CHECK-NEXT:          "LineNumber": 24
+// CHECK-NEXT:          "LineNumber": 25
 // CHECK-NEXT:        },
 // CHECK-NEXT:        "Name": "MyTypedef",
 // CHECK-NEXT:        "Namespace": [
@@ -264,6 +291,18 @@ struct MyClass {
 // HTML-NEXT:             </li>
 // HTML-NEXT:         </ul>
 // HTML-NEXT:     </li>
+// HTML:              <a class="sidebar-item" href="#Friends">Friends</a>
+// HTML-NEXT:     </li>
+// HTML-NEXT:     <li>
+// HTML-NEXT:         <ul>
+// HTML-NEXT:             <li class="sidebar-item-container">
+// HTML-NEXT:                 <a class="sidebar-item" 
href="#{{([0-9A-F]{40})}}">friendFunction</a>
+// HTML-NEXT:             </li>
+// HTML-NEXT:             <li class="sidebar-item-container">
+// HTML-NEXT:                 <a class="sidebar-item" 
href="#{{([0-9A-F]{40})}}">Foo</a>
+// HTML-NEXT:             </li>
+// HTML-NEXT:         </ul>
+// HTML-NEXT:     </li>
 // HTML:      <section id="Classes" class="section-container">
 // HTML-NEXT:     <h2>Inner Classes</h2>
 // HTML-NEXT:     <ul class="class-container">
@@ -274,3 +313,20 @@ struct MyClass {
 // HTML-NEXT:         </li>
 // HTML-NEXT:     </ul>
 // HTML-NEXT: </section>
+// HTML:      <section id="Friends" class="section-container">
+// HTML-NEXT:     <h2>Friends</h2>
+// HTML-NEXT:     <div id="{{([0-9A-F]{40})}}" class="delimiter-container">
+// HTML-NEXT:         <pre><code class="language-cpp code-clang-doc">template 
&lt;typename T&gt;</code></pre>
+// HTML-NEXT:         <pre><code class="language-cpp code-clang-doc">void 
MyClass (int )</code></pre>
+// HTML-NEXT:         <div>
+// HTML-NEXT:             <p> This is a function template friend.</p>
+// HTML-NEXT:         </div>
+// HTML-NEXT:     </div>
+// HTML-NEXT:     <div id="{{([0-9A-F]{40})}}" class="delimiter-container">
+// HTML-NEXT:         <pre><code class="language-cpp code-clang-doc">class 
Foo</code></pre>
+// HTML-NEXT:         <div>
+// HTML-NEXT:             <p> This is a struct friend.</p>
+// HTML-NEXT:         </div>
+// HTML-NEXT:     </div>
+// HTML-NEXT: </section>
+

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to