[clang][extract-api] Add Objective-C protocol support

Add support for Objective-C protocol declarations in ExtractAPI.

Depends on D122446

Differential Revision: https://reviews.llvm.org/D122511
This commit is contained in:
Zixu Wang 2022-03-25 15:15:08 -07:00
parent 122638d97d
commit d1d34bafef
7 changed files with 277 additions and 0 deletions

View File

@ -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<StringRef, std::unique_ptr<ObjCInterfaceRecord>>;
/// A map to store the set of ObjCProtocolRecord%s with the declaration name
/// as the key.
using ObjCProtocolRecordMap =
llvm::MapVector<StringRef, std::unique_ptr<ObjCProtocolRecord>>;
/// 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

View File

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

View File

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

View File

@ -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 <typename FunctionT>
FunctionSignature
DeclarationFragmentsBuilder::getFunctionSignature(const FunctionT *Function) {

View File

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

View File

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

View File

@ -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 <Protocol>
@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"
}
}
]
}