[clang-tools-extra] 4a68bab - [clang-doc] Add template support.

Brett Wilson via cfe-commits cfe-commits at lists.llvm.org
Thu Dec 8 08:02:10 PST 2022


Author: Brett Wilson
Date: 2022-12-08T08:02:02-08:00
New Revision: 4a68babd9973f043fd3e40f159fbb990880606a6

URL: https://github.com/llvm/llvm-project/commit/4a68babd9973f043fd3e40f159fbb990880606a6
DIFF: https://github.com/llvm/llvm-project/commit/4a68babd9973f043fd3e40f159fbb990880606a6.diff

LOG: [clang-doc] Add template support.

Reads template information from the AST and adds template parameters and
specialization information to the corresponding clang-doc structures.

Add a "QualName" to the Reference struct which includes the full
qualified type name. The Reference object represents a link in the
HTML/MD generators so is based on the unqualified name. But this does
not encode C-V qualifiers or template information that decorate the
name. The new QualName member encodes all of this information and also
makes it easier for the generators or downsteam YAML consumers to
generate the full name (before they had to process the "Path").

In test code that was changed, remove made-up paths to built-in types
like "int". In addition to slightnly cleaning up the code, these types
do not have paths in real execution, and generating incorrect references
to nonexistant data may complicate future changes in the generators.

Convert llvm::Optional to std::optional (YAML library requires this for
the new usage, and this makes everything consistent according to the
llvm::Optional -> std::optional transition).

Differential Revision: https://reviews.llvm.org/D139154

Added: 
    clang-tools-extra/test/clang-doc/templates.cpp

Modified: 
    clang-tools-extra/clang-doc/BitcodeReader.cpp
    clang-tools-extra/clang-doc/BitcodeWriter.cpp
    clang-tools-extra/clang-doc/BitcodeWriter.h
    clang-tools-extra/clang-doc/Representation.cpp
    clang-tools-extra/clang-doc/Representation.h
    clang-tools-extra/clang-doc/Serialize.cpp
    clang-tools-extra/clang-doc/YAMLGenerator.cpp
    clang-tools-extra/test/clang-doc/single-file-public.cpp
    clang-tools-extra/test/clang-doc/single-file.cpp
    clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp
    clang-tools-extra/unittests/clang-doc/SerializeTest.cpp
    clang-tools-extra/unittests/clang-doc/YAMLGeneratorTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clang-doc/BitcodeReader.cpp b/clang-tools-extra/clang-doc/BitcodeReader.cpp
index c6928f60f3970..9ac60fa73a782 100644
--- a/clang-tools-extra/clang-doc/BitcodeReader.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeReader.cpp
@@ -350,6 +350,8 @@ llvm::Error parseRecord(const Record &R, unsigned ID, llvm::StringRef Blob,
     return decodeRecord(R, I->USR, Blob);
   case REFERENCE_NAME:
     return decodeRecord(R, I->Name, Blob);
+  case REFERENCE_QUAL_NAME:
+    return decodeRecord(R, I->QualName, Blob);
   case REFERENCE_TYPE:
     return decodeRecord(R, I->RefType, Blob);
   case REFERENCE_PATH:
@@ -362,6 +364,29 @@ llvm::Error parseRecord(const Record &R, unsigned ID, llvm::StringRef Blob,
   }
 }
 
+llvm::Error parseRecord(const Record &R, unsigned ID, llvm::StringRef Blob,
+                        TemplateInfo *I) {
+  // Currently there are no child records of TemplateInfo (only child blocks).
+  return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                 "invalid field for TemplateParamInfo");
+}
+
+llvm::Error parseRecord(const Record &R, unsigned ID, llvm::StringRef Blob,
+                        TemplateSpecializationInfo *I) {
+  if (ID == TEMPLATE_SPECIALIZATION_OF)
+    return decodeRecord(R, I->SpecializationOf, Blob);
+  return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                 "invalid field for TemplateParamInfo");
+}
+
+llvm::Error parseRecord(const Record &R, unsigned ID, llvm::StringRef Blob,
+                        TemplateParamInfo *I) {
+  if (ID == TEMPLATE_PARAM_CONTENTS)
+    return decodeRecord(R, I->Contents, Blob);
+  return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                 "invalid field for TemplateParamInfo");
+}
+
 template <typename T> llvm::Expected<CommentInfo *> getCommentInfo(T I) {
   return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                  "invalid type cannot contain CommentInfo");
@@ -594,6 +619,45 @@ template <> void addChild(BaseRecordInfo *I, FunctionInfo &&R) {
   I->Children.Functions.emplace_back(std::move(R));
 }
 
+// TemplateParam children. These go into either a TemplateInfo (for template
+// parameters) or TemplateSpecializationInfo (for the specialization's
+// parameters).
+template <typename T> void addTemplateParam(T I, TemplateParamInfo &&P) {
+  llvm::errs() << "invalid container for template parameter";
+  exit(1);
+}
+template <> void addTemplateParam(TemplateInfo *I, TemplateParamInfo &&P) {
+  I->Params.emplace_back(std::move(P));
+}
+template <>
+void addTemplateParam(TemplateSpecializationInfo *I, TemplateParamInfo &&P) {
+  I->Params.emplace_back(std::move(P));
+}
+
+// Template info. These apply to either records or functions.
+template <typename T> void addTemplate(T I, TemplateInfo &&P) {
+  llvm::errs() << "invalid container for template info";
+  exit(1);
+}
+template <> void addTemplate(RecordInfo *I, TemplateInfo &&P) {
+  I->Template.emplace(std::move(P));
+}
+template <> void addTemplate(FunctionInfo *I, TemplateInfo &&P) {
+  I->Template.emplace(std::move(P));
+}
+
+// Template specializations go only into template records.
+template <typename T>
+void addTemplateSpecialization(T I, TemplateSpecializationInfo &&TSI) {
+  llvm::errs() << "invalid container for template specialization info";
+  exit(1);
+}
+template <>
+void addTemplateSpecialization(TemplateInfo *I,
+                               TemplateSpecializationInfo &&TSI) {
+  I->Specialization.emplace(std::move(TSI));
+}
+
 // Read records from bitcode into a given info.
 template <typename T>
 llvm::Error ClangDocBitcodeReader::readRecord(unsigned ID, T I) {
@@ -718,6 +782,27 @@ llvm::Error ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) {
     addChild(I, std::move(EV));
     return llvm::Error::success();
   }
+  case BI_TEMPLATE_BLOCK_ID: {
+    TemplateInfo TI;
+    if (auto Err = readBlock(ID, &TI))
+      return Err;
+    addTemplate(I, std::move(TI));
+    return llvm::Error::success();
+  }
+  case BI_TEMPLATE_SPECIALIZATION_BLOCK_ID: {
+    TemplateSpecializationInfo TSI;
+    if (auto Err = readBlock(ID, &TSI))
+      return Err;
+    addTemplateSpecialization(I, std::move(TSI));
+    return llvm::Error::success();
+  }
+  case BI_TEMPLATE_PARAM_BLOCK_ID: {
+    TemplateParamInfo TPI;
+    if (auto Err = readBlock(ID, &TPI))
+      return Err;
+    addTemplateParam(I, std::move(TPI));
+    return llvm::Error::success();
+  }
   case BI_TYPEDEF_BLOCK_ID: {
     TypedefInfo TI;
     if (auto Err = readBlock(ID, &TI))

diff  --git a/clang-tools-extra/clang-doc/BitcodeWriter.cpp b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
index bb0698a5a4028..8a5647cb6a755 100644
--- a/clang-tools-extra/clang-doc/BitcodeWriter.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
@@ -121,7 +121,10 @@ static const llvm::IndexedMap<llvm::StringRef, BlockIdToIndexFunctor>
           {BI_BASE_RECORD_BLOCK_ID, "BaseRecordBlock"},
           {BI_FUNCTION_BLOCK_ID, "FunctionBlock"},
           {BI_COMMENT_BLOCK_ID, "CommentBlock"},
-          {BI_REFERENCE_BLOCK_ID, "ReferenceBlock"}};
+          {BI_REFERENCE_BLOCK_ID, "ReferenceBlock"},
+          {BI_TEMPLATE_BLOCK_ID, "TemplateBlock"},
+          {BI_TEMPLATE_SPECIALIZATION_BLOCK_ID, "TemplateSpecializationBlock"},
+          {BI_TEMPLATE_PARAM_BLOCK_ID, "TemplateParamBlock"}};
       assert(Inits.size() == BlockIdCount);
       for (const auto &Init : Inits)
         BlockIdNameMap[Init.first] = Init.second;
