Skip to content

Commit a9e02e9

Browse files
committed
[TBDGen] Add support for Objective-C Categories.
Emit Objective-C Categories for extensions that have the @objc attribute directly (or indirectly via one of its methods, subscripts, etc) attached. Also associate and emit all methods for that category into the API JSON file. This fixes rdar://94734748.
1 parent fb5ef68 commit a9e02e9

File tree

8 files changed

+247
-46
lines changed

8 files changed

+247
-46
lines changed

lib/TBDGen/APIGen.cpp

+41-2
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,22 @@ ObjCInterfaceRecord *API::addObjCClass(StringRef name, APILinkage linkage,
4242
return interface;
4343
}
4444

45-
void API::addObjCMethod(ObjCInterfaceRecord *cls, StringRef name, APILoc loc,
45+
ObjCCategoryRecord *API::addObjCCategory(StringRef name, APILinkage linkage,
46+
APILoc loc, APIAccess access,
47+
APIAvailability availability,
48+
StringRef interface) {
49+
auto *category = new (allocator)
50+
ObjCCategoryRecord(name, linkage, loc, access, availability, interface);
51+
categories.push_back(category);
52+
return category;
53+
}
54+
55+
void API::addObjCMethod(ObjCContainerRecord *record, StringRef name, APILoc loc,
4656
APIAccess access, bool isInstanceMethod,
4757
bool isOptional, APIAvailability availability) {
4858
auto method = new (allocator) ObjCMethodRecord(
4959
name, loc, access, isInstanceMethod, isOptional, availability);
50-
cls->methods.push_back(method);
60+
record->methods.push_back(method);
5161
}
5262

5363
static void serialize(llvm::json::OStream &OS, APIAccess access) {
@@ -151,6 +161,30 @@ static void serialize(llvm::json::OStream &OS,
151161
});
152162
}
153163

164+
static void serialize(llvm::json::OStream &OS,
165+
const ObjCCategoryRecord &record) {
166+
OS.object([&]() {
167+
OS.attribute("name", record.name);
168+
serialize(OS, record.access);
169+
serialize(OS, record.loc);
170+
serialize(OS, record.linkage);
171+
serialize(OS, record.availability);
172+
OS.attribute("interface", record.interface);
173+
OS.attributeArray("instanceMethods", [&]() {
174+
for (auto &method : record.methods) {
175+
if (method->isInstanceMethod)
176+
serialize(OS, *method);
177+
}
178+
});
179+
OS.attributeArray("classMethods", [&]() {
180+
for (auto &method : record.methods) {
181+
if (!method->isInstanceMethod)
182+
serialize(OS, *method);
183+
}
184+
});
185+
});
186+
}
187+
154188
void API::writeAPIJSONFile(llvm::raw_ostream &os, bool PrettyPrint) {
155189
unsigned indentSize = PrettyPrint ? 2 : 0;
156190
llvm::json::OStream JSON(os, indentSize);
@@ -167,6 +201,11 @@ void API::writeAPIJSONFile(llvm::raw_ostream &os, bool PrettyPrint) {
167201
for (const auto *i : interfaces)
168202
serialize(JSON, *i);
169203
});
204+
JSON.attributeArray("categories", [&]() {
205+
llvm::sort(categories, sortAPIRecords);
206+
for (const auto *c : categories)
207+
serialize(JSON, *c);
208+
});
170209
JSON.attribute("version", "1.0");
171210
});
172211
}

lib/TBDGen/APIGen.h

+18-2
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,16 @@ struct ObjCInterfaceRecord : ObjCContainerRecord {
152152
superClassName(superClassName.data(), superClassName.size()) {}
153153
};
154154

155+
struct ObjCCategoryRecord : ObjCContainerRecord {
156+
std::string interface;
157+
158+
ObjCCategoryRecord(StringRef name, APILinkage linkage, APILoc loc,
159+
APIAccess access, APIAvailability availability,
160+
StringRef interface)
161+
: ObjCContainerRecord(name, linkage, loc, access, availability),
162+
interface(interface.data(), interface.size()) {}
163+
};
164+
155165
class API {
156166
public:
157167
API(const llvm::Triple &triple) : target(triple) {}
@@ -167,18 +177,24 @@ class API {
167177
APIAvailability availability,
168178
StringRef superClassName);
169179

170-
void addObjCMethod(ObjCInterfaceRecord *cls, StringRef name, APILoc loc,
180+
ObjCCategoryRecord *addObjCCategory(StringRef name, APILinkage linkage,
181+
APILoc loc, APIAccess access,
182+
APIAvailability availability,
183+
StringRef interface);
184+
185+
void addObjCMethod(ObjCContainerRecord *record, StringRef name, APILoc loc,
171186
APIAccess access, bool isInstanceMethod, bool isOptional,
172187
APIAvailability availability);
173188

174-
void writeAPIJSONFile(llvm::raw_ostream &os, bool PrettyPrint = false);
189+
void writeAPIJSONFile(raw_ostream &os, bool PrettyPrint = false);
175190

176191
private:
177192
const llvm::Triple target;
178193

179194
llvm::BumpPtrAllocator allocator;
180195
std::vector<GlobalRecord*> globals;
181196
std::vector<ObjCInterfaceRecord*> interfaces;
197+
std::vector<ObjCCategoryRecord *> categories;
182198
};
183199

184200
} // end namespace apigen

lib/TBDGen/TBDGen.cpp

+64-31
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,15 @@ void TBDGenVisitor::visitAbstractFunctionDecl(AbstractFunctionDecl *AFD) {
747747
if (AFD->hasAsync()) {
748748
addAsyncFunctionPointerSymbol(SILDeclRef(AFD));
749749
}
750+
751+
// Skip non objc compatible methods or non-public methods.
752+
if (isa<DestructorDecl>(AFD) || !AFD->isObjC() ||
753+
AFD->getFormalAccess() != AccessLevel::Public)
754+
return;
755+
if (auto *CD = dyn_cast<ClassDecl>(AFD->getDeclContext()))
756+
recorder.addObjCMethod(CD, SILDeclRef(AFD));
757+
else if (auto *ED = dyn_cast<ExtensionDecl>(AFD->getDeclContext()))
758+
recorder.addObjCMethod(ED, SILDeclRef(AFD));
750759
}
751760

752761
void TBDGenVisitor::visitFuncDecl(FuncDecl *FD) {
@@ -956,30 +965,9 @@ void TBDGenVisitor::visitClassDecl(ClassDecl *CD) {
956965
}
957966

958967
TBD.addMethodDescriptor(method);
959-
960-
if (auto methodOrCtorOrDtor = method.getDecl()) {
961-
// Skip non objc compatible methods or non-public methods.
962-
if (!methodOrCtorOrDtor->isObjC() ||
963-
methodOrCtorOrDtor->getFormalAccess() != AccessLevel::Public)
964-
return;
965-
966-
// only handle FuncDecl here. Initializers are handled in
967-
// visitConstructorDecl.
968-
if (isa<FuncDecl>(methodOrCtorOrDtor))
969-
recorder.addObjCMethod(CD, method);
970-
}
971968
}
972969

973-
void addMethodOverride(SILDeclRef baseRef, SILDeclRef derivedRef) {
974-
if (auto methodOrCtorOrDtor = derivedRef.getDecl()) {
975-
if (!methodOrCtorOrDtor->isObjC() ||
976-
methodOrCtorOrDtor->getFormalAccess() != AccessLevel::Public)
977-
return;
978-
979-
if (isa<FuncDecl>(methodOrCtorOrDtor))
980-
recorder.addObjCMethod(CD, derivedRef);
981-
}
982-
}
970+
void addMethodOverride(SILDeclRef baseRef, SILDeclRef derivedRef) {}
983971

