[llvm-branch-commits] [clang-tools-extra] [clang-doc] Add friends to class template (PR #173960)
Erick Velez via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Fri Jan 2 09:49:34 PST 2026
https://github.com/evelez7 updated https://github.com/llvm/llvm-project/pull/173960
>From a5e59fa777d2e182314fe0b68578178278f902a9 Mon Sep 17 00:00:00 2001
From: Erick Velez <erickvelez7 at gmail.com>
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 <{{#Parameters}}{{Param}}{{^End}}, {{/End}}{{/Parameters}}></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}}<{{#Parameters}}{{Param}}{{^End}}, {{/End}}{{/Parameters}}>{{/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 <typename T></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>
+
More information about the llvm-branch-commits
mailing list