Author: Zixu Wang Date: 2022-03-23T09:45:06-07:00 New Revision: 5bb5704c1b35023b8a6217a6eb7d98a47efe1ca2
URL: https://github.com/llvm/llvm-project/commit/5bb5704c1b35023b8a6217a6eb7d98a47efe1ca2 DIFF: https://github.com/llvm/llvm-project/commit/5bb5704c1b35023b8a6217a6eb7d98a47efe1ca2.diff LOG: [clang][extract-api] Add struct support - Add `StructFieldRecord` and `StructRecord` to store API information for structs - Implement `VisitRecordDecl` in `ExtractAPIVisitor` - Implement Symbol Graph serialization for struct records. - Add test case for struct records. Depends on D121873 Differential Revision: https://reviews.llvm.org/D122202 Added: clang/test/ExtractAPI/struct.c Modified: clang/include/clang/ExtractAPI/API.h clang/include/clang/ExtractAPI/DeclarationFragments.h clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h clang/lib/ExtractAPI/API.cpp clang/lib/ExtractAPI/DeclarationFragments.cpp clang/lib/ExtractAPI/ExtractAPIConsumer.cpp clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp Removed: ################################################################################ diff --git a/clang/include/clang/ExtractAPI/API.h b/clang/include/clang/ExtractAPI/API.h index 52b78d9188a1c..e8f9219358ccf 100644 --- a/clang/include/clang/ExtractAPI/API.h +++ b/clang/include/clang/ExtractAPI/API.h @@ -94,6 +94,8 @@ struct APIRecord { RK_Global, RK_EnumConstant, RK_Enum, + RK_StructField, + RK_Struct, }; private: @@ -176,6 +178,36 @@ struct EnumRecord : APIRecord { } }; +/// This holds information associated with struct fields. +struct StructFieldRecord : APIRecord { + StructFieldRecord(StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, + const DocComment &Comment, DeclarationFragments Declaration, + DeclarationFragments SubHeading) + : APIRecord(RK_StructField, Name, USR, Loc, Availability, + LinkageInfo::none(), Comment, Declaration, SubHeading) {} + + static bool classof(const APIRecord *Record) { + return Record->getKind() == RK_StructField; + } +}; + +/// This holds information associated with structs. +struct StructRecord : APIRecord { + SmallVector<APIRecordUniquePtr<StructFieldRecord>> Fields; + + StructRecord(StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading) + : APIRecord(RK_Struct, Name, USR, Loc, Availability, LinkageInfo::none(), + Comment, Declaration, SubHeading) {} + + static bool classof(const APIRecord *Record) { + return Record->getKind() == RK_Struct; + } +}; + /// APISet holds the set of API records collected from given inputs. class APISet { public: @@ -242,6 +274,31 @@ class APISet { DeclarationFragments Declaration, DeclarationFragments SubHeading); + /// Create and add a struct field record into the API set. + /// + /// Note: the caller is responsible for keeping the StringRef \p Name and + /// \p USR alive. APISet::copyString provides a way to copy strings into + /// APISet itself, and APISet::recordUSR(const Decl *D) is a helper method + /// to generate the USR for \c D and keep it alive in APISet. + StructFieldRecord *addStructField(StructRecord *Struct, StringRef Name, + StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading); + + /// Create and add a struct record into the API set. + /// + /// Note: the caller is responsible for keeping the StringRef \p Name and + /// \p USR alive. APISet::copyString provides a way to copy strings into + /// APISet itself, and APISet::recordUSR(const Decl *D) is a helper method + /// to generate the USR for \c D and keep it alive in APISet. + StructRecord *addStruct(StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading); + /// A map to store the set of GlobalRecord%s with the declaration name as the /// key. using GlobalRecordMap = @@ -252,6 +309,11 @@ class APISet { using EnumRecordMap = llvm::MapVector<StringRef, APIRecordUniquePtr<EnumRecord>>; + /// A map to store the set of StructRecord%s with the declaration name as the + /// key. + using StructRecordMap = + llvm::MapVector<StringRef, APIRecordUniquePtr<StructRecord>>; + /// Get the target triple for the ExtractAPI invocation. const llvm::Triple &getTarget() const { return Target; } @@ -260,6 +322,7 @@ class APISet { const GlobalRecordMap &getGlobals() const { return Globals; } const EnumRecordMap &getEnums() const { return Enums; } + const StructRecordMap &getStructs() const { return Structs; } /// Generate and store the USR of declaration \p D. /// @@ -285,6 +348,7 @@ class APISet { GlobalRecordMap Globals; EnumRecordMap Enums; + StructRecordMap Structs; }; } // namespace extractapi diff --git a/clang/include/clang/ExtractAPI/DeclarationFragments.h b/clang/include/clang/ExtractAPI/DeclarationFragments.h index 7e76a57a48189..f8ef431b8525d 100644 --- a/clang/include/clang/ExtractAPI/DeclarationFragments.h +++ b/clang/include/clang/ExtractAPI/DeclarationFragments.h @@ -196,6 +196,12 @@ class DeclarationFragmentsBuilder { /// Build DeclarationFragments for an enum declaration EnumDecl. static DeclarationFragments getFragmentsForEnum(const EnumDecl *); + /// Build DeclarationFragments for a field declaration FieldDecl. + static DeclarationFragments getFragmentsForField(const FieldDecl *); + + /// Build DeclarationFragments for a struct record declaration RecordDecl. + static DeclarationFragments getFragmentsForStruct(const RecordDecl *); + /// Build sub-heading fragments for a NamedDecl. static DeclarationFragments getSubHeading(const NamedDecl *); diff --git a/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h b/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h index 2423f511480d4..109833f474948 100644 --- a/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h +++ b/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h @@ -110,6 +110,9 @@ class SymbolGraphSerializer : public APISerializer { /// Serialize an enum record. void serializeEnumRecord(const EnumRecord &Record); + /// Serialize a struct record. + void serializeStructRecord(const StructRecord &Record); + public: SymbolGraphSerializer(const APISet &API, StringRef ProductName, APISerializerOption Options = {}) diff --git a/clang/lib/ExtractAPI/API.cpp b/clang/lib/ExtractAPI/API.cpp index fd9e92cc7b2bf..7aa82336ee2ca 100644 --- a/clang/lib/ExtractAPI/API.cpp +++ b/clang/lib/ExtractAPI/API.cpp @@ -84,6 +84,33 @@ EnumRecord *APISet::addEnum(StringRef Name, StringRef USR, PresumedLoc Loc, return Result.first->second.get(); } +StructFieldRecord *APISet::addStructField(StructRecord *Struct, StringRef Name, + StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading) { + auto Record = + APIRecordUniquePtr<StructFieldRecord>(new (Allocator) StructFieldRecord{ + Name, USR, Loc, Availability, Comment, Declaration, SubHeading}); + return Struct->Fields.emplace_back(std::move(Record)).get(); +} + +StructRecord *APISet::addStruct(StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading) { + auto Result = Structs.insert({Name, nullptr}); + if (Result.second) { + // Create the record if it does not already exist. + auto Record = APIRecordUniquePtr<StructRecord>(new (Allocator) StructRecord{ + Name, USR, Loc, Availability, Comment, Declaration, SubHeading}); + Result.first->second = std::move(Record); + } + return Result.first->second.get(); +} + StringRef APISet::recordUSR(const Decl *D) { SmallString<128> USR; index::generateUSRForDecl(D, USR); diff --git a/clang/lib/ExtractAPI/DeclarationFragments.cpp b/clang/lib/ExtractAPI/DeclarationFragments.cpp index daa01c7dcd13b..1980e8e6dcc69 100644 --- a/clang/lib/ExtractAPI/DeclarationFragments.cpp +++ b/clang/lib/ExtractAPI/DeclarationFragments.cpp @@ -430,6 +430,30 @@ DeclarationFragmentsBuilder::getFragmentsForEnum(const EnumDecl *EnumDecl) { return Fragments; } +DeclarationFragments +DeclarationFragmentsBuilder::getFragmentsForField(const FieldDecl *Field) { + DeclarationFragments After; + return getFragmentsForType(Field->getType(), Field->getASTContext(), After) + .appendSpace() + .append(Field->getName(), DeclarationFragments::FragmentKind::Identifier) + .append(std::move(After)); +} + +DeclarationFragments +DeclarationFragmentsBuilder::getFragmentsForStruct(const RecordDecl *Record) { + // TODO: After we support typedef records, if there's a typedef for this + // struct just use the declaration fragments of the typedef decl. + + DeclarationFragments Fragments; + Fragments.append("struct", DeclarationFragments::FragmentKind::Keyword); + + if (!Record->getName().empty()) + Fragments.appendSpace().append( + Record->getName(), DeclarationFragments::FragmentKind::Identifier); + + return Fragments; +} + FunctionSignature DeclarationFragmentsBuilder::getFunctionSignature(const FunctionDecl *Func) { FunctionSignature Signature; diff --git a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp index 9c8adcf9347ba..7007d08e839be 100644 --- a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp +++ b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp @@ -179,6 +179,41 @@ class ExtractAPIVisitor : public RecursiveASTVisitor<ExtractAPIVisitor> { return true; } + bool VisitRecordDecl(const RecordDecl *Decl) { + if (!Decl->isCompleteDefinition()) + return true; + + // Skip C++ structs/classes/unions + // TODO: support C++ records + if (isa<CXXRecordDecl>(Decl)) + return true; + + // Collect symbol information. + 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()); + + // Build declaration fragments and sub-heading for the struct. + DeclarationFragments Declaration = + DeclarationFragmentsBuilder::getFragmentsForStruct(Decl); + DeclarationFragments SubHeading = + DeclarationFragmentsBuilder::getSubHeading(Decl); + + StructRecord *StructRecord = API.addStruct( + Name, USR, Loc, Availability, Comment, Declaration, SubHeading); + + // Now collect information about the fields in this struct. + recordStructFields(StructRecord, Decl->fields()); + + return true; + } + private: /// Get availability information of the declaration \p D. AvailabilityInfo getAvailability(const Decl *D) const { @@ -238,6 +273,33 @@ class ExtractAPIVisitor : public RecursiveASTVisitor<ExtractAPIVisitor> { } } + /// Collect API information for the struct fields and associate with the + /// parent struct. + void recordStructFields(StructRecord *StructRecord, + const RecordDecl::field_range Fields) { + for (const auto *Field : Fields) { + // Collect symbol information. + StringRef Name = Field->getName(); + StringRef USR = API.recordUSR(Field); + PresumedLoc Loc = + Context.getSourceManager().getPresumedLoc(Field->getLocation()); + AvailabilityInfo Availability = getAvailability(Field); + DocComment Comment; + if (auto *RawComment = Context.getRawCommentForDeclNoCache(Field)) + Comment = RawComment->getFormattedLines(Context.getSourceManager(), + Context.getDiagnostics()); + + // Build declaration fragments and sub-heading for the struct field. + DeclarationFragments Declaration = + DeclarationFragmentsBuilder::getFragmentsForField(Field); + DeclarationFragments SubHeading = + DeclarationFragmentsBuilder::getSubHeading(Field); + + API.addStructField(StructRecord, Name, USR, Loc, Availability, Comment, + Declaration, SubHeading); + } + } + ASTContext &Context; APISet API; }; diff --git a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp index 574ea451e276c..57fcc8aa04d5b 100644 --- a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp +++ b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp @@ -367,6 +367,14 @@ Object serializeSymbolKind(const APIRecord &Record, Kind["identifier"] = AddLangPrefix("enum"); Kind["displayName"] = "Enumeration"; break; + case APIRecord::RK_StructField: + Kind["identifier"] = AddLangPrefix("property"); + Kind["displayName"] = "Instance Property"; + break; + case APIRecord::RK_Struct: + Kind["identifier"] = AddLangPrefix("struct"); + Kind["displayName"] = "Structure"; + break; } return Kind; @@ -474,6 +482,23 @@ void SymbolGraphSerializer::serializeEnumRecord(const EnumRecord &Record) { } } +void SymbolGraphSerializer::serializeStructRecord(const StructRecord &Record) { + auto Struct = serializeAPIRecord(Record); + if (!Struct) + return; + + Symbols.emplace_back(std::move(*Struct)); + + for (const auto &Field : Record.Fields) { + auto StructField = serializeAPIRecord(*Field); + if (!StructField) + continue; + + Symbols.emplace_back(std::move(*StructField)); + serializeRelationship(RelationshipKind::MemberOf, *Field, Record); + } +} + Object SymbolGraphSerializer::serialize() { Object Root; serializeObject(Root, "metadata", serializeMetadata()); @@ -487,6 +512,10 @@ Object SymbolGraphSerializer::serialize() { for (const auto &Enum : API.getEnums()) serializeEnumRecord(*Enum.second); + // Serialize struct records in the API set. + for (const auto &Struct : API.getStructs()) + serializeStructRecord(*Struct.second); + Root["symbols"] = std::move(Symbols); Root["relationhips"] = std::move(Relationships); diff --git a/clang/test/ExtractAPI/struct.c b/clang/test/ExtractAPI/struct.c new file mode 100644 index 0000000000000..9be2ff4a4ffed --- /dev/null +++ b/clang/test/ExtractAPI/struct.c @@ -0,0 +1,303 @@ +// 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.h -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.h +/// Color in RGBA +struct Color { + unsigned Red; + unsigned Green; + unsigned Blue; + /// Alpha channel for transparency + unsigned Alpha; +}; + +//--- 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:@S@Color@FI@Red", + "target": "c:@S@Color" + }, + { + "kind": "memberOf", + "source": "c:@S@Color@FI@Green", + "target": "c:@S@Color" + }, + { + "kind": "memberOf", + "source": "c:@S@Color@FI@Blue", + "target": "c:@S@Color" + }, + { + "kind": "memberOf", + "source": "c:@S@Color@FI@Alpha", + "target": "c:@S@Color" + } + ], + "symbols": [ + { + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "struct" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Color" + } + ], + "docComment": { + "lines": [ + { + "range": { + "end": { + "character": 18, + "line": 1 + }, + "start": { + "character": 5, + "line": 1 + } + }, + "text": "Color in RGBA" + } + ] + }, + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@S@Color" + }, + "kind": { + "displayName": "Structure", + "identifier": "c.struct" + }, + "location": { + "character": 8, + "line": 2, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Color" + } + ], + "title": "Color" + } + }, + { + "declarationFragments": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:i", + "spelling": "unsigned int" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Red" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@S@Color@FI@Red" + }, + "kind": { + "displayName": "Instance Property", + "identifier": "c.property" + }, + "location": { + "character": 12, + "line": 3, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Red" + } + ], + "title": "Red" + } + }, + { + "declarationFragments": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:i", + "spelling": "unsigned int" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Green" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@S@Color@FI@Green" + }, + "kind": { + "displayName": "Instance Property", + "identifier": "c.property" + }, + "location": { + "character": 12, + "line": 4, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Green" + } + ], + "title": "Green" + } + }, + { + "declarationFragments": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:i", + "spelling": "unsigned int" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Blue" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@S@Color@FI@Blue" + }, + "kind": { + "displayName": "Instance Property", + "identifier": "c.property" + }, + "location": { + "character": 12, + "line": 5, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Blue" + } + ], + "title": "Blue" + } + }, + { + "declarationFragments": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:i", + "spelling": "unsigned int" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Alpha" + } + ], + "docComment": { + "lines": [ + { + "range": { + "end": { + "character": 37, + "line": 6 + }, + "start": { + "character": 7, + "line": 6 + } + }, + "text": "Alpha channel for transparency" + } + ] + }, + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@S@Color@FI@Alpha" + }, + "kind": { + "displayName": "Instance Property", + "identifier": "c.property" + }, + "location": { + "character": 12, + "line": 7, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Alpha" + } + ], + "title": "Alpha" + } + } + ] +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits