Author: Zixu Wang Date: 2022-04-06T12:00:12-07:00 New Revision: 178aad9b946e3c5abe9df162e5c482fb4acae99c
URL: https://github.com/llvm/llvm-project/commit/178aad9b946e3c5abe9df162e5c482fb4acae99c DIFF: https://github.com/llvm/llvm-project/commit/178aad9b946e3c5abe9df162e5c482fb4acae99c.diff LOG: [clang][extract-api] Add Objective-C Category support Add (partial) support for Objective-C category records in ExtractAPI. The current ExtractAPI collects everything for an Objective-C category, but not fully serialized in the SymbolGraphSerializer. Categories extending external interfaces are disgarded during serialization, and categories extending known interfaces are merged (all members surfaced) into the interfaces. Differential Revision: https://reviews.llvm.org/D122774 Added: clang/test/ExtractAPI/objc_category.m 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 827cde953c31c..57b6a2ee5a43c 100644 --- a/clang/include/clang/ExtractAPI/API.h +++ b/clang/include/clang/ExtractAPI/API.h @@ -86,6 +86,7 @@ struct APIRecord { RK_ObjCIvar, RK_ObjCMethod, RK_ObjCInterface, + RK_ObjCCategory, RK_ObjCProtocol, RK_MacroDefinition, RK_Typedef, @@ -340,9 +341,33 @@ struct ObjCContainerRecord : APIRecord { virtual ~ObjCContainerRecord() = 0; }; +/// This holds information associated with Objective-C categories. +struct ObjCCategoryRecord : ObjCContainerRecord { + SymbolReference Interface; + + ObjCCategoryRecord(StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading, SymbolReference Interface) + : ObjCContainerRecord(RK_ObjCCategory, Name, USR, Loc, Availability, + LinkageInfo::none(), Comment, Declaration, + SubHeading), + Interface(Interface) {} + + static bool classof(const APIRecord *Record) { + return Record->getKind() == RK_ObjCCategory; + } + +private: + virtual void anchor(); +}; + /// This holds information associated with Objective-C interfaces/classes. struct ObjCInterfaceRecord : ObjCContainerRecord { SymbolReference SuperClass; + // ObjCCategoryRecord%s are stored in and owned by APISet. + SmallVector<ObjCCategoryRecord *> Categories; ObjCInterfaceRecord(StringRef Name, StringRef USR, PresumedLoc Loc, const AvailabilityInfo &Availability, LinkageInfo Linkage, @@ -512,6 +537,18 @@ class APISet { DeclarationFragments Declaration, DeclarationFragments SubHeading); + /// Create and add an Objective-C category 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. + ObjCCategoryRecord * + addObjCCategory(StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, + const DocComment &Comment, DeclarationFragments Declaration, + DeclarationFragments SubHeading, SymbolReference Interface); + /// Create and add an Objective-C interface record into the API set. /// /// Note: the caller is responsible for keeping the StringRef \p Name and @@ -618,6 +655,9 @@ class APISet { const RecordMap<GlobalRecord> &getGlobals() const { return Globals; } const RecordMap<EnumRecord> &getEnums() const { return Enums; } const RecordMap<StructRecord> &getStructs() const { return Structs; } + const RecordMap<ObjCCategoryRecord> &getObjCCategories() const { + return ObjCCategories; + } const RecordMap<ObjCInterfaceRecord> &getObjCInterfaces() const { return ObjCInterfaces; } @@ -662,6 +702,7 @@ class APISet { RecordMap<GlobalRecord> Globals; RecordMap<EnumRecord> Enums; RecordMap<StructRecord> Structs; + RecordMap<ObjCCategoryRecord> ObjCCategories; RecordMap<ObjCInterfaceRecord> ObjCInterfaces; RecordMap<ObjCProtocolRecord> ObjCProtocols; RecordMap<MacroDefinitionRecord> Macros; diff --git a/clang/include/clang/ExtractAPI/DeclarationFragments.h b/clang/include/clang/ExtractAPI/DeclarationFragments.h index 80fb5faf68783..55a6768eed9b9 100644 --- a/clang/include/clang/ExtractAPI/DeclarationFragments.h +++ b/clang/include/clang/ExtractAPI/DeclarationFragments.h @@ -204,6 +204,11 @@ class DeclarationFragmentsBuilder { /// Build DeclarationFragments for a struct record declaration RecordDecl. static DeclarationFragments getFragmentsForStruct(const RecordDecl *); + /// Build DeclarationFragments for an Objective-C category declaration + /// ObjCCategoryDecl. + static DeclarationFragments + getFragmentsForObjCCategory(const ObjCCategoryDecl *); + /// Build DeclarationFragments for an Objective-C interface declaration /// ObjCInterfaceDecl. static DeclarationFragments diff --git a/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h b/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h index 6c9611acd132a..93942ff05a0ae 100644 --- a/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h +++ b/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h @@ -124,6 +124,12 @@ class SymbolGraphSerializer : public APISerializer { /// containing common symbol information of \p Record. Optional<Object> serializeAPIRecord(const APIRecord &Record) const; + /// Helper method to serialize second-level member records of \p Record and + /// the member-of relationships. + template <typename MemberTy> + void serializeMembers(const APIRecord &Record, + const SmallVector<std::unique_ptr<MemberTy>> &Members); + /// Serialize the \p Kind relationship between \p Source and \p Target. /// /// Record the relationship between the two symbols in diff --git a/clang/lib/ExtractAPI/API.cpp b/clang/lib/ExtractAPI/API.cpp index 6d5ac8a1b40c9..1c33bbf4328cf 100644 --- a/clang/lib/ExtractAPI/API.cpp +++ b/clang/lib/ExtractAPI/API.cpp @@ -109,6 +109,24 @@ StructRecord *APISet::addStruct(StringRef Name, StringRef USR, PresumedLoc Loc, Declaration, SubHeading); } +ObjCCategoryRecord *APISet::addObjCCategory( + StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, const DocComment &Comment, + DeclarationFragments Declaration, DeclarationFragments SubHeading, + SymbolReference Interface) { + // Create the category record. + auto *Record = addTopLevelRecord(ObjCCategories, Name, USR, Loc, Availability, + Comment, Declaration, SubHeading, Interface); + + // If this category is extending a known interface, associate it with the + // ObjCInterfaceRecord. + auto It = ObjCInterfaces.find(Interface.Name); + if (It != ObjCInterfaces.end()) + It->second->Categories.push_back(Record); + + return Record; +} + ObjCInterfaceRecord *APISet::addObjCInterface( StringRef Name, StringRef USR, PresumedLoc Loc, const AvailabilityInfo &Availability, LinkageInfo Linkage, @@ -219,6 +237,7 @@ void StructRecord::anchor() {} void ObjCPropertyRecord::anchor() {} void ObjCInstanceVariableRecord::anchor() {} void ObjCMethodRecord::anchor() {} +void ObjCCategoryRecord::anchor() {} void ObjCInterfaceRecord::anchor() {} void ObjCProtocolRecord::anchor() {} void MacroDefinitionRecord::anchor() {} diff --git a/clang/lib/ExtractAPI/DeclarationFragments.cpp b/clang/lib/ExtractAPI/DeclarationFragments.cpp index 4e0e8da99972c..fa28fad358db9 100644 --- a/clang/lib/ExtractAPI/DeclarationFragments.cpp +++ b/clang/lib/ExtractAPI/DeclarationFragments.cpp @@ -526,6 +526,25 @@ DeclarationFragmentsBuilder::getFragmentsForMacro(StringRef Name, return Fragments; } +DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForObjCCategory( + const ObjCCategoryDecl *Category) { + DeclarationFragments Fragments; + + SmallString<128> InterfaceUSR; + index::generateUSRForDecl(Category->getClassInterface(), InterfaceUSR); + + Fragments.append("@interface", DeclarationFragments::FragmentKind::Keyword) + .appendSpace() + .append(Category->getClassInterface()->getName(), + DeclarationFragments::FragmentKind::TypeIdentifier, InterfaceUSR) + .append(" (", DeclarationFragments::FragmentKind::Text) + .append(Category->getName(), + DeclarationFragments::FragmentKind::Identifier) + .append(")", DeclarationFragments::FragmentKind::Text); + + return Fragments; +} + DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForObjCInterface( const ObjCInterfaceDecl *Interface) { DeclarationFragments Fragments; diff --git a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp index 493d490674b1c..7c2914da7ea0c 100644 --- a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp +++ b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp @@ -338,6 +338,39 @@ class ExtractAPIVisitor : public RecursiveASTVisitor<ExtractAPIVisitor> { return true; } + bool VisitObjCCategoryDecl(const ObjCCategoryDecl *Decl) { + // 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 category. + DeclarationFragments Declaration = + DeclarationFragmentsBuilder::getFragmentsForObjCCategory(Decl); + DeclarationFragments SubHeading = + DeclarationFragmentsBuilder::getSubHeading(Decl); + + const ObjCInterfaceDecl *InterfaceDecl = Decl->getClassInterface(); + SymbolReference Interface(InterfaceDecl->getName(), + API.recordUSR(InterfaceDecl)); + + ObjCCategoryRecord *ObjCCategoryRecord = + API.addObjCCategory(Name, USR, Loc, Availability, Comment, Declaration, + SubHeading, Interface); + + recordObjCMethods(ObjCCategoryRecord, Decl->methods()); + recordObjCProperties(ObjCCategoryRecord, Decl->properties()); + recordObjCInstanceVariables(ObjCCategoryRecord, Decl->ivars()); + recordObjCProtocols(ObjCCategoryRecord, Decl->protocols()); + + return true; + } + private: /// Get availability information of the declaration \p D. AvailabilityInfo getAvailability(const Decl *D) const { diff --git a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp index f38a5bbae7d00..efe3c276671da 100644 --- a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp +++ b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp @@ -401,6 +401,11 @@ Object serializeSymbolKind(const APIRecord &Record, Language Lang) { Kind["identifier"] = AddLangPrefix("class"); Kind["displayName"] = "Class"; break; + case APIRecord::RK_ObjCCategory: + // We don't serialize out standalone Objective-C category symbols yet. + llvm_unreachable("Serializing standalone Objective-C category symbols is " + "not supported."); + break; case APIRecord::RK_ObjCProtocol: Kind["identifier"] = AddLangPrefix("protocol"); Kind["displayName"] = "Protocol"; @@ -476,6 +481,21 @@ SymbolGraphSerializer::serializeAPIRecord(const APIRecord &Record) const { return Obj; } +template <typename MemberTy> +void SymbolGraphSerializer::serializeMembers( + const APIRecord &Record, + const SmallVector<std::unique_ptr<MemberTy>> &Members) { + for (const auto &Member : Members) { + auto MemberPathComponentGuard = makePathComponentGuard(Member->Name); + auto MemberRecord = serializeAPIRecord(*Member); + if (!MemberRecord) + continue; + + Symbols.emplace_back(std::move(*MemberRecord)); + serializeRelationship(RelationshipKind::MemberOf, *Member, Record); + } +} + StringRef SymbolGraphSerializer::getRelationshipString(RelationshipKind Kind) { switch (Kind) { case RelationshipKind::MemberOf: @@ -520,18 +540,7 @@ void SymbolGraphSerializer::serializeEnumRecord(const EnumRecord &Record) { return; Symbols.emplace_back(std::move(*Enum)); - - for (const auto &Constant : Record.Constants) { - auto EnumConstantPathComponentGuard = - makePathComponentGuard(Constant->Name); - auto EnumConstant = serializeAPIRecord(*Constant); - - if (!EnumConstant) - continue; - - Symbols.emplace_back(std::move(*EnumConstant)); - serializeRelationship(RelationshipKind::MemberOf, *Constant, Record); - } + serializeMembers(Record, Record.Constants); } void SymbolGraphSerializer::serializeStructRecord(const StructRecord &Record) { @@ -541,17 +550,7 @@ void SymbolGraphSerializer::serializeStructRecord(const StructRecord &Record) { return; Symbols.emplace_back(std::move(*Struct)); - - for (const auto &Field : Record.Fields) { - auto StructFieldPathComponentGuard = makePathComponentGuard(Field->Name); - auto StructField = serializeAPIRecord(*Field); - - if (!StructField) - continue; - - Symbols.emplace_back(std::move(*StructField)); - serializeRelationship(RelationshipKind::MemberOf, *Field, Record); - } + serializeMembers(Record, Record.Fields); } void SymbolGraphSerializer::serializeObjCContainerRecord( @@ -563,53 +562,33 @@ void SymbolGraphSerializer::serializeObjCContainerRecord( Symbols.emplace_back(std::move(*ObjCContainer)); - // Record instance variables and that the instance variables are members of - // the container. - for (const auto &Ivar : Record.Ivars) { - auto IvarPathComponentGuard = makePathComponentGuard(Ivar->Name); - auto ObjCIvar = serializeAPIRecord(*Ivar); - - if (!ObjCIvar) - continue; - - Symbols.emplace_back(std::move(*ObjCIvar)); - serializeRelationship(RelationshipKind::MemberOf, *Ivar, Record); - } - - // Record methods and that the methods are members of the container. - for (const auto &Method : Record.Methods) { - auto MethodPathComponentGuard = makePathComponentGuard(Method->Name); - auto ObjCMethod = serializeAPIRecord(*Method); - - if (!ObjCMethod) - continue; - - Symbols.emplace_back(std::move(*ObjCMethod)); - serializeRelationship(RelationshipKind::MemberOf, *Method, Record); - } - - // Record properties and that the properties are members of the container. - for (const auto &Property : Record.Properties) { - auto PropertyPathComponentGuard = makePathComponentGuard(Property->Name); - auto ObjCProperty = serializeAPIRecord(*Property); - - if (!ObjCProperty) - continue; - - Symbols.emplace_back(std::move(*ObjCProperty)); - serializeRelationship(RelationshipKind::MemberOf, *Property, Record); - } + serializeMembers(Record, Record.Ivars); + serializeMembers(Record, Record.Methods); + serializeMembers(Record, Record.Properties); for (const auto &Protocol : Record.Protocols) // Record that Record conforms to Protocol. serializeRelationship(RelationshipKind::ConformsTo, Record, Protocol); - if (auto *ObjCInterface = dyn_cast<ObjCInterfaceRecord>(&Record)) + if (auto *ObjCInterface = dyn_cast<ObjCInterfaceRecord>(&Record)) { if (!ObjCInterface->SuperClass.empty()) // If Record is an Objective-C interface record and it has a super class, // record that Record is inherited from SuperClass. serializeRelationship(RelationshipKind::InheritsFrom, Record, ObjCInterface->SuperClass); + + // Members of categories extending an interface are serialized as members of + // the interface. + for (const auto *Category : ObjCInterface->Categories) { + serializeMembers(Record, Category->Ivars); + serializeMembers(Record, Category->Methods); + serializeMembers(Record, Category->Properties); + + // Surface the protocols of the the category to the interface. + for (const auto &Protocol : Category->Protocols) + serializeRelationship(RelationshipKind::ConformsTo, Record, Protocol); + } + } } void SymbolGraphSerializer::serializeMacroDefinitionRecord( diff --git a/clang/test/ExtractAPI/objc_category.m b/clang/test/ExtractAPI/objc_category.m new file mode 100644 index 0000000000000..bc572adfcd161 --- /dev/null +++ b/clang/test/ExtractAPI/objc_category.m @@ -0,0 +1,311 @@ +// 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 -x objective-c-header -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 +@protocol Protocol; + +@interface Interface +@end + +@interface Interface (Category) <Protocol> +@property int Property; +- (void)InstanceMethod; ++ (void)ClassMethod; +@end + +//--- 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" + } + }, + "relationships": [ + { + "kind": "memberOf", + "source": "c:objc(cs)Interface(im)InstanceMethod", + "target": "c:objc(cs)Interface" + }, + { + "kind": "memberOf", + "source": "c:objc(cs)Interface(cm)ClassMethod", + "target": "c:objc(cs)Interface" + }, + { + "kind": "memberOf", + "source": "c:objc(cs)Interface(py)Property", + "target": "c:objc(cs)Interface" + }, + { + "kind": "conformsTo", + "source": "c:objc(cs)Interface", + "target": "c:objc(pl)Protocol" + } + ], + "symbols": [ + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "@interface" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Interface" + } + ], + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:objc(cs)Interface" + }, + "kind": { + "displayName": "Class", + "identifier": "objective-c.class" + }, + "location": { + "position": { + "character": 12, + "line": 3 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Interface" + } + ], + "title": "Interface" + }, + "pathComponents": [ + "Interface" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "text", + "spelling": "- (" + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:v", + "spelling": "void" + }, + { + "kind": "text", + "spelling": ")" + }, + { + "kind": "identifier", + "spelling": "InstanceMethod" + } + ], + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:objc(cs)Interface(im)InstanceMethod" + }, + "kind": { + "displayName": "Instance Method", + "identifier": "objective-c.method" + }, + "location": { + "position": { + "character": 1, + "line": 8 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "text", + "spelling": "- " + }, + { + "kind": "identifier", + "spelling": "InstanceMethod" + } + ], + "title": "InstanceMethod" + }, + "pathComponents": [ + "Interface", + "InstanceMethod" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "text", + "spelling": "+ (" + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:v", + "spelling": "void" + }, + { + "kind": "text", + "spelling": ")" + }, + { + "kind": "identifier", + "spelling": "ClassMethod" + } + ], + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:objc(cs)Interface(cm)ClassMethod" + }, + "kind": { + "displayName": "Type Method", + "identifier": "objective-c.type.method" + }, + "location": { + "position": { + "character": 1, + "line": 9 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "text", + "spelling": "+ " + }, + { + "kind": "identifier", + "spelling": "ClassMethod" + } + ], + "title": "ClassMethod" + }, + "pathComponents": [ + "Interface", + "ClassMethod" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "@property" + }, + { + "kind": "text", + "spelling": " (" + }, + { + "kind": "keyword", + "spelling": "atomic" + }, + { + "kind": "text", + "spelling": ", " + }, + { + "kind": "keyword", + "spelling": "assign" + }, + { + "kind": "text", + "spelling": ", " + }, + { + "kind": "keyword", + "spelling": "unsafe_unretained" + }, + { + "kind": "text", + "spelling": ", " + }, + { + "kind": "keyword", + "spelling": "readwrite" + }, + { + "kind": "text", + "spelling": ")" + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + }, + { + "kind": "identifier", + "spelling": "Property" + } + ], + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:objc(cs)Interface(py)Property" + }, + "kind": { + "displayName": "Instance Property", + "identifier": "objective-c.property" + }, + "location": { + "position": { + "character": 15, + "line": 7 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Property" + } + ], + "title": "Property" + }, + "pathComponents": [ + "Interface", + "Property" + ] + } + ] +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits