Skip to content

[clang-doc] add support for concepts #144430

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 21, 2025

Conversation

evelez7
Copy link
Member

@evelez7 evelez7 commented Jun 16, 2025

Add support for documenting concepts. This handles concepts and constraints on function and class templates.

Atomic constraints are not considered yet. We don't order constraints based on their conjunctive or disjunctive properties.

Copy link
Member Author

evelez7 commented Jun 16, 2025

@evelez7 evelez7 changed the title [clang-doc] document-concepts [clang-doc] add support for concepts Jun 16, 2025
@evelez7 evelez7 force-pushed the users/evelez7/clang-doc-precommit-concept-tests branch from 8d8b900 to b602047 Compare June 16, 2025 21:39
@evelez7 evelez7 force-pushed the users/evelez7/clang-doc-document-concepts branch from 9f39ac3 to ab20818 Compare June 16, 2025 21:39
@evelez7 evelez7 requested review from ilovepi and petrhosek June 16, 2025 21:40
Copy link
Contributor

@ilovepi ilovepi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see some unhandled switch cases in the CI diagnostics.

/home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/clang-doc/HTMLGenerator.cpp:968:11: warning: enumeration value 'IT_concept' not handled in switch [-Wswitch]
  968 |   switch (I->IT) {
      |           ^~~~~
/home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/clang-doc/HTMLGenerator.cpp:1001:11: warning: enumeration value 'IT_concept' not handled in switch [-Wswitch]
 1001 |   switch (IT) {
      |           ^~

There's also a failing test:

******************** TEST 'Clang Tools :: clang-doc/json/function-requires.cpp' FAILED ********************
Exit Code: 1

Command Output (stdout):
--
Emiting docs in json format.
Mapping decls...
Collecting infos...
Reducing 1 infos...
Generating docs...
Generating assets for docs...

--
Command Output (stderr):
--
rm -rf /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/clang/tools/extra/test/clang-doc/json/Output/function-requires.cpp.tmp && mkdir -p /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/clang/tools/extra/test/clang-doc/json/Output/function-requires.cpp.tmp # RUN: at line 1
+ rm -rf /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/clang/tools/extra/test/clang-doc/json/Output/function-requires.cpp.tmp
+ mkdir -p /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/clang/tools/extra/test/clang-doc/json/Output/function-requires.cpp.tmp
clang-doc --extra-arg -std=c++20 --output=/home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/clang/tools/extra/test/clang-doc/json/Output/function-requires.cpp.tmp --format=json --executor=standalone /home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/test/clang-doc/json/function-requires.cpp # RUN: at line 2
+ clang-doc --extra-arg -std=c++20 --output=/home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/clang/tools/extra/test/clang-doc/json/Output/function-requires.cpp.tmp --format=json --executor=standalone /home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/test/clang-doc/json/function-requires.cpp
Error while trying to load a compilation database:
Could not auto-detect compilation database for file "/home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/test/clang-doc/json/function-requires.cpp"
No compilation database found in /home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/test/clang-doc/json or any parent directory
fixed-compilation-database: Error while opening fixed database: No such file or directory
json-compilation-database: Error while opening JSON database: No such file or directory
Running without flags.
FileCheck /home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/test/clang-doc/json/function-requires.cpp < /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/clang/tools/extra/test/clang-doc/json/Output/function-requires.cpp.tmp/GlobalNamespace/index.json # RUN: at line 3
+ FileCheck /home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/test/clang-doc/json/function-requires.cpp
/home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/test/clang-doc/json/function-requires.cpp:42:16: error: CHECK-NEXT: is not on the line after the previous match
// CHECK-NEXT: "Parameters": [
               ^
<stdin>:80:2: note: 'next' match was here
 "Parameters": [
 ^
<stdin>:70:15: note: previous match ended here
 "Template": {
              ^
<stdin>:71:1: note: non-matching line after previous match is here
 "Constraints": [
^

Input file: <stdin>
Check file: /home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/test/clang-doc/json/function-requires.cpp

-dump-input=help explains the following input dump.

Input was:
<<<<<<
         .
         .
         .
        75:  "Path": "", 
        76:  "QualName": "Incrementable", 
        77:  "USR": "8EA2F4AC745967890D43D844DBD3EA14C16396C7" 
        78:  } 
        79:  ], 
        80:  "Parameters": [ 
next:42      !~~~~~~~~~~~~~~  error: match on wrong line
        81:  "typename T" 
        82:  ] 
        83:  }, 
        84:  "USR": "CE80985A5378877E50F5FF1D8DE73E856B8AF683" 
        85:  }, 
         .
         .
         .
>>>>>>

--

********************

@evelez7 evelez7 force-pushed the users/evelez7/clang-doc-document-concepts branch 2 times, most recently from 56da9c0 to e3e7a47 Compare June 17, 2025 07:20
@evelez7 evelez7 marked this pull request as ready for review June 17, 2025 16:45
@llvmbot
Copy link
Member

llvmbot commented Jun 17, 2025

@llvm/pr-subscribers-clang-tools-extra

Author: Erick Velez (evelez7)

Changes

Patch is 40.83 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/144430.diff

19 Files Affected:

  • (modified) clang-tools-extra/clang-doc/BitcodeReader.cpp (+72)
  • (modified) clang-tools-extra/clang-doc/BitcodeWriter.cpp (+41-3)
  • (modified) clang-tools-extra/clang-doc/BitcodeWriter.h (+11-1)
  • (modified) clang-tools-extra/clang-doc/HTMLGenerator.cpp (+4)
  • (modified) clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp (+2)
  • (modified) clang-tools-extra/clang-doc/JSONGenerator.cpp (+46)
  • (modified) clang-tools-extra/clang-doc/MDGenerator.cpp (+5)
  • (modified) clang-tools-extra/clang-doc/Mapper.cpp (+4)
  • (modified) clang-tools-extra/clang-doc/Mapper.h (+1)
  • (modified) clang-tools-extra/clang-doc/Representation.cpp (+13)
  • (modified) clang-tools-extra/clang-doc/Representation.h (+25-1)
  • (modified) clang-tools-extra/clang-doc/Serialize.cpp (+91-1)
  • (modified) clang-tools-extra/clang-doc/Serialize.h (+4)
  • (modified) clang-tools-extra/clang-doc/YAMLGenerator.cpp (+2)
  • (modified) clang-tools-extra/test/clang-doc/json/class-requires.cpp (+9-9)
  • (added) clang-tools-extra/test/clang-doc/json/compound-constraints.cpp (+121)
  • (modified) clang-tools-extra/test/clang-doc/json/concept.cpp (+24-24)
  • (modified) clang-tools-extra/test/clang-doc/json/function-requires.cpp (+18-18)
  • (modified) clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp (+2)
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..60dbd4def6780 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -248,6 +248,26 @@ 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;
+}
+
+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 +297,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 +502,19 @@ static void serializeInfo(const NamespaceInfo &I, json::Object &Obj,
     Obj["Functions"] = FunctionsArray;
   }
 