984972
void addPlaceholder(MissingMemberDecl *) {}
985973

@@ -1001,10 +989,6 @@ void TBDGenVisitor::visitConstructorDecl(ConstructorDecl *CD) {
1001989
addAsyncFunctionPointerSymbol(
1002990
SILDeclRef(CD, SILDeclRef::Kind::Initializer));
1003991
}
1004-
if (auto parentClass = CD->getParent()->getSelfClassDecl()) {
1005-
if (parentClass->isObjC() || CD->isObjC())
1006-
recorder.addObjCMethod(parentClass, SILDeclRef(CD));
1007-
}
1008992
}
1009993

1010994
visitAbstractFunctionDecl(CD);
@@ -1397,8 +1381,11 @@ class APIGenRecorder final : public APIRecorder {
13971381
addOrGetObjCInterface(decl);
13981382
}
13991383

1400-
void addObjCMethod(const ClassDecl *cls,
1401-
SILDeclRef method) override {
1384+
void addObjCCategory(const ExtensionDecl *decl) override {
1385+
addOrGetObjCCategory(decl);
1386+
}
1387+
1388+
void addObjCMethod(const GenericContext *ctx, SILDeclRef method) override {
14021389
SmallString<128> buffer;
14031390
StringRef name = getSelectorName(method, buffer);
14041391
apigen::APIAvailability availability;
@@ -1413,12 +1400,23 @@ class APIGenRecorder final : public APIRecorder {
14131400
access = apigen::APIAccess::Private;
14141401
}
14151402

1416-
auto *clsRecord = addOrGetObjCInterface(cls);
1417-
api.addObjCMethod(clsRecord, name, moduleLoc, access, isInstanceMethod,
1418-
false, availability);
1403+
apigen::ObjCContainerRecord *record = nullptr;
1404+
if (auto *cls = dyn_cast<ClassDecl>(ctx))
1405+
record = addOrGetObjCInterface(cls);
1406+
else if (auto *ext = dyn_cast<ExtensionDecl>(ctx))
1407+
record = addOrGetObjCCategory(ext);
1408+
1409+
if (record)
1410+
api.addObjCMethod(record, name, moduleLoc, access, isInstanceMethod,
1411+
false, availability);
14191412
}
14201413

14211414
private:
1415+
/// Follow the naming schema that IRGen uses for Categories (see
1416+
/// ClassDataBuilder).
1417+
using CategoryNameKey = std::pair<const ClassDecl *, const ModuleDecl *>;
1418+
llvm::DenseMap<CategoryNameKey, unsigned> CategoryCounts;
1419+
14221420
apigen::APIAvailability getAvailability(const Decl *decl) {
14231421
bool unavailable = false;
14241422
std::string introduced, obsoleted;
@@ -1475,11 +1473,46 @@ class APIGenRecorder final : public APIRecorder {
14751473
return cls;
14761474
}
14771475

1476+
void buildCategoryName(const ExtensionDecl *ext, const ClassDecl *cls,
1477+
SmallVectorImpl<char> &s) {
1478+
llvm::raw_svector_ostream os(s);
1479+
ModuleDecl *module = ext->getParentModule();
1480+
os << module->getName();
1481+
unsigned categoryCount = CategoryCounts[{cls, module}]++;
1482+
if (categoryCount > 0)
1483+
os << categoryCount;
1484+
}
1485+
1486+
apigen::ObjCCategoryRecord *addOrGetObjCCategory(const ExtensionDecl *decl) {
1487+
auto entry = categoryMap.find(decl);
1488+
if (entry != categoryMap.end())
1489+
return entry->second;
1490+
1491+
SmallString<128> interfaceBuffer;
1492+
SmallString<128> nameBuffer;
1493+
ClassDecl *cls = decl->getSelfClassDecl();
1494+
auto interface = cls->getObjCRuntimeName(interfaceBuffer);
1495+
buildCategoryName(decl, cls, nameBuffer);
1496+
apigen::APIAvailability availability = getAvailability(decl);
1497+
apigen::APIAccess access =
1498+
decl->isSPI() ? apigen::APIAccess::Private : apigen::APIAccess::Public;
1499+
apigen::APILinkage linkage =
1500+
decl->getMaxAccessLevel() == AccessLevel::Public
1501+
? apigen::APILinkage::Exported
1502+
: apigen::APILinkage::Internal;
1503+
auto category = api.addObjCCategory(nameBuffer, linkage, moduleLoc, access,
1504+
availability, interface);
1505+
categoryMap.try_emplace(decl, category);
1506+
return category;
1507+
}
1508+
14781509
apigen::API &api;
14791510
ModuleDecl *module;
14801511
apigen::APILoc moduleLoc;
14811512

14821513
llvm::DenseMap<const ClassDecl*, apigen::ObjCInterfaceRecord*> classMap;
1514+
llvm::DenseMap<const ExtensionDecl *, apigen::ObjCCategoryRecord *>
1515+
categoryMap;
14831516
};
14841517

14851518
apigen::API APIGenRequest::evaluate(Evaluator &evaluator,

lib/TBDGen/TBDGenVisitor.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ class APIRecorder {
6868
virtual void addSymbol(StringRef name, llvm::MachO::SymbolKind kind,
6969
SymbolSource source) {}
7070
virtual void addObjCInterface(const ClassDecl *decl) {}
71-
virtual void addObjCMethod(const ClassDecl *cls, SILDeclRef method) {}
71+
virtual void addObjCCategory(const ExtensionDecl *decl) {}
72+
virtual void addObjCMethod(const GenericContext *ctx, SILDeclRef method) {}
7273
};
7374

7475
class SimpleAPIRecorder final : public APIRecorder {

test/APIJSON/apigen.swift

+5-4
Original file line numberDiff line numberDiff line change
@@ -364,12 +364,12 @@ public func myFunction2() {}
364364
// CHECK-NEXT: "super": "NSObject",
365365
// CHECK-NEXT: "instanceMethods": [
366366
// CHECK-NEXT: {
367-
// CHECK-NEXT: "name": "init",
367+
// CHECK-NEXT: "name": "method1",
368368
// CHECK-NEXT: "access": "public",
369369
// CHECK-NEXT: "file": "/@input/MyModule.swiftinterface"
370370
// CHECK-NEXT: },
371371
// CHECK-NEXT: {
372-
// CHECK-NEXT: "name": "method1",
372+
// CHECK-NEXT: "name": "init",
373373
// CHECK-NEXT: "access": "public",
374374
// CHECK-NEXT: "file": "/@input/MyModule.swiftinterface"
375375
// CHECK-NEXT: }
@@ -416,17 +416,18 @@ public func myFunction2() {}
416416
// CHECK-NEXT: "super": "_TtC8MyModule4Test",
417417
// CHECK-NEXT: "instanceMethods": [
418418
// CHECK-NEXT: {
419-
// CHECK-NEXT: "name": "init",
419+
// CHECK-NEXT: "name": "method1",
420420
// CHECK-NEXT: "access": "public",
421421
// CHECK-NEXT: "file": "/@input/MyModule.swiftinterface"
422422
// CHECK-NEXT: },
423423
// CHECK-NEXT: {
424-
// CHECK-NEXT: "name": "method1",
424+
// CHECK-NEXT: "name": "init",
425425
// CHECK-NEXT: "access": "public",
426426
// CHECK-NEXT: "file": "/@input/MyModule.swiftinterface"
427427
// CHECK-NEXT: }
428428
// CHECK-NEXT: ],
429429
// CHECK-NEXT: "classMethods": []
430430
// CHECK-NEXT: }
431431
// CHECK-NEXT: ],
432+
// CHECK-NEXT: "categories": [],
432433
// CHECK-NEXT: "version": "1.0"

0 commit comments

Comments
 (0)