blob: 333bcf8310ae54230806ca8936eee492851f5d5c [file] [log] [blame]
//===--- ModuleContentsWriter.cpp - Walk a module's decls to print ObjC ---===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "ModuleContentsWriter.h"
#include "DeclAndTypePrinter.h"
#include "swift/AST/ExistentialLayout.h"
#include "swift/AST/Module.h"
#include "swift/AST/ProtocolConformance.h"
#include "swift/AST/SwiftNameTranslation.h"
#include "swift/AST/TypeDeclFinder.h"
#include "swift/ClangImporter/ClangImporter.h"
#include "clang/AST/Decl.h"
#include "llvm/Support/raw_ostream.h"
using namespace swift;
using namespace swift::objc_translation;
using DelayedMemberSet = DeclAndTypePrinter::DelayedMemberSet;
/// Returns true if \p decl represents an <os/object.h> type.
static bool isOSObjectType(const clang::Decl *decl) {
auto *named = dyn_cast_or_null<clang::NamedDecl>(decl);
if (!named)
return false;
return !DeclAndTypePrinter::maybeGetOSObjectBaseName(named).empty();
}
namespace {
class ReferencedTypeFinder : public TypeDeclFinder {
friend TypeDeclFinder;
llvm::function_ref<void(ReferencedTypeFinder &, const TypeDecl *)> Callback;
bool NeedsDefinition = false;
explicit ReferencedTypeFinder(decltype(Callback) callback)
: Callback(callback) {}
Action visitNominalType(NominalType *nominal) override {
Callback(*this, nominal->getDecl());
return Action::SkipChildren;
}
Action visitTypeAliasType(TypeAliasType *aliasTy) override {
if (aliasTy->getDecl()->hasClangNode() &&
!aliasTy->getDecl()->isCompatibilityAlias()) {
assert(!aliasTy->getGenericSignature());
Callback(*this, aliasTy->getDecl());
} else {
Type(aliasTy->getSinglyDesugaredType()).walk(*this);
}
return Action::SkipChildren;
}
/// Returns true if \p paramTy has any constraints other than being
/// class-bound ("conforms to" AnyObject).
static bool isConstrained(GenericSignature sig,
GenericTypeParamType *paramTy) {
if (sig->getSuperclassBound(paramTy))
return true;
auto conformsTo = sig->getConformsTo(paramTy);
return !conformsTo.empty();
}
Action visitBoundGenericType(BoundGenericType *boundGeneric) override {
auto *decl = boundGeneric->getDecl();
NeedsDefinition = true;
Callback(*this, decl);
NeedsDefinition = false;
bool isObjCGeneric = decl->hasClangNode();
auto sig = decl->getGenericSignature();
for_each(boundGeneric->getGenericArgs(),
sig->getInnermostGenericParams(),
[&](Type argTy, GenericTypeParamType *paramTy) {
// FIXME: I think there's a bug here with recursive generic types.
if (isObjCGeneric && isConstrained(sig, paramTy))
NeedsDefinition = true;
argTy.walk(*this);
NeedsDefinition = false;
});
return Action::SkipChildren;
}
public:
bool needsDefinition() const {
return NeedsDefinition;
}
static void walk(Type ty, decltype(Callback) callback) {
ty.walk(ReferencedTypeFinder(callback));
}
};
class ModuleWriter {
enum class EmissionState {
NotYetDefined = 0,
DefinitionRequested,
Defined
};
raw_ostream &os;
SmallPtrSetImpl<ImportModuleTy> &imports;
ModuleDecl &M;
llvm::DenseMap<const TypeDecl *, std::pair<EmissionState, bool>> seenTypes;
std::vector<const Decl *> declsToWrite;
DelayedMemberSet delayedMembers;
DeclAndTypePrinter printer;
public:
ModuleWriter(raw_ostream &os, llvm::SmallPtrSetImpl<ImportModuleTy> &imports,
ModuleDecl &mod, AccessLevel access)
: os(os), imports(imports), M(mod), printer(M, os, delayedMembers, access){}
/// Returns true if we added the decl's module to the import set, false if
/// the decl is a local decl.
///
/// The standard library is special-cased: we assume that any types from it
/// will be handled explicitly rather than needing an explicit @import.
bool addImport(const Decl *D) {
ModuleDecl *otherModule = D->getModuleContext();
if (otherModule == &M)
return false;
if (otherModule->isStdlibModule() ||
otherModule->isBuiltinModule())
return true;
// Don't need a module for SIMD types in C.
if (otherModule->getName() == M.getASTContext().Id_simd)
return true;
// If there's a Clang node, see if it comes from an explicit submodule.
// Import that instead, looking through any implicit submodules.
if (auto clangNode = D->getClangNode()) {
auto importer =
static_cast<ClangImporter *>(M.getASTContext().getClangModuleLoader());
if (const auto *clangModule = importer->getClangOwningModule(clangNode)) {
while (clangModule && !clangModule->IsExplicit)
clangModule = clangModule->Parent;
if (clangModule) {
imports.insert(clangModule);
return true;
}
}
}
imports.insert(otherModule);
return true;
}
bool hasBeenRequested(const TypeDecl *D) const {
return seenTypes.lookup(D).first >= EmissionState::DefinitionRequested;
}
bool tryRequire(const TypeDecl *D) {
if (addImport(D)) {
seenTypes[D] = { EmissionState::Defined, true };
return true;
}
auto &state = seenTypes[D];
return state.first == EmissionState::Defined;
}
bool require(const TypeDecl *D) {
if (addImport(D)) {
seenTypes[D] = { EmissionState::Defined, true };
return true;
}
auto &state = seenTypes[D];
switch (state.first) {
case EmissionState::NotYetDefined:
case EmissionState::DefinitionRequested:
state.first = EmissionState::DefinitionRequested;
declsToWrite.push_back(D);
return false;
case EmissionState::Defined:
return true;
}
llvm_unreachable("Unhandled EmissionState in switch.");
}
void forwardDeclare(const NominalTypeDecl *NTD,
llvm::function_ref<void(void)> Printer) {
if (NTD->getModuleContext()->isStdlibModule())
return;
auto &state = seenTypes[NTD];
if (state.second)
return;
Printer();
state.second = true;
}
bool forwardDeclare(const ClassDecl *CD) {
if (!CD->isObjC() ||
CD->getForeignClassKind() == ClassDecl::ForeignKind::CFType ||
isOSObjectType(CD->getClangDecl())) {
return false;
}
forwardDeclare(CD, [&]{ os << "@class " << getNameForObjC(CD) << ";\n"; });
return true;
}
void forwardDeclare(const ProtocolDecl *PD) {
assert(PD->isObjC() ||
*PD->getKnownProtocolKind() == KnownProtocolKind::Error);
forwardDeclare(PD, [&]{
os << "@protocol " << getNameForObjC(PD) << ";\n";
});
}
void forwardDeclare(const EnumDecl *ED) {
assert(ED->isObjC() || ED->hasClangNode());
forwardDeclare(ED, [&]{
os << "enum " << getNameForObjC(ED) << " : ";
printer.print(ED->getRawType());
os << ";\n";
});
}
bool forwardDeclareMemberTypes(DeclRange members, const Decl *container) {
switch (container->getKind()) {
case DeclKind::Class:
case DeclKind::Protocol:
case DeclKind::Extension:
break;
default:
llvm_unreachable("unexpected container kind");
}
bool hadAnyDelayedMembers = false;
SmallVector<ValueDecl *, 4> nestedTypes;
for (auto member : members) {
auto VD = dyn_cast<ValueDecl>(member);
if (!VD || !printer.shouldInclude(VD))
continue;
// Catch nested types and emit their definitions /after/ this class.
if (isa<TypeDecl>(VD)) {
// Don't emit nested types that are just implicitly @objc.
// You should have to opt into this, since they are even less
// namespaced than usual.
if (std::any_of(VD->getAttrs().begin(), VD->getAttrs().end(),
[](const DeclAttribute *attr) {
return isa<ObjCAttr>(attr) && !attr->isImplicit();
})) {
nestedTypes.push_back(VD);
}
continue;
}
bool needsToBeIndividuallyDelayed = false;
ReferencedTypeFinder::walk(VD->getInterfaceType(),
[&](ReferencedTypeFinder &finder,
const TypeDecl *TD) {
if (TD == container)
return;
if (finder.needsDefinition() && isa<NominalTypeDecl>(TD)) {
// We can delay individual members of classes; do so if necessary.
if (isa<ClassDecl>(container)) {
if (!tryRequire(TD)) {
needsToBeIndividuallyDelayed = true;
hadAnyDelayedMembers = true;
}
return;
}
// Extensions can always be delayed wholesale.
if (isa<ExtensionDecl>(container)) {
if (!require(TD))
hadAnyDelayedMembers = true;
return;
}
// Protocols should be delayed wholesale unless we might have a cycle.
auto *proto = cast<ProtocolDecl>(container);
if (!hasBeenRequested(proto) || !hasBeenRequested(TD)) {
if (!require(TD))
hadAnyDelayedMembers = true;
return;
}
// Otherwise, we have a cyclic dependency. Give up and continue with
// regular forward-declarations even though this will lead to an
// error; there's nothing we can do here.
// FIXME: It would be nice to diagnose this.
}
if (auto CD = dyn_cast<ClassDecl>(TD)) {
if (!forwardDeclare(CD)) {
(void)addImport(CD);
}
} else if (auto PD = dyn_cast<ProtocolDecl>(TD)) {
forwardDeclare(PD);
} else if (auto TAD = dyn_cast<TypeAliasDecl>(TD)) {
if (TAD->hasClangNode())
(void)addImport(TD);
} else if (addImport(TD)) {
return;
} else if (auto ED = dyn_cast<EnumDecl>(TD)) {
forwardDeclare(ED);
} else if (isa<AbstractTypeParamDecl>(TD)) {
llvm_unreachable("should not see type params here");
} else {
assert(false && "unknown local type decl");
}
});
if (needsToBeIndividuallyDelayed) {
assert(isa<ClassDecl>(container));
delayedMembers.insert(VD);
}
}
declsToWrite.insert(declsToWrite.end()-1, nestedTypes.rbegin(),
nestedTypes.rend());
// Separate forward declarations from the class itself.
return !hadAnyDelayedMembers;
}
bool writeClass(const ClassDecl *CD) {
if (addImport(CD))
return true;
if (seenTypes[CD].first == EmissionState::Defined)
return true;
bool allRequirementsSatisfied = true;
const ClassDecl *superclass = nullptr;
if ((superclass = CD->getSuperclassDecl())) {
allRequirementsSatisfied &= require(superclass);
}
for (auto proto : CD->getLocalProtocols(
ConformanceLookupKind::OnlyExplicit))
if (printer.shouldInclude(proto))
allRequirementsSatisfied &= require(proto);
if (!allRequirementsSatisfied)
return false;
(void)forwardDeclareMemberTypes(CD->getMembers(), CD);
seenTypes[CD] = { EmissionState::Defined, true };
os << '\n';
printer.print(CD);
return true;
}
bool writeFunc(const FuncDecl *FD) {
if (addImport(FD))
return true;
printer.print(FD);
return true;
}
bool writeProtocol(const ProtocolDecl *PD) {
if (addImport(PD))
return true;
if (seenTypes[PD].first == EmissionState::Defined)
return true;
bool allRequirementsSatisfied = true;
for (auto proto : PD->getInheritedProtocols()) {
assert(proto->isObjC());
allRequirementsSatisfied &= require(proto);
}
if (!allRequirementsSatisfied)
return false;
if (!forwardDeclareMemberTypes(PD->getMembers(), PD))
return false;
seenTypes[PD] = { EmissionState::Defined, true };
os << '\n';
printer.print(PD);
return true;
}
bool writeExtension(const ExtensionDecl *ED) {
bool allRequirementsSatisfied = true;
const ClassDecl *CD = ED->getSelfClassDecl();
allRequirementsSatisfied &= require(CD);
for (auto proto : ED->getLocalProtocols())
if (printer.shouldInclude(proto))
allRequirementsSatisfied &= require(proto);
if (!allRequirementsSatisfied)
return false;
// This isn't rolled up into the previous set of requirements because
// it /also/ prints forward declarations, and the header is a little
// prettier if those are as close as possible to the necessary extension.
if (!forwardDeclareMemberTypes(ED->getMembers(), ED))
return false;
os << '\n';
printer.print(ED);
return true;
}
bool writeEnum(const EnumDecl *ED) {
if (addImport(ED))
return true;
if (seenTypes[ED].first == EmissionState::Defined)
return true;
seenTypes[ED] = {EmissionState::Defined, true};
printer.print(ED);
ASTContext &ctx = M.getASTContext();
SmallVector<ProtocolConformance *, 1> conformances;
auto errorTypeProto = ctx.getProtocol(KnownProtocolKind::Error);
if (ED->lookupConformance(&M, errorTypeProto, conformances)) {
bool hasDomainCase = std::any_of(ED->getAllElements().begin(),
ED->getAllElements().end(),
[](const EnumElementDecl *elem) {
return elem->getName().str() == "Domain";
});
if (!hasDomainCase) {
os << "static NSString * _Nonnull const " << getNameForObjC(ED)
<< "Domain = @\"" << getErrorDomainStringForObjC(ED) << "\";\n";
}
}
return true;
}
void write() {
SmallVector<Decl *, 64> decls;
M.getTopLevelDecls(decls);
auto newEnd = std::remove_if(decls.begin(), decls.end(),
[this](const Decl *D) -> bool {
if (auto VD = dyn_cast<ValueDecl>(D))
return !printer.shouldInclude(VD);
if (auto ED = dyn_cast<ExtensionDecl>(D)) {
auto baseClass = ED->getSelfClassDecl();
return !baseClass || !printer.shouldInclude(baseClass) ||
baseClass->isForeign();
}
return true;
});
decls.erase(newEnd, decls.end());
// REVERSE sort the decls, since we are going to copy them onto a stack.
llvm::array_pod_sort(decls.begin(), decls.end(),
[](Decl * const *lhs, Decl * const *rhs) -> int {
enum : int {
Ascending = -1,
Equivalent = 0,
Descending = 1,
};
assert(*lhs != *rhs && "duplicate top-level decl");
auto getSortName = [](const Decl *D) -> StringRef {
if (auto VD = dyn_cast<ValueDecl>(D))
return VD->getBaseName().userFacingName();
if (auto ED = dyn_cast<ExtensionDecl>(D)) {
auto baseClass = ED->getSelfClassDecl();
return baseClass->getName().str();
}
llvm_unreachable("unknown top-level ObjC decl");
};
// Sort by names.
int result = getSortName(*rhs).compare(getSortName(*lhs));
if (result != 0)
return result;
// Prefer value decls to extensions.
assert(!(isa<ValueDecl>(*lhs) && isa<ValueDecl>(*rhs)));
if (isa<ValueDecl>(*lhs) && !isa<ValueDecl>(*rhs))
return Descending;
if (!isa<ValueDecl>(*lhs) && isa<ValueDecl>(*rhs))
return Ascending;
// Break ties in extensions by putting smaller extensions last (in reverse
// order).
// FIXME: This will end up taking linear time.
auto lhsMembers = cast<ExtensionDecl>(*lhs)->getMembers();
auto rhsMembers = cast<ExtensionDecl>(*rhs)->getMembers();
unsigned numLHSMembers = std::distance(lhsMembers.begin(),
lhsMembers.end());
unsigned numRHSMembers = std::distance(rhsMembers.begin(),
rhsMembers.end());
if (numLHSMembers != numRHSMembers)
return numLHSMembers < numRHSMembers ? Descending : Ascending;
// Or the extension with fewer protocols.
auto lhsProtos = cast<ExtensionDecl>(*lhs)->getLocalProtocols();
auto rhsProtos = cast<ExtensionDecl>(*rhs)->getLocalProtocols();
if (lhsProtos.size() != rhsProtos.size())
return lhsProtos.size() < rhsProtos.size() ? Descending : Ascending;
// If that fails, arbitrarily pick the extension whose protocols are
// alphabetically first.
auto mismatch =
std::mismatch(lhsProtos.begin(), lhsProtos.end(), rhsProtos.begin(),
[] (const ProtocolDecl *nextLHSProto,
const ProtocolDecl *nextRHSProto) {
return nextLHSProto->getName() != nextRHSProto->getName();
});
if (mismatch.first == lhsProtos.end())
return Equivalent;
StringRef lhsProtoName = (*mismatch.first)->getName().str();
return lhsProtoName.compare((*mismatch.second)->getName().str());
});
assert(declsToWrite.empty());
declsToWrite.assign(decls.begin(), decls.end());
while (!declsToWrite.empty()) {
const Decl *D = declsToWrite.back();
bool success = true;
if (isa<ValueDecl>(D)) {
if (auto CD = dyn_cast<ClassDecl>(D))
success = writeClass(CD);
else if (auto PD = dyn_cast<ProtocolDecl>(D))
success = writeProtocol(PD);
else if (auto ED = dyn_cast<EnumDecl>(D))
success = writeEnum(ED);
else if (auto ED = dyn_cast<FuncDecl>(D))
success = writeFunc(ED);
else
llvm_unreachable("unknown top-level ObjC value decl");
} else if (auto ED = dyn_cast<ExtensionDecl>(D)) {
success = writeExtension(ED);
} else {
llvm_unreachable("unknown top-level ObjC decl");
}
if (success) {
assert(declsToWrite.back() == D);
os << "\n";
declsToWrite.pop_back();
}
}
if (!delayedMembers.empty()) {
auto groupBegin = delayedMembers.begin();
for (auto i = groupBegin, e = delayedMembers.end(); i != e; ++i) {
if ((*i)->getDeclContext() != (*groupBegin)->getDeclContext()) {
printer.printAdHocCategory(make_range(groupBegin, i));
groupBegin = i;
}
}
printer.printAdHocCategory(make_range(groupBegin, delayedMembers.end()));
}
}
};
} // end anonymous namespace
void
swift::printModuleContentsAsObjC(raw_ostream &os,
llvm::SmallPtrSetImpl<ImportModuleTy> &imports,
ModuleDecl &M, AccessLevel minRequiredAccess) {
ModuleWriter(os, imports, M, minRequiredAccess).write();
}