https://github.com/evelez7 updated https://github.com/llvm/llvm-project/pull/144430
>From 72e4a2441b93546c3e275078d1525ae01e73e361 Mon Sep 17 00:00:00 2001 From: Erick Velez <erickvel...@gmail.com> Date: Mon, 16 Jun 2025 10:50:35 -0700 Subject: [PATCH] remove extraneous change --- 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 | 46 +++++++ 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, 494 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..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 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 ""; } _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits