Skip to content

Commit 178aad9

Browse files
committed
[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
1 parent 6b30623 commit 178aad9

File tree

8 files changed

+473
-60
lines changed

8 files changed

+473
-60
lines changed

clang/include/clang/ExtractAPI/API.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ struct APIRecord {
8686
RK_ObjCIvar,
8787
RK_ObjCMethod,
8888
RK_ObjCInterface,
89+
RK_ObjCCategory,
8990
RK_ObjCProtocol,
9091
RK_MacroDefinition,
9192
RK_Typedef,
@@ -340,9 +341,33 @@ struct ObjCContainerRecord : APIRecord {
340341
virtual ~ObjCContainerRecord() = 0;
341342
};
342343

344+
/// This holds information associated with Objective-C categories.
345+
struct ObjCCategoryRecord : ObjCContainerRecord {
346+
SymbolReference Interface;
347+
348+
ObjCCategoryRecord(StringRef Name, StringRef USR, PresumedLoc Loc,
349+
const AvailabilityInfo &Availability,
350+
const DocComment &Comment,
351+
DeclarationFragments Declaration,
352+
DeclarationFragments SubHeading, SymbolReference Interface)
353+
: ObjCContainerRecord(RK_ObjCCategory, Name, USR, Loc, Availability,
354+
LinkageInfo::none(), Comment, Declaration,
355+
SubHeading),
356+
Interface(Interface) {}
357+
358+
static bool classof(const APIRecord *Record) {
359+
return Record->getKind() == RK_ObjCCategory;
360+
}
361+
362+
private:
363+
virtual void anchor();
364+
};
365+
343366
/// This holds information associated with Objective-C interfaces/classes.
344367
struct ObjCInterfaceRecord : ObjCContainerRecord {
345368
SymbolReference SuperClass;
369+
// ObjCCategoryRecord%s are stored in and owned by APISet.
370+
SmallVector<ObjCCategoryRecord *> Categories;
346371

347372
ObjCInterfaceRecord(StringRef Name, StringRef USR, PresumedLoc Loc,
348373
const AvailabilityInfo &Availability, LinkageInfo Linkage,
@@ -512,6 +537,18 @@ class APISet {
512537
DeclarationFragments Declaration,
513538
DeclarationFragments SubHeading);
514539

540+
/// Create and add an Objective-C category record into the API set.
541+
///
542+
/// Note: the caller is responsible for keeping the StringRef \p Name and
543+
/// \p USR alive. APISet::copyString provides a way to copy strings into
544+
/// APISet itself, and APISet::recordUSR(const Decl *D) is a helper method
545+
/// to generate the USR for \c D and keep it alive in APISet.
546+
ObjCCategoryRecord *
547+
addObjCCategory(StringRef Name, StringRef USR, PresumedLoc Loc,
548+
const AvailabilityInfo &Availability,
549+
const DocComment &Comment, DeclarationFragments Declaration,
550+
DeclarationFragments SubHeading, SymbolReference Interface);
551+
515552
/// Create and add an Objective-C interface record into the API set.
516553
///
517554
/// Note: the caller is responsible for keeping the StringRef \p Name and
@@ -618,6 +655,9 @@ class APISet {
618655
const RecordMap<GlobalRecord> &getGlobals() const { return Globals; }
619656
const RecordMap<EnumRecord> &getEnums() const { return Enums; }
620657
const RecordMap<StructRecord> &getStructs() const { return Structs; }
658+
const RecordMap<ObjCCategoryRecord> &getObjCCategories() const {
659+
return ObjCCategories;
660+
}
621661
const RecordMap<ObjCInterfaceRecord> &getObjCInterfaces() const {
622662
return ObjCInterfaces;
623663
}
@@ -662,6 +702,7 @@ class APISet {
662702
RecordMap<GlobalRecord> Globals;
663703
RecordMap<EnumRecord> Enums;
664704
RecordMap<StructRecord> Structs;
705+
RecordMap<ObjCCategoryRecord> ObjCCategories;
665706
RecordMap<ObjCInterfaceRecord> ObjCInterfaces;
666707
RecordMap<ObjCProtocolRecord> ObjCProtocols;
667708
RecordMap<MacroDefinitionRecord> Macros;

clang/include/clang/ExtractAPI/DeclarationFragments.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@ class DeclarationFragmentsBuilder {
204204
/// Build DeclarationFragments for a struct record declaration RecordDecl.
205205
static DeclarationFragments getFragmentsForStruct(const RecordDecl *);
206206

207+
/// Build DeclarationFragments for an Objective-C category declaration
208+
/// ObjCCategoryDecl.
209+
static DeclarationFragments
210+
getFragmentsForObjCCategory(const ObjCCategoryDecl *);
211+
207212
/// Build DeclarationFragments for an Objective-C interface declaration
208213
/// ObjCInterfaceDecl.
209214
static DeclarationFragments

clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ class SymbolGraphSerializer : public APISerializer {
124124
/// containing common symbol information of \p Record.
125125
Optional<Object> serializeAPIRecord(const APIRecord &Record) const;
126126

127+
/// Helper method to serialize second-level member records of \p Record and
128+
/// the member-of relationships.
129+
template <typename MemberTy>
130+
void serializeMembers(const APIRecord &Record,
131+
const SmallVector<std::unique_ptr<MemberTy>> &Members);
132+
127133
/// Serialize the \p Kind relationship between \p Source and \p Target.
128134
///
129135
/// Record the relationship between the two symbols in

clang/lib/ExtractAPI/API.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,24 @@ StructRecord *APISet::addStruct(StringRef Name, StringRef USR, PresumedLoc Loc,
109109
Declaration, SubHeading);
110110
}
111111

112+
ObjCCategoryRecord *APISet::addObjCCategory(
113+
StringRef Name, StringRef USR, PresumedLoc Loc,
114+
const AvailabilityInfo &Availability, const DocComment &Comment,
115+
DeclarationFragments Declaration, DeclarationFragments SubHeading,
116+
SymbolReference Interface) {
117+
// Create the category record.
118+
auto *Record = addTopLevelRecord(ObjCCategories, Name, USR, Loc, Availability,
119+
Comment, Declaration, SubHeading, Interface);
120+
121+
// If this category is extending a known interface, associate it with the
122+
// ObjCInterfaceRecord.
123+
auto It = ObjCInterfaces.find(Interface.Name);
124+
if (It != ObjCInterfaces.end())
125+
It->second->Categories.push_back(Record);
126+
127+
return Record;
128+
}
129+
112130
ObjCInterfaceRecord *APISet::addObjCInterface(
113131
StringRef Name, StringRef USR, PresumedLoc Loc,
114132
const AvailabilityInfo &Availability, LinkageInfo Linkage,
@@ -219,6 +237,7 @@ void StructRecord::anchor() {}
219237
void ObjCPropertyRecord::anchor() {}
220238
void ObjCInstanceVariableRecord::anchor() {}
221239
void ObjCMethodRecord::anchor() {}
240+
void ObjCCategoryRecord::anchor() {}
222241
void ObjCInterfaceRecord::anchor() {}
223242
void ObjCProtocolRecord::anchor() {}
224243
void MacroDefinitionRecord::anchor() {}

clang/lib/ExtractAPI/DeclarationFragments.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,25 @@ DeclarationFragmentsBuilder::getFragmentsForMacro(StringRef Name,
526526
return Fragments;
527527
}
528528

529+
DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForObjCCategory(
530+
const ObjCCategoryDecl *Category) {
531+
DeclarationFragments Fragments;
532+
533+
SmallString<128> InterfaceUSR;
534+
index::generateUSRForDecl(Category->getClassInterface(), InterfaceUSR);
535+
536+
Fragments.append("@interface", DeclarationFragments::FragmentKind::Keyword)
537+
.appendSpace()
538+
.append(Category->getClassInterface()->getName(),
539+
DeclarationFragments::FragmentKind::TypeIdentifier, InterfaceUSR)
540+
.append(" (", DeclarationFragments::FragmentKind::Text)
541+
.append(Category->getName(),
542+
DeclarationFragments::FragmentKind::Identifier)
543+
.append(")", DeclarationFragments::FragmentKind::Text);
544+
545+
return Fragments;
546+
}
547+
529548
DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForObjCInterface(
530549
const ObjCInterfaceDecl *Interface) {
531550
DeclarationFragments Fragments;

clang/lib/ExtractAPI/ExtractAPIConsumer.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,39 @@ class ExtractAPIVisitor : public RecursiveASTVisitor<ExtractAPIVisitor> {
338338
return true;
339339
}
340340

341+
bool VisitObjCCategoryDecl(const ObjCCategoryDecl *Decl) {
342+
// Collect symbol information.
343+
StringRef Name = Decl->getName();
344+
StringRef USR = API.recordUSR(Decl);
345+
PresumedLoc Loc =
346+
Context.getSourceManager().getPresumedLoc(Decl->getLocation());
347+
AvailabilityInfo Availability = getAvailability(Decl);
348+
DocComment Comment;
349+
if (auto *RawComment = Context.getRawCommentForDeclNoCache(Decl))
350+
Comment = RawComment->getFormattedLines(Context.getSourceManager(),
351+
Context.getDiagnostics());
352+
// Build declaration fragments and sub-heading for the category.
353+
DeclarationFragments Declaration =
354+
DeclarationFragmentsBuilder::getFragmentsForObjCCategory(Decl);
355+
DeclarationFragments SubHeading =
356+
DeclarationFragmentsBuilder::getSubHeading(Decl);
357+
358+
const ObjCInterfaceDecl *InterfaceDecl = Decl->getClassInterface();
359+
SymbolReference Interface(InterfaceDecl->getName(),
360+
API.recordUSR(InterfaceDecl));
361+
362+
ObjCCategoryRecord *ObjCCategoryRecord =
363+
API.addObjCCategory(Name, USR, Loc, Availability, Comment, Declaration,
364+
SubHeading, Interface);
365+
366+
recordObjCMethods(ObjCCategoryRecord, Decl->methods());
367+
recordObjCProperties(ObjCCategoryRecord, Decl->properties());
368+
recordObjCInstanceVariables(ObjCCategoryRecord, Decl->ivars());
369+
recordObjCProtocols(ObjCCategoryRecord, Decl->protocols());
370+
371+
return true;
372+
}
373+
341374
private:
342375
/// Get availability information of the declaration \p D.
343376
AvailabilityInfo getAvailability(const Decl *D) const {

clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp

Lines changed: 39 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,11 @@ Object serializeSymbolKind(const APIRecord &Record, Language Lang) {
401401
Kind["identifier"] = AddLangPrefix("class");
402402
Kind["displayName"] = "Class";
403403
break;
404+
case APIRecord::RK_ObjCCategory:
405+
// We don't serialize out standalone Objective-C category symbols yet.
406+
llvm_unreachable("Serializing standalone Objective-C category symbols is "
407+
"not supported.");
408+
break;
404409
case APIRecord::RK_ObjCProtocol:
405410
Kind["identifier"] = AddLangPrefix("protocol");
406411
Kind["displayName"] = "Protocol";
@@ -476,6 +481,21 @@ SymbolGraphSerializer::serializeAPIRecord(const APIRecord &Record) const {
476481
return Obj;
477482
}
478483

484+
template <typename MemberTy>
485+
void SymbolGraphSerializer::serializeMembers(
486+
const APIRecord &Record,
487+
const SmallVector<std::unique_ptr<MemberTy>> &Members) {
488+
for (const auto &Member : Members) {
489+
auto MemberPathComponentGuard = makePathComponentGuard(Member->Name);
490+
auto MemberRecord = serializeAPIRecord(*Member);
491+
if (!MemberRecord)
492+
continue;
493+
494+
Symbols.emplace_back(std::move(*MemberRecord));
495+
serializeRelationship(RelationshipKind::MemberOf, *Member, Record);
496+
}
497+
}
498+
479499
StringRef SymbolGraphSerializer::getRelationshipString(RelationshipKind Kind) {
480500
switch (Kind) {
481501
case RelationshipKind::MemberOf:
@@ -520,18 +540,7 @@ void SymbolGraphSerializer::serializeEnumRecord(const EnumRecord &Record) {
520540
return;
521541

522542
Symbols.emplace_back(std::move(*Enum));
523-
524-
for (const auto &Constant : Record.Constants) {
525-
auto EnumConstantPathComponentGuard =
526-
makePathComponentGuard(Constant->Name);
527-
auto EnumConstant = serializeAPIRecord(*Constant);
528-
529-
if (!EnumConstant)
530-
continue;
531-
532-
Symbols.emplace_back(std::move(*EnumConstant));
533-
serializeRelationship(RelationshipKind::MemberOf, *Constant, Record);
534-
}
543+
serializeMembers(Record, Record.Constants);
535544
}
536545

537546
void SymbolGraphSerializer::serializeStructRecord(const StructRecord &Record) {
@@ -541,17 +550,7 @@ void SymbolGraphSerializer::serializeStructRecord(const StructRecord &Record) {
541550
return;
542551

543552
Symbols.emplace_back(std::move(*Struct));
544-
545-
for (const auto &Field : Record.Fields) {
546-
auto StructFieldPathComponentGuard = makePathComponentGuard(Field->Name);
547-
auto StructField = serializeAPIRecord(*Field);
548-
549-
if (!StructField)
550-
continue;
551-
552-
Symbols.emplace_back(std::move(*StructField));
553-
serializeRelationship(RelationshipKind::MemberOf, *Field, Record);
554-
}
553+
serializeMembers(Record, Record.Fields);
555554
}
556555

557556
void SymbolGraphSerializer::serializeObjCContainerRecord(
@@ -563,53 +562,33 @@ void SymbolGraphSerializer::serializeObjCContainerRecord(
563562

564563
Symbols.emplace_back(std::move(*ObjCContainer));
565564

566-
// Record instance variables and that the instance variables are members of
567-
// the container.
568-
for (const auto &Ivar : Record.Ivars) {
569-
auto IvarPathComponentGuard = makePathComponentGuard(Ivar->Name);
570-
auto ObjCIvar = serializeAPIRecord(*Ivar);
571-
572-
if (!ObjCIvar)
573-
continue;
574-
575-
Symbols.emplace_back(std::move(*ObjCIvar));
576-
serializeRelationship(RelationshipKind::MemberOf, *Ivar, Record);
577-
}
578-
579-
// Record methods and that the methods are members of the container.
580-
for (const auto &Method : Record.Methods) {
581-
auto MethodPathComponentGuard = makePathComponentGuard(Method->Name);
582-
auto ObjCMethod = serializeAPIRecord(*Method);
583-
584-
if (!ObjCMethod)
585-
continue;
586-
587-
Symbols.emplace_back(std::move(*ObjCMethod));
588-
serializeRelationship(RelationshipKind::MemberOf, *Method, Record);
589-
}
590-
591-
// Record properties and that the properties are members of the container.
592-
for (const auto &Property : Record.Properties) {
593-
auto PropertyPathComponentGuard = makePathComponentGuard(Property->Name);
594-
auto ObjCProperty = serializeAPIRecord(*Property);
595-
596-
if (!ObjCProperty)
597-
continue;
598-
599-
Symbols.emplace_back(std::move(*ObjCProperty));
600-
serializeRelationship(RelationshipKind::MemberOf, *Property, Record);
601-
}
565+
serializeMembers(Record, Record.Ivars);
566+
serializeMembers(Record, Record.Methods);
567+
serializeMembers(Record, Record.Properties);
602568

603569
for (const auto &Protocol : Record.Protocols)
604570
// Record that Record conforms to Protocol.
605571
serializeRelationship(RelationshipKind::ConformsTo, Record, Protocol);
606572

607-
if (auto *ObjCInterface = dyn_cast<ObjCInterfaceRecord>(&Record))
573+
if (auto *ObjCInterface = dyn_cast<ObjCInterfaceRecord>(&Record)) {
608574
if (!ObjCInterface->SuperClass.empty())
609575
// If Record is an Objective-C interface record and it has a super class,
610576
// record that Record is inherited from SuperClass.
611577
serializeRelationship(RelationshipKind::InheritsFrom, Record,
612578
ObjCInterface->SuperClass);
579+
580+
// Members of categories extending an interface are serialized as members of
581+
// the interface.
582+
for (const auto *Category : ObjCInterface->Categories) {
583+
serializeMembers(Record, Category->Ivars);
584+
serializeMembers(Record, Category->Methods);
585+
serializeMembers(Record, Category->Properties);
586+
587+
// Surface the protocols of the the category to the interface.
588+
for (const auto &Protocol : Category->Protocols)
589+
serializeRelationship(RelationshipKind::ConformsTo, Record, Protocol);
590+
}
591+
}
613592
}
614593

615594
void SymbolGraphSerializer::serializeMacroDefinitionRecord(

0 commit comments

Comments
 (0)