+  if (!I.Children.Concepts.empty()) {
+    json::Value ConceptsArray = Array();
+    auto &ConceptsArrayRef = *ConceptsArray.getAsArray();
+    ConceptsArrayRef.reserve(I.Children.Concepts.size());
+    for (const auto &Concept : I.Children.Concepts) {
+      json::Value ConceptVal = Object();
+      auto &ConceptObj = *ConceptVal.getAsObject();
+      serializeInfo(Concept, ConceptObj, RepositoryUrl);
+      ConceptsArrayRef.push_back(ConceptVal);
+    }
+    Obj["Concepts"] = ConceptsArray;
+  }
+
   serializeCommonChildren(I.Children, Obj, RepositoryUrl);
 }
 
@@ -520,6 +565,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 ConceptI...
[truncated]

Copy link
Member Author

evelez7 commented Jun 17, 2025

Linux CI shows failing but looks like all tests passed despite that.

Added compound constraint support which just goes through the nested expressions until it reaches the constraint.

@evelez7 evelez7 requested a review from ilovepi June 17, 2025 16:49
@evelez7 evelez7 force-pushed the users/evelez7/clang-doc-document-concepts branch from e3e7a47 to 72e4a24 Compare June 17, 2025 16:53
@ilovepi
Copy link
Contributor

ilovepi commented Jun 18, 2025

Linux CI shows failing but looks like all tests passed despite that.

Added compound constraint support which just goes through the nested expressions until it reaches the constraint.

I've just been clicking re-run on those when i see it. you may want to file abug about it. or chime in on an existing one.

@evelez7 evelez7 marked this pull request as draft June 19, 2025 00:44
@evelez7 evelez7 force-pushed the users/evelez7/clang-doc-document-concepts branch 2 times, most recently from 9754f70 to 2febdc8 Compare June 20, 2025 03:37
@evelez7 evelez7 marked this pull request as ready for review June 20, 2025 04:27
Copy link
Contributor

@ilovepi ilovepi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. overall really good improvement. most of my comments are me noticing bad existing code we should fix.

I also left a few nit comments to address, but they're rather minor.

Comment on lines 623 to 624
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"invalid type cannot contain Reference");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we make it more obvious that this is related to concepts somehow? you just have a single arm in the switch, so it may also be preferable to just use an if, unless you plan to support more feild types.

Copy link
Member Author

@evelez7 evelez7 Jun 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at it now all of the reference error messages are not great. Makes it sound like the type cannot hold any Reference, but it should sound more like this info doesn't hold a Reference to this type.

Maybe in another patch, the FieldId can be emitted as part of the error message?

Something like "ConstraintInfo cannot contain Reference to {FieldId}".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM.

Comment on lines +696 to +697
template <> void addTemplate(ConceptInfo *I, TemplateInfo &&P) {
I->Template = std::move(P);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not something you need to fix here, but dang do we require a lot of boiler-plate changes to the APIs. It's almost like we're not holding templates in the normal way...

@evelez7 evelez7 force-pushed the users/evelez7/clang-doc-precommit-concept-tests branch from b602047 to 9c18fd3 Compare June 20, 2025 21:20
@evelez7 evelez7 force-pushed the users/evelez7/clang-doc-document-concepts branch from 2febdc8 to 63143f0 Compare June 20, 2025 21:20
Base automatically changed from users/evelez7/clang-doc-precommit-concept-tests to main June 20, 2025 23:38
@evelez7 evelez7 force-pushed the users/evelez7/clang-doc-document-concepts branch from 63143f0 to 00e0bac Compare June 20, 2025 23:40
Copy link
Member Author

evelez7 commented Jun 21, 2025

Merge activity

  • Jun 21, 12:38 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jun 21, 12:39 AM UTC: @evelez7 merged this pull request with Graphite.

@evelez7 evelez7 merged commit 8050a6e into main Jun 21, 2025
7 checks passed
@evelez7 evelez7 deleted the users/evelez7/clang-doc-document-concepts branch June 21, 2025 00:39
Jaddyen pushed a commit to Jaddyen/llvm-project that referenced this pull request Jun 23, 2025
Add support for documenting concepts. This handles concepts and constraints on function and class templates.

Atomic constraints are not considered yet. We don't order constraints based on their conjunctive or disjunctive properties.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants