Author: Erick Velez
Date: 2026-05-29T14:46:29-07:00
New Revision: 02cf363d17487378d0a2a89e1e95738d0945ab4c

URL: 
https://github.com/llvm/llvm-project/commit/02cf363d17487378d0a2a89e1e95738d0945ab4c
DIFF: 
https://github.com/llvm/llvm-project/commit/02cf363d17487378d0a2a89e1e95738d0945ab4c.diff

LOG: [clang-doc] Add specialization info to record references (#191252)

Class specializations listed in a namespace's page had the problem of
being indistinguishable from each other since they couldn't display
their template arguments. They would just be displayed as a series of
the base
template's name. Now, we can display those arguments in HTML
for a better experience.

In JSONGenerator, we query the set of Infos to find the Reference's
corresponding RecordInfo. That requires keeping a pointer to the map in
JSONGenerator.

Fixes #181771

Added: 
    

Modified: 
    clang-tools-extra/clang-doc/JSONGenerator.cpp
    clang-tools-extra/clang-doc/assets/namespace-template.mustache
    clang-tools-extra/test/clang-doc/json/class-specialization.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp 
b/clang-tools-extra/clang-doc/JSONGenerator.cpp
index ee1acc46c6f48..0ab71b4a5b5c9 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -66,6 +66,8 @@ class JSONGenerator : public Generator {
   void serializeMDReference(const Reference &Ref, Object &ReferenceObj,
                             StringRef BasePath);
 
+  void serializeClassSpecializations(SymbolID ClassUSR, Object &ReferenceObj);
+
   // Convenience lambdas to pass to serializeArray.
   auto serializeInfoLambda() {
     return [this](const auto &Info, Object &Object) {
@@ -79,6 +81,7 @@ class JSONGenerator : public Generator {
   }
 
   llvm::DenseMap<const Info *, SmallVector<Context, 4>> ContextsMap;
+  llvm::StringMap<doc::Info *> *Infos = nullptr;
   const ClangDocContext *CDCtx;
   bool Markdown;
 
@@ -458,6 +461,42 @@ void JSONGenerator::serializeCommonAttributes(const Info 
&I,
     generateContext(I, Obj);
 }
 
+static auto SerializeTemplateParam = [](const TemplateParamInfo &Param,
+                                        Object &JsonObj) {
+  JsonObj["Param"] = Param.Contents;
+};
+
+static void serializeTemplateSpecialization(TemplateInfo Template,
+                                            Object &TemplateObj) {
+  json::Value TemplateSpecializationVal = Object();
+  auto &TemplateSpecializationObj = *TemplateSpecializationVal.getAsObject();
+  TemplateSpecializationObj["SpecializationOf"] =
+      toHex(toStringRef(Template.Specialization->SpecializationOf));
+  if (!Template.Specialization->Params.empty()) {
+    bool VerticalDisplay =
+        Template.Specialization->Params.size() > getMaxParamWrapLimit();
+    serializeArray(Template.Specialization->Params, TemplateSpecializationObj,
+                   "Parameters", SerializeTemplateParam, "SpecParamEnd",
+                   [VerticalDisplay](Object &JsonObj) {
+                     JsonObj["VerticalDisplay"] = VerticalDisplay;
+                   });
+  }
+  TemplateObj["Specialization"] = TemplateSpecializationVal;
+}
+
+void JSONGenerator::serializeClassSpecializations(SymbolID ClassUSR,
+                                                  Object &ReferenceObj) {
+  if (!Infos)
+    return;
+  auto *Class = Infos->lookup(toHex(ClassUSR));
+  if (!Class || Class->IT != InfoType::IT_record)
+    return;
+  RecordInfo *ClassInfo = static_cast<RecordInfo *>(Class);
+  if (!ClassInfo->Template || !ClassInfo->Template->Specialization)
+    return;
+  serializeTemplateSpecialization(ClassInfo->Template.value(), ReferenceObj);
+}
+
 void JSONGenerator::serializeReference(const Reference &Ref,
                                        Object &ReferenceObj) {
   insertNonEmpty("Path", Ref.Path, ReferenceObj);
@@ -501,9 +540,14 @@ void JSONGenerator::serializeCommonChildren(
   }
 
   if (!Children.Records.empty()) {
-    ReferenceFunc SerializeReferenceFunc = MDReferenceLambda
-                                               ? MDReferenceLambda.value()
+    ReferenceFunc BaseFunc = MDReferenceLambda ? MDReferenceLambda.value()
                                                : serializeReferenceLambda();
+
+    ReferenceFunc SerializeReferenceFunc =
+        [this, BaseFunc](const Reference &Ref, Object &Object) {
+          BaseFunc(Ref, Object);
+          serializeClassSpecializations(Ref.USR, Object);
+        };
     serializeArray(Children.Records, Obj, "Records", SerializeReferenceFunc);
     Obj["HasRecords"] = true;
   }
@@ -539,27 +583,9 @@ void JSONGenerator::serializeInfo(const ConstraintInfo &I, 
Object &Obj) {
 void JSONGenerator::serializeInfo(const TemplateInfo &Template, Object &Obj) {
   json::Value TemplateVal = Object();
   auto &TemplateObj = *TemplateVal.getAsObject();
-  auto SerializeTemplateParam = [](const TemplateParamInfo &Param,
-                                   Object &JsonObj) {
-    JsonObj["Param"] = Param.Contents;
-  };
-
-  if (Template.Specialization) {
-    json::Value TemplateSpecializationVal = Object();
-    auto &TemplateSpecializationObj = *TemplateSpecializationVal.getAsObject();
-    TemplateSpecializationObj["SpecializationOf"] =
-        toHex(toStringRef(Template.Specialization->SpecializationOf));
-    if (!Template.Specialization->Params.empty()) {
-      bool VerticalDisplay =
-          Template.Specialization->Params.size() > getMaxParamWrapLimit();
-      serializeArray(Template.Specialization->Params, 
TemplateSpecializationObj,
-                     "Parameters", SerializeTemplateParam, "SpecParamEnd",
-                     [VerticalDisplay](Object &JsonObj) {
-                       JsonObj["VerticalDisplay"] = VerticalDisplay;
-                     });
-    }
-    TemplateObj["Specialization"] = TemplateSpecializationVal;
-  }
+
+  if (Template.Specialization)
+    serializeTemplateSpecialization(Template, TemplateObj);
 
   if (!Template.Params.empty()) {
     bool VerticalDisplay = Template.Params.size() > getMaxParamWrapLimit();
@@ -972,6 +998,7 @@ Error JSONGenerator::generateDocumentation(StringRef 
RootDir,
                                            const ClangDocContext &CDCtx,
                                            std::string DirName) {
   this->CDCtx = &CDCtx;
+  this->Infos = &Infos;
   StringSet<> CreatedDirs;
   StringMap<std::vector<doc::Info *>> FileToInfos;
   for (const auto &Group : Infos) {

diff  --git a/clang-tools-extra/clang-doc/assets/namespace-template.mustache 
b/clang-tools-extra/clang-doc/assets/namespace-template.mustache
index 3956cf0cd7ffd..13ad96254e9d1 100644
--- a/clang-tools-extra/clang-doc/assets/namespace-template.mustache
+++ b/clang-tools-extra/clang-doc/assets/namespace-template.mustache
@@ -164,7 +164,7 @@
                         {{#Records}}
                         <li id="{{USR}}" style="max-height: 40px;">
                             <a href="{{DocumentationFileName}}.html">
-                                <pre><code class="language-cpp 
code-clang-doc">class {{Name}}</code></pre>
+                                <pre><code class="language-cpp 
code-clang-doc">class 
{{Name}}{{#Specialization}}&lt;{{#Parameters}}{{Param}}{{^SpecParamEnd}}, 
{{/SpecParamEnd}}{{/Parameters}}&gt;{{/Specialization}}</code></pre>
                             </a>
                         </li>
                         {{/Records}}

diff  --git a/clang-tools-extra/test/clang-doc/json/class-specialization.cpp 
b/clang-tools-extra/test/clang-doc/json/class-specialization.cpp
index e7bb423f9bce7..c2b09ab53b0e9 100644
--- a/clang-tools-extra/test/clang-doc/json/class-specialization.cpp
+++ b/clang-tools-extra/test/clang-doc/json/class-specialization.cpp
@@ -1,7 +1,9 @@
 // RUN: rm -rf %t && mkdir -p %t
-// RUN: clang-doc --pretty-json --output=%t --format=json 
--executor=standalone %s
+// RUN: clang-doc --pretty-json --output=%t --format=html 
--executor=standalone %s
 // RUN: FileCheck %s < %t/json/GlobalNamespace/_ZTV7MyClass.json 
--check-prefix=BASE
 // RUN: FileCheck %s < %t/json/GlobalNamespace/_ZTV7MyClassIiE.json 
--check-prefix=SPECIALIZATION
+// RUN: FileCheck %s < %t/json/GlobalNamespace/index.json 
--check-prefix=JSON-NAMESPACE
+// RUN: FileCheck %s < %t/html/GlobalNamespace/index.html 
--check-prefix=HTML-NAMESPACE
 
 template<typename T> struct MyClass {};
 
@@ -43,3 +45,47 @@ template<> struct MyClass<int> {};
 // SPECIALIZATION-NEXT:      "VerticalDisplay": false
 // SPECIALIZATION-NEXT:    }
 // SPECIALIZATION-NEXT:  },
+
+// JSON-NAMESPACE:       "Records": [
+// JSON-NAMESPACE-NEXT:    {
+// JSON-NAMESPACE-NEXT:      "DocumentationFileName": "_ZTV7MyClass",
+// JSON-NAMESPACE-NEXT:      "Name": "MyClass",
+// JSON-NAMESPACE-NEXT:      "Path": "GlobalNamespace",
+// JSON-NAMESPACE-NEXT:      "QualName": "MyClass",
+// JSON-NAMESPACE-NEXT:      "USR": "{{([0-9A-F]{40})}}"
+// JSON-NAMESPACE-NEXT:    },
+// JSON-NAMESPACE-NEXT:    {
+// JSON-NAMESPACE-NEXT:      "DocumentationFileName": "_ZTV7MyClassIiE",
+// JSON-NAMESPACE-NEXT:      "End": true,
+// JSON-NAMESPACE-NEXT:      "Name": "MyClass",
+// JSON-NAMESPACE-NEXT:      "Path": "GlobalNamespace",
+// JSON-NAMESPACE-NEXT:      "QualName": "MyClass",
+// JSON-NAMESPACE-NEXT:      "Specialization": {
+// JSON-NAMESPACE-NEXT:        "Parameters": [
+// JSON-NAMESPACE-NEXT:          {
+// JSON-NAMESPACE-NEXT:            "Param": "int",
+// JSON-NAMESPACE-NEXT:            "SpecParamEnd": true
+// JSON-NAMESPACE-NEXT:          }
+// JSON-NAMESPACE-NEXT:        ],
+// JSON-NAMESPACE-NEXT:        "SpecializationOf": "{{([0-9A-F]{40})}}",
+// JSON-NAMESPACE-NEXT:        "VerticalDisplay": false
+// JSON-NAMESPACE-NEXT:      },
+// JSON-NAMESPACE-NEXT:      "USR": "{{([0-9A-F]{40})}}"
+// JSON-NAMESPACE-NEXT:    }
+// JSON-NAMESPACE-NEXT:  ]
+
+// HTML-NAMESPACE:      <section id="Records" class="section-container">
+// HTML-NAMESPACE-NEXT:     <h2>Records</h2>
+// HTML-NAMESPACE-NEXT:     <ul class="class-container">
+// HTML-NAMESPACE-NEXT:         <li id="{{([0-9A-F]{40})}}" style="max-height: 
40px;">
+// HTML-NAMESPACE-NEXT:             <a href="_ZTV7MyClass.html">
+// HTML-NAMESPACE-NEXT:                 <pre><code class="language-cpp 
code-clang-doc">class MyClass</code></pre>
+// HTML-NAMESPACE-NEXT:             </a>
+// HTML-NAMESPACE-NEXT:         </li>
+// HTML-NAMESPACE-NEXT:         <li id="{{([0-9A-F]{40})}}" style="max-height: 
40px;">
+// HTML-NAMESPACE-NEXT:             <a href="_ZTV7MyClassIiE.html">
+// HTML-NAMESPACE-NEXT:                 <pre><code class="language-cpp 
code-clang-doc">class MyClass&lt;int&gt;</code></pre>
+// HTML-NAMESPACE-NEXT:             </a>
+// HTML-NAMESPACE-NEXT:         </li>
+// HTML-NAMESPACE-NEXT:     </ul>
+// HTML-NAMESPACE-NEXT: </section>


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

Reply via email to