[llvm-branch-commits] [clang-tools-extra] [clang-doc] add support for concepts (PR #144430)

Erick Velez via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Thu Jun 19 09:00:12 PDT 2025


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

>From 9754f70f3255e250c51c177bb00ef955e8d92f9c Mon Sep 17 00:00:00 2001
From: Erick Velez <erickvelez7 at gmail.com>
Date: Mon, 16 Jun 2025 10:50:35 -0700
Subject: [PATCH] add serializeArray for infos with URLs

---
 clang-tools-extra/clang-doc/BitcodeReader.cpp |  72 +++++++++++
 clang-tools-extra/clang-doc/BitcodeWriter.cpp |  44 ++++++-
 clang-tools-extra/clang-doc/BitcodeWriter.h   |  12 +-
 clang-tools-extra/clang-doc/HTMLGenerator.cpp |   4 +
 .../clang-doc/HTMLMustacheGenerator.cpp       |   2 +
 clang-tools-extra/clang-doc/JSONGenerator.cpp |  54 ++++++++
 clang-tools-extra/clang-doc/MDGenerator.cpp   |   5 +
 clang-tools-extra/clang-doc/Mapper.cpp        |   4 +
 clang-tools-extra/clang-doc/Mapper.h          |   1 +
 .../clang-doc/Representation.cpp              |  13 ++
 clang-tools-extra/clang-doc/Representation.h  |  26 +++-
 clang-tools-extra/clang-doc/Serialize.cpp     |  90 +++++++++++++
 clang-tools-extra/clang-doc/Serialize.h       |   4 +
 clang-tools-extra/clang-doc/YAMLGenerator.cpp |   2 +
 .../test/clang-doc/json/class-requires.cpp    |  18 +--
 .../clang-doc/json/compound-constraints.cpp   | 121 ++++++++++++++++++
 .../test/clang-doc/json/concept.cpp           |  48 +++----
 .../test/clang-doc/json/function-requires.cpp |  36 +++---
 .../unittests/clang-doc/BitcodeTest.cpp       |   2 +
 19 files changed, 502 insertions(+), 56 deletions(-)
 create mode 100644 clang-tools-extra/test/clang-doc/json/compound-constraints.cpp

diff --git a/clang-tools-extra/clang-doc/BitcodeReader.cpp b/clang-tools-extra/clang-doc/BitcodeReader.cpp
index 35058abab0663..5b70280e7dba8 100644
--- a/clang-tools-extra/clang-doc/BitcodeReader.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeReader.cpp
@@ -92,6 +92,7 @@ static llvm::Error decodeRecord(const Record &R, InfoType &Field,
   case InfoType::IT_default:
   case InfoType::IT_enum:
   case InfoType::IT_typedef:
+  case InfoType::IT_concept:
     Field = IT;
     return llvm::Error::success();
   }
@@ -108,6 +109,7 @@ static llvm::Error decodeRecord(const Record &R, FieldId &Field,
   case FieldId::F_type:
   case FieldId::F_child_namespace:
   case FieldId::F_child_record:
+  case FieldId::F_concept:
   case FieldId::F_default:
     Field = F;
     return llvm::Error::success();
@@ -391,6 +393,29 @@ static llvm::Error parseRecord(const Record &R, unsigned ID,
                                  "invalid field for TemplateParamInfo");
 }
 
+static llvm::Error parseRecord(const Record &R, unsigned ID,
+                               llvm::StringRef Blob, ConceptInfo *I) {
+  switch (ID) {
+  case CONCEPT_USR:
+    return decodeRecord(R, I->USR, Blob);
+  case CONCEPT_NAME:
+    return decodeRecord(R, I->Name, Blob);
+  case CONCEPT_IS_TYPE:
+    return decodeRecord(R, I->IsType, Blob);
+  case CONCEPT_CONSTRAINT_EXPRESSION:
+    return decodeRecord(R, I->ConstraintExpression, Blob);
+  }
+  llvm_unreachable("invalid field for ConceptInfo");
+}
+
+static llvm::Error parseRecord(const Record &R, unsigned ID,
+                               llvm::StringRef Blob, ConstraintInfo *I) {
+  if (ID == CONSTRAINT_EXPRESSION)
+    return decodeRecord(R, I->Expression, Blob);
+  return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                 "invalid field for ConstraintInfo");
+}
+
 template <typename T> static llvm::Expected<CommentInfo *> getCommentInfo(T I) {
   return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                  "invalid type cannot contain CommentInfo");
@@ -429,6 +454,10 @@ template <> llvm::Expected<CommentInfo *> getCommentInfo(CommentInfo *I) {
   return I->Children.back().get();
 }
 
+template <> llvm::Expected<CommentInfo *> getCommentInfo(ConceptInfo *I) {
+  return &I->Description.emplace_back();
+}
+
 // When readSubBlock encounters a TypeInfo sub-block, it calls addTypeInfo on
 // the parent block to set it. The template specializations define what to do
 // for each supported parent block.
@@ -584,6 +613,18 @@ template <> llvm::Error addReference(RecordInfo *I, Reference &&R, FieldId F) {
   }
 }
 
