| //===--- 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(); |
| } |