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

>From 673a8bfc8fd5bc16e267e0646290f874e678e753 Mon Sep 17 00:00:00 2001
From: Erick Velez <[email protected]>
Date: Thu, 9 Apr 2026 10:26:22 -0700
Subject: [PATCH 1/3] [clang-doc] Add specialization info to record references

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. There would just be 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 to get the template data. That requires keeping a 
pointer to the map in
JSONGenerator.
---
 clang-tools-extra/clang-doc/JSONGenerator.cpp | 77 +++++++++++++------
 .../assets/namespace-template.mustache        |  2 +-
 .../clang-doc/json/class-specialization.cpp   | 48 +++++++++++-
 3 files changed, 102 insertions(+), 25 deletions(-)

diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp 
b/clang-tools-extra/clang-doc/JSONGenerator.cpp
index 8db7eaafcfb30..0c4998e1f9562 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -49,6 +49,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) {
@@ -61,6 +63,8 @@ class JSONGenerator : public Generator {
     };
   }
 
+  StringMap<OwnedPtr<Info>> *Infos = nullptr;
+
 public:
   static const char *Format;
   const ClangDocContext *CDCtx;
@@ -433,6 +437,45 @@ 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 ClassIt = Infos->find(toStringRef(toHex(ClassUSR)));
+  if (ClassIt == Infos->end())
+    return;
+  Info *Class = ClassIt->second.get();
+  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);
@@ -478,9 +521,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;
   }
@@ -516,27 +564,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();
@@ -950,6 +980,7 @@ Error JSONGenerator::generateDocumentation(
     StringRef RootDir, llvm::StringMap<doc::OwnedPtr<doc::Info>> Infos,
     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 344ec08e9a21f..0892ed36abe04 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 --output=%t --format=json --executor=standalone %s
+// RUN: clang-doc --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>

>From 7ef3222579e84757b7c38c77c2cda6f9e3ab5430 Mon Sep 17 00:00:00 2001
From: Erick Velez <[email protected]>
Date: Thu, 28 May 2026 23:57:46 -0700
Subject: [PATCH 2/3] fix stuff from merge

---
 clang-tools-extra/clang-doc/JSONGenerator.cpp                 | 4 +++-
 .../test/clang-doc/json/class-specialization.cpp              | 2 +-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp 
b/clang-tools-extra/clang-doc/JSONGenerator.cpp
index d6679e7f4170a..42bc93aa03cf3 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -81,7 +81,9 @@ class JSONGenerator : public Generator {
   }
 
   llvm::DenseMap<const Info *, SmallVector<Context, 4>> ContextsMap;
+  llvm::StringMap<doc::Info *> *Infos = nullptr;
   const ClangDocContext *CDCtx;
+
   bool Markdown;
 
 public:
@@ -490,7 +492,7 @@ void JSONGenerator::serializeClassSpecializations(SymbolID 
ClassUSR,
   auto ClassIt = Infos->find(toStringRef(toHex(ClassUSR)));
   if (ClassIt == Infos->end())
     return;
-  Info *Class = ClassIt->second.get();
+  Info *Class = ClassIt->second;
   if (!Class || Class->IT != InfoType::IT_record)
     return;
   RecordInfo *ClassInfo = static_cast<RecordInfo *>(Class);
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 a364b9ec6974c..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,5 +1,5 @@
 // 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

>From 06a77603223d66d38812e3f204b63923c84309bc Mon Sep 17 00:00:00 2001
From: Erick Velez <[email protected]>
Date: Fri, 29 May 2026 00:18:08 -0700
Subject: [PATCH 3/3] avoid extraneous toStringRef

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

diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp 
b/clang-tools-extra/clang-doc/JSONGenerator.cpp
index 42bc93aa03cf3..7bcb2eef7f7a4 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -489,7 +489,7 @@ void JSONGenerator::serializeClassSpecializations(SymbolID 
ClassUSR,
                                                   Object &ReferenceObj) {
   if (!Infos)
     return;
-  auto ClassIt = Infos->find(toStringRef(toHex(ClassUSR)));
+  auto ClassIt = Infos->find(toHex(ClassUSR));
   if (ClassIt == Infos->end())
     return;
   Info *Class = ClassIt->second;

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

Reply via email to