[clang-tools-extra] 8050a6e - [clang-doc] add support for concepts (#144430)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Jun 20 17:39:35 PDT 2025
Author: Erick Velez
Date: 2025-06-20T17:39:31-07:00
New Revision: 8050a6e0732c6614ce3e5296fdeb5a3c36bde26d
URL: https://github.com/llvm/llvm-project/commit/8050a6e0732c6614ce3e5296fdeb5a3c36bde26d
DIFF: https://github.com/llvm/llvm-project/commit/8050a6e0732c6614ce3e5296fdeb5a3c36bde26d.diff
LOG: [clang-doc] add support for concepts (#144430)
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.
Added:
clang-tools-extra/test/clang-doc/json/compound-constraints.cpp
Modified:
clang-tools-extra/clang-doc/BitcodeReader.cpp
clang-tools-extra/clang-doc/BitcodeWriter.cpp
clang-tools-extra/clang-doc/BitcodeWriter.h
clang-tools-extra/clang-doc/HTMLGenerator.cpp
clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
clang-tools-extra/clang-doc/JSONGenerator.cpp
clang-tools-extra/clang-doc/MDGenerator.cpp
clang-tools-extra/clang-doc/Mapper.cpp
clang-tools-extra/clang-doc/Mapper.h
clang-tools-extra/clang-doc/Representation.cpp
clang-tools-extra/clang-doc/Representation.h
clang-tools-extra/clang-doc/Serialize.cpp
clang-tools-extra/clang-doc/Serialize.h
clang-tools-extra/clang-doc/YAMLGenerator.cpp
clang-tools-extra/test/clang-doc/json/class-requires.cpp
clang-tools-extra/test/clang-doc/json/concept.cpp
clang-tools-extra/test/clang-doc/json/function-requires.cpp
clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp
Removed:
################################################################################
diff --git a/clang-tools-extra/clang-doc/BitcodeReader.cpp b/clang-tools-extra/clang-doc/BitcodeReader.cpp
index 35058abab0663..66852931226bf 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->ConstraintExpr, 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,17 @@ template <> llvm::Error addReference(RecordInfo *I, Reference &&R, FieldId F) {
}
}
+template <>
+llvm::Error addReference(ConstraintInfo *I, Reference &&R, FieldId F) {
+ if (F == FieldId::F_concept) {
+ I->ConceptRef = std::move(R);
+ return llvm::Error::success();
+ }
+ return llvm::createStringError(
+ llvm::inconvertibleErrorCode(),
+ "ConstraintInfo cannot contain this Reference");
+}
+
template <typename T, typename ChildInfoType>
static void addChild(T I, ChildInfoType &&R) {
llvm::errs() << "invalid child type for info";
@@ -600,6 +640,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 +692,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 +708,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) {
@@ -716,6 +770,8 @@ llvm::Error ClangDocBitcodeReader::readBlock(unsigned ID, T I) {
}
}
+// TODO: Create a helper that can receive a function to reduce repetition for
+// most blocks.
template <typename T>
llvm::Error ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) {
llvm::TimeTraceScope("Reducing infos", "readSubBlock");
@@ -817,6 +873,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 +992,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 +1034,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..b7308c012786f 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.ConstraintExpr, 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..8a37621597c6a 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -26,6 +26,15 @@ 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);
+
+// Convenience lambda to pass to serializeArray.
+// If a serializeInfo needs a RepositoryUrl, create a local lambda that captures
+// the optional.
+static auto SerializeInfoLambda = [](const ConstraintInfo &Info,
+ Object &Object) {
+ serializeInfo(Info, Object);
+};
static json::Object serializeLocation(const Location &Loc,
std::optional<StringRef> RepositoryUrl) {
@@ -248,6 +257,27 @@ static void serializeCommonChildren(const ScopeChildren &Children,
}
}
+template <typename T, typename SerializationFunc>
+static void serializeArray(const std::vector<T> &Records, Object &Obj,
+ const std::string &Key,
+ SerializationFunc SerializeInfo) {
+ 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.ConstraintExpr;
+}
+
static void serializeInfo(const TemplateInfo &Template, Object &Obj) {
json::Value TemplateVal = Object();
auto &TemplateObj = *TemplateVal.getAsObject();
@@ -277,9 +307,21 @@ static void serializeInfo(const TemplateInfo &Template, Object &Obj) {
TemplateObj["Parameters"] = ParamsArray;
}
+ if (!Template.Constraints.empty())
+ serializeArray(Template.Constraints, TemplateObj, "Constraints",
+ SerializeInfoLambda);
+
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;
@@ -457,6 +499,10 @@ static void serializeInfo(const NamespaceInfo &I, json::Object &Obj,
Obj["Namespaces"] = NamespacesArray;
}
+ auto SerializeInfo = [RepositoryUrl](const auto &Info, Object &Object) {
+ serializeInfo(Info, Object, RepositoryUrl);
+ };
+
if (!I.Children.Functions.empty()) {
json::Value FunctionsArray = Array();
auto &FunctionsArrayRef = *FunctionsArray.getAsArray();
@@ -470,6 +516,9 @@ static void serializeInfo(const NamespaceInfo &I, json::Object &Obj,
Obj["Functions"] = FunctionsArray;
}
+ if (!I.Children.Concepts.empty())
+ serializeArray(I.Children.Concepts, Obj, "Concepts", SerializeInfo);
+
serializeCommonChildren(I.Children, Obj, RepositoryUrl);
}
@@ -520,6 +569,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 71a926f1c73e0..286aeeea1001b 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");
@@ -288,6 +290,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));
}
@@ -352,6 +355,19 @@ void TypedefInfo::merge(TypedefInfo &&Other) {
SymbolInfo::merge(std::move(Other));
}
+void ConceptInfo::merge(ConceptInfo &&Other) {
+ assert(mergeable(Other));
+ if (!IsType)
+ IsType = Other.IsType;
+ if (ConstraintExpression.empty())
+ ConstraintExpression = std::move(Other.ConstraintExpression);
+ if (Template.Constraints.empty())
+ Template.Constraints = std::move(Other.Template.Constraints);
+ if (Template.Params.empty())
+ Template.Params = std::move(Other.Template.Params);
+ SymbolInfo::merge(std::move(Other));
+}
+
BaseRecordInfo::BaseRecordInfo() : RecordInfo() {}
BaseRecordInfo::BaseRecordInfo(SymbolID USR, StringRef Name, StringRef Path,
@@ -388,6 +404,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)));
}
@@ -453,6 +472,7 @@ void ScopeChildren::sort() {
llvm::sort(Functions.begin(), Functions.end());
llvm::sort(Enums.begin(), Enums.end());
llvm::sort(Typedefs.begin(), Typedefs.end());
+ llvm::sort(Concepts.begin(), Concepts.end());
}
} // namespace doc
} // namespace clang
diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h
index 75da500645819..b23069f2bd324 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> ConstraintExpr;
+};
+
// 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 820e8bfd8e644..5f3e5c37fa34d 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 "";
@@ -491,6 +504,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.
//
@@ -531,6 +548,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");
@@ -740,6 +758,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.ConstraintExpr = 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.ConstraintExpr = 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) {
@@ -751,6 +813,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 =
@@ -903,6 +967,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)) {
@@ -1074,6 +1140,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 624f71c6bf9b3..887c9d79146a0 100644
--- a/clang-tools-extra/test/clang-doc/json/concept.cpp
+++ b/clang-tools-extra/test/clang-doc/json/concept.cpp
@@ -1,5 +1,6 @@
// 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
// Requires that T suports post and pre-incrementing.
template<typename T>
@@ -8,30 +9,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 cfe-commits
mailing list