@@ -186,9 +189,12 @@ static const llvm::IndexedMap<RecordIdDsc, RecordIdToIndexFunctor>
           {FUNCTION_IS_METHOD, {"IsMethod", &BoolAbbrev}},
           {REFERENCE_USR, {"USR", &SymbolIDAbbrev}},
           {REFERENCE_NAME, {"Name", &StringAbbrev}},
+          {REFERENCE_QUAL_NAME, {"QualName", &StringAbbrev}},
           {REFERENCE_TYPE, {"RefType", &IntAbbrev}},
           {REFERENCE_PATH, {"Path", &StringAbbrev}},
           {REFERENCE_FIELD, {"Field", &IntAbbrev}},
+          {TEMPLATE_PARAM_CONTENTS, {"Contents", &StringAbbrev}},
+          {TEMPLATE_SPECIALIZATION_OF, {"SpecializationOf", &SymbolIDAbbrev}},
           {TYPEDEF_USR, {"USR", &SymbolIDAbbrev}},
           {TYPEDEF_NAME, {"Name", &StringAbbrev}},
           {TYPEDEF_DEFLOCATION, {"DefLocation", &LocationAbbrev}},
@@ -244,8 +250,12 @@ static const std::vector<std::pair<BlockId, std::vector<RecordId>>>
           FUNCTION_ACCESS, FUNCTION_IS_METHOD}},
         // Reference Block
         {BI_REFERENCE_BLOCK_ID,
-         {REFERENCE_USR, REFERENCE_NAME, REFERENCE_TYPE, REFERENCE_PATH,
-          REFERENCE_FIELD}}};
+         {REFERENCE_USR, REFERENCE_NAME, REFERENCE_QUAL_NAME, REFERENCE_TYPE,
+          REFERENCE_PATH, REFERENCE_FIELD}},
+        // Template Blocks.
+        {BI_TEMPLATE_BLOCK_ID, {}},
+        {BI_TEMPLATE_PARAM_BLOCK_ID, {TEMPLATE_PARAM_CONTENTS}},
+        {BI_TEMPLATE_SPECIALIZATION_BLOCK_ID, {TEMPLATE_SPECIALIZATION_OF}}};
 
 // AbbreviationMap
 
@@ -378,6 +388,8 @@ void ClangDocBitcodeWriter::emitRecord(unsigned Val, RecordId ID) {
   Stream.EmitRecordWithAbbrev(Abbrevs.get(ID), Record);
 }
 
+void ClangDocBitcodeWriter::emitRecord(const TemplateInfo &Templ) {}
+
 bool ClangDocBitcodeWriter::prepRecordData(RecordId ID, bool ShouldEmit) {
   assert(RecordIdNameMap[ID] && "Unknown RecordId.");
   if (!ShouldEmit)
@@ -416,6 +428,7 @@ void ClangDocBitcodeWriter::emitBlock(const Reference &R, FieldId Field) {
   StreamSubBlockGuard Block(Stream, BI_REFERENCE_BLOCK_ID);
   emitRecord(R.USR, REFERENCE_USR);
   emitRecord(R.Name, REFERENCE_NAME);
+  emitRecord(R.QualName, REFERENCE_QUAL_NAME);
   emitRecord((unsigned)R.RefType, REFERENCE_TYPE);
   emitRecord(R.Path, REFERENCE_PATH);
   emitRecord((unsigned)Field, REFERENCE_FIELD);
@@ -556,6 +569,8 @@ void ClangDocBitcodeWriter::emitBlock(const RecordInfo &I) {
     emitBlock(C);
   for (const auto &C : I.Children.Typedefs)
     emitBlock(C);
+  if (I.Template)
+    emitBlock(*I.Template);
 }
 
 void ClangDocBitcodeWriter::emitBlock(const BaseRecordInfo &I) {
@@ -591,6 +606,28 @@ void ClangDocBitcodeWriter::emitBlock(const FunctionInfo &I) {
   emitBlock(I.ReturnType);
   for (const auto &N : I.Params)
     emitBlock(N);
+  if (I.Template)
+    emitBlock(*I.Template);
+}
+
+void ClangDocBitcodeWriter::emitBlock(const TemplateInfo &T) {
+  StreamSubBlockGuard Block(Stream, BI_TEMPLATE_BLOCK_ID);
+  for (const auto &P : T.Params)
+    emitBlock(P);
+  if (T.Specialization)
+    emitBlock(*T.Specialization);
+}
+
+void ClangDocBitcodeWriter::emitBlock(const TemplateSpecializationInfo &T) {
+  StreamSubBlockGuard Block(Stream, BI_TEMPLATE_SPECIALIZATION_BLOCK_ID);
+  emitRecord(T.SpecializationOf, TEMPLATE_SPECIALIZATION_OF);
+  for (const auto &P : T.Params)
+    emitBlock(P);
+}
+
+void ClangDocBitcodeWriter::emitBlock(const TemplateParamInfo &T) {
+  StreamSubBlockGuard Block(Stream, BI_TEMPLATE_PARAM_BLOCK_ID);
+  emitRecord(T.Contents, TEMPLATE_PARAM_CONTENTS);
 }
 
 bool ClangDocBitcodeWriter::dispatchInfoForWrite(Info *I) {

diff  --git a/clang-tools-extra/clang-doc/BitcodeWriter.h b/clang-tools-extra/clang-doc/BitcodeWriter.h
index 5a2514a19b30d..9a572e40e352f 100644
--- a/clang-tools-extra/clang-doc/BitcodeWriter.h
+++ b/clang-tools-extra/clang-doc/BitcodeWriter.h
@@ -17,7 +17,6 @@
 
 #include "Representation.h"
 #include "clang/AST/AST.h"
-#include "llvm/ADT/APSInt.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
@@ -64,6 +63,9 @@ enum BlockId {
   BI_FUNCTION_BLOCK_ID,
   BI_COMMENT_BLOCK_ID,
   BI_REFERENCE_BLOCK_ID,
+  BI_TEMPLATE_BLOCK_ID,
+  BI_TEMPLATE_SPECIALIZATION_BLOCK_ID,
+  BI_TEMPLATE_PARAM_BLOCK_ID,
   BI_TYPEDEF_BLOCK_ID,
   BI_LAST,
   BI_FIRST = BI_VERSION_BLOCK_ID
@@ -121,9 +123,12 @@ enum RecordId {
   BASE_RECORD_IS_PARENT,
   REFERENCE_USR,
   REFERENCE_NAME,
+  REFERENCE_QUAL_NAME,
   REFERENCE_TYPE,
   REFERENCE_PATH,
   REFERENCE_FIELD,
+  TEMPLATE_PARAM_CONTENTS,
+  TEMPLATE_SPECIALIZATION_OF,
   TYPEDEF_USR,
   TYPEDEF_NAME,
   TYPEDEF_DEFLOCATION,
@@ -169,6 +174,9 @@ class ClangDocBitcodeWriter {
   void emitBlock(const FieldTypeInfo &B);
   void emitBlock(const MemberTypeInfo &T);
   void emitBlock(const CommentInfo &B);
+  void emitBlock(const TemplateInfo &T);
+  void emitBlock(const TemplateSpecializationInfo &T);
+  void emitBlock(const TemplateParamInfo &T);
   void emitBlock(const Reference &B, FieldId F);
 
 private:
@@ -215,7 +223,7 @@ class ClangDocBitcodeWriter {
   void emitRecord(bool Value, RecordId ID);
   void emitRecord(int Value, RecordId ID);
   void emitRecord(unsigned Value, RecordId ID);
-  void emitRecord(llvm::APSInt Value, RecordId ID);
+  void emitRecord(const TemplateInfo &Templ);
   bool prepRecordData(RecordId ID, bool ShouldEmit = true);
 
   // Emission of appropriate abbreviation type.

diff  --git a/clang-tools-extra/clang-doc/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp
index 27b83d67513d9..c1279997813a8 100644
--- a/clang-tools-extra/clang-doc/Representation.cpp
+++ b/clang-tools-extra/clang-doc/Representation.cpp
@@ -250,6 +250,8 @@ void RecordInfo::merge(RecordInfo &&Other) {
   reduceChildren(Children.Enums, std::move(Other.Children.Enums));
   reduceChildren(Children.Typedefs, std::move(Other.Children.Typedefs));
   SymbolInfo::merge(std::move(Other));
+  if (!Template)
+    Template = Other.Template;
 }
 
 void EnumInfo::merge(EnumInfo &&Other) {
@@ -274,6 +276,8 @@ void FunctionInfo::merge(FunctionInfo &&Other) {
   if (Params.empty())
     Params = std::move(Other.Params);
   SymbolInfo::merge(std::move(Other));
+  if (!Template)
+    Template = Other.Template;
 }
 
 void TypedefInfo::merge(TypedefInfo &&Other) {

diff  --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h
index 2a690883b33e7..564488ceb027f 100644
--- a/clang-tools-extra/clang-doc/Representation.h
+++ b/clang-tools-extra/clang-doc/Representation.h
@@ -18,7 +18,6 @@
 #include "clang/Basic/Specifiers.h"
 #include "clang/Tooling/StandaloneExecution.h"
 #include "llvm/ADT/APSInt.h"
-#include "llvm/ADT/Optional.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringExtras.h"
 #include <array>
@@ -117,13 +116,21 @@ struct CommentInfo {
 };
 
 struct Reference {
+  // This variant (that takes no qualified name parameter) uses the Name as the
+  // QualName (very useful in unit tests to reduce verbosity). This can't use an
+  // empty string to indicate the default because we need to accept the empty
+  // string as a valid input for the global namespace (it will have
+  // "GlobalNamespace" as the name, but an empty QualName).
   Reference(SymbolID USR = SymbolID(), StringRef Name = StringRef(),
-            InfoType IT = InfoType::IT_default, StringRef Path = StringRef())
-      : USR(USR), Name(Name), RefType(IT), Path(Path) {}
+            InfoType IT = InfoType::IT_default)
+      : USR(USR), Name(Name), QualName(Name), RefType(IT) {}
+  Reference(SymbolID USR, StringRef Name, InfoType IT, StringRef QualName,
+            StringRef Path = StringRef())
+      : USR(USR), Name(Name), QualName(QualName), RefType(IT), Path(Path) {}
 
   bool operator==(const Reference &Other) const {
-    return std::tie(USR, Name, RefType) ==
-           std::tie(Other.USR, Other.Name, Other.RefType);
+    return std::tie(USR, Name, QualName, RefType) ==
+           std::tie(Other.USR, Other.Name, QualName, Other.RefType);
   }
 
   bool mergeable(const Reference &Other);
@@ -136,7 +143,17 @@ struct Reference {
   llvm::SmallString<16> getFileBaseName() const;
 
   SymbolID USR = SymbolID(); // Unique identifier for referenced decl
-  SmallString<16> Name;      // Name of type (possibly unresolved).
+
+  // Name of type (possibly unresolved). Not including namespaces or template
+  // parameters (so for a std::vector<int> this would be "vector"). See also
+  // QualName.
+  SmallString<16> Name;
+
+  // Full qualified name of this type, including namespaces and template
+  // parameter (for example this could be "std::vector<int>"). Contrast to
+  // Name.
+  SmallString<16> QualName;
+
   InfoType RefType = InfoType::IT_default; // Indicates the type of this
                                            // Reference (namespace, record,
                                            // function, enum, default).
@@ -169,13 +186,46 @@ struct TypeInfo {
   // Convenience constructor for when there is no symbol ID or info type
   // (normally used for built-in types in tests).
   TypeInfo(StringRef Name, StringRef Path = StringRef())
-      : Type(SymbolID(), Name, InfoType::IT_default, Path) {}
+      : Type(SymbolID(), Name, InfoType::IT_default, Name, Path) {}
 
   bool operator==(const TypeInfo &Other) const { return Type == Other.Type; }
 
   Reference Type; // Referenced type in this info.
 };
 
+// Represents one template parameter.
+//
+// This is a very simple serialization of the text of the source code of the
+// template parameter. It is saved in a struct so there is a place to add the
+// name and default values in the future if needed.
+struct TemplateParamInfo {
+  TemplateParamInfo() = default;
+  explicit TemplateParamInfo(StringRef Contents) : Contents(Contents) {}
+
+  // The literal contents of the code for that specifies this template parameter
+  // for this declaration. Typical values will be "class T" and
+  // "typename T = int".
+  SmallString<16> Contents;
+};
+
+struct TemplateSpecializationInfo {
+  // Indicates the declaration that this specializes.
+  SymbolID SpecializationOf;
+
+  // Template parameters applying to the specialized record/function.
+  std::vector<TemplateParamInfo> Params;
+};
+
+// Records the template information for a struct or function that is a template
+// or an explicit template specialization.
+struct TemplateInfo {
+  // May be empty for non-partial specializations.
+  std::vector<TemplateParamInfo> Params;
+
+  // Set when this is a specialization of another record/function.
+  std::optional<TemplateSpecializationInfo> Specialization;
+};
+
 // Info for field types.
 struct FieldTypeInfo : public TypeInfo {
   FieldTypeInfo() = default;
@@ -317,6 +367,13 @@ struct FunctionInfo : public SymbolInfo {
   // with value 0 to be used as the default.
   // (AS_public = 0, AS_protected = 1, AS_private = 2, AS_none = 3)
   AccessSpecifier Access = AccessSpecifier::AS_public;
+
+  // Full qualified name of this function, including namespaces and template
+  // specializations.
+  SmallString<16> FullName;
+
+  // When present, this function is a template or specialization.
+  std::optional<TemplateInfo> Template;
 };
 
 // TODO: Expand to allow for documenting templating, inheritance access,
@@ -332,6 +389,13 @@ struct RecordInfo : public SymbolInfo {
   // Type of this record (struct, class, union, interface).
   TagTypeKind TagType = TagTypeKind::TTK_Struct;
 
+  // Full qualified name of this record, including namespaces and template
+  // specializations.
+  SmallString<16> FullName;
+
+  // When present, this record is a template or specialization.
+  std::optional<TemplateInfo> Template;
+
   // Indicates if the record was declared using a typedef. Things like anonymous
   // structs in a typedef:
   //   typedef struct { ... } foo_t;
@@ -433,12 +497,12 @@ struct Index : public Reference {
   Index(StringRef Name, StringRef JumpToSection)
       : Reference(SymbolID(), Name), JumpToSection(JumpToSection) {}
   Index(SymbolID USR, StringRef Name, InfoType IT, StringRef Path)
-      : Reference(USR, Name, IT, Path) {}
+      : Reference(USR, Name, IT, Name, Path) {}
   // This is used to look for a USR in a vector of Indexes using std::find
   bool operator==(const SymbolID &Other) const { return USR == Other; }
   bool operator<(const Index &Other) const;
 
-  llvm::Optional<SmallString<16>> JumpToSection;
+  std::optional<SmallString<16>> JumpToSection;
   std::vector<Index> Children;
 
   void sort();
@@ -467,7 +531,7 @@ struct ClangDocContext {
                             // to definition locations will only be generated if
                             // the file is in this dir.
   // URL of repository that hosts code used for links to definition locations.
-  llvm::Optional<std::string> RepositoryUrl;
+  std::optional<std::string> RepositoryUrl;
   // Path of CSS stylesheets that will be copied to OutDirectory and used to
   // style all HTML files.
   std::vector<std::string> UserStylesheets;

diff  --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp
index 66a938d488e1d..ac8e253ac06ea 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -250,7 +250,7 @@ TypeInfo getTypeInfoForType(const QualType &T) {
     IT = InfoType::IT_default;
   }
   return TypeInfo(Reference(getUSRForDecl(TD), TD->getNameAsString(), IT,
-                            getInfoRelativePath(TD)));
+                            T.getAsString(), getInfoRelativePath(TD)));
 }
 
 static bool isPublic(const clang::AccessSpecifier AS,
@@ -281,12 +281,12 @@ static bool shouldSerializeInfo(bool PublicOnly, bool IsInAnonymousNamespace,
 // See MakeAndInsertIntoParent().
 static void InsertChild(ScopeChildren &Scope, const NamespaceInfo &Info) {
   Scope.Namespaces.emplace_back(Info.USR, Info.Name, InfoType::IT_namespace,
-                                getInfoRelativePath(Info.Namespace));
+                                Info.Name, getInfoRelativePath(Info.Namespace));
 }
 
 static void InsertChild(ScopeChildren &Scope, const RecordInfo &Info) {
   Scope.Records.emplace_back(Info.USR, Info.Name, InfoType::IT_record,
-                             getInfoRelativePath(Info.Namespace));
+                             Info.Name, getInfoRelativePath(Info.Namespace));
 }
 
 static void InsertChild(ScopeChildren &Scope, EnumInfo Info) {
@@ -405,10 +405,7 @@ static void parseParameters(FunctionInfo &I, const FunctionDecl *D) {
   for (const ParmVarDecl *P : D->parameters()) {
     FieldTypeInfo &FieldInfo = I.Params.emplace_back(
         getTypeInfoForType(P->getOriginalType()), P->getNameAsString());
-
-    if (const Expr *DefaultArg = P->getDefaultArg()) {
-      FieldInfo.DefaultValue = getSourceCode(D, DefaultArg->getSourceRange());
-    }
+    FieldInfo.DefaultValue = getSourceCode(D, P->getDefaultArgRange());
   }
 }
 
@@ -424,18 +421,19 @@ static void parseBases(RecordInfo &I, const CXXRecordDecl *D) {
     if (const auto *Ty = B.getType()->getAs<TemplateSpecializationType>()) {
       const TemplateDecl *D = Ty->getTemplateName().getAsTemplateDecl();
       I.Parents.emplace_back(getUSRForDecl(D), B.getType().getAsString(),
-                             InfoType::IT_record);
+                             InfoType::IT_record, B.getType().getAsString());
     } else if (const RecordDecl *P = getRecordDeclForType(B.getType()))
       I.Parents.emplace_back(getUSRForDecl(P), P->getNameAsString(),
-                             InfoType::IT_record, getInfoRelativePath(P));
+                             InfoType::IT_record, P->getQualifiedNameAsString(),
+                             getInfoRelativePath(P));
     else
       I.Parents.emplace_back(SymbolID(), B.getType().getAsString());
   }
   for (const CXXBaseSpecifier &B : D->vbases()) {
     if (const RecordDecl *P = getRecordDeclForType(B.getType()))
-      I.VirtualParents.emplace_back(getUSRForDecl(P), P->getNameAsString(),
-                                    InfoType::IT_record,
-                                    getInfoRelativePath(P));
+      I.VirtualParents.emplace_back(
+          getUSRForDecl(P), P->getNameAsString(), InfoType::IT_record,
+          P->getQualifiedNameAsString(), getInfoRelativePath(P));
     else
       I.VirtualParents.emplace_back(SymbolID(), B.getType().getAsString());
   }
@@ -455,16 +453,19 @@ populateParentNamespaces(llvm::SmallVector<Reference, 4> &Namespaces,
       } else
         Namespace = N->getNameAsString();
       Namespaces.emplace_back(getUSRForDecl(N), Namespace,
-                              InfoType::IT_namespace);
+                              InfoType::IT_namespace,
+                              N->getQualifiedNameAsString());
     } else if (const auto *N = dyn_cast<RecordDecl>(DC))
       Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(),
-                              InfoType::IT_record);
+                              InfoType::IT_record,
+                              N->getQualifiedNameAsString());
     else if (const auto *N = dyn_cast<FunctionDecl>(DC))
       Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(),
-                              InfoType::IT_function);
+                              InfoType::IT_function,
+                              N->getQualifiedNameAsString());
     else if (const auto *N = dyn_cast<EnumDecl>(DC))
       Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(),
-                              InfoType::IT_enum);
+                              InfoType::IT_enum, N->getQualifiedNameAsString());
   } while ((DC = DC->getParent()));
   // The global namespace should be added to the list of namespaces if the decl
   // corresponds to a Record and if it doesn't have any namespace (because this
@@ -476,6 +477,30 @@ populateParentNamespaces(llvm::SmallVector<Reference, 4> &Namespaces,
                             InfoType::IT_namespace);
 }
 
+void PopulateTemplateParameters(std::optional<TemplateInfo> &TemplateInfo,
+                                const clang::Decl *D) {
+  if (const TemplateParameterList *ParamList =
+          D->getDescribedTemplateParams()) {
+    if (!TemplateInfo) {
+      TemplateInfo.emplace();
+    }
+    for (const NamedDecl *ND : *ParamList) {
+      TemplateInfo->Params.emplace_back(
+          getSourceCode(ND, ND->getSourceRange()));
+    }
+  }
+}
+
+TemplateParamInfo TemplateArgumentToInfo(const clang::Decl *D,
+                                         const TemplateArgument &Arg) {
+  // The TemplateArgument's pretty printing handles all the normal cases
+  // well enough for our requirements.
+  std::string Str;
+  llvm::raw_string_ostream Stream(Str);
+  Arg.print(PrintingPolicy(D->getLangOpts()), Stream, false);
+  return TemplateParamInfo(Str);
+}
+
 template <typename T>
 static void populateInfo(Info &I, const T *D, const FullComment *C,
                          bool &IsInAnonymousNamespace) {
@@ -508,6 +533,26 @@ static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D,
                      IsInAnonymousNamespace);
   I.ReturnType = getTypeInfoForType(D->getReturnType());
   parseParameters(I, D);
+
+  PopulateTemplateParameters(I.Template, D);
+
+  // Handle function template specializations.
+  if (const FunctionTemplateSpecializationInfo *FTSI =
+          D->getTemplateSpecializationInfo()) {
+    if (!I.Template)
+      I.Template.emplace();
+    I.Template->Specialization.emplace();
+    auto &Specialization = *I.Template->Specialization;
+
+    Specialization.SpecializationOf = getUSRForDecl(FTSI->getTemplate());
+
+    // Template parameters to the specialization.
+    if (FTSI->TemplateArguments) {
+      for (const TemplateArgument &Arg : FTSI->TemplateArguments->asArray()) {
+        Specialization.Params.push_back(TemplateArgumentToInfo(D, Arg));
+      }
+    }
+  }
 }
 
 static void populateMemberTypeInfo(MemberTypeInfo &I, const FieldDecl *D) {
@@ -627,6 +672,46 @@ emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber,
   }
   I->Path = getInfoRelativePath(I->Namespace);
 
