diff --git a/clang/include/clang/ExtractAPI/API.h b/clang/include/clang/ExtractAPI/API.h index a2a462be42cd..57397e7c256e 100644 --- a/clang/include/clang/ExtractAPI/API.h +++ b/clang/include/clang/ExtractAPI/API.h @@ -82,6 +82,7 @@ struct APIRecord { RK_ObjCIvar, RK_ObjCMethod, RK_ObjCInterface, + RK_ObjCProtocol, }; private: @@ -354,6 +355,25 @@ private: virtual void anchor(); }; +/// This holds information associated with Objective-C protocols. +struct ObjCProtocolRecord : ObjCContainerRecord { + ObjCProtocolRecord(StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading) + : ObjCContainerRecord(RK_ObjCProtocol, Name, USR, Loc, Availability, + LinkageInfo::none(), Comment, Declaration, + SubHeading) {} + + static bool classof(const APIRecord *Record) { + return Record->getKind() == RK_ObjCProtocol; + } + +private: + virtual void anchor(); +}; + /// APISet holds the set of API records collected from given inputs. class APISet { public: @@ -497,6 +517,19 @@ public: DeclarationFragments SubHeading, ObjCInstanceVariableRecord::AccessControl Access); + /// Create and add an Objective-C protocol 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. + ObjCProtocolRecord *addObjCProtocol(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 = @@ -516,6 +549,11 @@ public: using ObjCInterfaceRecordMap = llvm::MapVector>; + /// A map to store the set of ObjCProtocolRecord%s with the declaration name + /// as the key. + using ObjCProtocolRecordMap = + llvm::MapVector>; + /// Get the target triple for the ExtractAPI invocation. const llvm::Triple &getTarget() const { return Target; } @@ -528,6 +566,9 @@ public: const ObjCInterfaceRecordMap &getObjCInterfaces() const { return ObjCInterfaces; } + const ObjCProtocolRecordMap &getObjCProtocols() const { + return ObjCProtocols; + } /// Generate and store the USR of declaration \p D. /// @@ -557,6 +598,7 @@ private: EnumRecordMap Enums; StructRecordMap Structs; ObjCInterfaceRecordMap ObjCInterfaces; + ObjCProtocolRecordMap ObjCProtocols; }; } // namespace extractapi diff --git a/clang/include/clang/ExtractAPI/DeclarationFragments.h b/clang/include/clang/ExtractAPI/DeclarationFragments.h index f147abb4f425..ca5b014a1f5a 100644 --- a/clang/include/clang/ExtractAPI/DeclarationFragments.h +++ b/clang/include/clang/ExtractAPI/DeclarationFragments.h @@ -217,6 +217,11 @@ public: static DeclarationFragments getFragmentsForObjCProperty(const ObjCPropertyDecl *); + /// Build DeclarationFragments for an Objective-C protocol declaration + /// ObjCProtocolDecl. + static DeclarationFragments + getFragmentsForObjCProtocol(const ObjCProtocolDecl *); + /// Build sub-heading fragments for a NamedDecl. static DeclarationFragments getSubHeading(const NamedDecl *); diff --git a/clang/lib/ExtractAPI/API.cpp b/clang/lib/ExtractAPI/API.cpp index 2456022523ad..1c9314c0be10 100644 --- a/clang/lib/ExtractAPI/API.cpp +++ b/clang/lib/ExtractAPI/API.cpp @@ -161,6 +161,20 @@ ObjCInstanceVariableRecord *APISet::addObjCInstanceVariable( return Container->Ivars.emplace_back(std::move(Record)).get(); } +ObjCProtocolRecord *APISet::addObjCProtocol( + StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, const DocComment &Comment, + DeclarationFragments Declaration, DeclarationFragments SubHeading) { + auto Result = ObjCProtocols.insert({Name, nullptr}); + if (Result.second) { + // Create the record if it does not already exist. + auto Record = std::make_unique( + 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); @@ -193,3 +207,4 @@ void ObjCPropertyRecord::anchor() {} void ObjCInstanceVariableRecord::anchor() {} void ObjCMethodRecord::anchor() {} void ObjCInterfaceRecord::anchor() {} +void ObjCProtocolRecord::anchor() {} diff --git a/clang/lib/ExtractAPI/DeclarationFragments.cpp b/clang/lib/ExtractAPI/DeclarationFragments.cpp index b35388ec52ad..7c6c9dcde9ee 100644 --- a/clang/lib/ExtractAPI/DeclarationFragments.cpp +++ b/clang/lib/ExtractAPI/DeclarationFragments.cpp @@ -621,6 +621,35 @@ DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForObjCProperty( .append(std::move(After)); } +DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForObjCProtocol( + const ObjCProtocolDecl *Protocol) { + DeclarationFragments Fragments; + // Build basic protocol declaration. + Fragments.append("@protocol", DeclarationFragments::FragmentKind::Keyword) + .appendSpace() + .append(Protocol->getName(), + DeclarationFragments::FragmentKind::Identifier); + + // If this protocol conforms to other protocols, build the conformance list. + if (!Protocol->protocols().empty()) { + Fragments.append(" <", DeclarationFragments::FragmentKind::Text); + for (ObjCProtocolDecl::protocol_iterator It = Protocol->protocol_begin(); + It != Protocol->protocol_end(); It++) { + // Add a leading comma if this is not the first protocol rendered. + if (It != Protocol->protocol_begin()) + Fragments.append(", ", DeclarationFragments::FragmentKind::Text); + + SmallString<128> USR; + index::generateUSRForDecl(*It, USR); + Fragments.append((*It)->getName(), + DeclarationFragments::FragmentKind::TypeIdentifier, USR); + } + Fragments.append(">", DeclarationFragments::FragmentKind::Text); + } + + return Fragments; +} + template FunctionSignature DeclarationFragmentsBuilder::getFunctionSignature(const FunctionT *Function) { diff --git a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp index f8b1c9b20fb1..bcc01dbd710d 100644 --- a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp +++ b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp @@ -260,6 +260,38 @@ public: return true; } + bool VisitObjCProtocolDecl(const ObjCProtocolDecl *Decl) { + // Skip forward declaration for protocols (@protocol). + if (!Decl->isThisDeclarationADefinition()) + 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 protocol. + DeclarationFragments Declaration = + DeclarationFragmentsBuilder::getFragmentsForObjCProtocol(Decl); + DeclarationFragments SubHeading = + DeclarationFragmentsBuilder::getSubHeading(Decl); + + ObjCProtocolRecord *ObjCProtocolRecord = API.addObjCProtocol( + Name, USR, Loc, Availability, Comment, Declaration, SubHeading); + + recordObjCMethods(ObjCProtocolRecord, Decl->methods()); + recordObjCProperties(ObjCProtocolRecord, Decl->properties()); + recordObjCProtocols(ObjCProtocolRecord, 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 3faf9cd75103..1acb67aac67d 100644 --- a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp +++ b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp @@ -393,6 +393,10 @@ Object serializeSymbolKind(const APIRecord &Record, Language Lang) { Kind["identifier"] = AddLangPrefix("class"); Kind["displayName"] = "Class"; break; + case APIRecord::RK_ObjCProtocol: + Kind["identifier"] = AddLangPrefix("protocol"); + Kind["displayName"] = "Protocol"; + break; } return Kind; @@ -593,6 +597,10 @@ Object SymbolGraphSerializer::serialize() { for (const auto &ObjCInterface : API.getObjCInterfaces()) serializeObjCContainerRecord(*ObjCInterface.second); + // Serialize Objective-C protocol records in the API set. + for (const auto &ObjCProtocol : API.getObjCProtocols()) + serializeObjCContainerRecord(*ObjCProtocol.second); + Root["symbols"] = std::move(Symbols); Root["relationhips"] = std::move(Relationships); diff --git a/clang/test/ExtractAPI/objc_protocol.m b/clang/test/ExtractAPI/objc_protocol.m new file mode 100644 index 000000000000..1b6c55ec474d --- /dev/null +++ b/clang/test/ExtractAPI/objc_protocol.m @@ -0,0 +1,146 @@ +// 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 +@end + +@protocol AnotherProtocol +@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" + } + }, + "relationhips": [ + { + "kind": "conformsTo", + "source": "c:objc(pl)AnotherProtocol", + "target": "c:objc(pl)Protocol" + } + ], + "symbols": [ + { + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "@protocol" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Protocol" + } + ], + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:objc(pl)Protocol" + }, + "kind": { + "displayName": "Protocol", + "identifier": "objective-c.protocol" + }, + "location": { + "character": 11, + "line": 1, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Protocol" + } + ], + "title": "Protocol" + } + }, + { + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "@protocol" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "AnotherProtocol" + }, + { + "kind": "text", + "spelling": " <" + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:objc(pl)Protocol", + "spelling": "Protocol" + }, + { + "kind": "text", + "spelling": ">" + } + ], + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:objc(pl)AnotherProtocol" + }, + "kind": { + "displayName": "Protocol", + "identifier": "objective-c.protocol" + }, + "location": { + "character": 11, + "line": 4, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "AnotherProtocol" + } + ], + "title": "AnotherProtocol" + } + } + ] +}