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 <{{#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> + _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