+  PopulateTemplateParameters(I->Template, D);
+
+  // Full and partial specializations.
+  if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(D)) {
+    if (!I->Template)
+      I->Template.emplace();
+    I->Template->Specialization.emplace();
+    auto &Specialization = *I->Template->Specialization;
+
+    // What this is a specialization of.
+    auto SpecOf = CTSD->getSpecializedTemplateOrPartial();
+    if (SpecOf.is<ClassTemplateDecl *>()) {
+      Specialization.SpecializationOf =
+          getUSRForDecl(SpecOf.get<ClassTemplateDecl *>());
+    } else if (SpecOf.is<ClassTemplatePartialSpecializationDecl *>()) {
+      Specialization.SpecializationOf =
+          getUSRForDecl(SpecOf.get<ClassTemplatePartialSpecializationDecl *>());
+    }
+
+    // Parameters to the specilization. For partial specializations, get the
+    // parameters "as written" from the ClassTemplatePartialSpecializationDecl
+    // because the non-explicit template parameters will have generated internal
+    // placeholder names rather than the names the user typed that match the
+    // template parameters.
+    if (const ClassTemplatePartialSpecializationDecl *CTPSD =
+            dyn_cast<ClassTemplatePartialSpecializationDecl>(D)) {
+      if (const ASTTemplateArgumentListInfo *AsWritten =
+              CTPSD->getTemplateArgsAsWritten()) {
+        for (unsigned i = 0; i < AsWritten->getNumTemplateArgs(); i++) {
+          Specialization.Params.emplace_back(
+              getSourceCode(D, (*AsWritten)[i].getSourceRange()));
+        }
+      }
+    } else {
+      for (const TemplateArgument &Arg : CTSD->getTemplateArgs().asArray()) {
+        Specialization.Params.push_back(TemplateArgumentToInfo(D, Arg));
+      }
+    }
+  }
+
   // Records are inserted into the parent by reference, so we need to return
   // both the parent and the record itself.
   auto Parent = MakeAndInsertIntoParent<const RecordInfo &>(*I);
@@ -669,7 +754,8 @@ emitInfo(const CXXMethodDecl *D, const FullComment *FC, int LineNumber,
 
   SymbolID ParentUSR = getUSRForDecl(Parent);
   Func.Parent =
-      Reference{ParentUSR, Parent->getNameAsString(), InfoType::IT_record};
+      Reference{ParentUSR, Parent->getNameAsString(), InfoType::IT_record,
+                Parent->getQualifiedNameAsString()};
   Func.Access = D->getAccess();
 
   // Info is wrapped in its parent scope so is returned in the second position.
@@ -731,8 +817,10 @@ emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber,
     return {};
 
   Enum.Scoped = D->isScoped();
-  if (D->isFixed())
-    Enum.BaseType = TypeInfo(D->getIntegerType().getAsString());
+  if (D->isFixed()) {
+    auto Name = D->getIntegerType().getAsString();
+    Enum.BaseType = TypeInfo(Name, Name);
+  }
   parseEnumerators(Enum, D);
 
   // Info is wrapped in its parent scope so is returned in the second position.

diff  --git a/clang-tools-extra/clang-doc/YAMLGenerator.cpp b/clang-tools-extra/clang-doc/YAMLGenerator.cpp
index 0e662c0d9006c..57cb294e0ab69 100644
--- a/clang-tools-extra/clang-doc/YAMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/YAMLGenerator.cpp
@@ -9,6 +9,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "Generators.h"
+#include "Representation.h"
 #include "llvm/Support/YAMLTraits.h"
 #include "llvm/Support/raw_ostream.h"
 #include <optional>
@@ -24,6 +25,7 @@ LLVM_YAML_IS_SEQUENCE_VECTOR(CommentInfo)
 LLVM_YAML_IS_SEQUENCE_VECTOR(FunctionInfo)
 LLVM_YAML_IS_SEQUENCE_VECTOR(EnumInfo)
 LLVM_YAML_IS_SEQUENCE_VECTOR(EnumValueInfo)
+LLVM_YAML_IS_SEQUENCE_VECTOR(TemplateParamInfo)
 LLVM_YAML_IS_SEQUENCE_VECTOR(TypedefInfo)
 LLVM_YAML_IS_SEQUENCE_VECTOR(BaseRecordInfo)
 LLVM_YAML_IS_SEQUENCE_VECTOR(std::unique_ptr<CommentInfo>)
@@ -143,6 +145,7 @@ static void RecordInfoMapping(IO &IO, RecordInfo &I) {
   IO.mapOptional("ChildFunctions", I.Children.Functions);
   IO.mapOptional("ChildEnums", I.Children.Enums);
   IO.mapOptional("ChildTypedefs", I.Children.Typedefs);
+  IO.mapOptional("Template", I.Template);
 }
 
 static void CommentInfoMapping(IO &IO, CommentInfo &I) {
@@ -175,6 +178,7 @@ template <> struct MappingTraits<Reference> {
   static void mapping(IO &IO, Reference &Ref) {
     IO.mapOptional("Type", Ref.RefType, InfoType::IT_default);
     IO.mapOptional("Name", Ref.Name, SmallString<16>());
+    IO.mapOptional("QualName", Ref.QualName, SmallString<16>());
     IO.mapOptional("USR", Ref.USR, SymbolID());
     IO.mapOptional("Path", Ref.Path, SmallString<128>());
   }
@@ -268,6 +272,28 @@ template <> struct MappingTraits<FunctionInfo> {
     // the AS that shouldn't be part of the output. Even though AS_public is the
     // default in the struct, it should be displayed in the YAML output.
     IO.mapOptional("Access", I.Access, clang::AccessSpecifier::AS_none);
+    IO.mapOptional("Template", I.Template);
+  }
+};
+
+template <> struct MappingTraits<TemplateParamInfo> {
+  static void mapping(IO &IO, TemplateParamInfo &I) {
+    IO.mapOptional("Contents", I.Contents);
+  }
+};
+
+template <> struct MappingTraits<TemplateSpecializationInfo> {
+  static void mapping(IO &IO, TemplateSpecializationInfo &I) {
+    IO.mapOptional("SpecializationOf", I.SpecializationOf);
+    IO.mapOptional("Params", I.Params);
+  }
+};
+
+template <> struct MappingTraits<TemplateInfo> {
+  static void mapping(IO &IO, TemplateInfo &I) {
+    IO.mapOptional("Params", I.Params);
+    IO.mapOptional("Specialization", I.Specialization,
+                   std::optional<TemplateSpecializationInfo>());
   }
 };
 

diff  --git a/clang-tools-extra/test/clang-doc/single-file-public.cpp b/clang-tools-extra/test/clang-doc/single-file-public.cpp
index 91c3146bfef62..82e81749a1c52 100644
--- a/clang-tools-extra/test/clang-doc/single-file-public.cpp
+++ b/clang-tools-extra/test/clang-doc/single-file-public.cpp
@@ -28,8 +28,9 @@ void Record::function_public() {}
 // CHECK-NEXT: Namespace:
 // CHECK-NEXT:   - Type:             Namespace
 // CHECK-NEXT:     Name:             'GlobalNamespace'
+// CHECK-NEXT:     QualName:         'GlobalNamespace'
 // CHECK-NEXT: DefLocation:
-// CHECK-NEXT:   LineNumber:      [[@LINE-20]]
+// CHECK-NEXT:   LineNumber:      12
 // CHECK-NEXT:   Filename:        '{{.*}}'
 // CHECK-NEXT: TagType:         Class
 // CHECK-NEXT: ChildFunctions:
@@ -38,22 +39,26 @@ void Record::function_public() {}
 // CHECK-NEXT:     Namespace:
 // CHECK-NEXT:       - Type:            Record
 // CHECK-NEXT:         Name:            'Record'
+// CHECK-NEXT:         QualName:        'Record'
 // CHECK-NEXT:         USR:             '{{([0-9A-F]{40})}}'
 // CHECK-NEXT:       - Type:            Namespace
 // CHECK-NEXT:         Name:            'GlobalNamespace'
+// CHECK-NEXT:         QualName:        'GlobalNamespace'
 // CHECK-NEXT:     DefLocation:
-// CHECK-NEXT:         LineNumber:      [[@LINE-23]]
+// CHECK-NEXT:         LineNumber:      22
 // CHECK-NEXT:         Filename:        '{{.*}}'
 // CHECK-NEXT:     Location:
-// CHECK-NEXT:       - LineNumber:      [[@LINE-31]]
+// CHECK-NEXT:       - LineNumber:      17
 // CHECK-NEXT:         Filename:        '{{.*}}'
 // CHECK-NEXT:     IsMethod:        true
 // CHECK-NEXT:     Parent:
 // CHECK-NEXT:         Type:            Record
 // CHECK-NEXT:         Name:            'Record'
+// CHECK-NEXT:         QualName:        'Record'
 // CHECK-NEXT:         USR:             '{{([0-9A-F]{40})}}'
 // CHECK-NEXT:     ReturnType:
 // CHECK-NEXT:       Type:
 // CHECK-NEXT:         Name:            'void'
+// CHECK-NEXT:         QualName:        'void'
 // CHECK-NEXT:     Access:			Public
 // CHECK-NEXT: ...

diff  --git a/clang-tools-extra/test/clang-doc/single-file.cpp b/clang-tools-extra/test/clang-doc/single-file.cpp
index 3703f5d2209f4..211afb3b45e52 100644
--- a/clang-tools-extra/test/clang-doc/single-file.cpp
+++ b/clang-tools-extra/test/clang-doc/single-file.cpp
@@ -11,21 +11,23 @@ void function(int x);
 void function(int x) {}
 
 // CHECK: ---
-// CHECK-NEXT: USR:             '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-NEXT: USR:             '{{([0-9A-F]{40})}}'
 // CHECK-NEXT: ChildFunctions:
-// CHECK-NEXT:   - USR:             '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-NEXT:   - USR:             '{{([0-9A-F]{40})}}'
 // CHECK-NEXT:    Name:            'function'
 // CHECK-NEXT:    DefLocation:
-// CHECK-NEXT:      LineNumber:      [[@LINE-8]]
+// CHECK-NEXT:      LineNumber:      11
 // CHECK-NEXT:      Filename:        '{{.*}}
 // CHECK-NEXT:    Location:
-// CHECK-NEXT:      - LineNumber:      [[@LINE-13]]
+// CHECK-NEXT:      - LineNumber:      9
 // CHECK-NEXT:        Filename:        '{{.*}}'
 // CHECK-NEXT:    Params:
 // CHECK-NEXT:      - Type:
 // CHECK-NEXT:          Name:            'int'
+// CHECK-NEXT:          QualName:        'int'
 // CHECK-NEXT:        Name:            'x'
 // CHECK-NEXT:    ReturnType:
 // CHECK-NEXT:      Type:
 // CHECK-NEXT:        Name:            'void'
+// CHECK-NEXT:        QualName:        'void'
 // CHECK-NEXT:...

diff  --git a/clang-tools-extra/test/clang-doc/templates.cpp b/clang-tools-extra/test/clang-doc/templates.cpp
new file mode 100644
index 0000000000000..eb7f4599629f4
--- /dev/null
+++ b/clang-tools-extra/test/clang-doc/templates.cpp
@@ -0,0 +1,76 @@
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+// RUN: clang-doc --doxygen --executor=standalone -p %t %t/test.cpp -output=%t/docs
+// RUN: cat %t/docs/index.yaml | FileCheck %s --check-prefix=CHECK
+// RUN: rm -rf %t
+
+template<typename T, int U = 1>
+void function<bool, 0>(T x) {}
+
+template<>
+void function<bool, 0>(bool x) {}
+
+template<class... T>
+void ParamPackFunction(T... args);
+
+// CHECK: ---
+// CHECK-NEXT: USR:             '{{([0-9A-F]{40})}}'
+// CHECK-NEXT: ChildFunctions:
+// CHECK-NEXT:   - USR:             '{{([0-9A-F]{40})}}'
+// CHECK-NEXT:     Name:            'function'
+// CHECK-NEXT:     DefLocation:
+// CHECK-NEXT:       LineNumber:      10
+// CHECK-NEXT:       Filename:        '{{.*}}'
+// CHECK-NEXT:     Params:
+// CHECK-NEXT:       - Type:
+// CHECK-NEXT:           Name:            'T'
+// CHECK-NEXT:           QualName:        'T'
+// CHECK-NEXT:         Name:            'x'
+// CHECK-NEXT:     ReturnType:
+// CHECK-NEXT:       Type:
+// CHECK-NEXT:         Name:            'void'
+// CHECK-NEXT:         QualName:        'void'
+// CHECK-NEXT:     Template:
+// CHECK-NEXT:       Params:
+// CHECK-NEXT:         - Contents:        'typename T'
+// CHECK-NEXT:         - Contents:        'int U = 1'
+// CHECK-NEXT:   - USR:             '{{([0-9A-F]{40})}}'
+// CHECK-NEXT:     Name:            'function'
+// CHECK-NEXT:     DefLocation:
+// CHECK-NEXT:       LineNumber:      12
+// CHECK-NEXT:       Filename:        '{{.*}}'
+// CHECK-NEXT:     Params:
+// CHECK-NEXT:       - Type:
+// CHECK-NEXT:           Name:            '_Bool'
+// CHECK-NEXT:           QualName:        '_Bool'
+// CHECK-NEXT:         Name:            'x'
+// CHECK-NEXT:     ReturnType:
+// CHECK-NEXT:       Type:
+// CHECK-NEXT:         Name:            'void'
+// CHECK-NEXT:         QualName:        'void'
+// CHECK-NEXT:     Template:
+// CHECK-NEXT:       Specialization:
+// CHECK-NEXT:         SpecializationOf: '{{([0-9A-F]{40})}}'
+// CHECK-NEXT:         Params:
+// CHECK-NEXT:           - Contents:        'bool'
+// CHECK-NEXT:           - Contents:        '0'
+// CHECK-NEXT:  - USR:             '{{([0-9A-F]{40})}}'
+// CHECK-NEXT:    Name:            'ParamPackFunction'
+// CHECK-NEXT:    Location:
+// CHECK-NEXT:      - LineNumber:      16
+// CHECK-NEXT:        Filename:        '{{.*}}'
+// CHECK-NEXT:    Params:
+// CHECK-NEXT:      - Type:
+// CHECK-NEXT:          Name:            'T...'
+// CHECK-NEXT:          QualName:        'T...'
+// CHECK-NEXT:        Name:            'args'
+// CHECK-NEXT:    ReturnType:
+// CHECK-NEXT:      Type:
+// CHECK-NEXT:        Name:            'void'
+// CHECK-NEXT:        QualName:        'void'
+// CHECK-NEXT:    Template:
+// CHECK-NEXT:      Params:
+// CHECK-NEXT:        - Contents:        'class... T'
+// CHECK-NEXT: ...

diff  --git a/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp b/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp
index d91225352c85b..5141259161653 100644
--- a/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp
+++ b/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp
@@ -44,9 +44,10 @@ TEST(HTMLGeneratorTest, emitNamespaceHTML) {
   I.Namespace.emplace_back(EmptySID, "A", InfoType::IT_namespace);
 
   I.Children.Namespaces.emplace_back(EmptySID, "ChildNamespace",
-                                     InfoType::IT_namespace, "Namespace");
+                                     InfoType::IT_namespace,
+                                     "Namespace::ChildNamespace", "Namespace");
   I.Children.Records.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record,
-                                  "Namespace");
+                                  "Namespace::ChildStruct", "Namespace");
   I.Children.Functions.emplace_back();
   I.Children.Functions.back().Access = AccessSpecifier::AS_none;
   I.Children.Functions.back().Name = "OneFunction";
@@ -152,14 +153,13 @@ TEST(HTMLGeneratorTest, emitRecordHTML) {
 
   SmallString<16> PathTo;
   llvm::sys::path::native("path/to", PathTo);
-  I.Members.emplace_back(TypeInfo("int", "X/Y"), "X",
-                         AccessSpecifier::AS_private);
+  I.Members.emplace_back(TypeInfo("int"), "X", AccessSpecifier::AS_private);
   I.TagType = TagTypeKind::TTK_Class;
-  I.Parents.emplace_back(EmptySID, "F", InfoType::IT_record, PathTo);
+  I.Parents.emplace_back(EmptySID, "F", InfoType::IT_record, "F", PathTo);
   I.VirtualParents.emplace_back(EmptySID, "G", InfoType::IT_record);
 
   I.Children.Records.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record,
-                                  "X/Y/Z/r");
+                                  "X::Y::Z::r::ChildStruct", "X/Y/Z/r");
   I.Children.Functions.emplace_back();
   I.Children.Functions.back().Name = "OneFunction";
   I.Children.Enums.emplace_back();
@@ -195,11 +195,7 @@ TEST(HTMLGeneratorTest, emitRecordHTML) {
     </p>
     <h2 id="Members">Members</h2>
     <ul>
-      <li>
-        private 
-        <a href="../../../X/Y/int.html">int</a>
-         X
-      </li>
+      <li>private int X</li>
     </ul>
     <h2 id="Records">Records</h2>
     <ul>
@@ -277,8 +273,8 @@ TEST(HTMLGeneratorTest, emitFunctionHTML) {
 
   SmallString<16> PathTo;
   llvm::sys::path::native("path/to", PathTo);
-  I.ReturnType =
-      TypeInfo(Reference(EmptySID, "float", InfoType::IT_default, PathTo));
+  I.ReturnType = TypeInfo(
+      Reference(EmptySID, "float", InfoType::IT_default, "float", PathTo));
   I.Params.emplace_back(TypeInfo("int", PathTo), "P");
   I.IsMethod = true;
   I.Parent = Reference(EmptySID, "Parent", InfoType::IT_record);

diff  --git a/clang-tools-extra/unittests/clang-doc/SerializeTest.cpp b/clang-tools-extra/unittests/clang-doc/SerializeTest.cpp
index 05b62451adbc5..ca370de5a516e 100644
--- a/clang-tools-extra/unittests/clang-doc/SerializeTest.cpp
+++ b/clang-tools-extra/unittests/clang-doc/SerializeTest.cpp
@@ -390,7 +390,7 @@ class J : public I<int> {} ;)raw",
   RecordInfo *F = InfoAsRecord(Infos[0].get());
   RecordInfo ExpectedF(EmptySID, /*Name=*/"F", /*Path=*/"GlobalNamespace");
   ExpectedF.Namespace.emplace_back(EmptySID, "GlobalNamespace",
-                                   InfoType::IT_namespace);
+                                   InfoType::IT_namespace, "");
   ExpectedF.TagType = TagTypeKind::TTK_Class;
   ExpectedF.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"});
   CheckRecordInfo(&ExpectedF, F);
