[clang] 7ba37f4 - [clang][ExtractAPI] Add support for C++ class templates and concepts

Erick Velez via cfe-commits cfe-commits at lists.llvm.org
Fri Aug 18 13:41:17 PDT 2023


Author: Erick Velez
Date: 2023-08-18T13:40:22-07:00
New Revision: 7ba37f4e46a5bbb1dc42f1ea1722296ea32034d5

URL: https://github.com/llvm/llvm-project/commit/7ba37f4e46a5bbb1dc42f1ea1722296ea32034d5
DIFF: https://github.com/llvm/llvm-project/commit/7ba37f4e46a5bbb1dc42f1ea1722296ea32034d5.diff

LOG: [clang][ExtractAPI] Add support for C++ class templates and concepts

Add has_template template, DeclarationFragmentBuilder functions, and tests for class templates, specializations/partial specs, and concepts.

Depends on D157007

Reviewed By: dang

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

Added: 
    clang/test/ExtractAPI/class_template.cpp
    clang/test/ExtractAPI/class_template_param_inheritance.cpp
    clang/test/ExtractAPI/class_template_partial_spec.cpp
    clang/test/ExtractAPI/class_template_spec.cpp
    clang/test/ExtractAPI/concept.cpp

Modified: 
    clang/include/clang/ExtractAPI/API.h
    clang/include/clang/ExtractAPI/DeclarationFragments.h
    clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
    clang/include/clang/ExtractAPI/Serialization/SerializerBase.h
    clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h
    clang/lib/ExtractAPI/API.cpp
    clang/lib/ExtractAPI/DeclarationFragments.cpp
    clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/ExtractAPI/API.h b/clang/include/clang/ExtractAPI/API.h
index a965f49c8e91b2..83ff982be559b9 100644
--- a/clang/include/clang/ExtractAPI/API.h
+++ b/clang/include/clang/ExtractAPI/API.h
@@ -36,6 +36,86 @@
 namespace clang {
 namespace extractapi {
 
+class Template {
+  struct TemplateParameter {
+    // "class", "typename", or concept name
+    std::string Type;
+    std::string Name;
+    unsigned int Index;
+    unsigned int Depth;
+    bool IsParameterPack;
+
+    TemplateParameter(std::string Type, std::string Name, unsigned int Index,
+                      unsigned int Depth, bool IsParameterPack)
+        : Type(Type), Name(Name), Index(Index), Depth(Depth),
+          IsParameterPack(IsParameterPack) {}
+  };
+
+  struct TemplateConstraint {
+    // type name of the constraint, if it has one
+    std::string Type;
+    std::string Kind;
+    std::string LHS, RHS;
+  };
+  llvm::SmallVector<TemplateParameter> Parameters;
+  llvm::SmallVector<TemplateConstraint> Constraints;
+
+public:
+  Template() = default;
+
+  Template(const TemplateDecl *Decl) {
+    for (auto *const Parameter : *Decl->getTemplateParameters()) {
+      const auto *Param = dyn_cast<TemplateTypeParmDecl>(Parameter);
+      if (!Param) // some params are null
+        continue;
+      std::string Type;
+      if (Param->hasTypeConstraint())
+        Type = Param->getTypeConstraint()->getNamedConcept()->getName().str();
+      else if (Param->wasDeclaredWithTypename())
+        Type = "typename";
+      else
+        Type = "class";
+
+      addTemplateParameter(Type, Param->getName().str(), Param->getIndex(),
+                           Param->getDepth(), Param->isParameterPack());
+    }
+  }
+
+  Template(const ClassTemplatePartialSpecializationDecl *Decl) {
+    for (auto *const Parameter : *Decl->getTemplateParameters()) {
+      const auto *Param = dyn_cast<TemplateTypeParmDecl>(Parameter);
+      if (!Param) // some params are null
+        continue;
+      std::string Type;
+      if (Param->hasTypeConstraint())
+        Type = Param->getTypeConstraint()->getNamedConcept()->getName().str();
+      else if (Param->wasDeclaredWithTypename())
+        Type = "typename";
+      else
+        Type = "class";
+
+      addTemplateParameter(Type, Param->getName().str(), Param->getIndex(),
+                           Param->getDepth(), Param->isParameterPack());
+    }
+  }
+
+  const llvm::SmallVector<TemplateParameter> &getParameters() const {
+    return Parameters;
+  }
+
+  const llvm::SmallVector<TemplateConstraint> &getConstraints() const {
+    return Constraints;
+  }
+
+  void addTemplateParameter(std::string Type, std::string Name,
+                            unsigned int Index, unsigned int Depth,
+                            bool IsParameterPack) {
+    Parameters.emplace_back(Type, Name, Index, Depth, IsParameterPack);
+  }
+
+  bool empty() const { return Parameters.empty() && Constraints.empty(); }
+};
+
 /// DocComment is a vector of RawComment::CommentLine.
 ///
 /// Each line represents one line of striped documentation comment,
@@ -69,6 +149,10 @@ struct APIRecord {
     RK_StaticField,
     RK_CXXField,
     RK_CXXClass,
+    RK_ClassTemplate,
+    RK_ClassTemplateSpecialization,
+    RK_ClassTemplatePartialSpecialization,
+    RK_Concept,
     RK_CXXStaticMethod,
     RK_CXXInstanceMethod,
     RK_CXXConstructorMethod,
@@ -644,6 +728,75 @@ struct CXXClassRecord : APIRecord {
   virtual void anchor();
 };
 
+struct ClassTemplateRecord : CXXClassRecord {
+  Template Templ;
+
+  ClassTemplateRecord(StringRef USR, StringRef Name, PresumedLoc Loc,
+                      AvailabilitySet Availabilities, const DocComment &Comment,
+                      DeclarationFragments Declaration,
+                      DeclarationFragments SubHeading, Template Template,
+                      bool IsFromSystemHeader)
+      : CXXClassRecord(USR, Name, Loc, std::move(Availabilities), Comment,
+                       Declaration, SubHeading, RK_ClassTemplate,
+                       IsFromSystemHeader),
+        Templ(Template) {}
+
+  static bool classof(const APIRecord *Record) {
+    return Record->getKind() == RK_ClassTemplate;
+  }
+};
+
+struct ClassTemplateSpecializationRecord : CXXClassRecord {
+  ClassTemplateSpecializationRecord(StringRef USR, StringRef Name,
+                                    PresumedLoc Loc,
+                                    AvailabilitySet Availabilities,
+                                    const DocComment &Comment,
+                                    DeclarationFragments Declaration,
+                                    DeclarationFragments SubHeading,
+                                    bool IsFromSystemHeader)
+      : CXXClassRecord(USR, Name, Loc, std::move(Availabilities), Comment,
+                       Declaration, SubHeading, RK_ClassTemplateSpecialization,
+                       IsFromSystemHeader) {}
+
+  static bool classof(const APIRecord *Record) {
+    return Record->getKind() == RK_ClassTemplateSpecialization;
+  }
+};
+
+struct ClassTemplatePartialSpecializationRecord : CXXClassRecord {
+  Template Templ;
+  ClassTemplatePartialSpecializationRecord(
+      StringRef USR, StringRef Name, PresumedLoc Loc,
+      AvailabilitySet Availabilities, const DocComment &Comment,
+      DeclarationFragments Declaration, DeclarationFragments SubHeading,
+      Template Template, bool IsFromSystemHeader)
+      : CXXClassRecord(USR, Name, Loc, std::move(Availabilities), Comment,
+                       Declaration, SubHeading, RK_ClassTemplateSpecialization,
+                       IsFromSystemHeader),
+        Templ(Template) {}
+
+  static bool classof(const APIRecord *Record) {
+    return Record->getKind() == RK_ClassTemplatePartialSpecialization;
+  }
+};
+
+struct ConceptRecord : APIRecord {
+  Template Templ;
+  ConceptRecord(StringRef USR, StringRef Name, PresumedLoc Loc,
+                AvailabilitySet Availabilities, const DocComment &Comment,
+                DeclarationFragments Declaration,
+                DeclarationFragments SubHeading, Template Template,
+                bool IsFromSystemHeader)
+      : APIRecord(RK_Concept, USR, Name, Loc, std::move(Availabilities),
+                  LinkageInfo::none(), Comment, Declaration, SubHeading,
+                  IsFromSystemHeader),
+        Templ(Template) {}
+
+  static bool classof(const APIRecord *Record) {
+    return Record->getKind() == RK_Concept;
+  }
+};
+
 /// This holds information associated with Objective-C categories.
 struct ObjCCategoryRecord : ObjCContainerRecord {
   SymbolReference Interface;
@@ -779,6 +932,13 @@ template <typename RecordTy> struct has_access : public std::false_type {};
 template <> struct has_access<CXXMethodRecord> : public std::true_type {};
 template <> struct has_access<CXXFieldRecord> : public std::true_type {};
 
+template <typename RecordTy> struct has_template : public std::false_type {};
+template <> struct has_template<ClassTemplateRecord> : public std::true_type {};
+template <>
+struct has_template<ClassTemplatePartialSpecializationRecord>
+    : public std::true_type {};
+template <> struct has_template<ConceptRecord> : public std::true_type {};
+
 /// APISet holds the set of API records collected from given inputs.
 class APISet {
 public:
@@ -876,6 +1036,26 @@ class APISet {
               DeclarationFragments Declaration, DeclarationFragments SubHeading,
               APIRecord::RecordKind Kind, bool IsFromSystemHeader);
 
+  ClassTemplateRecord *
+  addClassTemplate(StringRef Name, StringRef USR, PresumedLoc Loc,
+                   AvailabilitySet Availability, const DocComment &Comment,
+                   DeclarationFragments Declaration,
+                   DeclarationFragments SubHeading, Template Template,
+                   bool IsFromSystemHeader);
+
+  ClassTemplateSpecializationRecord *addClassTemplateSpecialization(
+      StringRef Name, StringRef USR, PresumedLoc Loc,
+      AvailabilitySet Availability, const DocComment &Comment,
+      DeclarationFragments Declaration, DeclarationFragments SubHeading,
+      bool IsFromSystemHeader);
+
+  ClassTemplatePartialSpecializationRecord *
+  addClassTemplatePartialSpecialization(
+      StringRef Name, StringRef USR, PresumedLoc Loc,
+      AvailabilitySet Availability, const DocComment &Comment,
+      DeclarationFragments Declaration, DeclarationFragments SubHeading,
+      Template Template, bool IsFromSystemHeader);
+
   CXXMethodRecord *
   addCXXMethod(CXXClassRecord *CXXClassRecord, StringRef Name, StringRef USR,
                PresumedLoc Loc, AvailabilitySet Availability,
@@ -890,6 +1070,13 @@ class APISet {
       FunctionSignature Signature, bool IsConstructor, AccessControl Access,
       bool IsFromSystemHeader);
 
+  ConceptRecord *addConcept(StringRef Name, StringRef USR, PresumedLoc Loc,
+                            AvailabilitySet Availability,
+                            const DocComment &Comment,
+                            DeclarationFragments Declaration,
+                            DeclarationFragments SubHeading, Template Template,
+                            bool IsFromSystemHeader);
+
   /// Create and add an Objective-C category record into the API set.
   ///
   /// Note: the caller is responsible for keeping the StringRef \p Name and
@@ -1018,6 +1205,19 @@ class APISet {
   const RecordMap<EnumRecord> &getEnums() const { return Enums; }
   const RecordMap<StructRecord> &getStructs() const { return Structs; }
   const RecordMap<CXXClassRecord> &getCXXClasses() const { return CXXClasses; }
+  const RecordMap<ConceptRecord> &getConcepts() const { return Concepts; }
+  const RecordMap<ClassTemplateRecord> &getClassTemplates() const {
+    return ClassTemplates;
+  }
+  const RecordMap<ClassTemplateSpecializationRecord> &
+  getClassTemplateSpecializations() const {
+    return ClassTemplateSpecializations;
+  }
+  const RecordMap<ClassTemplatePartialSpecializationRecord> &
+  getClassTemplatePartialSpecializations() const {
+    return ClassTemplatePartialSpecializations;
+  }
+  const RecordMap<ConceptRecord> &getRecords() const { return Concepts; }
   const RecordMap<ObjCCategoryRecord> &getObjCCategories() const {
     return ObjCCategories;
   }
@@ -1071,10 +1271,15 @@ class APISet {
   llvm::DenseMap<StringRef, APIRecord *> USRBasedLookupTable;
   RecordMap<GlobalFunctionRecord> GlobalFunctions;
   RecordMap<GlobalVariableRecord> GlobalVariables;
+  RecordMap<ConceptRecord> Concepts;
   RecordMap<StaticFieldRecord> StaticFields;
   RecordMap<EnumRecord> Enums;
   RecordMap<StructRecord> Structs;
   RecordMap<CXXClassRecord> CXXClasses;
+  RecordMap<ClassTemplateRecord> ClassTemplates;
+  RecordMap<ClassTemplateSpecializationRecord> ClassTemplateSpecializations;
+  RecordMap<ClassTemplatePartialSpecializationRecord>
+      ClassTemplatePartialSpecializations;
   RecordMap<ObjCCategoryRecord> ObjCCategories;
   RecordMap<ObjCInterfaceRecord> ObjCInterfaces;
   RecordMap<ObjCProtocolRecord> ObjCProtocols;

diff  --git a/clang/include/clang/ExtractAPI/DeclarationFragments.h b/clang/include/clang/ExtractAPI/DeclarationFragments.h
index dc8a02e8811aed..c0271de91acfb2 100644
--- a/clang/include/clang/ExtractAPI/DeclarationFragments.h
+++ b/clang/include/clang/ExtractAPI/DeclarationFragments.h
@@ -22,6 +22,7 @@
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclObjC.h"
+#include "clang/AST/DeclTemplate.h"
 #include "clang/Lex/MacroInfo.h"
 #include "llvm/ADT/StringRef.h"
 #include <vector>
@@ -161,6 +162,11 @@ class DeclarationFragments {
     return *this;
   }
 
+  DeclarationFragments &pop_back() {
+    Fragments.pop_back();
+    return *this;
+  }
+
   /// Append a text Fragment of a space character.
   ///
   /// \returns a reference to the DeclarationFragments object itself after
@@ -292,6 +298,28 @@ class DeclarationFragmentsBuilder {
   static DeclarationFragments
   getFragmentsForOverloadedOperator(const CXXMethodDecl *);
 
+  static DeclarationFragments
+      getFragmentsForTemplateParameters(ArrayRef<NamedDecl *>);
+
+  static std::string getNameForTemplateArgument(const ArrayRef<NamedDecl *>,
+                                                std::string);
+
+  static DeclarationFragments
+  getFragmentsForTemplateArguments(const ArrayRef<TemplateArgument>,
+                                   ASTContext &,
+                                   const std::optional<ArrayRef<NamedDecl *>>);
+
+  static DeclarationFragments getFragmentsForConcept(const ConceptDecl *);
+
+  static DeclarationFragments
+  getFragmentsForRedeclarableTemplate(const RedeclarableTemplateDecl *);
+
+  static DeclarationFragments getFragmentsForClassTemplateSpecialization(
+      const ClassTemplateSpecializationDecl *);
+
+  static DeclarationFragments getFragmentsForClassTemplatePartialSpecialization(
+      const ClassTemplatePartialSpecializationDecl *);
+
   /// Build DeclarationFragments for an Objective-C category declaration
   /// ObjCCategoryDecl.
   static DeclarationFragments

diff  --git a/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h b/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
index 904942a69691a6..0392b6db2da0ad 100644
--- a/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
+++ b/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
@@ -15,6 +15,7 @@
 #define LLVM_CLANG_EXTRACTAPI_EXTRACT_API_VISITOR_H
 
 #include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclTemplate.h"
 #include "clang/Basic/OperatorKinds.h"
 #include "clang/Basic/Specifiers.h"
 #include "clang/ExtractAPI/DeclarationFragments.h"
@@ -52,10 +53,24 @@ class ExtractAPIVisitorBase : public RecursiveASTVisitor<Derived> {
 
   bool WalkUpFromCXXRecordDecl(const CXXRecordDecl *Decl);
 
+  bool WalkUpFromClassTemplateSpecializationDecl(
+      const ClassTemplateSpecializationDecl *Decl);
+
+  bool WalkUpFromClassTemplatePartialSpecializationDecl(
+      const ClassTemplatePartialSpecializationDecl *Decl);
+
   bool VisitRecordDecl(const RecordDecl *Decl);
 
   bool VisitCXXRecordDecl(const CXXRecordDecl *Decl);
 
+  bool VisitConceptDecl(const ConceptDecl *Decl);
+
+  bool VisitClassTemplateSpecializationDecl(
+      const ClassTemplateSpecializationDecl *Decl);
+
+  bool VisitClassTemplatePartialSpecializationDecl(
+      const ClassTemplatePartialSpecializationDecl *Decl);
+
   bool VisitObjCInterfaceDecl(const ObjCInterfaceDecl *Decl);
 
   bool VisitObjCProtocolDecl(const ObjCProtocolDecl *Decl);
@@ -128,6 +143,29 @@ class ExtractAPIVisitorBase : public RecursiveASTVisitor<Derived> {
   Derived &getDerivedExtractAPIVisitor() {
     return *static_cast<Derived *>(this);
   }
+
+  SmallVector<SymbolReference> getBases(const CXXRecordDecl *Decl) {
+    // FIXME: store AccessSpecifier given by inheritance
+    SmallVector<SymbolReference> Bases;
+    for (const auto BaseSpecifier : Decl->bases()) {
+      // skip classes not inherited as public
+      if (BaseSpecifier.getAccessSpecifier() != AccessSpecifier::AS_public)
+        continue;
+      SymbolReference BaseClass;
+      if (BaseSpecifier.getType().getTypePtr()->isTemplateTypeParmType()) {
+        BaseClass.Name = API.copyString(BaseSpecifier.getType().getAsString());
+        BaseClass.USR = API.recordUSR(
+            BaseSpecifier.getType()->getAs<TemplateTypeParmType>()->getDecl());
+      } else {
+        CXXRecordDecl *BaseClassDecl =
+            BaseSpecifier.getType().getTypePtr()->getAsCXXRecordDecl();
+        BaseClass.Name = BaseClassDecl->getName();
+        BaseClass.USR = API.recordUSR(BaseClassDecl);
+      }
+      Bases.emplace_back(BaseClass);
+    }
+    return Bases;
+  }
 };
 
 template <typename T>
@@ -328,6 +366,22 @@ bool ExtractAPIVisitorBase<Derived>::WalkUpFromCXXRecordDecl(
   return true;
 }
 
+template <typename Derived>
+bool ExtractAPIVisitorBase<Derived>::WalkUpFromClassTemplateSpecializationDecl(
+    const ClassTemplateSpecializationDecl *Decl) {
+  getDerivedExtractAPIVisitor().VisitClassTemplateSpecializationDecl(Decl);
+  return true;
+}
+
+template <typename Derived>
+bool ExtractAPIVisitorBase<Derived>::
+    WalkUpFromClassTemplatePartialSpecializationDecl(
+        const ClassTemplatePartialSpecializationDecl *Decl) {
+  getDerivedExtractAPIVisitor().VisitClassTemplatePartialSpecializationDecl(
+      Decl);
+  return true;
+}
+
 template <typename Derived>
 bool ExtractAPIVisitorBase<Derived>::VisitRecordDecl(const RecordDecl *Decl) {
   if (!getDerivedExtractAPIVisitor().shouldDeclBeIncluded(Decl))
@@ -368,7 +422,8 @@ bool ExtractAPIVisitorBase<Derived>::VisitRecordDecl(const RecordDecl *Decl) {
 template <typename Derived>
 bool ExtractAPIVisitorBase<Derived>::VisitCXXRecordDecl(
     const CXXRecordDecl *Decl) {
-  if (!getDerivedExtractAPIVisitor().shouldDeclBeIncluded(Decl))
+  if (!getDerivedExtractAPIVisitor().shouldDeclBeIncluded(Decl) ||
+      Decl->isImplicit())
     return true;
 
   StringRef Name = Decl->getName();
@@ -393,22 +448,22 @@ bool ExtractAPIVisitorBase<Derived>::VisitCXXRecordDecl(
   else
     Kind = APIRecord::RecordKind::RK_CXXClass;
 
-  CXXClassRecord *CXXClassRecord =
-      API.addCXXClass(Name, USR, Loc, AvailabilitySet(Decl), Comment,
-                      Declaration, SubHeading, Kind, isInSystemHeader(Decl));
+  CXXClassRecord *CXXClassRecord;
+  if (Decl->getDescribedClassTemplate()) {
+    // Inject template fragments before class fragments.
+    Declaration.insert(
+        Declaration.begin(),
+        DeclarationFragmentsBuilder::getFragmentsForRedeclarableTemplate(
+            Decl->getDescribedClassTemplate()));
+    CXXClassRecord = API.addClassTemplate(
+        Name, USR, Loc, AvailabilitySet(Decl), Comment, Declaration, SubHeading,
+        Template(Decl->getDescribedClassTemplate()), isInSystemHeader(Decl));
+  } else
+    CXXClassRecord =
+        API.addCXXClass(Name, USR, Loc, AvailabilitySet(Decl), Comment,
+                        Declaration, SubHeading, Kind, isInSystemHeader(Decl));
 
-  // FIXME: store AccessSpecifier given by inheritance
-  for (const auto BaseSpecifier : Decl->bases()) {
-    // skip classes not inherited as public
-    if (BaseSpecifier.getAccessSpecifier() != AccessSpecifier::AS_public)
-      continue;
-    SymbolReference BaseClass;
-    CXXRecordDecl *BaseClassDecl =
-        BaseSpecifier.getType().getTypePtr()->getAsCXXRecordDecl();
-    BaseClass.Name = BaseClassDecl->getName();
-    BaseClass.USR = API.recordUSR(BaseClassDecl);
-    CXXClassRecord->Bases.emplace_back(BaseClass);
-  }
+  CXXClassRecord->Bases = getBases(Decl);
 
   getDerivedExtractAPIVisitor().recordCXXFields(CXXClassRecord, Decl->fields());
   getDerivedExtractAPIVisitor().recordCXXMethods(CXXClassRecord,
@@ -416,6 +471,97 @@ bool ExtractAPIVisitorBase<Derived>::VisitCXXRecordDecl(
   return true;
 }
 
+template <typename Derived>
+bool ExtractAPIVisitorBase<Derived>::VisitConceptDecl(const ConceptDecl *Decl) {
+  if (!getDerivedExtractAPIVisitor().shouldDeclBeIncluded(Decl))
+    return true;
+
+  StringRef Name = Decl->getName();
+  StringRef USR = API.recordUSR(Decl);
+  PresumedLoc Loc =
+      Context.getSourceManager().getPresumedLoc(Decl->getLocation());
+  DocComment Comment;
+  if (auto *RawComment =
+          getDerivedExtractAPIVisitor().fetchRawCommentForDecl(Decl))
+    Comment = RawComment->getFormattedLines(Context.getSourceManager(),
+                                            Context.getDiagnostics());
+  DeclarationFragments Declaration =
+      DeclarationFragmentsBuilder::getFragmentsForConcept(Decl);
+  DeclarationFragments SubHeading =
+      DeclarationFragmentsBuilder::getSubHeading(Decl);
+  API.addConcept(Name, USR, Loc, AvailabilitySet(Decl), Comment, Declaration,
+                 SubHeading, Template(Decl), isInSystemHeader(Decl));
+  return true;
+}
+
+template <typename Derived>
+bool ExtractAPIVisitorBase<Derived>::VisitClassTemplateSpecializationDecl(
+    const ClassTemplateSpecializationDecl *Decl) {
+  if (!getDerivedExtractAPIVisitor().shouldDeclBeIncluded(Decl))
+    return true;
+
+  StringRef Name = Decl->getName();
+  StringRef USR = API.recordUSR(Decl);
+  PresumedLoc Loc =
+      Context.getSourceManager().getPresumedLoc(Decl->getLocation());
+  DocComment Comment;
+  if (auto *RawComment =
+          getDerivedExtractAPIVisitor().fetchRawCommentForDecl(Decl))
+    Comment = RawComment->getFormattedLines(Context.getSourceManager(),
+                                            Context.getDiagnostics());
+  DeclarationFragments Declaration =
+      DeclarationFragmentsBuilder::getFragmentsForClassTemplateSpecialization(
+          Decl);
+  DeclarationFragments SubHeading =
+      DeclarationFragmentsBuilder::getSubHeading(Decl);
+
+  auto *ClassTemplateSpecializationRecord = API.addClassTemplateSpecialization(
+      Name, USR, Loc, AvailabilitySet(Decl), Comment, Declaration, SubHeading,
+      isInSystemHeader(Decl));
+
+  ClassTemplateSpecializationRecord->Bases = getBases(Decl);
+  getDerivedExtractAPIVisitor().recordCXXFields(
+      ClassTemplateSpecializationRecord, Decl->fields());
+  getDerivedExtractAPIVisitor().recordCXXMethods(
+      ClassTemplateSpecializationRecord, Decl->methods());
+  return true;
+}
+
+template <typename Derived>
+bool ExtractAPIVisitorBase<Derived>::
+    VisitClassTemplatePartialSpecializationDecl(
+        const ClassTemplatePartialSpecializationDecl *Decl) {
+  if (!getDerivedExtractAPIVisitor().shouldDeclBeIncluded(Decl))
+    return true;
+
+  StringRef Name = Decl->getName();
+  StringRef USR = API.recordUSR(Decl);
+  PresumedLoc Loc =
+      Context.getSourceManager().getPresumedLoc(Decl->getLocation());
+  DocComment Comment;
+  if (auto *RawComment =
+          getDerivedExtractAPIVisitor().fetchRawCommentForDecl(Decl))
+    Comment = RawComment->getFormattedLines(Context.getSourceManager(),
+                                            Context.getDiagnostics());
+  DeclarationFragments Declaration = DeclarationFragmentsBuilder::
+      getFragmentsForClassTemplatePartialSpecialization(Decl);
+  DeclarationFragments SubHeading =
+      DeclarationFragmentsBuilder::getSubHeading(Decl);
+
+  auto *ClassTemplatePartialSpecRecord =
+      API.addClassTemplatePartialSpecialization(
+          Name, USR, Loc, AvailabilitySet(Decl), Comment, Declaration,
+          SubHeading, Template(Decl), isInSystemHeader(Decl));
+
+  ClassTemplatePartialSpecRecord->Bases = getBases(Decl);
+
+  getDerivedExtractAPIVisitor().recordCXXFields(ClassTemplatePartialSpecRecord,
+                                                Decl->fields());
+  getDerivedExtractAPIVisitor().recordCXXMethods(ClassTemplatePartialSpecRecord,
+                                                 Decl->methods());
+  return true;
+}
+
 template <typename Derived>
 bool ExtractAPIVisitorBase<Derived>::VisitObjCInterfaceDecl(
     const ObjCInterfaceDecl *Decl) {

diff  --git a/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h b/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h
index 68c20b3f6c6f9d..2bcd480b60e1c9 100644
--- a/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h
+++ b/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h
@@ -33,6 +33,14 @@ template <typename Derived> class APISetVisitor {
 
     getDerived()->traverseCXXClassRecords();
 
+    getDerived()->traverseClassTemplateRecords();
+
+    getDerived()->traverseClassTemplateSpecializationRecords();
+
+    getDerived()->traverseClassTemplatePartialSpecializationRecords();
+
+    getDerived()->traverseConcepts();
+
     getDerived()->traverseStructRecords();
 
     getDerived()->traverseObjCInterfaces();
@@ -76,6 +84,30 @@ template <typename Derived> class APISetVisitor {
       getDerived()->visitCXXClassRecord(*Class.second);
   }
 
+  void traverseClassTemplateRecords() {
+    for (const auto &ClassTemplate : API.getClassTemplates())
+      getDerived()->visitClassTemplateRecord(*ClassTemplate.second);
+  }
+
+  void traverseClassTemplateSpecializationRecords() {
+    for (const auto &ClassTemplateSpecialization :
+         API.getClassTemplateSpecializations())
+      getDerived()->visitClassTemplateSpecializationRecord(
+          *ClassTemplateSpecialization.second);
+  }
+
+  void traverseClassTemplatePartialSpecializationRecords() {
+    for (const auto &ClassTemplatePartialSpecialization :
+         API.getClassTemplatePartialSpecializations())
+      getDerived()->visitClassTemplatePartialSpecializationRecord(
+          *ClassTemplatePartialSpecialization.second);
+  }
+
+  void traverseConcepts() {
+    for (const auto &Concept : API.getConcepts())
+      getDerived()->visitConceptRecord(*Concept.second);
+  }
+
   void traverseObjCInterfaces() {
     for (const auto &Interface : API.getObjCInterfaces())
       getDerived()->visitObjCContainerRecord(*Interface.second);
@@ -117,6 +149,14 @@ template <typename Derived> class APISetVisitor {
 
   void visitCXXClassRecord(const CXXClassRecord &Record){};
 
+  void visitClassTemplateRecord(const ClassTemplateRecord &Record){};
+
+  void visitClassTemplateSpecializationRecord(
+      const ClassTemplateSpecializationRecord &Record){};
+
+  void visitClassTemplatePartialSpecializationRecord(
+      const ClassTemplatePartialSpecializationRecord &Record){};
+
   /// Visit an Objective-C container record.
   void visitObjCContainerRecord(const ObjCContainerRecord &Record){};
 

diff  --git a/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h b/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h
index ab60b5c71f2289..7b315c9f6d410e 100644
--- a/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h
+++ b/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h
@@ -97,6 +97,10 @@ class SymbolGraphSerializer : public APISetVisitor<SymbolGraphSerializer> {
   /// Get the string representation of the relationship kind.
   static StringRef getRelationshipString(RelationshipKind Kind);
 
+  enum ConstraintKind { Conformance, ConditionalConformance };
+
+  static StringRef getConstraintString(ConstraintKind Kind);
+
 private:
   /// Just serialize the currently recorded objects in Symbol Graph format.
   Object serializeCurrentGraph();
@@ -171,6 +175,16 @@ class SymbolGraphSerializer : public APISetVisitor<SymbolGraphSerializer> {
 
   void visitCXXClassRecord(const CXXClassRecord &Record);
 
+  void visitClassTemplateRecord(const ClassTemplateRecord &Record);
+
+  void visitClassTemplateSpecializationRecord(
+      const ClassTemplateSpecializationRecord &Record);
+
+  void visitClassTemplatePartialSpecializationRecord(
+      const ClassTemplatePartialSpecializationRecord &Record);
+
+  void visitConceptRecord(const ConceptRecord &Record);
+
   /// Visit an Objective-C container record.
   void visitObjCContainerRecord(const ObjCContainerRecord &Record);
 

diff  --git a/clang/lib/ExtractAPI/API.cpp b/clang/lib/ExtractAPI/API.cpp
index d9baa188f1be5b..c1c4bc7a0b4cf7 100644
--- a/clang/lib/ExtractAPI/API.cpp
+++ b/clang/lib/ExtractAPI/API.cpp
@@ -159,6 +159,50 @@ APISet::addCXXClass(StringRef Name, StringRef USR, PresumedLoc Loc,
                            SubHeading, Kind, IsFromSystemHeader);
 }
 
+ClassTemplateRecord *APISet::addClassTemplate(
+    StringRef Name, StringRef USR, PresumedLoc Loc,
+    AvailabilitySet Availability, const DocComment &Comment,
+    DeclarationFragments Declaration, DeclarationFragments SubHeading,
+    Template Template, bool IsFromSystemHeader) {
+
+  return addTopLevelRecord(USRBasedLookupTable, ClassTemplates, USR, Name, Loc,
+                           std::move(Availability), Comment, Declaration,
+                           SubHeading, Template, IsFromSystemHeader);
+}
+
+ClassTemplateSpecializationRecord *APISet::addClassTemplateSpecialization(
+    StringRef Name, StringRef USR, PresumedLoc Loc,
+    AvailabilitySet Availability, const DocComment &Comment,
+    DeclarationFragments Declaration, DeclarationFragments SubHeading,
+    bool IsFromSystemHeader) {
+  return addTopLevelRecord(USRBasedLookupTable, ClassTemplateSpecializations,
+                           USR, Name, Loc, std::move(Availability), Comment,
+                           Declaration, SubHeading, IsFromSystemHeader);
+}
+
+ClassTemplatePartialSpecializationRecord *
+APISet::addClassTemplatePartialSpecialization(
+    StringRef Name, StringRef USR, PresumedLoc Loc,
+    AvailabilitySet Availability, const DocComment &Comment,
+    DeclarationFragments Declaration, DeclarationFragments SubHeading,
+    Template Template, bool IsFromSystemHeader) {
+  return addTopLevelRecord(USRBasedLookupTable,
+                           ClassTemplatePartialSpecializations, USR, Name, Loc,
+                           std::move(Availability), Comment, Declaration,
+                           SubHeading, Template, IsFromSystemHeader);
+}
+
+ConceptRecord *APISet::addConcept(StringRef Name, StringRef USR,
+                                  PresumedLoc Loc, AvailabilitySet Availability,
+                                  const DocComment &Comment,
+                                  DeclarationFragments Declaration,
+                                  DeclarationFragments SubHeading,
+                                  Template Template, bool IsFromSystemHeader) {
+  return addTopLevelRecord(USRBasedLookupTable, Concepts, USR, Name, Loc,
+                           std::move(Availability), Comment, Declaration,
+                           SubHeading, Template, IsFromSystemHeader);
+}
+
 CXXMethodRecord *APISet::addCXXMethod(
     CXXClassRecord *CXXClassRecord, StringRef Name, StringRef USR,
     PresumedLoc Loc, AvailabilitySet Availability, const DocComment &Comment,

diff  --git a/clang/lib/ExtractAPI/DeclarationFragments.cpp b/clang/lib/ExtractAPI/DeclarationFragments.cpp
index 703142a910764c..ec8ae56f71e161 100644
--- a/clang/lib/ExtractAPI/DeclarationFragments.cpp
+++ b/clang/lib/ExtractAPI/DeclarationFragments.cpp
@@ -735,6 +735,174 @@ DeclarationFragmentsBuilder::getFragmentsForOverloadedOperator(
   return Fragments.append(";", DeclarationFragments::FragmentKind::Text);
 }
 
+// Get fragments for template parameters, e.g. T in tempalte<typename T> ...
+DeclarationFragments
+DeclarationFragmentsBuilder::getFragmentsForTemplateParameters(
+    ArrayRef<NamedDecl *> ParameterArray) {
+  DeclarationFragments Fragments;
+  for (unsigned i = 0, end = ParameterArray.size(); i != end; ++i) {
+    if (i)
+      Fragments.append(",", DeclarationFragments::FragmentKind::Text)
+          .appendSpace();
+
+    const auto *TemplateParam =
+        dyn_cast<TemplateTypeParmDecl>(ParameterArray[i]);
+    if (!TemplateParam)
+      continue;
+    if (TemplateParam->hasTypeConstraint())
+      Fragments.append(TemplateParam->getTypeConstraint()
+                           ->getNamedConcept()
+                           ->getName()
+                           .str(),
+                       DeclarationFragments::FragmentKind::TypeIdentifier);
+    else if (TemplateParam->wasDeclaredWithTypename())
+      Fragments.append("typename", DeclarationFragments::FragmentKind::Keyword);
+    else
+      Fragments.append("class", DeclarationFragments::FragmentKind::Keyword);
+
+    if (TemplateParam->isParameterPack())
+      Fragments.append("...", DeclarationFragments::FragmentKind::Text);
+
+    Fragments.appendSpace().append(
+        TemplateParam->getName(),
+        DeclarationFragments::FragmentKind::GenericParameter);
+  }
+  return Fragments;
+}
+
+// Find the name of a template argument from the template's parameters.
+std::string DeclarationFragmentsBuilder::getNameForTemplateArgument(
+    const ArrayRef<NamedDecl *> TemplateParameters, std::string TypeParameter) {
+  // The arg is a generic parameter from a partial spec, e.g.
+  // T in template<typename T> Foo<T, int>.
+  //
+  // Those names appear as "type-parameter-<index>-<depth>", so we must find its
+  // name from the template's parameter list.
+  for (unsigned i = 0; i < TemplateParameters.size(); ++i) {
+    const auto *Parameter =
+        dyn_cast<TemplateTypeParmDecl>(TemplateParameters[i]);
+    if (TypeParameter.compare("type-parameter-" +
+                              std::to_string(Parameter->getDepth()) + "-" +
+                              std::to_string(Parameter->getIndex())) == 0)
+      return std::string(TemplateParameters[i]->getName());
+  }
+  llvm_unreachable("Could not find the name of a template argument.");
+}
+
+// Get fragments for template arguments, e.g. int in template<typename T>
+// Foo<int>;
+//
+// Note: TemplateParameters is only necessary if the Decl is a
+// PartialSpecialization, where we need the parameters to deduce the name of the
+// generic arguments.
+DeclarationFragments
+DeclarationFragmentsBuilder::getFragmentsForTemplateArguments(
+    const ArrayRef<TemplateArgument> TemplateArguments, ASTContext &Context,
+    const std::optional<ArrayRef<NamedDecl *>> TemplateParameters) {
+  DeclarationFragments Fragments;
+  for (unsigned i = 0, end = TemplateArguments.size(); i != end; ++i) {
+    if (i)
+      Fragments.append(",", DeclarationFragments::FragmentKind::Text)
+          .appendSpace();
+
+    std::string Type = TemplateArguments[i].getAsType().getAsString();
+    DeclarationFragments After;
+    DeclarationFragments ArgumentFragment =
+        getFragmentsForType(TemplateArguments[i].getAsType(), Context, After);
+
+    if (ArgumentFragment.begin()->Spelling.substr(0, 14).compare(
+            "type-parameter") == 0) {
+      std::string ProperArgName = getNameForTemplateArgument(
+          TemplateParameters.value(), ArgumentFragment.begin()->Spelling);
+      ArgumentFragment.begin()->Spelling.swap(ProperArgName);
+    }
+    Fragments.append(std::move(ArgumentFragment));
+
+    if (TemplateArguments[i].isPackExpansion())
+      Fragments.append("...", DeclarationFragments::FragmentKind::Text);
+  }
+  return Fragments;
+}
+
+DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForConcept(
+    const ConceptDecl *Concept) {
+  DeclarationFragments Fragments;
+  return Fragments
+      .append("template", DeclarationFragments::FragmentKind::Keyword)
+      .append("<", DeclarationFragments::FragmentKind::Text)
+      .append(getFragmentsForTemplateParameters(
+          Concept->getTemplateParameters()->asArray()))
+      .append("> ", DeclarationFragments::FragmentKind::Text)
+      .append("concept", DeclarationFragments::FragmentKind::Keyword)
+      .appendSpace()
+      .append(Concept->getName().str(),
+              DeclarationFragments::FragmentKind::Identifier)
+      .append(";", DeclarationFragments::FragmentKind::Text);
+}
+
+DeclarationFragments
+DeclarationFragmentsBuilder::getFragmentsForRedeclarableTemplate(
+    const RedeclarableTemplateDecl *RedeclarableTemplate) {
+  DeclarationFragments Fragments;
+  Fragments.append("template", DeclarationFragments::FragmentKind::Keyword)
+      .append("<", DeclarationFragments::FragmentKind::Text)
+      .append(getFragmentsForTemplateParameters(
+          RedeclarableTemplate->getTemplateParameters()->asArray()))
+      .append(">", DeclarationFragments::FragmentKind::Text)
+      .appendSpace();
+
+  if (isa<TypeAliasTemplateDecl>(RedeclarableTemplate))
+    Fragments.appendSpace()
+        .append("using", DeclarationFragments::FragmentKind::Keyword)
+        .appendSpace()
+        .append(RedeclarableTemplate->getName(),
+                DeclarationFragments::FragmentKind::Identifier);
+  // the templated records will be resposbible for injecting their templates
+  return Fragments.appendSpace();
+}
+
+DeclarationFragments
+DeclarationFragmentsBuilder::getFragmentsForClassTemplateSpecialization(
+    const ClassTemplateSpecializationDecl *Decl) {
+  DeclarationFragments Fragments;
+  return Fragments
+      .append("template", DeclarationFragments::FragmentKind::Keyword)
+      .append("<", DeclarationFragments::FragmentKind::Text)
+      .append(">", DeclarationFragments::FragmentKind::Text)
+      .appendSpace()
+      .append(DeclarationFragmentsBuilder::getFragmentsForCXXClass(
+          cast<CXXRecordDecl>(Decl)))
+      .pop_back() // there is an extra semicolon now
+      .append("<", DeclarationFragments::FragmentKind::Text)
+      .append(
+          getFragmentsForTemplateArguments(Decl->getTemplateArgs().asArray(),
+                                           Decl->getASTContext(), std::nullopt))
+      .append(">", DeclarationFragments::FragmentKind::Text)
+      .append(";", DeclarationFragments::FragmentKind::Text);
+}
+
+DeclarationFragments
+DeclarationFragmentsBuilder::getFragmentsForClassTemplatePartialSpecialization(
+    const ClassTemplatePartialSpecializationDecl *Decl) {
+  DeclarationFragments Fragments;
+  return Fragments
+      .append("template", DeclarationFragments::FragmentKind::Keyword)
+      .append("<", DeclarationFragments::FragmentKind::Text)
+      .append(getFragmentsForTemplateParameters(
+          Decl->getTemplateParameters()->asArray()))
+      .append(">", DeclarationFragments::FragmentKind::Text)
+      .appendSpace()
+      .append(DeclarationFragmentsBuilder::getFragmentsForCXXClass(
+          cast<CXXRecordDecl>(Decl)))
+      .pop_back() // there is an extra semicolon now
+      .append("<", DeclarationFragments::FragmentKind::Text)
+      .append(getFragmentsForTemplateArguments(
+          Decl->getTemplateArgs().asArray(), Decl->getASTContext(),
+          Decl->getTemplateParameters()->asArray()))
+      .append(">", DeclarationFragments::FragmentKind::Text)
+      .append(";", DeclarationFragments::FragmentKind::Text);
+}
+
 DeclarationFragments
 DeclarationFragmentsBuilder::getFragmentsForMacro(StringRef Name,
                                                   const MacroDirective *MD) {

diff  --git a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
index af52abe9f2e144..6c7a037f76e348 100644
--- a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
+++ b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
@@ -393,10 +393,17 @@ Object serializeSymbolKind(APIRecord::RecordKind RK, Language Lang) {
     Kind["identifier"] = AddLangPrefix("type.property");
     Kind["displayName"] = "Type Property";
     break;
+  case APIRecord::RK_ClassTemplate:
+  case APIRecord::RK_ClassTemplateSpecialization:
+  case APIRecord::RK_ClassTemplatePartialSpecialization:
   case APIRecord::RK_CXXClass:
     Kind["identifier"] = AddLangPrefix("class");
     Kind["displayName"] = "Class";
     break;
+  case APIRecord::RK_Concept:
+    Kind["identifier"] = AddLangPrefix("concept");
+    Kind["displayName"] = "Concept";
+    break;
   case APIRecord::RK_CXXStaticMethod:
     Kind["identifier"] = AddLangPrefix("type.method");
     Kind["displayName"] = "Static Method";
@@ -545,6 +552,52 @@ void serializeAccessMixin(Object &Paren, const RecordTy &Record) {
   serializeString(Paren, "accessLevel", accessLevel);
 }
 
+template <typename RecordTy>
+std::optional<Object> serializeTemplateMixinImpl(const RecordTy &Record,
+                                                 std::true_type) {
+  const auto &Template = Record.Templ;
+  if (Template.empty())
+    return std::nullopt;
+
+  Object Generics;
+  Array GenericParameters;
+  for (const auto Param : Template.getParameters()) {
+    Object Parameter;
+    Parameter["name"] = Param.Name;
+    Parameter["index"] = Param.Index;
+    Parameter["depth"] = Param.Depth;
+    GenericParameters.emplace_back(std::move(Parameter));
+  }
+  if (!GenericParameters.empty())
+    Generics["parameters"] = std::move(GenericParameters);
+
+  Array GenericConstraints;
+  for (const auto Constr : Template.getConstraints()) {
+    Object Constraint;
+    Constraint["kind"] = Constr.Kind;
+    Constraint["lhs"] = Constr.LHS;
+    Constraint["rhs"] = Constr.RHS;
+    GenericConstraints.emplace_back(std::move(Constraint));
+  }
+
+  if (!GenericConstraints.empty())
+    Generics["constraints"] = std::move(GenericConstraints);
+
+  return Generics;
+}
+
+template <typename RecordTy>
+std::optional<Object> serializeTemplateMixinImpl(const RecordTy &Record,
+                                                 std::false_type) {
+  return std::nullopt;
+}
+
+template <typename RecordTy>
+void serializeTemplateMixin(Object &Paren, const RecordTy &Record) {
+  serializeObject(Paren, "swiftGenerics",
+                  serializeTemplateMixinImpl(Record, has_template<RecordTy>()));
+}
+
 struct PathComponent {
   StringRef USR;
   StringRef Name;
@@ -691,6 +744,7 @@ SymbolGraphSerializer::serializeAPIRecord(const RecordTy &Record) const {
 
   serializeFunctionSignatureMixin(Obj, Record);
   serializeAccessMixin(Obj, Record);
+  serializeTemplateMixin(Obj, Record);
 
   return Obj;
 }
@@ -726,6 +780,16 @@ StringRef SymbolGraphSerializer::getRelationshipString(RelationshipKind Kind) {
   llvm_unreachable("Unhandled relationship kind");
 }
 
+StringRef SymbolGraphSerializer::getConstraintString(ConstraintKind Kind) {
+  switch (Kind) {
+  case ConstraintKind::Conformance:
+    return "conformance";
+  case ConstraintKind::ConditionalConformance:
+    return "conditionalConformance";
+  }
+  llvm_unreachable("Unhandled constraint kind");
+}
+
 void SymbolGraphSerializer::serializeRelationship(RelationshipKind Kind,
                                                   SymbolReference Source,
                                                   SymbolReference Target) {
@@ -796,6 +860,56 @@ void SymbolGraphSerializer::visitCXXClassRecord(const CXXClassRecord &Record) {
     serializeRelationship(RelationshipKind::InheritsFrom, Record, Base);
 }
 
+void SymbolGraphSerializer::visitClassTemplateRecord(
+    const ClassTemplateRecord &Record) {
+  auto Class = serializeAPIRecord(Record);
+  if (!Class)
+    return;
+
+  Symbols.emplace_back(std::move(*Class));
+  serializeMembers(Record, Record.Fields);
+  serializeMembers(Record, Record.Methods);
+
+  for (const auto Base : Record.Bases)
+    serializeRelationship(RelationshipKind::InheritsFrom, Record, Base);
+}
+
+void SymbolGraphSerializer::visitClassTemplateSpecializationRecord(
+    const ClassTemplateSpecializationRecord &Record) {
+  auto Class = serializeAPIRecord(Record);
+  if (!Class)
+    return;
+
+  Symbols.emplace_back(std::move(*Class));
+  serializeMembers(Record, Record.Fields);
+  serializeMembers(Record, Record.Methods);
+
+  for (const auto Base : Record.Bases)
+    serializeRelationship(RelationshipKind::InheritsFrom, Record, Base);
+}
+
+void SymbolGraphSerializer::visitClassTemplatePartialSpecializationRecord(
+    const ClassTemplatePartialSpecializationRecord &Record) {
+  auto Class = serializeAPIRecord(Record);
+  if (!Class)
+    return;
+
+  Symbols.emplace_back(std::move(*Class));
+  serializeMembers(Record, Record.Fields);
+  serializeMembers(Record, Record.Methods);
+
+  for (const auto Base : Record.Bases)
+    serializeRelationship(RelationshipKind::InheritsFrom, Record, Base);
+}
+
+void SymbolGraphSerializer::visitConceptRecord(const ConceptRecord &Record) {
+  auto Concept = serializeAPIRecord(Record);
+  if (!Concept)
+    return;
+
+  Symbols.emplace_back(std::move(*Concept));
+}
+
 void SymbolGraphSerializer::visitObjCContainerRecord(
     const ObjCContainerRecord &Record) {
   auto ObjCContainer = serializeAPIRecord(Record);

diff  --git a/clang/test/ExtractAPI/class_template.cpp b/clang/test/ExtractAPI/class_template.cpp
new file mode 100644
index 00000000000000..25895a6fefe06d
--- /dev/null
+++ b/clang/test/ExtractAPI/class_template.cpp
@@ -0,0 +1,133 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: sed -e "s at INPUT_DIR@%{/t:regex_replacement}@g" \
+// RUN: %t/reference.output.json.in >> %t/reference.output.json
+// RUN: %clang_cc1 -extract-api -triple arm64-apple-macosx \
+// RUN:   -x c++-header %t/input.h -o %t/output.json -verify
+
+// Generator version is not consistent across test runs, normalize it.
+// RUN: sed -e "s@\"generator\": \".*\"@\"generator\": \"?\"@g" \
+// RUN: %t/output.json >> %t/output-normalized.json
+// RUN: 
diff  %t/reference.output.json %t/output-normalized.json
+
+//--- input.h
+template<typename T> class Foo {};
+
+/// expected-no-diagnostics
+
+//--- reference.output.json.in
+{
+  "metadata": {
+    "formatVersion": {
+      "major": 0,
+      "minor": 5,
+      "patch": 3
+    },
+    "generator": "?"
+  },
+  "module": {
+    "name": "",
+    "platform": {
+      "architecture": "arm64",
+      "operatingSystem": {
+        "minimumVersion": {
+          "major": 11,
+          "minor": 0,
+          "patch": 0
+        },
+        "name": "macosx"
+      },
+      "vendor": "apple"
+    }
+  },
+  "relationships": [],
+  "symbols": [
+    {
+      "accessLevel": "public",
+      "declarationFragments": [
+        {
+          "kind": "keyword",
+          "spelling": "template"
+        },
+        {
+          "kind": "text",
+          "spelling": "<"
+        },
+        {
+          "kind": "keyword",
+          "spelling": "typename"
+        },
+        {
+          "kind": "text",
+          "spelling": " "
+        },
+        {
+          "kind": "genericParameter",
+          "spelling": "T"
+        },
+        {
+          "kind": "text",
+          "spelling": "> "
+        },
+        {
+          "kind": "keyword",
+          "spelling": "class"
+        },
+        {
+          "kind": "text",
+          "spelling": " "
+        },
+        {
+          "kind": "identifier",
+          "spelling": "Foo"
+        },
+        {
+          "kind": "text",
+          "spelling": ";"
+        }
+      ],
+      "identifier": {
+        "interfaceLanguage": "c++",
+        "precise": "c:@ST>1#T at Foo"
+      },
+      "kind": {
+        "displayName": "Class",
+        "identifier": "c++.class"
+      },
+      "location": {
+        "position": {
+          "character": 28,
+          "line": 1
+        },
+        "uri": "file://INPUT_DIR/input.h"
+      },
+      "names": {
+        "navigator": [
+          {
+            "kind": "identifier",
+            "spelling": "Foo"
+          }
+        ],
+        "subHeading": [
+          {
+            "kind": "identifier",
+            "spelling": "Foo"
+          }
+        ],
+        "title": "Foo"
+      },
+      "pathComponents": [
+        "Foo"
+      ],
+      "swiftGenerics": {
+        "parameters": [
+          {
+            "depth": 0,
+            "index": 0,
+            "name": "T"
+          }
+        ]
+      }
+    }
+  ]
+}

diff  --git a/clang/test/ExtractAPI/class_template_param_inheritance.cpp b/clang/test/ExtractAPI/class_template_param_inheritance.cpp
new file mode 100644
index 00000000000000..0e50e6081c914d
--- /dev/null
+++ b/clang/test/ExtractAPI/class_template_param_inheritance.cpp
@@ -0,0 +1,140 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: sed -e "s at INPUT_DIR@%{/t:regex_replacement}@g" \
+// RUN: %t/reference.output.json.in >> %t/reference.output.json
+// RUN: %clang_cc1 -extract-api -triple arm64-apple-macosx \
+// RUN:   -x c++-header %t/input.h -o %t/output.json -verify
+
+// Generator version is not consistent across test runs, normalize it.
+// RUN: sed -e "s@\"generator\": \".*\"@\"generator\": \"?\"@g" \
+// RUN: %t/output.json >> %t/output-normalized.json
+// RUN: 
diff  %t/reference.output.json %t/output-normalized.json
+
+//--- input.h
+template<typename T> class Foo : public T {};
+
+/// expected-no-diagnostics
+
+//--- reference.output.json.in
+{
+  "metadata": {
+    "formatVersion": {
+      "major": 0,
+      "minor": 5,
+      "patch": 3
+    },
+    "generator": "?"
+  },
+  "module": {
+    "name": "",
+    "platform": {
+      "architecture": "arm64",
+      "operatingSystem": {
+        "minimumVersion": {
+          "major": 11,
+          "minor": 0,
+          "patch": 0
+        },
+        "name": "macosx"
+      },
+      "vendor": "apple"
+    }
+  },
+  "relationships": [
+    {
+      "kind": "inheritsFrom",
+      "source": "c:@ST>1#T at Foo",
+      "target": "",
+      "targetFallback": "T"
+    }
+  ],
+  "symbols": [
+    {
+      "accessLevel": "public",
+      "declarationFragments": [
+        {
+          "kind": "keyword",
+          "spelling": "template"
+        },
+        {
+          "kind": "text",
+          "spelling": "<"
+        },
+        {
+          "kind": "keyword",
+          "spelling": "typename"
+        },
+        {
+          "kind": "text",
+          "spelling": " "
+        },
+        {
+          "kind": "genericParameter",
+          "spelling": "T"
+        },
+        {
+          "kind": "text",
+          "spelling": "> "
+        },
+        {
+          "kind": "keyword",
+          "spelling": "class"
+        },
+        {
+          "kind": "text",
+          "spelling": " "
+        },
+        {
+          "kind": "identifier",
+          "spelling": "Foo"
+        },
+        {
+          "kind": "text",
+          "spelling": ";"
+        }
+      ],
+      "identifier": {
+        "interfaceLanguage": "c++",
+        "precise": "c:@ST>1#T at Foo"
+      },
+      "kind": {
+        "displayName": "Class",
+        "identifier": "c++.class"
+      },
+      "location": {
+        "position": {
+          "character": 28,
+          "line": 1
+        },
+        "uri": "file://INPUT_DIR/input.h"
+      },
+      "names": {
+        "navigator": [
+          {
+            "kind": "identifier",
+            "spelling": "Foo"
+          }
+        ],
+        "subHeading": [
+          {
+            "kind": "identifier",
+            "spelling": "Foo"
+          }
+        ],
+        "title": "Foo"
+      },
+      "pathComponents": [
+        "Foo"
+      ],
+      "swiftGenerics": {
+        "parameters": [
+          {
+            "depth": 0,
+            "index": 0,
+            "name": "T"
+          }
+        ]
+      }
+    }
+  ]
+}

diff  --git a/clang/test/ExtractAPI/class_template_partial_spec.cpp b/clang/test/ExtractAPI/class_template_partial_spec.cpp
new file mode 100644
index 00000000000000..294bbb726bcf08
--- /dev/null
+++ b/clang/test/ExtractAPI/class_template_partial_spec.cpp
@@ -0,0 +1,261 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: sed -e "s at INPUT_DIR@%{/t:regex_replacement}@g" \
+// RUN: %t/reference.output.json.in >> %t/reference.output.json
+// RUN: %clang_cc1 -extract-api -triple arm64-apple-macosx \
+// RUN:   -x c++-header %t/input.h -o %t/output.json -verify
+
+// Generator version is not consistent across test runs, normalize it.
+// RUN: sed -e "s@\"generator\": \".*\"@\"generator\": \"?\"@g" \
+// RUN: %t/output.json >> %t/output-normalized.json
+// RUN: 
diff  %t/reference.output.json %t/output-normalized.json
+
+//--- input.h
+template<typename X, typename Y> class Foo {};
+
+template<typename Z> class Foo<Z, int> {};
+
+/// expected-no-diagnostics
+
+//--- reference.output.json.in
+{
+  "metadata": {
+    "formatVersion": {
+      "major": 0,
+      "minor": 5,
+      "patch": 3
+    },
+    "generator": "?"
+  },
+  "module": {
+    "name": "",
+    "platform": {
+      "architecture": "arm64",
+      "operatingSystem": {
+        "minimumVersion": {
+          "major": 11,
+          "minor": 0,
+          "patch": 0
+        },
+        "name": "macosx"
+      },
+      "vendor": "apple"
+    }
+  },
+  "relationships": [],
+  "symbols": [
+    {
+      "accessLevel": "public",
+      "declarationFragments": [
+        {
+          "kind": "keyword",
+          "spelling": "template"
+        },
+        {
+          "kind": "text",
+          "spelling": "<"
+        },
+        {
+          "kind": "keyword",
+          "spelling": "typename"
+        },
+        {
+          "kind": "text",
+          "spelling": " "
+        },
+        {
+          "kind": "genericParameter",
+          "spelling": "X"
+        },
+        {
+          "kind": "text",
+          "spelling": ", "
+        },
+        {
+          "kind": "keyword",
+          "spelling": "typename"
+        },
+        {
+          "kind": "text",
+          "spelling": " "
+        },
+        {
+          "kind": "genericParameter",
+          "spelling": "Y"
+        },
+        {
+          "kind": "text",
+          "spelling": "> "
+        },
+        {
+          "kind": "keyword",
+          "spelling": "class"
+        },
+        {
+          "kind": "text",
+          "spelling": " "
+        },
+        {
+          "kind": "identifier",
+          "spelling": "Foo"
+        },
+        {
+          "kind": "text",
+          "spelling": ";"
+        }
+      ],
+      "identifier": {
+        "interfaceLanguage": "c++",
+        "precise": "c:@ST>2#T#T at Foo"
+      },
+      "kind": {
+        "displayName": "Class",
+        "identifier": "c++.class"
+      },
+      "location": {
+        "position": {
+          "character": 40,
+          "line": 1
+        },
+        "uri": "file://INPUT_DIR/input.h"
+      },
+      "names": {
+        "navigator": [
+          {
+            "kind": "identifier",
+            "spelling": "Foo"
+          }
+        ],
+        "subHeading": [
+          {
+            "kind": "identifier",
+            "spelling": "Foo"
+          }
+        ],
+        "title": "Foo"
+      },
+      "pathComponents": [
+        "Foo"
+      ],
+      "swiftGenerics": {
+        "parameters": [
+          {
+            "depth": 0,
+            "index": 0,
+            "name": "X"
+          },
+          {
+            "depth": 0,
+            "index": 1,
+            "name": "Y"
+          }
+        ]
+      }
+    },
+    {
+      "accessLevel": "public",
+      "declarationFragments": [
+        {
+          "kind": "keyword",
+          "spelling": "template"
+        },
+        {
+          "kind": "text",
+          "spelling": "<"
+        },
+        {
+          "kind": "keyword",
+          "spelling": "typename"
+        },
+        {
+          "kind": "text",
+          "spelling": " "
+        },
+        {
+          "kind": "genericParameter",
+          "spelling": "Z"
+        },
+        {
+          "kind": "text",
+          "spelling": "> "
+        },
+        {
+          "kind": "keyword",
+          "spelling": "class"
+        },
+        {
+          "kind": "text",
+          "spelling": " "
+        },
+        {
+          "kind": "identifier",
+          "spelling": "Foo"
+        },
+        {
+          "kind": "text",
+          "spelling": "<"
+        },
+        {
+          "kind": "typeIdentifier",
+          "preciseIdentifier": "c:t0.0",
+          "spelling": "Z"
+        },
+        {
+          "kind": "text",
+          "spelling": ", "
+        },
+        {
+          "kind": "typeIdentifier",
+          "preciseIdentifier": "c:I",
+          "spelling": "int"
+        },
+        {
+          "kind": "text",
+          "spelling": ">;"
+        }
+      ],
+      "identifier": {
+        "interfaceLanguage": "c++",
+        "precise": "c:@SP>1#T at Foo>#t0.0#I"
+      },
+      "kind": {
+        "displayName": "Class",
+        "identifier": "c++.class"
+      },
+      "location": {
+        "position": {
+          "character": 28,
+          "line": 3
+        },
+        "uri": "file://INPUT_DIR/input.h"
+      },
+      "names": {
+        "navigator": [
+          {
+            "kind": "identifier",
+            "spelling": "Foo"
+          }
+        ],
+        "subHeading": [
+          {
+            "kind": "identifier",
+            "spelling": "Foo"
+          }
+        ],
+        "title": "Foo"
+      },
+      "pathComponents": [
+        "Foo"
+      ],
+      "swiftGenerics": {
+        "parameters": [
+          {
+            "depth": 0,
+            "index": 0,
+            "name": "Z"
+          }
+        ]
+      }
+    }
+  ]
+}

diff  --git a/clang/test/ExtractAPI/class_template_spec.cpp b/clang/test/ExtractAPI/class_template_spec.cpp
new file mode 100644
index 00000000000000..166b03911db7dc
--- /dev/null
+++ b/clang/test/ExtractAPI/class_template_spec.cpp
@@ -0,0 +1,206 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: sed -e "s at INPUT_DIR@%{/t:regex_replacement}@g" \
+// RUN: %t/reference.output.json.in >> %t/reference.output.json
+// RUN: %clang_cc1 -extract-api -triple arm64-apple-macosx \
+// RUN:   -x c++-header %t/input.h -o %t/output.json -verify
+
+// Generator version is not consistent across test runs, normalize it.
+// RUN: sed -e "s@\"generator\": \".*\"@\"generator\": \"?\"@g" \
+// RUN: %t/output.json >> %t/output-normalized.json
+// RUN: 
diff  %t/reference.output.json %t/output-normalized.json
+
+//--- input.h
+template<typename T> class Foo {};
+
+template<> class Foo<int> {};
+
+/// expected-no-diagnostics
+
+//--- reference.output.json.in
+{
+  "metadata": {
+    "formatVersion": {
+      "major": 0,
+      "minor": 5,
+      "patch": 3
+    },
+    "generator": "?"
+  },
+  "module": {
+    "name": "",
+    "platform": {
+      "architecture": "arm64",
+      "operatingSystem": {
+        "minimumVersion": {
+          "major": 11,
+          "minor": 0,
+          "patch": 0
+        },
+        "name": "macosx"
+      },
+      "vendor": "apple"
+    }
+  },
+  "relationships": [],
+  "symbols": [
+    {
+      "accessLevel": "public",
+      "declarationFragments": [
+        {
+          "kind": "keyword",
+          "spelling": "template"
+        },
+        {
+          "kind": "text",
+          "spelling": "<"
+        },
+        {
+          "kind": "keyword",
+          "spelling": "typename"
+        },
+        {
+          "kind": "text",
+          "spelling": " "
+        },
+        {
+          "kind": "genericParameter",
+          "spelling": "T"
+        },
+        {
+          "kind": "text",
+          "spelling": "> "
+        },
+        {
+          "kind": "keyword",
+          "spelling": "class"
+        },
+        {
+          "kind": "text",
+          "spelling": " "
+        },
+        {
+          "kind": "identifier",
+          "spelling": "Foo"
+        },
+        {
+          "kind": "text",
+          "spelling": ";"
+        }
+      ],
+      "identifier": {
+        "interfaceLanguage": "c++",
+        "precise": "c:@ST>1#T at Foo"
+      },
+      "kind": {
+        "displayName": "Class",
+        "identifier": "c++.class"
+      },
+      "location": {
+        "position": {
+          "character": 28,
+          "line": 1
+        },
+        "uri": "file://INPUT_DIR/input.h"
+      },
+      "names": {
+        "navigator": [
+          {
+            "kind": "identifier",
+            "spelling": "Foo"
+          }
+        ],
+        "subHeading": [
+          {
+            "kind": "identifier",
+            "spelling": "Foo"
+          }
+        ],
+        "title": "Foo"
+      },
+      "pathComponents": [
+        "Foo"
+      ],
+      "swiftGenerics": {
+        "parameters": [
+          {
+            "depth": 0,
+            "index": 0,
+            "name": "T"
+          }
+        ]
+      }
+    },
+    {
+      "accessLevel": "public",
+      "declarationFragments": [
+        {
+          "kind": "keyword",
+          "spelling": "template"
+        },
+        {
+          "kind": "text",
+          "spelling": "<> "
+        },
+        {
+          "kind": "keyword",
+          "spelling": "class"
+        },
+        {
+          "kind": "text",
+          "spelling": " "
+        },
+        {
+          "kind": "identifier",
+          "spelling": "Foo"
+        },
+        {
+          "kind": "text",
+          "spelling": "<"
+        },
+        {
+          "kind": "typeIdentifier",
+          "preciseIdentifier": "c:I",
+          "spelling": "int"
+        },
+        {
+          "kind": "text",
+          "spelling": ">;"
+        }
+      ],
+      "identifier": {
+        "interfaceLanguage": "c++",
+        "precise": "c:@S at Foo>#I"
+      },
+      "kind": {
+        "displayName": "Class",
+        "identifier": "c++.class"
+      },
+      "location": {
+        "position": {
+          "character": 18,
+          "line": 3
+        },
+        "uri": "file://INPUT_DIR/input.h"
+      },
+      "names": {
+        "navigator": [
+          {
+            "kind": "identifier",
+            "spelling": "Foo"
+          }
+        ],
+        "subHeading": [
+          {
+            "kind": "identifier",
+            "spelling": "Foo"
+          }
+        ],
+        "title": "Foo"
+      },
+      "pathComponents": [
+        "Foo"
+      ]
+    }
+  ]
+}

diff  --git a/clang/test/ExtractAPI/concept.cpp b/clang/test/ExtractAPI/concept.cpp
new file mode 100644
index 00000000000000..08c3c832ce3a06
--- /dev/null
+++ b/clang/test/ExtractAPI/concept.cpp
@@ -0,0 +1,133 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: sed -e "s at INPUT_DIR@%{/t:regex_replacement}@g" \
+// RUN: %t/reference.output.json.in >> %t/reference.output.json
+// RUN: %clang_cc1 -std=c++20 -extract-api -triple arm64-apple-macosx \
+// RUN:   -x c++-header %t/input.h -o %t/output.json -verify
+
+// Generator version is not consistent across test runs, normalize it.
+// RUN: sed -e "s@\"generator\": \".*\"@\"generator\": \"?\"@g" \
+// RUN: %t/output.json >> %t/output-normalized.json
+// RUN: 
diff  %t/reference.output.json %t/output-normalized.json
+
+//--- input.h
+template<typename T> concept Foo = true;
+
+/// expected-no-diagnostics
+
+//--- reference.output.json.in
+{
+  "metadata": {
+    "formatVersion": {
+      "major": 0,
+      "minor": 5,
+      "patch": 3
+    },
+    "generator": "?"
+  },
+  "module": {
+    "name": "",
+    "platform": {
+      "architecture": "arm64",
+      "operatingSystem": {
+        "minimumVersion": {
+          "major": 11,
+          "minor": 0,
+          "patch": 0
+        },
+        "name": "macosx"
+      },
+      "vendor": "apple"
+    }
+  },
+  "relationships": [],
+  "symbols": [
+    {
+      "accessLevel": "public",
+      "declarationFragments": [
+        {
+          "kind": "keyword",
+          "spelling": "template"
+        },
+        {
+          "kind": "text",
+          "spelling": "<"
+        },
+        {
+          "kind": "keyword",
+          "spelling": "typename"
+        },
+        {
+          "kind": "text",
+          "spelling": " "
+        },
+        {
+          "kind": "genericParameter",
+          "spelling": "T"
+        },
+        {
+          "kind": "text",
+          "spelling": "> "
+        },
+        {
+          "kind": "keyword",
+          "spelling": "concept"
+        },
+        {
+          "kind": "text",
+          "spelling": " "
+        },
+        {
+          "kind": "identifier",
+          "spelling": "Foo"
+        },
+        {
+          "kind": "text",
+          "spelling": ";"
+        }
+      ],
+      "identifier": {
+        "interfaceLanguage": "c++",
+        "precise": "c:@CT at Foo"
+      },
+      "kind": {
+        "displayName": "Concept",
+        "identifier": "c++.concept"
+      },
+      "location": {
+        "position": {
+          "character": 30,
+          "line": 1
+        },
+        "uri": "file://INPUT_DIR/input.h"
+      },
+      "names": {
+        "navigator": [
+          {
+            "kind": "identifier",
+            "spelling": "Foo"
+          }
+        ],
+        "subHeading": [
+          {
+            "kind": "identifier",
+            "spelling": "Foo"
+          }
+        ],
+        "title": "Foo"
+      },
+      "pathComponents": [
+        "Foo"
+      ],
+      "swiftGenerics": {
+        "parameters": [
+          {
+            "depth": 0,
+            "index": 0,
+            "name": "T"
+          }
+        ]
+      }
+    }
+  ]
+}


        


More information about the cfe-commits mailing list