diff --git a/clang/include/clang/SymbolGraph/API.h b/clang/include/clang/SymbolGraph/API.h --- a/clang/include/clang/SymbolGraph/API.h +++ b/clang/include/clang/SymbolGraph/API.h @@ -43,6 +43,8 @@ /// Discriminator for LLVM-style RTTI (dyn_cast<> et al.) enum RecordKind { RK_Global, + RK_EnumConstant, + RK_Enum, }; private: @@ -88,6 +90,34 @@ } }; +struct EnumConstantRecord : APIRecord { + EnumConstantRecord(StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading) + : APIRecord(RK_EnumConstant, Name, USR, Loc, Availability, + LinkageInfo::none(), Comment, Declaration, SubHeading) {} + + static bool classof(const APIRecord *Record) { + return Record->getKind() == RK_EnumConstant; + } +}; + +struct EnumRecord : APIRecord { + SmallVector Constants; + + EnumRecord(StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, const DocComment &Comment, + DeclarationFragments Declaration, DeclarationFragments SubHeading) + : APIRecord(RK_Enum, Name, USR, Loc, Availability, LinkageInfo::none(), + Comment, Declaration, SubHeading) {} + + static bool classof(const APIRecord *Record) { + return Record->getKind() == RK_Enum; + } +}; + class APISet { public: APISet(const llvm::Triple &Target, const LangOptions &LangOpts) @@ -116,13 +146,28 @@ DeclarationFragments SubHeading, FunctionSignature Signature); + EnumConstantRecord *addEnumConstant(EnumRecord *Enum, StringRef Name, + StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading); + + EnumRecord *addEnum(StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading); + StringRef recordUSR(const Decl *D); StringRef copyString(StringRef String, llvm::BumpPtrAllocator &Allocator); StringRef copyString(StringRef String); using GlobalRecordMap = llvm::MapVector; + using EnumRecordMap = llvm::MapVector; const GlobalRecordMap &getGlobals() const { return Globals; } + const EnumRecordMap &getEnums() const { return Enums; } private: llvm::BumpPtrAllocator Allocator; @@ -130,6 +175,7 @@ const LangOptions LangOpts; GlobalRecordMap Globals; + EnumRecordMap Enums; }; } // namespace symbolgraph diff --git a/clang/include/clang/SymbolGraph/DeclarationFragments.h b/clang/include/clang/SymbolGraph/DeclarationFragments.h --- a/clang/include/clang/SymbolGraph/DeclarationFragments.h +++ b/clang/include/clang/SymbolGraph/DeclarationFragments.h @@ -117,6 +117,10 @@ public: static DeclarationFragments getFragmentsForVar(const VarDecl *); static DeclarationFragments getFragmentsForFunction(const FunctionDecl *); + static DeclarationFragments + getFragmentsForEnumConstant(const EnumConstantDecl *); + static DeclarationFragments getFragmentsForEnum(const EnumDecl *); + static DeclarationFragments getSubHeading(const NamedDecl *); static FunctionSignature getFunctionSignature(const FunctionDecl *); diff --git a/clang/include/clang/SymbolGraph/Serialization.h b/clang/include/clang/SymbolGraph/Serialization.h --- a/clang/include/clang/SymbolGraph/Serialization.h +++ b/clang/include/clang/SymbolGraph/Serialization.h @@ -36,11 +36,19 @@ Object serialize(); void serialize(raw_ostream &os); + enum RelationshipKind { + MemberOf, + }; + private: + void serializeGlobalRecord(const GlobalRecord &Record); + void serializeEnumRecord(const EnumRecord &Record); + Object serializeMetadata() const; Object serializeModule() const; Optional serializeAPIRecord(const APIRecord &Record) const; - void serializeGlobalRecord(const GlobalRecord &Record); + void serializeRelationship(RelationshipKind Kind, const APIRecord &Source, + const APIRecord &Target); bool shouldSkip(const APIRecord &Record) const; diff --git a/clang/lib/SymbolGraph/API.cpp b/clang/lib/SymbolGraph/API.cpp --- a/clang/lib/SymbolGraph/API.cpp +++ b/clang/lib/SymbolGraph/API.cpp @@ -59,6 +59,30 @@ Comment, Fragments, SubHeading, Signature); } +EnumConstantRecord *APISet::addEnumConstant( + EnumRecord *Enum, StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, const DocComment &Comment, + DeclarationFragments Declaration, DeclarationFragments SubHeading) { + EnumConstantRecord *Record = new (Allocator) EnumConstantRecord{ + Name, USR, Loc, Availability, Comment, Declaration, SubHeading}; + Enum->Constants.push_back(Record); + return Record; +} + +EnumRecord *APISet::addEnum(StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading) { + auto Result = Enums.insert({Name, nullptr}); + if (Result.second) { + EnumRecord *Record = new (Allocator) EnumRecord{ + Name, USR, Loc, Availability, Comment, Declaration, SubHeading}; + Result.first->second = Record; + } + return Result.first->second; +} + StringRef APISet::recordUSR(const Decl *D) { SmallString<128> USR; index::generateUSRForDecl(D, USR); diff --git a/clang/lib/SymbolGraph/DeclarationFragments.cpp b/clang/lib/SymbolGraph/DeclarationFragments.cpp --- a/clang/lib/SymbolGraph/DeclarationFragments.cpp +++ b/clang/lib/SymbolGraph/DeclarationFragments.cpp @@ -399,6 +399,35 @@ return Fragments; } +DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForEnumConstant( + const EnumConstantDecl *EnumConstDecl) { + DeclarationFragments Fragments; + return Fragments.append(EnumConstDecl->getName(), + DeclarationFragments::FragmentKind::Identifier); +} + +DeclarationFragments +DeclarationFragmentsBuilder::getFragmentsForEnum(const EnumDecl *EnumDecl) { + // TODO: After we support typedef records, if there's a typedef for this enum + // just use the declaration fragments of the typedef decl. + + DeclarationFragments Fragments, After; + Fragments.append("enum", DeclarationFragments::FragmentKind::Keyword); + + if (!EnumDecl->getName().empty()) + Fragments.appendSpace().append( + EnumDecl->getName(), DeclarationFragments::FragmentKind::Identifier); + + QualType IntegerType = EnumDecl->getIntegerType(); + if (!IntegerType.isNull()) + Fragments.append(": ", DeclarationFragments::FragmentKind::Text) + .append( + getFragmentsForType(IntegerType, EnumDecl->getASTContext(), After)) + .append(std::move(After)); + + return Fragments; +} + FunctionSignature DeclarationFragmentsBuilder::getFunctionSignature(const FunctionDecl *Func) { FunctionSignature Signature; diff --git a/clang/lib/SymbolGraph/ExtractAPIConsumer.cpp b/clang/lib/SymbolGraph/ExtractAPIConsumer.cpp --- a/clang/lib/SymbolGraph/ExtractAPIConsumer.cpp +++ b/clang/lib/SymbolGraph/ExtractAPIConsumer.cpp @@ -134,6 +134,36 @@ return true; } + bool VisitEnumDecl(const EnumDecl *Decl) { + if (!Decl->isComplete()) + return true; + + // Skip forward declaration. + if (!Decl->isThisDeclarationADefinition()) + return true; + + StringRef Name = Decl->getName(); + StringRef USR = API.recordUSR(Decl); + PresumedLoc Loc = + Context.getSourceManager().getPresumedLoc(Decl->getLocation()); + AvailabilityInfo Availability = getAvailability(Decl); + DocComment Comment; + if (auto *RawComment = Context.getRawCommentForDeclNoCache(Decl)) + Comment = RawComment->getFormattedLines(Context.getSourceManager(), + Context.getDiagnostics()); + DeclarationFragments Declaration = + DeclarationFragmentsBuilder::getFragmentsForEnum(Decl); + DeclarationFragments SubHeading = + DeclarationFragmentsBuilder::getSubHeading(Decl); + + EnumRecord *EnumRecord = API.addEnum(Name, USR, Loc, Availability, Comment, + Declaration, SubHeading); + + recordEnumConstants(EnumRecord, Decl->enumerators()); + + return true; + } + private: AvailabilityInfo getAvailability(const Decl *D) const { StringRef PlatformName = Context.getTargetInfo().getPlatformName(); @@ -164,6 +194,28 @@ return Availability; } + void recordEnumConstants(EnumRecord *EnumRecord, + const EnumDecl::enumerator_range Constants) { + for (const auto *Constant : Constants) { + StringRef Name = Constant->getName(); + StringRef USR = API.recordUSR(Constant); + PresumedLoc Loc = + Context.getSourceManager().getPresumedLoc(Constant->getLocation()); + AvailabilityInfo Availability = getAvailability(Constant); + DocComment Comment; + if (auto *RawComment = Context.getRawCommentForDeclNoCache(Constant)) + Comment = RawComment->getFormattedLines(Context.getSourceManager(), + Context.getDiagnostics()); + DeclarationFragments Declaration = + DeclarationFragmentsBuilder::getFragmentsForEnumConstant(Constant); + DeclarationFragments SubHeading = + DeclarationFragmentsBuilder::getSubHeading(Constant); + + API.addEnumConstant(EnumRecord, Name, USR, Loc, Availability, Comment, + Declaration, SubHeading); + } + } + ASTContext &Context; APISet API; }; diff --git a/clang/lib/SymbolGraph/Serialization.cpp b/clang/lib/SymbolGraph/Serialization.cpp --- a/clang/lib/SymbolGraph/Serialization.cpp +++ b/clang/lib/SymbolGraph/Serialization.cpp @@ -225,17 +225,21 @@ // SymbolGraph: Symbol::kind static Object serializeSymbolKind(const APIRecord &Record, const LangOptions &LangOpts) { + auto AddLangPrefix = [&LangOpts](StringRef S) -> std::string { + return (getLanguageName(LangOpts) + "." + S).str(); + }; + Object Kind; switch (Record.getKind()) { - case APIRecord::RK_Global: + case APIRecord::RK_Global: { auto *GR = dyn_cast(&Record); switch (GR->GlobalKind) { case GVKind::Function: - Kind["identifier"] = (getLanguageName(LangOpts) + ".func").str(); + Kind["identifier"] = AddLangPrefix("func"); Kind["displayName"] = "Function"; break; case GVKind::Variable: - Kind["identifier"] = (getLanguageName(LangOpts) + ".var").str(); + Kind["identifier"] = AddLangPrefix("var"); Kind["displayName"] = "Global Variable"; break; case GVKind::Unknown: @@ -244,6 +248,15 @@ } break; } + case APIRecord::RK_EnumConstant: + Kind["identifier"] = AddLangPrefix("enum.case"); + Kind["displayName"] = "Enum Case"; + break; + case APIRecord::RK_Enum: + Kind["identifier"] = AddLangPrefix("enum"); + Kind["displayName"] = "Enum"; + break; + } return Kind; } @@ -297,6 +310,21 @@ return Obj; } +void Serializer::serializeRelationship(RelationshipKind Kind, + const APIRecord &Source, + const APIRecord &Target) { + Object Relationship; + Relationship["source"] = Source.USR; + Relationship["target"] = Target.USR; + switch (Kind) { + case RelationshipKind::MemberOf: + Relationship["kind"] = "memberOf"; + break; + } + + Relationships.emplace_back(std::move(Relationship)); +} + void Serializer::serializeGlobalRecord(const GlobalRecord &Record) { auto Obj = serializeAPIRecord(Record); if (!Obj) @@ -309,6 +337,23 @@ Symbols.emplace_back(std::move(*Obj)); } +void Serializer::serializeEnumRecord(const EnumRecord &Record) { + auto Enum = serializeAPIRecord(Record); + if (!Enum) + return; + + Symbols.emplace_back(std::move(*Enum)); + + for (const auto &Constant : Record.Constants) { + auto EnumConstant = serializeAPIRecord(*Constant); + if (!EnumConstant) + continue; + + Symbols.emplace_back(std::move(*EnumConstant)); + serializeRelationship(RelationshipKind::MemberOf, *Constant, Record); + } +} + Object Serializer::serialize() { Object Root; serializeObject(Root, "metadata", serializeMetadata()); @@ -317,6 +362,9 @@ for (const auto &Global : API.getGlobals()) serializeGlobalRecord(*Global.second); + for (const auto &Enum : API.getEnums()) + serializeEnumRecord(*Enum.second); + Root["symbols"] = std::move(Symbols); Root["relationhips"] = std::move(Relationships); diff --git a/clang/test/SymbolGraph/enum.c b/clang/test/SymbolGraph/enum.c new file mode 100644 --- /dev/null +++ b/clang/test/SymbolGraph/enum.c @@ -0,0 +1,505 @@ +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: sed -e "s@INPUT_DIR@%/t@g" %t/reference.output.json.in >> \ +// RUN: %t/reference.output.json +// RUN: %clang -extract-api -target arm64-apple-macosx \ +// RUN: %t/input.c -o %t/output.json | FileCheck -allow-empty %s + +// Generator version is not consistent across test runs, normalize it. +// RUN: sed -e "s@\"generator\": \".*\"@\"generator\": \"?\"@g" \ +// RUN: %t/output.json >> %t/output-normalized.json +// RUN: diff %t/reference.output.json %t/output-normalized.json + +// CHECK-NOT: error: +// CHECK-NOT: warning: + +//--- input.c +/// Kinds of vehicles +enum Vehicle { + Bicycle, + Car, + Train, ///< Move this to the top! -Sheldon + Ship, + Airplane, +}; + +enum Direction : unsigned char { + North = 0, + East, + South, + West +}; + +//--- reference.output.json.in +{ + "metadata": { + "formatVersion": { + "major": 0, + "minor": 5, + "patch": 3 + }, + "generator": "?" + }, + "module": { + "name": "", + "platform": { + "architecture": "arm64", + "operatingSystem": { + "minimumVersion": { + "major": 11, + "minor": 0, + "patch": 0 + }, + "name": "macosx" + }, + "vendor": "apple" + } + }, + "relationhips": [ + { + "kind": "memberOf", + "source": "c:@E@Vehicle@Bicycle", + "target": "c:@E@Vehicle" + }, + { + "kind": "memberOf", + "source": "c:@E@Vehicle@Car", + "target": "c:@E@Vehicle" + }, + { + "kind": "memberOf", + "source": "c:@E@Vehicle@Train", + "target": "c:@E@Vehicle" + }, + { + "kind": "memberOf", + "source": "c:@E@Vehicle@Ship", + "target": "c:@E@Vehicle" + }, + { + "kind": "memberOf", + "source": "c:@E@Vehicle@Airplane", + "target": "c:@E@Vehicle" + }, + { + "kind": "memberOf", + "source": "c:@E@Direction@North", + "target": "c:@E@Direction" + }, + { + "kind": "memberOf", + "source": "c:@E@Direction@East", + "target": "c:@E@Direction" + }, + { + "kind": "memberOf", + "source": "c:@E@Direction@South", + "target": "c:@E@Direction" + }, + { + "kind": "memberOf", + "source": "c:@E@Direction@West", + "target": "c:@E@Direction" + } + ], + "symbols": [ + { + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "enum" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Vehicle" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:i", + "spelling": "unsigned int" + } + ], + "docComment": { + "lines": [ + { + "range": { + "end": { + "character": 22, + "line": 1 + }, + "start": { + "character": 5, + "line": 1 + } + }, + "text": "Kinds of vehicles" + } + ] + }, + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Vehicle" + }, + "kind": { + "displayName": "Enum", + "identifier": "c.enum" + }, + "location": { + "character": 6, + "line": 2, + "uri": "file://INPUT_DIR/input.c" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Vehicle" + } + ], + "title": "Vehicle" + } + }, + { + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "Bicycle" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Vehicle@Bicycle" + }, + "kind": { + "displayName": "Enum Case", + "identifier": "c.enum.case" + }, + "location": { + "character": 3, + "line": 3, + "uri": "file://INPUT_DIR/input.c" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Bicycle" + } + ], + "title": "Bicycle" + } + }, + { + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "Car" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Vehicle@Car" + }, + "kind": { + "displayName": "Enum Case", + "identifier": "c.enum.case" + }, + "location": { + "character": 3, + "line": 4, + "uri": "file://INPUT_DIR/input.c" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Car" + } + ], + "title": "Car" + } + }, + { + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "Train" + } + ], + "docComment": { + "lines": [ + { + "range": { + "end": { + "character": 45, + "line": 5 + }, + "start": { + "character": 15, + "line": 5 + } + }, + "text": "Move this to the top! -Sheldon" + } + ] + }, + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Vehicle@Train" + }, + "kind": { + "displayName": "Enum Case", + "identifier": "c.enum.case" + }, + "location": { + "character": 3, + "line": 5, + "uri": "file://INPUT_DIR/input.c" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Train" + } + ], + "title": "Train" + } + }, + { + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "Ship" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Vehicle@Ship" + }, + "kind": { + "displayName": "Enum Case", + "identifier": "c.enum.case" + }, + "location": { + "character": 3, + "line": 6, + "uri": "file://INPUT_DIR/input.c" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Ship" + } + ], + "title": "Ship" + } + }, + { + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "Airplane" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Vehicle@Airplane" + }, + "kind": { + "displayName": "Enum Case", + "identifier": "c.enum.case" + }, + "location": { + "character": 3, + "line": 7, + "uri": "file://INPUT_DIR/input.c" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Airplane" + } + ], + "title": "Airplane" + } + }, + { + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "enum" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Direction" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:c", + "spelling": "unsigned char" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Direction" + }, + "kind": { + "displayName": "Enum", + "identifier": "c.enum" + }, + "location": { + "character": 6, + "line": 10, + "uri": "file://INPUT_DIR/input.c" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Direction" + } + ], + "title": "Direction" + } + }, + { + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "North" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Direction@North" + }, + "kind": { + "displayName": "Enum Case", + "identifier": "c.enum.case" + }, + "location": { + "character": 3, + "line": 11, + "uri": "file://INPUT_DIR/input.c" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "North" + } + ], + "title": "North" + } + }, + { + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "East" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Direction@East" + }, + "kind": { + "displayName": "Enum Case", + "identifier": "c.enum.case" + }, + "location": { + "character": 3, + "line": 12, + "uri": "file://INPUT_DIR/input.c" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "East" + } + ], + "title": "East" + } + }, + { + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "South" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Direction@South" + }, + "kind": { + "displayName": "Enum Case", + "identifier": "c.enum.case" + }, + "location": { + "character": 3, + "line": 13, + "uri": "file://INPUT_DIR/input.c" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "South" + } + ], + "title": "South" + } + }, + { + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "West" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Direction@West" + }, + "kind": { + "displayName": "Enum Case", + "identifier": "c.enum.case" + }, + "location": { + "character": 3, + "line": 14, + "uri": "file://INPUT_DIR/input.c" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "West" + } + ], + "title": "West" + } + } + ] +}