+template <>
+llvm::Error addReference(ConstraintInfo *I, Reference &&R, FieldId F) {
+  switch (F) {
+  case FieldId::F_concept:
+    I->ConceptRef = std::move(R);
+    return llvm::Error::success();
+  default:
+    return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                   "invalid type cannot contain Reference");
+  }
+}
+
 template <typename T, typename ChildInfoType>
 static void addChild(T I, ChildInfoType &&R) {
   llvm::errs() << "invalid child type for info";
@@ -600,6 +641,9 @@ template <> void addChild(NamespaceInfo *I, EnumInfo &&R) {
 template <> void addChild(NamespaceInfo *I, TypedefInfo &&R) {
   I->Children.Typedefs.emplace_back(std::move(R));
 }
+template <> void addChild(NamespaceInfo *I, ConceptInfo &&R) {
+  I->Children.Concepts.emplace_back(std::move(R));
+}
 
 // Record children:
 template <> void addChild(RecordInfo *I, FunctionInfo &&R) {
@@ -649,6 +693,9 @@ template <> void addTemplate(RecordInfo *I, TemplateInfo &&P) {
 template <> void addTemplate(FunctionInfo *I, TemplateInfo &&P) {
   I->Template.emplace(std::move(P));
 }
+template <> void addTemplate(ConceptInfo *I, TemplateInfo &&P) {
+  I->Template = std::move(P);
+}
 
 // Template specializations go only into template records.
 template <typename T>
@@ -662,6 +709,14 @@ void addTemplateSpecialization(TemplateInfo *I,
   I->Specialization.emplace(std::move(TSI));
 }
 
+template <typename T> static void addConstraint(T I, ConstraintInfo &&C) {
+  llvm::errs() << "invalid container for constraint info";
+  exit(1);
+}
+template <> void addConstraint(TemplateInfo *I, ConstraintInfo &&C) {
+  I->Constraints.emplace_back(std::move(C));
+}
+
 // Read records from bitcode into a given info.
 template <typename T>
 llvm::Error ClangDocBitcodeReader::readRecord(unsigned ID, T I) {
@@ -817,6 +872,20 @@ llvm::Error ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) {
     addChild(I, std::move(TI));
     return llvm::Error::success();
   }
+  case BI_CONSTRAINT_BLOCK_ID: {
+    ConstraintInfo CI;
+    if (auto Err = readBlock(ID, &CI))
+      return Err;
+    addConstraint(I, std::move(CI));
+    return llvm::Error::success();
+  }
+  case BI_CONCEPT_BLOCK_ID: {
+    ConceptInfo CI;
+    if (auto Err = readBlock(ID, &CI))
+      return Err;
+    addChild(I, std::move(CI));
+    return llvm::Error::success();
+  }
   default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                    "invalid subblock type");
@@ -922,6 +991,8 @@ ClangDocBitcodeReader::readBlockToInfo(unsigned ID) {
     return createInfo<EnumInfo>(ID);
   case BI_TYPEDEF_BLOCK_ID:
     return createInfo<TypedefInfo>(ID);
+  case BI_CONCEPT_BLOCK_ID:
+    return createInfo<ConceptInfo>(ID);
   case BI_FUNCTION_BLOCK_ID:
     return createInfo<FunctionInfo>(ID);
   default:
@@ -962,6 +1033,7 @@ ClangDocBitcodeReader::readBitcode() {
     case BI_RECORD_BLOCK_ID:
     case BI_ENUM_BLOCK_ID:
     case BI_TYPEDEF_BLOCK_ID:
+    case BI_CONCEPT_BLOCK_ID:
     case BI_FUNCTION_BLOCK_ID: {
       auto InfoOrErr = readBlockToInfo(ID);
       if (!InfoOrErr)
diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.cpp b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
index f8a6859169b01..330b919140343 100644
--- a/clang-tools-extra/clang-doc/BitcodeWriter.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
@@ -128,7 +128,9 @@ static const llvm::IndexedMap<llvm::StringRef, BlockIdToIndexFunctor>
           {BI_REFERENCE_BLOCK_ID, "ReferenceBlock"},
           {BI_TEMPLATE_BLOCK_ID, "TemplateBlock"},
           {BI_TEMPLATE_SPECIALIZATION_BLOCK_ID, "TemplateSpecializationBlock"},
-          {BI_TEMPLATE_PARAM_BLOCK_ID, "TemplateParamBlock"}};
+          {BI_TEMPLATE_PARAM_BLOCK_ID, "TemplateParamBlock"},
+          {BI_CONSTRAINT_BLOCK_ID, "ConstraintBlock"},
+          {BI_CONCEPT_BLOCK_ID, "ConceptBlock"}};
       assert(Inits.size() == BlockIdCount);
       for (const auto &Init : Inits)
         BlockIdNameMap[Init.first] = Init.second;
@@ -205,7 +207,13 @@ static const llvm::IndexedMap<RecordIdDsc, RecordIdToIndexFunctor>
           {TYPEDEF_USR, {"USR", &genSymbolIdAbbrev}},
           {TYPEDEF_NAME, {"Name", &genStringAbbrev}},
           {TYPEDEF_DEFLOCATION, {"DefLocation", &genLocationAbbrev}},
-          {TYPEDEF_IS_USING, {"IsUsing", &genBoolAbbrev}}};
+          {TYPEDEF_IS_USING, {"IsUsing", &genBoolAbbrev}},
+          {CONCEPT_USR, {"USR", &genSymbolIdAbbrev}},
+          {CONCEPT_NAME, {"Name", &genStringAbbrev}},
+          {CONCEPT_IS_TYPE, {"IsType", &genBoolAbbrev}},
+          {CONCEPT_CONSTRAINT_EXPRESSION,
+           {"ConstraintExpression", &genStringAbbrev}},
+          {CONSTRAINT_EXPRESSION, {"Expression", &genStringAbbrev}}};
       assert(Inits.size() == RecordIdCount);
       for (const auto &Init : Inits) {
         RecordIdNameMap[Init.first] = Init.second;
@@ -263,7 +271,13 @@ static const std::vector<std::pair<BlockId, std::vector<RecordId>>>
         // Template Blocks.
         {BI_TEMPLATE_BLOCK_ID, {}},
         {BI_TEMPLATE_PARAM_BLOCK_ID, {TEMPLATE_PARAM_CONTENTS}},
-        {BI_TEMPLATE_SPECIALIZATION_BLOCK_ID, {TEMPLATE_SPECIALIZATION_OF}}};
+        {BI_TEMPLATE_SPECIALIZATION_BLOCK_ID, {TEMPLATE_SPECIALIZATION_OF}},
+        // Concept Block
+        {BI_CONCEPT_BLOCK_ID,
+         {CONCEPT_USR, CONCEPT_NAME, CONCEPT_IS_TYPE,
+          CONCEPT_CONSTRAINT_EXPRESSION}},
+        // Constraint Block
+        {BI_CONSTRAINT_BLOCK_ID, {CONSTRAINT_EXPRESSION}}};
 
 // AbbreviationMap
 
@@ -524,6 +538,8 @@ void ClangDocBitcodeWriter::emitBlock(const NamespaceInfo &I) {
     emitBlock(C);
   for (const auto &C : I.Children.Typedefs)
     emitBlock(C);
+  for (const auto &C : I.Children.Concepts)
+    emitBlock(C);
 }
 
 void ClangDocBitcodeWriter::emitBlock(const EnumInfo &I) {
@@ -627,12 +643,25 @@ void ClangDocBitcodeWriter::emitBlock(const FunctionInfo &I) {
     emitBlock(*I.Template);
 }
 
+void ClangDocBitcodeWriter::emitBlock(const ConceptInfo &I) {
+  StreamSubBlockGuard Block(Stream, BI_CONCEPT_BLOCK_ID);
+  emitRecord(I.USR, CONCEPT_USR);
+  emitRecord(I.Name, CONCEPT_NAME);
+  for (const auto &CI : I.Description)
+    emitBlock(CI);
+  emitRecord(I.IsType, CONCEPT_IS_TYPE);
+  emitRecord(I.ConstraintExpression, CONCEPT_CONSTRAINT_EXPRESSION);
+  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);
+  for (const auto &C : T.Constraints)
+    emitBlock(C);
 }
 
 void ClangDocBitcodeWriter::emitBlock(const TemplateSpecializationInfo &T) {
@@ -647,6 +676,12 @@ void ClangDocBitcodeWriter::emitBlock(const TemplateParamInfo &T) {
   emitRecord(T.Contents, TEMPLATE_PARAM_CONTENTS);
 }
 
+void ClangDocBitcodeWriter::emitBlock(const ConstraintInfo &C) {
+  StreamSubBlockGuard Block(Stream, BI_CONSTRAINT_BLOCK_ID);
+  emitRecord(C.Expression, CONSTRAINT_EXPRESSION);
+  emitBlock(C.ConceptRef, FieldId::F_concept);
+}
+
 bool ClangDocBitcodeWriter::dispatchInfoForWrite(Info *I) {
   switch (I->IT) {
   case InfoType::IT_namespace:
@@ -664,6 +699,9 @@ bool ClangDocBitcodeWriter::dispatchInfoForWrite(Info *I) {
   case InfoType::IT_typedef:
     emitBlock(*static_cast<clang::doc::TypedefInfo *>(I));
     break;
+  case InfoType::IT_concept:
+    emitBlock(*static_cast<clang::doc::ConceptInfo *>(I));
+    break;
   case InfoType::IT_default:
     llvm::errs() << "Unexpected info, unable to write.\n";
     return true;
diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.h b/clang-tools-extra/clang-doc/BitcodeWriter.h
index e33a1aece883c..4d0c0c07805e7 100644
--- a/clang-tools-extra/clang-doc/BitcodeWriter.h
+++ b/clang-tools-extra/clang-doc/BitcodeWriter.h
@@ -66,7 +66,9 @@ enum BlockId {
   BI_TEMPLATE_BLOCK_ID,
   BI_TEMPLATE_SPECIALIZATION_BLOCK_ID,
   BI_TEMPLATE_PARAM_BLOCK_ID,
+  BI_CONSTRAINT_BLOCK_ID,
   BI_TYPEDEF_BLOCK_ID,
+  BI_CONCEPT_BLOCK_ID,
   BI_LAST,
   BI_FIRST = BI_VERSION_BLOCK_ID
 };
@@ -135,6 +137,11 @@ enum RecordId {
   TYPEDEF_NAME,
   TYPEDEF_DEFLOCATION,
   TYPEDEF_IS_USING,
+  CONCEPT_USR,
+  CONCEPT_NAME,
+  CONCEPT_IS_TYPE,
+  CONCEPT_CONSTRAINT_EXPRESSION,
+  CONSTRAINT_EXPRESSION,
   RI_LAST,
   RI_FIRST = VERSION
 };
@@ -150,7 +157,8 @@ enum class FieldId {
   F_vparent,
   F_type,
   F_child_namespace,
-  F_child_record
+  F_child_record,
+  F_concept
 };
 
 class ClangDocBitcodeWriter {
@@ -179,6 +187,8 @@ class ClangDocBitcodeWriter {
   void emitBlock(const TemplateInfo &T);
   void emitBlock(const TemplateSpecializationInfo &T);
   void emitBlock(const TemplateParamInfo &T);
+  void emitBlock(const ConceptInfo &T);
+  void emitBlock(const ConstraintInfo &T);
   void emitBlock(const Reference &B, FieldId F);
 
 private:
diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
index 7293a129177c9..935bbfee7a9b1 100644
--- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
@@ -985,6 +985,8 @@ llvm::Error HTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
     MainContentNodes =
         genHTML(*static_cast<clang::doc::TypedefInfo *>(I), CDCtx, InfoTitle);
     break;
+  case InfoType::IT_concept:
+    break;
   case InfoType::IT_default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                    "unexpected info type");
@@ -1011,6 +1013,8 @@ static std::string getRefType(InfoType IT) {
     return "enum";
   case InfoType::IT_typedef:
     return "typedef";
+  case InfoType::IT_concept:
+    return "concept";
   }
   llvm_unreachable("Unknown InfoType");
 }
diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
index 69c670b208440..81ba99c21e374 100644
--- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
@@ -585,6 +585,8 @@ Error MustacheHTMLGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
   case InfoType::IT_typedef:
     OS << "IT_typedef\n";
     break;
+  case InfoType::IT_concept:
+    break;
   case InfoType::IT_default:
     return createStringError(inconvertibleErrorCode(), "unexpected InfoType");
   }
diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp b/clang-tools-extra/clang-doc/JSONGenerator.cpp
index 0f7cbafcf5135..2e7df14f91184 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -26,6 +26,7 @@ static void serializeInfo(const TypedefInfo &I, json::Object &Obj,
                           std::optional<StringRef> RepositoryUrl);
 static void serializeInfo(const EnumInfo &I, json::Object &Obj,
                           std::optional<StringRef> RepositoryUrl);
+static void serializeInfo(const ConstraintInfo &I, Object &Obj);
 
 static json::Object serializeLocation(const Location &Loc,
                                       std::optional<StringRef> RepositoryUrl) {
@@ -248,6 +249,42 @@ static void serializeCommonChildren(const ScopeChildren &Children,
   }
 }
 
+template <typename T>
+static void serializeArray(const std::vector<T> &Records, Object &Obj,
+                           const std::string &Key) {
+  json::Value RecordsArray = Array();
+  auto &RecordsArrayRef = *RecordsArray.getAsArray();
+  RecordsArrayRef.reserve(Records.size());
+  for (const auto &Item : Records) {
+    json::Value ItemVal = Object();
+    auto &ItemObj = *ItemVal.getAsObject();
+    serializeInfo(Item, ItemObj);
+    RecordsArrayRef.push_back(ItemVal);
+  }
+  Obj[Key] = RecordsArray;
+}
+
+template <typename T>
+static void serializeArray(const std::vector<T> &Records, Object &Obj,
+                           const std::string &Key,
+                           std::optional<StringRef> RepositoryUrl) {
+  json::Value RecordsArray = Array();
+  auto &RecordsArrayRef = *RecordsArray.getAsArray();
+  RecordsArrayRef.reserve(Records.size());
+  for (const auto &Item : Records) {
+    json::Value ItemVal = Object();
+    auto &ItemObj = *ItemVal.getAsObject();
+    serializeInfo(Item, ItemObj, RepositoryUrl);
+    RecordsArrayRef.push_back(ItemVal);
+  }
+  Obj[Key] = RecordsArray;
+}
+
+static void serializeInfo(const ConstraintInfo &I, Object &Obj) {
+  serializeReference(I.ConceptRef, Obj);
+  Obj["Expression"] = I.Expression;
+}
+
 static void serializeInfo(const TemplateInfo &Template, Object &Obj) {
   json::Value TemplateVal = Object();
   auto &TemplateObj = *TemplateVal.getAsObject();
@@ -277,9 +314,21 @@ static void serializeInfo(const TemplateInfo &Template, Object &Obj) {
     TemplateObj["Parameters"] = ParamsArray;
   }
 
+  if (!Template.Constraints.empty()) {
+    serializeArray(Template.Constraints, TemplateObj, "Constraints");
+  }
+
   Obj["Template"] = TemplateVal;
 }
 
+static void serializeInfo(const ConceptInfo &I, Object &Obj,
+                          std::optional<StringRef> RepositoryUrl) {
+  serializeCommonAttributes(I, Obj, RepositoryUrl);
+  Obj["IsType"] = I.IsType;
+  Obj["ConstraintExpression"] = I.ConstraintExpression;
+  serializeInfo(I.Template, Obj);
+}
+
 static void serializeInfo(const TypeInfo &I, Object &Obj) {
   Obj["Name"] = I.Type.Name;
   Obj["QualName"] = I.Type.QualName;
@@ -470,6 +519,10 @@ static void serializeInfo(const NamespaceInfo &I, json::Object &Obj,
     Obj["Functions"] = FunctionsArray;
   }
 
+  if (!I.Children.Concepts.empty()) {
+    serializeArray(I.Children.Concepts, Obj, "Concepts", RepositoryUrl);
+  }
+
   serializeCommonChildren(I.Children, Obj, RepositoryUrl);
 }
 
@@ -520,6 +573,7 @@ Error JSONGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
   case InfoType::IT_record:
     serializeInfo(*static_cast<RecordInfo *>(I), Obj, CDCtx.RepositoryUrl);
     break;
+  case InfoType::IT_concept:
   case InfoType::IT_enum:
   case InfoType::IT_function:
   case InfoType::IT_typedef:
diff --git a/clang-tools-extra/clang-doc/MDGenerator.cpp b/clang-tools-extra/clang-doc/MDGenerator.cpp
index 2becccf8b07da..6e68e09cfa2a6 100644
--- a/clang-tools-extra/clang-doc/MDGenerator.cpp
+++ b/clang-tools-extra/clang-doc/MDGenerator.cpp
@@ -372,6 +372,9 @@ static llvm::Error genIndex(ClangDocContext &CDCtx) {
       case InfoType::IT_typedef:
         Type = "Typedef";
         break;
+      case InfoType::IT_concept:
+        Type = "Concept";
+        break;
       case InfoType::IT_default:
         Type = "Other";
       }
@@ -464,6 +467,8 @@ llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
   case InfoType::IT_typedef:
     genMarkdown(CDCtx, *static_cast<clang::doc::TypedefInfo *>(I), OS);
     break;
+  case InfoType::IT_concept:
+    break;
   case InfoType::IT_default:
     return createStringError(llvm::inconvertibleErrorCode(),
                              "unexpected InfoType");
diff --git a/clang-tools-extra/clang-doc/Mapper.cpp b/clang-tools-extra/clang-doc/Mapper.cpp
index 9f640b5325da4..6021e17b4696d 100644
--- a/clang-tools-extra/clang-doc/Mapper.cpp
+++ b/clang-tools-extra/clang-doc/Mapper.cpp
@@ -134,6 +134,10 @@ bool MapASTVisitor::VisitTypeAliasDecl(const TypeAliasDecl *D) {
   return mapDecl(D, /*isDefinition=*/true);
 }
 
+bool MapASTVisitor::VisitConceptDecl(const ConceptDecl *D) {
+  return mapDecl(D, true);
+}
+
 comments::FullComment *
 MapASTVisitor::getComment(const NamedDecl *D, const ASTContext &Context) const {
   RawComment *Comment = Context.getRawCommentForDeclNoCache(D);
diff --git a/clang-tools-extra/clang-doc/Mapper.h b/clang-tools-extra/clang-doc/Mapper.h
index 36322ea2bfb77..04dc5450c8ba3 100644
--- a/clang-tools-extra/clang-doc/Mapper.h
+++ b/clang-tools-extra/clang-doc/Mapper.h
@@ -41,6 +41,7 @@ class MapASTVisitor : public clang::RecursiveASTVisitor<MapASTVisitor>,
   bool VisitFunctionDecl(const FunctionDecl *D);
   bool VisitTypedefDecl(const TypedefDecl *D);
   bool VisitTypeAliasDecl(const TypeAliasDecl *D);
+  bool VisitConceptDecl(const ConceptDecl *D);
 
 private:
   template <typename T> bool mapDecl(const T *D, bool IsDefinition);
diff --git a/clang-tools-extra/clang-doc/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp
index 820d644ef8b83..320048aa0fbf8 100644
--- a/clang-tools-extra/clang-doc/Representation.cpp
+++ b/clang-tools-extra/clang-doc/Representation.cpp
@@ -143,6 +143,8 @@ mergeInfos(std::vector<std::unique_ptr<Info>> &Values) {
     return reduce<FunctionInfo>(Values);
   case InfoType::IT_typedef:
     return reduce<TypedefInfo>(Values);
+  case InfoType::IT_concept:
+    return reduce<ConceptInfo>(Values);
   case InfoType::IT_default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                    "unexpected info type");
@@ -287,6 +289,7 @@ void NamespaceInfo::merge(NamespaceInfo &&Other) {
   reduceChildren(Children.Functions, std::move(Other.Children.Functions));
   reduceChildren(Children.Enums, std::move(Other.Children.Enums));
   reduceChildren(Children.Typedefs, std::move(Other.Children.Typedefs));
+  reduceChildren(Children.Concepts, std::move(Other.Children.Concepts));
   mergeBase(std::move(Other));
 }
 
@@ -351,6 +354,13 @@ void TypedefInfo::merge(TypedefInfo &&Other) {
   SymbolInfo::merge(std::move(Other));
 }
 
+void ConceptInfo::merge(ConceptInfo &&Other) {
+  assert(mergeable(Other));
+  if (!IsType)
+    IsType = Other.IsType;
+  SymbolInfo::merge(std::move(Other));
+}
+
 BaseRecordInfo::BaseRecordInfo() : RecordInfo() {}
 
 BaseRecordInfo::BaseRecordInfo(SymbolID USR, StringRef Name, StringRef Path,
@@ -387,6 +397,9 @@ llvm::SmallString<16> Info::extractName() const {
   case InfoType::IT_function:
     return llvm::SmallString<16>("@nonymous_function_" +
                                  toHex(llvm::toStringRef(USR)));
+  case InfoType::IT_concept:
+    return llvm::SmallString<16>("@nonymous_concept_" +
+                                 toHex(llvm::toStringRef(USR)));
   case InfoType::IT_default:
     return llvm::SmallString<16>("@nonymous_" + toHex(llvm::toStringRef(USR)));
   }
diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h
index 75da500645819..b7be2d23cbc45 100644
--- a/clang-tools-extra/clang-doc/Representation.h
+++ b/clang-tools-extra/clang-doc/Representation.h
@@ -35,6 +35,7 @@ struct EnumInfo;
 struct FunctionInfo;
 struct Info;
 struct TypedefInfo;
+struct ConceptInfo;
 
 enum class InfoType {
   IT_default,
@@ -42,7 +43,8 @@ enum class InfoType {
   IT_record,
   IT_function,
   IT_enum,
-  IT_typedef
+  IT_typedef,
+  IT_concept
 };
 
 enum class CommentKind {
@@ -166,6 +168,7 @@ struct ScopeChildren {
   std::vector<FunctionInfo> Functions;
   std::vector<EnumInfo> Enums;
   std::vector<TypedefInfo> Typedefs;
+  std::vector<ConceptInfo> Concepts;
 
   void sort();
 };
@@ -211,6 +214,15 @@ struct TemplateSpecializationInfo {
   std::vector<TemplateParamInfo> Params;
 };
 
+struct ConstraintInfo {
+  ConstraintInfo() = default;
+  ConstraintInfo(SymbolID USR, StringRef Name)
+      : ConceptRef(USR, Name, InfoType::IT_concept) {}
+  Reference ConceptRef;
+
+  SmallString<16> Expression; // The expression that defines the constraint.
+};
+
 // Records the template information for a struct or function that is a template
 // or an explicit template specialization.
 struct TemplateInfo {
@@ -219,6 +231,7 @@ struct TemplateInfo {
 
   // Set when this is a specialization of another record/function.
   std::optional<TemplateSpecializationInfo> Specialization;
+  std::vector<ConstraintInfo> Constraints;
 };
 
 // Info for field types.
@@ -513,6 +526,17 @@ struct EnumInfo : public SymbolInfo {
   llvm::SmallVector<EnumValueInfo, 4> Members; // List of enum members.
 };
 
+struct ConceptInfo : public SymbolInfo {
+  ConceptInfo() : SymbolInfo(InfoType::IT_concept) {}
+  ConceptInfo(SymbolID USR) : SymbolInfo(InfoType::IT_concept, USR) {}
+
+  void merge(ConceptInfo &&I);
+
+  bool IsType;
+  TemplateInfo Template;
+  SmallString<16> ConstraintExpression;
+};
+
 struct Index : public Reference {
   Index() = default;
   Index(StringRef Name) : Reference(SymbolID(), Name) {}
diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp
index e8f1a9cee2675..a3f186cb1daca 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -21,6 +21,17 @@ namespace clang {
 namespace doc {
 namespace serialize {
 
+namespace {
+static SmallString<16> exprToString(const clang::Expr *E) {
+  clang::LangOptions Opts;
+  clang::PrintingPolicy Policy(Opts);
+  SmallString<16> Result;
+  llvm::raw_svector_ostream OS(Result);
+  E->printPretty(OS, nullptr, Policy);
+  return Result;
+}
+} // namespace
+
 SymbolID hashUSR(llvm::StringRef USR) {
   return llvm::SHA1::hash(arrayRefFromStringRef(USR));
 }
@@ -388,6 +399,8 @@ std::string serialize(std::unique_ptr<Info> &I) {
     return serialize(*static_cast<EnumInfo *>(I.get()));
   case InfoType::IT_function:
     return serialize(*static_cast<FunctionInfo *>(I.get()));
+  case InfoType::IT_concept:
+    return serialize(*static_cast<ConceptInfo *>(I.get()));
   case InfoType::IT_typedef:
   case InfoType::IT_default:
     return "";
@@ -490,6 +503,10 @@ static void InsertChild(ScopeChildren &Scope, TypedefInfo Info) {
   Scope.Typedefs.push_back(std::move(Info));
 }
 
+static void InsertChild(ScopeChildren &Scope, ConceptInfo Info) {
+  Scope.Concepts.push_back(std::move(Info));
+}
+
 // Creates a parent of the correct type for the given child and inserts it into
 // that parent.
 //
@@ -530,6 +547,7 @@ static std::unique_ptr<Info> makeAndInsertIntoParent(ChildType Child) {
   case InfoType::IT_enum:
   case InfoType::IT_function:
   case InfoType::IT_typedef:
+  case InfoType::IT_concept:
     break;
   }
   llvm_unreachable("Invalid reference type for parent namespace");
@@ -739,6 +757,50 @@ static void populateSymbolInfo(SymbolInfo &I, const T *D, const FullComment *C,
     I.Loc.emplace_back(Loc);
 }
 
+static void
+handleCompoundConstraints(const Expr *Constraint,
+                          std::vector<ConstraintInfo> &ConstraintInfos) {
+  if (Constraint->getStmtClass() == Stmt::ParenExprClass) {
+    handleCompoundConstraints(dyn_cast<ParenExpr>(Constraint)->getSubExpr(),
+                              ConstraintInfos);
+  } else if (Constraint->getStmtClass() == Stmt::BinaryOperatorClass) {
+    auto *BinaryOpExpr = dyn_cast<BinaryOperator>(Constraint);
+    handleCompoundConstraints(BinaryOpExpr->getLHS(), ConstraintInfos);
+    handleCompoundConstraints(BinaryOpExpr->getRHS(), ConstraintInfos);
+  } else if (Constraint->getStmtClass() ==
+             Stmt::ConceptSpecializationExprClass) {
+    auto *Concept = dyn_cast<ConceptSpecializationExpr>(Constraint);
+    ConstraintInfo CI(getUSRForDecl(Concept->getNamedConcept()),
+                      Concept->getNamedConcept()->getNameAsString());
+    CI.Expression = exprToString(Concept);
+    ConstraintInfos.push_back(CI);
+  }
+}
+
+static void populateConstraints(TemplateInfo &I, const TemplateDecl *D) {
+  if (!D || !D->hasAssociatedConstraints())
+    return;
+
+  SmallVector<AssociatedConstraint> AssociatedConstraints;
+  D->getAssociatedConstraints(AssociatedConstraints);
+  for (const auto &Constraint : AssociatedConstraints) {
+    if (!Constraint)
+      continue;
+
+    // TODO: Investigate if atomic constraints need to be handled specifically.
+    if (const auto *ConstraintExpr =
+            dyn_cast_or_null<ConceptSpecializationExpr>(
+                Constraint.ConstraintExpr)) {
+      ConstraintInfo CI(getUSRForDecl(ConstraintExpr->getNamedConcept()),
+                        ConstraintExpr->getNamedConcept()->getNameAsString());
+      CI.Expression = exprToString(ConstraintExpr);
+      I.Constraints.push_back(std::move(CI));
+    } else {
+      handleCompoundConstraints(Constraint.ConstraintExpr, I.Constraints);
+    }
+  }
+}
+
 static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D,
                                  const FullComment *FC, Location Loc,
                                  bool &IsInAnonymousNamespace) {
@@ -750,6 +812,8 @@ static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D,
   I.IsStatic = D->isStatic();
 
   populateTemplateParameters(I.Template, D);
+  if (I.Template)
+    populateConstraints(I.Template.value(), D->getDescribedFunctionTemplate());
 
   // Handle function template specializations.
   if (const FunctionTemplateSpecializationInfo *FTSI =
@@ -902,6 +966,8 @@ emitInfo(const RecordDecl *D, const FullComment *FC, Location Loc,
   RI->Path = getInfoRelativePath(RI->Namespace);
 
   populateTemplateParameters(RI->Template, D);
+  if (RI->Template)
+    populateConstraints(RI->Template.value(), D->getDescribedTemplate());
 
   // Full and partial specializations.
   if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(D)) {
@@ -1073,6 +1139,30 @@ emitInfo(const EnumDecl *D, const FullComment *FC, Location Loc,
   return {nullptr, makeAndInsertIntoParent<EnumInfo &&>(std::move(Enum))};
 }
 
+std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
+emitInfo(const ConceptDecl *D, const FullComment *FC, const Location &Loc,
+         bool PublicOnly) {
+  ConceptInfo Concept;
+
+  bool IsInAnonymousNamespace = false;
+  populateInfo(Concept, D, FC, IsInAnonymousNamespace);
+  Concept.IsType = D->isTypeConcept();
+  Concept.DefLoc = Loc;
+  Concept.ConstraintExpression = exprToString(D->getConstraintExpr());
+
+  if (auto *ConceptParams = D->getTemplateParameters()) {
+    for (const auto *Param : ConceptParams->asArray()) {
+      Concept.Template.Params.emplace_back(
+          getSourceCode(Param, Param->getSourceRange()));
+    }
+  }
+
+  if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D))
+    return {};
+
+  return {nullptr, makeAndInsertIntoParent<ConceptInfo &&>(std::move(Concept))};
+}
+
 } // namespace serialize
 } // namespace doc
 } // namespace clang
diff --git a/clang-tools-extra/clang-doc/Serialize.h b/clang-tools-extra/clang-doc/Serialize.h
index 7e6cbb70721ec..497b09bb339f8 100644
--- a/clang-tools-extra/clang-doc/Serialize.h
+++ b/clang-tools-extra/clang-doc/Serialize.h
@@ -68,6 +68,10 @@ std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
 emitInfo(const TypeAliasDecl *D, const FullComment *FC, Location Loc,
          bool PublicOnly);
 
+std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
+emitInfo(const ConceptDecl *D, const FullComment *FC, const Location &Loc,
+         bool PublicOnly);
+
 // Function to hash a given USR value for storage.
 // As USRs (Unified Symbol Resolution) could be large, especially for functions
 // with long type arguments, we use 160-bits SHA1(USR) values to
diff --git a/clang-tools-extra/clang-doc/YAMLGenerator.cpp b/clang-tools-extra/clang-doc/YAMLGenerator.cpp
index 897b5d5ae4c98..f958871046981 100644
--- a/clang-tools-extra/clang-doc/YAMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/YAMLGenerator.cpp
@@ -408,6 +408,8 @@ llvm::Error YAMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
   case InfoType::IT_typedef:
     InfoYAML << *static_cast<clang::doc::TypedefInfo *>(I);
     break;
+  case InfoType::IT_concept:
+    break;
   case InfoType::IT_default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                    "unexpected InfoType");
diff --git a/clang-tools-extra/test/clang-doc/json/class-requires.cpp b/clang-tools-extra/test/clang-doc/json/class-requires.cpp
index af108a402b403..2dd25771d6d8b 100644
--- a/clang-tools-extra/test/clang-doc/json/class-requires.cpp
+++ b/clang-tools-extra/test/clang-doc/json/class-requires.cpp
@@ -18,15 +18,15 @@ struct MyClass;
 // CHECK-NEXT:  "Path": "GlobalNamespace",
 // CHECK-NEXT:  "TagType": "struct",
 // CHECK-NEXT:  "Template": {
-// CHECK-NOT:     "Constraints": [
-// CHECK-NOT:       {
-// CHECK-NOT:         "Expression": "Addable<T>",
-// CHECK-NOT:         "Name": "Addable",
-// CHECK-NOT:         "Path": "",
-// CHECK-NOT:         "QualName": "Addable",
-// CHECK-NOT:         "USR": "{{[0-9A-F]*}}"
-// CHECK-NOT:       }
-// CHECK-NOT:     ],
+// CHECK-NEXT:    "Constraints": [
+// CHECK-NEXT:      {
+// CHECK-NEXT:        "Expression": "Addable<T>",
+// CHECK-NEXT:        "Name": "Addable",
+// CHECK-NEXT:        "Path": "",
+// CHECK-NEXT:        "QualName": "Addable",
+// CHECK-NEXT:        "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:      }
+// CHECK-NEXT:    ],
 // CHECK-NEXT:    "Parameters": [
 // CHECK-NEXT:      "typename T"
 // CHECK-NEXT:    ]
diff --git a/clang-tools-extra/test/clang-doc/json/compound-constraints.cpp b/clang-tools-extra/test/clang-doc/json/compound-constraints.cpp
new file mode 100644
index 0000000000000..b49dec5cc78c5
--- /dev/null
+++ b/clang-tools-extra/test/clang-doc/json/compound-constraints.cpp
@@ -0,0 +1,121 @@
+// RUN: rm -rf %t && mkdir -p %t
+// RUN: clang-doc --extra-arg -std=c++20 --output=%t --format=json --executor=standalone %s
+// RUN: FileCheck %s < %t/GlobalNamespace/index.json
+
+template<typename T> concept Incrementable = requires (T a) {
+  a++;
+};
+
+template<typename T> concept Decrementable = requires (T a) {
+  a--;
+};
+
+template<typename T> concept PreIncrementable = requires (T a) {
+  ++a;
+};
+
+template<typename T> concept PreDecrementable = requires (T a) {
+  --a;
+};
+
+template<typename T> requires Incrementable<T> && Decrementable<T> void One();
+
+template<typename T> requires (Incrementable<T> && Decrementable<T>) void Two();
+
+template<typename T> requires (Incrementable<T> && Decrementable<T>) || (PreIncrementable<T> && PreDecrementable<T>) void Three();
+
+template<typename T> requires (Incrementable<T> && Decrementable<T>) || PreIncrementable<T> void Four();
+
+// CHECK:         "Name": "One",
+// CHECK:         "Template": {
+// CHECK-NEXT:      "Constraints": [
+// CHECK-NEXT:        {
+// CHECK-NEXT:          "Expression": "Incrementable<T>",
+// CHECK-NEXT:          "Name": "Incrementable",
+// CHECK-NEXT:          "Path": "",
+// CHECK-NEXT:          "QualName": "Incrementable",
+// CHECK-NEXT:          "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:        },
+// CHECK-NEXT:        {
+// CHECK-NEXT:          "Expression": "Decrementable<T>",
+// CHECK-NEXT:          "Name": "Decrementable",
+// CHECK-NEXT:          "Path": "",
+// CHECK-NEXT:          "QualName": "Decrementable",
+// CHECK-NEXT:          "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:        }
+// CHECK-NEXT:      ],
+// CHECK:         "Name": "Two",
+// CHECK:         "Template": {
+// CHECK-NEXT:      "Constraints": [
+// CHECK-NEXT:        {
+// CHECK-NEXT:          "Expression": "Incrementable<T>",
+// CHECK-NEXT:          "Name": "Incrementable",
+// CHECK-NEXT:          "Path": "",
+// CHECK-NEXT:          "QualName": "Incrementable",
+// CHECK-NEXT:          "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:        },
+// CHECK-NEXT:        {
+// CHECK-NEXT:          "Expression": "Decrementable<T>",
+// CHECK-NEXT:          "Name": "Decrementable",
+// CHECK-NEXT:          "Path": "",
+// CHECK-NEXT:          "QualName": "Decrementable",
+// CHECK-NEXT:          "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:        }
+// CHECK-NEXT:      ],
+// CHECK:         "Name": "Three",
+// CHECK:         "Template": {
+// CHECK-NEXT:      "Constraints": [
+// CHECK-NEXT:        {
+// CHECK-NEXT:          "Expression": "Incrementable<T>",
+// CHECK-NEXT:          "Name": "Incrementable",
+// CHECK-NEXT:          "Path": "",
+// CHECK-NEXT:          "QualName": "Incrementable",
+// CHECK-NEXT:          "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:        },
+// CHECK-NEXT:        {
+// CHECK-NEXT:          "Expression": "Decrementable<T>",
+// CHECK-NEXT:          "Name": "Decrementable",
+// CHECK-NEXT:          "Path": "",
+// CHECK-NEXT:          "QualName": "Decrementable",
+// CHECK-NEXT:          "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:        },
+// CHECK-NEXT:        {
+// CHECK-NEXT:          "Expression": "PreIncrementable<T>",
+// CHECK-NEXT:          "Name": "PreIncrementable",
+// CHECK-NEXT:          "Path": "",
+// CHECK-NEXT:          "QualName": "PreIncrementable",
+// CHECK-NEXT:          "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:        },
+// CHECK-NEXT:        {
+// CHECK-NEXT:          "Expression": "PreDecrementable<T>",
+// CHECK-NEXT:          "Name": "PreDecrementable",
+// CHECK-NEXT:          "Path": "",
+// CHECK-NEXT:          "QualName": "PreDecrementable",
+// CHECK-NEXT:          "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:        }
+// CHECK-NEXT:      ],
+// CHECK:         "Name": "Four",
+// CHECK:         "Template": {
+// CHECK-NEXT:      "Constraints": [
+// CHECK-NEXT:        {
+// CHECK-NEXT:          "Expression": "Incrementable<T>",
+// CHECK-NEXT:          "Name": "Incrementable",
+// CHECK-NEXT:          "Path": "",
+// CHECK-NEXT:          "QualName": "Incrementable",
+// CHECK-NEXT:          "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:        },
+// CHECK-NEXT:        {
+// CHECK-NEXT:          "Expression": "Decrementable<T>",
+// CHECK-NEXT:          "Name": "Decrementable",
+// CHECK-NEXT:          "Path": "",
+// CHECK-NEXT:          "QualName": "Decrementable",
+// CHECK-NEXT:          "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:        },
+// CHECK-NEXT:        {
+// CHECK-NEXT:          "Expression": "PreIncrementable<T>",
+// CHECK-NEXT:          "Name": "PreIncrementable",
+// CHECK-NEXT:          "Path": "",
+// CHECK-NEXT:          "QualName": "PreIncrementable",
+// CHECK-NEXT:          "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:        }
+// CHECK-NEXT:      ],
diff --git a/clang-tools-extra/test/clang-doc/json/concept.cpp b/clang-tools-extra/test/clang-doc/json/concept.cpp
index 78be607d1f2f1..a742e7a53bbbd 100644
--- a/clang-tools-extra/test/clang-doc/json/concept.cpp
+++ b/clang-tools-extra/test/clang-doc/json/concept.cpp
@@ -8,30 +8,30 @@ concept Incrementable = requires(T x) {
   x++;
 };
 
-// CHECK:      {
-// CHECK-NOT:    "Concepts": [
-// CHECK-NOT:      {
-// CHECK-NOT:        "ConstraintExpression": "requires (T x) { ++x; x++; }",
-// CHECK-NOT:        "Description": [
-// CHECK-NOT:        {
-// CHECK-NOT:          "FullComment": {
-// CHECK-NOT:            "Children": [
-// CHECK-NOT:              {
-// CHECK-NOT:                "ParagraphComment": {
-// CHECK-NOT:                  "Children": [
-// CHECK-NOT:                    {
-// CHECK-NOT:                      "TextComment": " Requires that T suports post and pre-incrementing."
-// CHECK-NOT:        },
-// CHECK-NOT:        "IsType": true,
-// CHECK-NOT:        "Name": "Incrementable",
-// CHECK-NOT:        "Template": {
-// CHECK-NOT:          "Parameters": [
-// CHECK-NOT:            "typename T"
-// CHECK-NOT:          ]
-// CHECK-NOT:        },
-// CHECK-NOT:        "USR": "{{[0-9A-F]*}}"
-// CHECK-NOT:      }
-// CHECK-NOT:    ],
+// CHECK:       {
+// CHECK-NEXT:    "Concepts": [
+// CHECK-NEXT:    {
+// CHECK-NEXT:      "ConstraintExpression": "requires (T x) { ++x; x++; }",
+// CHECK-NEXT:      "Description": [
+// CHECK-NEXT:        {
+// CHECK-NEXT:          "FullComment": {
+// CHECK-NEXT:            "Children": [
+// CHECK-NEXT:              {
+// CHECK-NEXT:                "ParagraphComment": {
+// CHECK-NEXT:                  "Children": [
+// CHECK-NEXT:                    {
+// CHECK-NEXT:                      "TextComment": " Requires that T suports post and pre-incrementing."
+// CHECK:            },
+// CHECK-NEXT:        "IsType": true,
+// CHECK-NEXT:        "Name": "Incrementable",
+// CHECK-NEXT:        "Template": {
+// CHECK-NEXT:          "Parameters": [
+// CHECK-NEXT:            "typename T"
+// CHECK-NEXT:          ]
+// CHECK-NEXT:        },
+// CHECK-NEXT:        "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:      }
+// CHECK-NEXT:    ],
 // CHECK:        "Name": "",
 // CHECK:        "USR": "0000000000000000000000000000000000000000"
 // CHECK:      }
diff --git a/clang-tools-extra/test/clang-doc/json/function-requires.cpp b/clang-tools-extra/test/clang-doc/json/function-requires.cpp
index aa62464d07b4b..99eb2bdb898f3 100644
--- a/clang-tools-extra/test/clang-doc/json/function-requires.cpp
+++ b/clang-tools-extra/test/clang-doc/json/function-requires.cpp
@@ -30,15 +30,15 @@ template<Incrementable T> Incrementable auto incrementTwo(T t);
 // CHECK-NEXT:        "USR": "0000000000000000000000000000000000000000"
 // CHECK-NEXT:      },
 // CHECK-NEXT:      "Template": {
-// CHECK-NOT:         "Constraints": [
-// CHECK-NOT:           {
-// CHECK-NOT:             "Expression": "Incrementable<T>",
-// CHECK-NOT:             "Name": "Incrementable",
-// CHECK-NOT:             "Path": "",
-// CHECK-NOT:             "QualName": "Incrementable",
-// CHECK-NOT:             "USR": "{{[0-9A-F]*}}"
-// CHECK-NOT:           }
-// CHECK-NOT:         ],
+// CHECK-NEXT:        "Constraints": [
+// CHECK-NEXT:          {
+// CHECK-NEXT:            "Expression": "Incrementable<T>",
+// CHECK-NEXT:            "Name": "Incrementable",
+// CHECK-NEXT:            "Path": "",
+// CHECK-NEXT:            "QualName": "Incrementable",
+// CHECK-NEXT:            "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:          }
+// CHECK-NEXT:        ],
 // CHECK-NEXT:        "Parameters": [
 // CHECK-NEXT:          "typename T"
 // CHECK-NEXT:        ]
@@ -62,15 +62,15 @@ template<Incrementable T> Incrementable auto incrementTwo(T t);
 // CHECK-NEXT:        "USR": "0000000000000000000000000000000000000000"
 // CHECK-NEXT:      },
 // CHECK-NEXT:      "Template": {
-// CHECK-NOT:         "Constraints": [
-// CHECK-NOT:           {
-// CHECK-NOT:             "Expression": "Incrementable<T>",
-// CHECK-NOT:             "Name": "Incrementable",
-// CHECK-NOT:             "Path": "",
-// CHECK-NOT:             "QualName": "Incrementable",
-// CHECK-NOT:             "USR": "{{[0-9A-F]*}}"
-// CHECK-NOT:           }
-// CHECK-NOT:         ],
+// CHECK-NEXT:        "Constraints": [
+// CHECK-NEXT:          {
+// CHECK-NEXT:            "Expression": "Incrementable<T>",
+// CHECK-NEXT:            "Name": "Incrementable",
+// CHECK-NEXT:            "Path": "",
+// CHECK-NEXT:            "QualName": "Incrementable",
+// CHECK-NEXT:            "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:          }
+// CHECK-NEXT:        ],
 // CHECK-NEXT:        "Parameters": [
 // CHECK-NEXT:          "Incrementable T"
 // CHECK-NEXT:        ]
diff --git a/clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp b/clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp
index 659870d2a5c0d..a38dfd3036604 100644
--- a/clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp
+++ b/clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp
@@ -37,6 +37,8 @@ static std::string writeInfo(Info *I) {
     return writeInfo(*static_cast<FunctionInfo *>(I));
   case InfoType::IT_typedef:
     return writeInfo(*static_cast<TypedefInfo *>(I));
+  case InfoType::IT_concept:
+    return writeInfo(*static_cast<ConceptInfo *>(I));
   case InfoType::IT_default:
     return "";
   }



More information about the llvm-branch-commits mailing list