@@ -410,9 +410,10 @@ class J : public I<int> {} ;)raw",
   ExpectedE.Namespace.emplace_back(EmptySID, "GlobalNamespace",
                                    InfoType::IT_namespace);
   ExpectedE.Parents.emplace_back(EmptySID, /*Name=*/"F", InfoType::IT_record,
-                                 /*Path*=*/"GlobalNamespace");
-  ExpectedE.VirtualParents.emplace_back(
-      EmptySID, /*Name=*/"G", InfoType::IT_record, /*Path*=*/"GlobalNamespace");
+                                 /*QualName=*/"", /*Path*=*/"GlobalNamespace");
+  ExpectedE.VirtualParents.emplace_back(EmptySID, /*Name=*/"G",
+                                        InfoType::IT_record, /*QualName=*/"G",
+                                        /*Path*=*/"GlobalNamespace");
   ExpectedE.Bases.emplace_back(EmptySID, /*Name=*/"F",
                                /*Path=*/"GlobalNamespace", false,
                                AccessSpecifier::AS_public, true);
@@ -455,9 +456,10 @@ class J : public I<int> {} ;)raw",
   ExpectedH.TagType = TagTypeKind::TTK_Class;
   ExpectedH.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"});
   ExpectedH.Parents.emplace_back(EmptySID, /*Name=*/"E", InfoType::IT_record,
-                                 /*Path=*/"GlobalNamespace");
-  ExpectedH.VirtualParents.emplace_back(
-      EmptySID, /*Name=*/"G", InfoType::IT_record, /*Path=*/"GlobalNamespace");
+                                 /*QualName=*/"E", /*Path=*/"GlobalNamespace");
+  ExpectedH.VirtualParents.emplace_back(EmptySID, /*Name=*/"G",
+                                        InfoType::IT_record, /*QualName=*/"G",
+                                        /*Path=*/"GlobalNamespace");
   ExpectedH.Bases.emplace_back(EmptySID, /*Name=*/"E",
                                /*Path=*/"GlobalNamespace", false,
                                AccessSpecifier::AS_private, true);
@@ -562,7 +564,7 @@ TEST(SerializeTest, emitChildRecords) {
   NamespaceInfo *ParentA = InfoAsNamespace(Infos[1].get());
   NamespaceInfo ExpectedParentA(EmptySID);
   ExpectedParentA.Children.Records.emplace_back(
-      EmptySID, "A", InfoType::IT_record, "GlobalNamespace");
+      EmptySID, "A", InfoType::IT_record, "A", "GlobalNamespace");
   CheckNamespaceInfo(&ExpectedParentA, ParentA);
 
   RecordInfo *ParentB = InfoAsRecord(Infos[3].get());
@@ -570,13 +572,13 @@ TEST(SerializeTest, emitChildRecords) {
   llvm::SmallString<128> ExpectedParentBPath("GlobalNamespace/A");
   llvm::sys::path::native(ExpectedParentBPath);
   ExpectedParentB.Children.Records.emplace_back(
-      EmptySID, "B", InfoType::IT_record, ExpectedParentBPath);
+      EmptySID, "B", InfoType::IT_record, "A::B", ExpectedParentBPath);
   CheckRecordInfo(&ExpectedParentB, ParentB);
 
   NamespaceInfo *ParentC = InfoAsNamespace(Infos[7].get());
   NamespaceInfo ExpectedParentC(EmptySID);
   ExpectedParentC.Children.Records.emplace_back(
-      EmptySID, "C", InfoType::IT_record, "@nonymous_namespace");
+      EmptySID, "C", InfoType::IT_record, "C", "@nonymous_namespace");
   CheckNamespaceInfo(&ExpectedParentC, ParentC);
 }
 
@@ -594,8 +596,8 @@ TEST(SerializeTest, emitChildNamespaces) {
 
   NamespaceInfo *ParentB = InfoAsNamespace(Infos[3].get());
   NamespaceInfo ExpectedParentB(EmptySID);
-  ExpectedParentB.Children.Namespaces.emplace_back(EmptySID, "B",
-                                                   InfoType::IT_namespace, "A");
+  ExpectedParentB.Children.Namespaces.emplace_back(
+      EmptySID, "B", InfoType::IT_namespace, "A::B", "A");
   CheckNamespaceInfo(&ExpectedParentB, ParentB);
 }
 
@@ -626,5 +628,105 @@ TEST(SerializeTests, emitTypedefs) {
   EXPECT_EQ("double", SecondTD.Underlying.Type.Name);
 }
 
+TEST(SerializeTests, emitFunctionTemplate) {
+  EmittedInfoList Infos;
+  // A template and a specialization.
+  ExtractInfosFromCode("template<typename T = int> void GetFoo(T);\n"
+                       "template<> void GetFoo<bool>(bool);",
+                       2,
+                       /*Public=*/false, Infos);
+
+  // First info will be the global namespace.
+  NamespaceInfo *GlobalNS1 = InfoAsNamespace(Infos[0].get());
+  ASSERT_EQ(1u, GlobalNS1->Children.Functions.size());
+
+  const FunctionInfo &Func1 = GlobalNS1->Children.Functions[0];
+  EXPECT_EQ("GetFoo", Func1.Name);
+  ASSERT_TRUE(Func1.Template);
+  EXPECT_FALSE(Func1.Template->Specialization); // Not a specialization.
+
+  // Template parameter.
+  ASSERT_EQ(1u, Func1.Template->Params.size());
+  EXPECT_EQ("typename T = int", Func1.Template->Params[0].Contents);
+
+  // The second will be another global namespace with the function in it (the
+  // global namespace is duplicated because the items haven't been merged at the
+  // serialization phase of processing).
+  NamespaceInfo *GlobalNS2 = InfoAsNamespace(Infos[1].get());
+  ASSERT_EQ(1u, GlobalNS2->Children.Functions.size());
+
+  // This one is a template specialization.
+  const FunctionInfo &Func2 = GlobalNS2->Children.Functions[0];
+  EXPECT_EQ("GetFoo", Func2.Name);
+  ASSERT_TRUE(Func2.Template);
+  EXPECT_TRUE(Func2.Template->Params.empty()); // No template params.
+  ASSERT_TRUE(Func2.Template->Specialization);
+
+  // Specialization values.
+  ASSERT_EQ(1u, Func2.Template->Specialization->Params.size());
+  EXPECT_EQ("bool", Func2.Template->Specialization->Params[0].Contents);
+  EXPECT_EQ(Func1.USR, Func2.Template->Specialization->SpecializationOf);
+}
+
+TEST(SerializeTests, emitClassTemplate) {
+  EmittedInfoList Infos;
+  // This will generate 2x the number of infos: each Record will be followed by
+  // a copy of the global namespace containing it (this test checks the data
+  // pre-merge).
+  ExtractInfosFromCode(
+      "template<int I> class MyTemplate { int i[I]; };\n"
+      "template<> class MyTemplate<0> {};\n"
+      "template<typename T, int U = 1> class OtherTemplate {};\n"
+      "template<int U> class OtherTemplate<MyTemplate<0>, U> {};",
+      8,
+      /*Public=*/false, Infos);
+
+  // First record.
+  const RecordInfo *Rec1 = InfoAsRecord(Infos[0].get());
+  EXPECT_EQ("MyTemplate", Rec1->Name);
+  ASSERT_TRUE(Rec1->Template);
+  EXPECT_FALSE(Rec1->Template->Specialization); // Not a specialization.
+
+  // First record template parameter.
+  ASSERT_EQ(1u, Rec1->Template->Params.size());
+  EXPECT_EQ("int I", Rec1->Template->Params[0].Contents);
+
+  // Second record.
+  const RecordInfo *Rec2 = InfoAsRecord(Infos[2].get());
+  EXPECT_EQ("MyTemplate", Rec2->Name);
+  ASSERT_TRUE(Rec2->Template);
+  EXPECT_TRUE(Rec2->Template->Params.empty()); // No template params.
+  ASSERT_TRUE(Rec2->Template->Specialization);
+
+  // Second record specialization values.
+  ASSERT_EQ(1u, Rec2->Template->Specialization->Params.size());
+  EXPECT_EQ("0", Rec2->Template->Specialization->Params[0].Contents);
+  EXPECT_EQ(Rec1->USR, Rec2->Template->Specialization->SpecializationOf);
+
+  // Third record.
+  const RecordInfo *Rec3 = InfoAsRecord(Infos[4].get());
+  EXPECT_EQ("OtherTemplate", Rec3->Name);
+  ASSERT_TRUE(Rec3->Template);
+
+  // Third record template parameters.
+  ASSERT_EQ(2u, Rec3->Template->Params.size());
+  EXPECT_EQ("typename T", Rec3->Template->Params[0].Contents);
+  EXPECT_EQ("int U = 1", Rec3->Template->Params[1].Contents);
+
+  // Fourth record.
+  const RecordInfo *Rec4 = InfoAsRecord(Infos[6].get());
+  EXPECT_EQ("OtherTemplate", Rec3->Name);
+  ASSERT_TRUE(Rec4->Template);
+  ASSERT_TRUE(Rec4->Template->Specialization);
+
+  // Fourth record template + specialization parameters.
+  ASSERT_EQ(1u, Rec4->Template->Params.size());
+  EXPECT_EQ("int U", Rec4->Template->Params[0].Contents);
+  ASSERT_EQ(2u, Rec4->Template->Specialization->Params.size());
+  EXPECT_EQ("MyTemplate<0>",
+            Rec4->Template->Specialization->Params[0].Contents);
+  EXPECT_EQ("U", Rec4->Template->Specialization->Params[1].Contents);
+}
+
 } // namespace doc
 } // end namespace clang

diff  --git a/clang-tools-extra/unittests/clang-doc/YAMLGeneratorTest.cpp b/clang-tools-extra/unittests/clang-doc/YAMLGeneratorTest.cpp
index d6410f28282e4..535d49169b5df 100644
--- a/clang-tools-extra/unittests/clang-doc/YAMLGeneratorTest.cpp
+++ b/clang-tools-extra/unittests/clang-doc/YAMLGeneratorTest.cpp
@@ -28,10 +28,11 @@ TEST(YAMLGeneratorTest, emitNamespaceYAML) {
   I.Path = "path/to/A";
   I.Namespace.emplace_back(EmptySID, "A", InfoType::IT_namespace);
 
-  I.Children.Namespaces.emplace_back(EmptySID, "ChildNamespace",
-                                     InfoType::IT_namespace,
-                                     "path/to/A/Namespace");
+  I.Children.Namespaces.emplace_back(
+      EmptySID, "ChildNamespace", InfoType::IT_namespace,
+      "path::to::A::Namespace::ChildNamespace", "path/to/A/Namespace");
   I.Children.Records.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record,
+                                  "path::to::A::Namespace::ChildStruct",
                                   "path/to/A/Namespace");
   I.Children.Functions.emplace_back();
   I.Children.Functions.back().Name = "OneFunction";
@@ -53,13 +54,16 @@ Path:            'path/to/A'
 Namespace:
   - Type:            Namespace
     Name:            'A'
+    QualName:        'A'
 ChildNamespaces:
   - Type:            Namespace
     Name:            'ChildNamespace'
+    QualName:        'path::to::A::Namespace::ChildNamespace'
     Path:            'path/to/A/Namespace'
 ChildRecords:
   - Type:            Record
     Name:            'ChildStruct'
+    QualName:        'path::to::A::Namespace::ChildStruct'
     Path:            'path/to/A/Namespace'
 ChildFunctions:
   - USR:             '0000000000000000000000000000000000000000'
@@ -83,8 +87,7 @@ TEST(YAMLGeneratorTest, emitRecordYAML) {
   I.DefLoc = Location(10, llvm::SmallString<16>{"test.cpp"});
   I.Loc.emplace_back(12, llvm::SmallString<16>{"test.cpp"});
 
-  I.Members.emplace_back(TypeInfo("int", "path/to/int"), "X",
-                         AccessSpecifier::AS_private);
+  I.Members.emplace_back(TypeInfo("int"), "X", AccessSpecifier::AS_private);
 
   // Member documentation.
   CommentInfo TopComment;
@@ -103,15 +106,15 @@ TEST(YAMLGeneratorTest, emitRecordYAML) {
                        AccessSpecifier::AS_public, true);
   I.Bases.back().Children.Functions.emplace_back();
   I.Bases.back().Children.Functions.back().Name = "InheritedFunctionOne";
-  I.Bases.back().Members.emplace_back(TypeInfo("int", "path/to/int"), "N",
+  I.Bases.back().Members.emplace_back(TypeInfo("int"), "N",
                                       AccessSpecifier::AS_private);
   // F is in the global namespace
   I.Parents.emplace_back(EmptySID, "F", InfoType::IT_record, "");
   I.VirtualParents.emplace_back(EmptySID, "G", InfoType::IT_record,
-                                "path/to/G");
+                                "path::to::G::G", "path/to/G");
 
   I.Children.Records.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record,
-                                  "path/to/A/r");
+                                  "path::to::A::r::ChildStruct", "path/to/A/r");
   I.Children.Functions.emplace_back();
   I.Children.Functions.back().Name = "OneFunction";
   I.Children.Enums.emplace_back();
@@ -131,6 +134,7 @@ Path:            'path/to/A'
 Namespace:
   - Type:            Namespace
     Name:            'A'
+    QualName:        'A'
 DefLocation:
   LineNumber:      10
   Filename:        'test.cpp'
@@ -142,7 +146,7 @@ IsTypeDef:       true
 Members:
   - Type:
       Name:            'int'
-      Path:            'path/to/int'
+      QualName:        'int'
     Name:            'X'
     Access:          Private
     Description:
@@ -161,7 +165,7 @@ IsTypeDef:       true
     Members:
       - Type:
           Name:            'int'
-          Path:            'path/to/int'
+          QualName:        'int'
         Name:            'N'
         Access:          Private
     ChildFunctions:
@@ -178,10 +182,12 @@ IsTypeDef:       true
 VirtualParents:
   - Type:            Record
     Name:            'G'
+    QualName:        'path::to::G::G'
     Path:            'path/to/G'
 ChildRecords:
   - Type:            Record
     Name:            'ChildStruct'
+    QualName:        'path::to::A::r::ChildStruct'
     Path:            'path/to/A/r'
 ChildFunctions:
   - USR:             '0000000000000000000000000000000000000000'
@@ -206,10 +212,9 @@ TEST(YAMLGeneratorTest, emitFunctionYAML) {
 
   I.Access = AccessSpecifier::AS_none;
 
-  I.ReturnType = TypeInfo(
-      Reference(EmptySID, "void", InfoType::IT_default, "path/to/void"));
-  I.Params.emplace_back(TypeInfo("int", "path/to/int"), "P");
-  I.Params.emplace_back(TypeInfo("double", "path/to/double"), "D");
+  I.ReturnType = TypeInfo(Reference(EmptySID, "void", InfoType::IT_default));
+  I.Params.emplace_back(TypeInfo("int"), "P");
+  I.Params.emplace_back(TypeInfo("double"), "D");
   I.Params.back().DefaultValue = "2.0 * M_PI";
   I.IsMethod = true;
   I.Parent = Reference(EmptySID, "Parent", InfoType::IT_record);
@@ -227,6 +232,7 @@ Name:            'f'
 Namespace:
   - Type:            Namespace
     Name:            'A'
+    QualName:        'A'
 DefLocation:
   LineNumber:      10
   Filename:        'test.cpp'
@@ -237,20 +243,21 @@ IsMethod:        true
 Parent:
   Type:            Record
   Name:            'Parent'
+  QualName:        'Parent'
 Params:
   - Type:
       Name:            'int'
-      Path:            'path/to/int'
+      QualName:        'int'
     Name:            'P'
   - Type:
       Name:            'double'
-      Path:            'path/to/double'
+      QualName:        'double'
     Name:            'D'
     DefaultValue:    '2.0 * M_PI'
 ReturnType:
   Type:
     Name:            'void'
-    Path:            'path/to/void'
+    QualName:        'void'
 ...
 )raw";
   EXPECT_EQ(Expected, Actual.str());
@@ -284,6 +291,7 @@ Name:            'e'
 Namespace:
   - Type:            Namespace
     Name:            'A'
+    QualName:        'A'
 DefLocation:
   LineNumber:      10
   Filename:        'test.cpp'
@@ -322,6 +330,7 @@ Scoped:          true
 BaseType:
   Type:
     Name:            'short'
+    QualName:        'short'
 Members:
   - Name:            'X'
     Value:           '-9876'
@@ -349,6 +358,7 @@ USR:             '0000000000000000000000000000000000000000'
 Name:            'MyUsing'
 Underlying:
   Name:            'int'
+  QualName:        'int'
 IsUsing:         true
 ...
 )raw";
@@ -548,13 +558,16 @@ Name:            'f'
 Params:
   - Type:
       Name:            'int'
+      QualName:        'int'
     Name:            'I'
   - Type:
       Name:            'int'
+      QualName:        'int'
     Name:            'J'
 ReturnType:
   Type:
     Name:            'void'
+    QualName:        'void'
 ...
 )raw";
 


        


More information about the cfe-commits mailing list