Skip to content

[TBDGen] Add support for Objective-C Categories. #59749

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 45 additions & 6 deletions lib/TBDGen/APIGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,40 @@
namespace swift {
namespace apigen {

void API::addSymbol(llvm::StringRef symbol, APILoc loc, APILinkage linkage,
void API::addSymbol(StringRef symbol, APILoc loc, APILinkage linkage,
APIFlags flags, APIAccess access,
APIAvailability availability) {
auto *global = new (allocator) GlobalRecord(
symbol, loc, linkage, flags, access, GVKind::Function, availability);
globals.push_back(global);
}

ObjCInterfaceRecord *API::addObjCClass(llvm::StringRef name, APILinkage linkage,
ObjCInterfaceRecord *API::addObjCClass(StringRef name, APILinkage linkage,
APILoc loc, APIAccess access,
APIAvailability availability,
llvm::StringRef superClassName) {
StringRef superClassName) {
auto *interface = new (allocator) ObjCInterfaceRecord(
name, linkage, loc, access, availability, superClassName);
interfaces.push_back(interface);
return interface;
}

void API::addObjCMethod(ObjCInterfaceRecord *cls, llvm::StringRef name,
APILoc loc, APIAccess access, bool isInstanceMethod,
ObjCCategoryRecord *API::addObjCCategory(StringRef name, APILinkage linkage,
APILoc loc, APIAccess access,
APIAvailability availability,
StringRef interface) {
auto *category = new (allocator)
ObjCCategoryRecord(name, linkage, loc, access, availability, interface);
categories.push_back(category);
return category;
}

void API::addObjCMethod(ObjCContainerRecord *record, StringRef name, APILoc loc,
APIAccess access, bool isInstanceMethod,
bool isOptional, APIAvailability availability) {
auto method = new (allocator) ObjCMethodRecord(
name, loc, access, isInstanceMethod, isOptional, availability);
cls->methods.push_back(method);
record->methods.push_back(method);
}

static void serialize(llvm::json::OStream &OS, APIAccess access) {
Expand Down Expand Up @@ -151,6 +161,30 @@ static void serialize(llvm::json::OStream &OS,
});
}

static void serialize(llvm::json::OStream &OS,
const ObjCCategoryRecord &record) {
OS.object([&]() {
OS.attribute("name", record.name);
serialize(OS, record.access);
serialize(OS, record.loc);
serialize(OS, record.linkage);
serialize(OS, record.availability);
OS.attribute("interface", record.interface);
OS.attributeArray("instanceMethods", [&]() {
for (auto &method : record.methods) {
if (method->isInstanceMethod)
serialize(OS, *method);
}
});
OS.attributeArray("classMethods", [&]() {
for (auto &method : record.methods) {
if (!method->isInstanceMethod)
serialize(OS, *method);
}
});
});
}

void API::writeAPIJSONFile(llvm::raw_ostream &os, bool PrettyPrint) {
unsigned indentSize = PrettyPrint ? 2 : 0;
llvm::json::OStream JSON(os, indentSize);
Expand All @@ -167,6 +201,11 @@ void API::writeAPIJSONFile(llvm::raw_ostream &os, bool PrettyPrint) {
for (const auto *i : interfaces)
serialize(JSON, *i);
});
JSON.attributeArray("categories", [&]() {
llvm::sort(categories, sortAPIRecords);
for (const auto *c : categories)
serialize(JSON, *c);
});
JSON.attribute("version", "1.0");
});
}
Expand Down
50 changes: 33 additions & 17 deletions lib/TBDGen/APIGen.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#ifndef SWIFT_APIGEN_APIGEN_H
#define SWIFT_APIGEN_APIGEN_H

#include "swift/Basic/LLVM.h"
#include "llvm/ADT/BitmaskEnum.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/Optional.h"
Expand Down Expand Up @@ -57,7 +58,7 @@ class APILoc {
APILoc(std::string file, unsigned line, unsigned col)
: file(file), line(line), col(col) {}

llvm::StringRef getFilename() const { return file; }
StringRef getFilename() const { return file; }
unsigned getLine() const { return line; }
unsigned getColumn() const { return col; }

Expand Down Expand Up @@ -85,10 +86,10 @@ struct APIRecord {
APIAccess access;
APIAvailability availability;

APIRecord(llvm::StringRef name, APILoc loc, APILinkage linkage,
APIFlags flags, APIAccess access, APIAvailability availability)
: name(name.data(), name.size()), loc(loc), linkage(linkage), flags(flags),
access(access), availability(availability) {}
APIRecord(StringRef name, APILoc loc, APILinkage linkage, APIFlags flags,
APIAccess access, APIAvailability availability)
: name(name.data(), name.size()), loc(loc), linkage(linkage),
flags(flags), access(access), availability(availability) {}

bool isWeakDefined() const {
return (flags & APIFlags::WeakDefined) == APIFlags::WeakDefined;
Expand Down Expand Up @@ -116,9 +117,8 @@ enum class GVKind : uint8_t {
struct GlobalRecord : APIRecord {
GVKind kind;

GlobalRecord(llvm::StringRef name, APILoc loc, APILinkage linkage,
APIFlags flags, APIAccess access, GVKind kind,
APIAvailability availability)
GlobalRecord(StringRef name, APILoc loc, APILinkage linkage, APIFlags flags,
APIAccess access, GVKind kind, APIAvailability availability)
: APIRecord(name, loc, linkage, flags, access, availability), kind(kind) {
}
};
Expand All @@ -127,7 +127,7 @@ struct ObjCMethodRecord : APIRecord {
bool isInstanceMethod;
bool isOptional;

ObjCMethodRecord(llvm::StringRef name, APILoc loc, APIAccess access,
ObjCMethodRecord(StringRef name, APILoc loc, APIAccess access,
bool isInstanceMethod, bool isOptional,
APIAvailability availability)
: APIRecord(name, loc, APILinkage::Unknown, APIFlags::None, access,
Expand All @@ -138,47 +138,63 @@ struct ObjCMethodRecord : APIRecord {
struct ObjCContainerRecord : APIRecord {
std::vector<ObjCMethodRecord*> methods;

ObjCContainerRecord(llvm::StringRef name, APILinkage linkage, APILoc loc,
ObjCContainerRecord(StringRef name, APILinkage linkage, APILoc loc,
APIAccess access, const APIAvailability &availability)
: APIRecord(name, loc, linkage, APIFlags::None, access, availability) {}
};

struct ObjCInterfaceRecord : ObjCContainerRecord {
std::string superClassName;
ObjCInterfaceRecord(llvm::StringRef name, APILinkage linkage, APILoc loc,
ObjCInterfaceRecord(StringRef name, APILinkage linkage, APILoc loc,
APIAccess access, APIAvailability availability,
llvm::StringRef superClassName)
StringRef superClassName)
: ObjCContainerRecord(name, linkage, loc, access, availability),
superClassName(superClassName.data(), superClassName.size()) {}
};

struct ObjCCategoryRecord : ObjCContainerRecord {
std::string interface;

ObjCCategoryRecord(StringRef name, APILinkage linkage, APILoc loc,
APIAccess access, APIAvailability availability,
StringRef interface)
: ObjCContainerRecord(name, linkage, loc, access, availability),
interface(interface.data(), interface.size()) {}
};

class API {
public:
API(const llvm::Triple &triple) : target(triple) {}

const llvm::Triple &getTarget() const { return target; }

void addSymbol(llvm::StringRef symbol, APILoc loc, APILinkage linkage,
void addSymbol(StringRef symbol, APILoc loc, APILinkage linkage,
APIFlags flags, APIAccess access,
APIAvailability availability);

ObjCInterfaceRecord *addObjCClass(llvm::StringRef name, APILinkage linkage,
ObjCInterfaceRecord *addObjCClass(StringRef name, APILinkage linkage,
APILoc loc, APIAccess access,
APIAvailability availability,
llvm::StringRef superClassName);
StringRef superClassName);

ObjCCategoryRecord *addObjCCategory(StringRef name, APILinkage linkage,
APILoc loc, APIAccess access,
APIAvailability availability,
StringRef interface);

void addObjCMethod(ObjCInterfaceRecord *cls, llvm::StringRef name, APILoc loc,
void addObjCMethod(ObjCContainerRecord *record, StringRef name, APILoc loc,
APIAccess access, bool isInstanceMethod, bool isOptional,
APIAvailability availability);

void writeAPIJSONFile(llvm::raw_ostream &os, bool PrettyPrint = false);
void writeAPIJSONFile(raw_ostream &os, bool PrettyPrint = false);

private:
const llvm::Triple target;

llvm::BumpPtrAllocator allocator;
std::vector<GlobalRecord*> globals;
std::vector<ObjCInterfaceRecord*> interfaces;
std::vector<ObjCCategoryRecord *> categories;
};

} // end namespace apigen
Expand Down
95 changes: 64 additions & 31 deletions lib/TBDGen/TBDGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,15 @@ void TBDGenVisitor::visitAbstractFunctionDecl(AbstractFunctionDecl *AFD) {
if (AFD->hasAsync()) {
addAsyncFunctionPointerSymbol(SILDeclRef(AFD));
}

// Skip non objc compatible methods or non-public methods.
if (isa<DestructorDecl>(AFD) || !AFD->isObjC() ||
AFD->getFormalAccess() != AccessLevel::Public)
return;
if (auto *CD = dyn_cast<ClassDecl>(AFD->getDeclContext()))
recorder.addObjCMethod(CD, SILDeclRef(AFD));
else if (auto *ED = dyn_cast<ExtensionDecl>(AFD->getDeclContext()))
recorder.addObjCMethod(ED, SILDeclRef(AFD));
}

void TBDGenVisitor::visitFuncDecl(FuncDecl *FD) {
Expand Down Expand Up @@ -956,30 +965,9 @@ void TBDGenVisitor::visitClassDecl(ClassDecl *CD) {
}

TBD.addMethodDescriptor(method);

if (auto methodOrCtorOrDtor = method.getDecl()) {
// Skip non objc compatible methods or non-public methods.
if (!methodOrCtorOrDtor->isObjC() ||
methodOrCtorOrDtor->getFormalAccess() != AccessLevel::Public)
return;

// only handle FuncDecl here. Initializers are handled in
// visitConstructorDecl.
if (isa<FuncDecl>(methodOrCtorOrDtor))
recorder.addObjCMethod(CD, method);
}
}

void addMethodOverride(SILDeclRef baseRef, SILDeclRef derivedRef) {
if (auto methodOrCtorOrDtor = derivedRef.getDecl()) {
if (!methodOrCtorOrDtor->isObjC() ||
methodOrCtorOrDtor->getFormalAccess() != AccessLevel::Public)
return;

if (isa<FuncDecl>(methodOrCtorOrDtor))
recorder.addObjCMethod(CD, derivedRef);
}
}
void addMethodOverride(SILDeclRef baseRef, SILDeclRef derivedRef) {}

void addPlaceholder(MissingMemberDecl *) {}

Expand All @@ -1001,10 +989,6 @@ void TBDGenVisitor::visitConstructorDecl(ConstructorDecl *CD) {
addAsyncFunctionPointerSymbol(
SILDeclRef(CD, SILDeclRef::Kind::Initializer));
}
if (auto parentClass = CD->getParent()->getSelfClassDecl()) {
if (parentClass->isObjC() || CD->isObjC())
recorder.addObjCMethod(parentClass, SILDeclRef(CD));
}
}

visitAbstractFunctionDecl(CD);
Expand Down Expand Up @@ -1397,8 +1381,11 @@ class APIGenRecorder final : public APIRecorder {
addOrGetObjCInterface(decl);
}

void addObjCMethod(const ClassDecl *cls,
SILDeclRef method) override {
void addObjCCategory(const ExtensionDecl *decl) override {
addOrGetObjCCategory(decl);
}

void addObjCMethod(const GenericContext *ctx, SILDeclRef method) override {
SmallString<128> buffer;
StringRef name = getSelectorName(method, buffer);
apigen::APIAvailability availability;
Expand All @@ -1413,12 +1400,23 @@ class APIGenRecorder final : public APIRecorder {
access = apigen::APIAccess::Private;
}

auto *clsRecord = addOrGetObjCInterface(cls);
api.addObjCMethod(clsRecord, name, moduleLoc, access, isInstanceMethod,
false, availability);
apigen::ObjCContainerRecord *record = nullptr;
if (auto *cls = dyn_cast<ClassDecl>(ctx))
record = addOrGetObjCInterface(cls);
else if (auto *ext = dyn_cast<ExtensionDecl>(ctx))
record = addOrGetObjCCategory(ext);

if (record)
api.addObjCMethod(record, name, moduleLoc, access, isInstanceMethod,
false, availability);
}

private:
/// Follow the naming schema that IRGen uses for Categories (see
/// ClassDataBuilder).
using CategoryNameKey = std::pair<const ClassDecl *, const ModuleDecl *>;
llvm::DenseMap<CategoryNameKey, unsigned> CategoryCounts;

apigen::APIAvailability getAvailability(const Decl *decl) {
bool unavailable = false;
std::string introduced, obsoleted;
Expand Down Expand Up @@ -1475,11 +1473,46 @@ class APIGenRecorder final : public APIRecorder {
return cls;
}

void buildCategoryName(const ExtensionDecl *ext, const ClassDecl *cls,
SmallVectorImpl<char> &s) {
llvm::raw_svector_ostream os(s);
ModuleDecl *module = ext->getParentModule();
os << module->getName();
unsigned categoryCount = CategoryCounts[{cls, module}]++;
if (categoryCount > 0)
os << categoryCount;
}

apigen::ObjCCategoryRecord *addOrGetObjCCategory(const ExtensionDecl *decl) {
auto entry = categoryMap.find(decl);
if (entry != categoryMap.end())
return entry->second;

SmallString<128> interfaceBuffer;
SmallString<128> nameBuffer;
ClassDecl *cls = decl->getSelfClassDecl();
auto interface = cls->getObjCRuntimeName(interfaceBuffer);
buildCategoryName(decl, cls, nameBuffer);
apigen::APIAvailability availability = getAvailability(decl);
apigen::APIAccess access =
decl->isSPI() ? apigen::APIAccess::Private : apigen::APIAccess::Public;
apigen::APILinkage linkage =
decl->getMaxAccessLevel() == AccessLevel::Public
? apigen::APILinkage::Exported
: apigen::APILinkage::Internal;
auto category = api.addObjCCategory(nameBuffer, linkage, moduleLoc, access,
availability, interface);
categoryMap.try_emplace(decl, category);
return category;
}

apigen::API &api;
ModuleDecl *module;
apigen::APILoc moduleLoc;

llvm::DenseMap<const ClassDecl*, apigen::ObjCInterfaceRecord*> classMap;
llvm::DenseMap<const ExtensionDecl *, apigen::ObjCCategoryRecord *>
categoryMap;
};

apigen::API APIGenRequest::evaluate(Evaluator &evaluator,
Expand Down
3 changes: 2 additions & 1 deletion lib/TBDGen/TBDGenVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ class APIRecorder {
virtual void addSymbol(StringRef name, llvm::MachO::SymbolKind kind,
SymbolSource source) {}
virtual void addObjCInterface(const ClassDecl *decl) {}
virtual void addObjCMethod(const ClassDecl *cls, SILDeclRef method) {}
virtual void addObjCCategory(const ExtensionDecl *decl) {}
virtual void addObjCMethod(const GenericContext *ctx, SILDeclRef method) {}
};

class SimpleAPIRecorder final : public APIRecorder {
Expand Down
Loading