[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.
This commit is contained in:
Erick Velez
2025-06-20 17:39:31 -07:00
committed by GitHub
parent 2dfcc4375f
commit 8050a6e073
19 changed files with 507 additions and 56 deletions

View File

@@ -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)

View File

@@ -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;

View File

@@ -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 @@ public:
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:

View File

@@ -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");
}

View File

@@ -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");
}

View File

@@ -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:

View File

@@ -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");

View File

@@ -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);

View File

@@ -41,6 +41,7 @@ public:
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);

View File

@@ -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

View File

@@ -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) {}

View File

@@ -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

View File

@@ -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

View File

@@ -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");

View File

@@ -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: ]

View File

@@ -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: ],

View File

@@ -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: }

View File

@@ -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: ]

View File

@@ -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 "";
}