| //===--- ImportResolution.cpp - Import Resolution -------------------------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2020 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file performs import resolution. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #define DEBUG_TYPE "swift-import-resolution" |
| #include "swift/AST/ASTWalker.h" |
| #include "swift/AST/DiagnosticsSema.h" |
| #include "swift/AST/ModuleLoader.h" |
| #include "swift/AST/ModuleNameLookup.h" |
| #include "swift/AST/NameLookup.h" |
| #include "swift/AST/SourceFile.h" |
| #include "swift/AST/SubstitutionMap.h" |
| #include "swift/AST/TypeCheckRequests.h" |
| #include "swift/Basic/Statistic.h" |
| #include "swift/ClangImporter/ClangModule.h" |
| #include "swift/Parse/Parser.h" |
| #include "swift/Subsystems.h" |
| #include "clang/Basic/Module.h" |
| #include "llvm/ADT/DenseMap.h" |
| #include "llvm/ADT/TinyPtrVector.h" |
| #include "llvm/ADT/Twine.h" |
| #include "llvm/Support/Debug.h" |
| #include "llvm/Support/Host.h" |
| #include "llvm/Support/Path.h" |
| #include "llvm/Support/SaveAndRestore.h" |
| #include <algorithm> |
| #include <system_error> |
| using namespace swift; |
| |
| //===----------------------------------------------------------------------===// |
| // MARK: ImportResolver and supporting types |
| //===----------------------------------------------------------------------===// |
| |
| namespace { |
| /// Represents an import which the ImportResolver knows exists, but which has |
| /// not yet had its options checked, module loaded, or cross-imports found. |
| /// |
| /// An UnboundImport may represent a physical ImportDecl written in the |
| /// source, or it may represent a cross-import overlay that has been found and |
| /// needs to be loaded. |
| struct UnboundImport { |
| /// Information about the import. Use this field, not \c getImportDecl(), to |
| /// determine the behavior expected for this import. |
| AttributedImport<UnloadedImportedModule> import; |
| |
| /// The source location to use when diagnosing errors for this import. |
| SourceLoc importLoc; |
| |
| /// If this UnboundImport directly represents an ImportDecl, contains the |
| /// ImportDecl it represents. This should only be used for diagnostics and |
| /// for updating the AST; if you want to read information about the import, |
| /// get it from the \c import field rather than from the \c ImportDecl. |
| /// |
| /// If this UnboundImport represents a cross-import, contains the declaring |
| /// module's \c ModuleDecl. |
| PointerUnion<NullablePtr<ImportDecl>, ModuleDecl *> |
| importOrUnderlyingModuleDecl; |
| |
| NullablePtr<ImportDecl> getImportDecl() const { |
| return importOrUnderlyingModuleDecl.is<NullablePtr<ImportDecl>>() ? |
| importOrUnderlyingModuleDecl.get<NullablePtr<ImportDecl>>() : nullptr; |
| } |
| |
| NullablePtr<ModuleDecl> getUnderlyingModule() const { |
| return importOrUnderlyingModuleDecl.is<ModuleDecl *>() ? |
| importOrUnderlyingModuleDecl.get<ModuleDecl *>() : nullptr; |
| } |
| |
| /// Create an UnboundImport for a user-written import declaration. |
| explicit UnboundImport(ImportDecl *ID); |
| |
| /// Create an UnboundImport for an unloaded implicit import. |
| explicit UnboundImport(AttributedImport<UnloadedImportedModule> implicit); |
| |
| /// Create an UnboundImport for a cross-import overlay. |
| explicit UnboundImport(ASTContext &ctx, |
| const UnboundImport &base, Identifier overlayName, |
| const AttributedImport<ImportedModule> &declaringImport, |
| const AttributedImport<ImportedModule> &bystandingImport); |
| |
| /// Diagnoses if the import would simply load the module \p SF already |
| /// belongs to, with no actual effect. |
| /// |
| /// Some apparent self-imports do actually load a different module; this |
| /// method allows them. |
| bool checkNotTautological(const SourceFile &SF); |
| |
| /// Make sure the module actually loaded, and diagnose if it didn't. |
| bool checkModuleLoaded(ModuleDecl *M, SourceFile &SF); |
| |
| /// Find the top-level module for this module; that is, if \p M is the |
| /// module \c Foo.Bar.Baz, this finds \c Foo. |
| /// |
| /// Specifically, this method returns: |
| /// |
| /// \li \p M if \p M is a top-level module. |
| /// \li \c nullptr if \p M is a submodule of \c SF's parent module. (This |
| /// corner case can occur in mixed-source frameworks, where Swift code |
| /// can import a Clang submodule of itself.) |
| /// \li The top-level parent (i.e. ancestor with no parent) module above |
| /// \p M otherwise. |
| NullablePtr<ModuleDecl> getTopLevelModule(ModuleDecl *M, SourceFile &SF); |
| |
| /// Diagnose any errors concerning the \c @_exported, \c @_implementationOnly, |
| /// \c \@testable, or \c @_private attributes, including a |
| /// non-implementation-only import of a fragile library from a resilient one. |
| void validateOptions(NullablePtr<ModuleDecl> topLevelModule, SourceFile &SF); |
| |
| /// Create an \c AttributedImport<ImportedModule> from the information in this |
| /// UnboundImport. |
| AttributedImport<ImportedModule> |
| makeAttributedImport(ModuleDecl *module) const { |
| return import.getLoaded(module); |
| } |
| |
| private: |
| void validatePrivate(ModuleDecl *topLevelModule); |
| void validateImplementationOnly(ASTContext &ctx); |
| void validateTestable(ModuleDecl *topLevelModule); |
| void validateResilience(NullablePtr<ModuleDecl> topLevelModule, |
| SourceFile &SF); |
| |
| /// Diagnoses an inability to import \p modulePath in this situation and, if |
| /// \p attrs is provided and has an \p attrKind, invalidates the attribute and |
| /// offers a fix-it to remove it. |
| void diagnoseInvalidAttr(DeclAttrKind attrKind, DiagnosticEngine &diags, |
| Diag<Identifier> diagID); |
| }; |
| |
| class ImportResolver final : public DeclVisitor<ImportResolver> { |
| friend DeclVisitor<ImportResolver>; |
| |
| SourceFile &SF; |
| ASTContext &ctx; |
| |
| /// Imports which still need their options checked, modules loaded, and |
| /// cross-imports found. |
| SmallVector<UnboundImport, 4> unboundImports; |
| |
| /// The list of fully bound imports. |
| SmallVector<AttributedImport<ImportedModule>, 16> boundImports; |
| |
| /// All imported modules which should be considered when cross-importing. |
| /// This is basically the transitive import graph, but with only top-level |
| /// modules and without reexports from Objective-C modules. |
| /// |
| /// We use a \c SmallSetVector here because this doubles as the worklist for |
| /// cross-importing, so we want to keep it in order; this is feasible |
| /// because this set is usually fairly small. |
| SmallSetVector<AttributedImport<ImportedModule>, 64> crossImportableModules; |
| |
| /// The subset of \c crossImportableModules which may declare cross-imports. |
| /// |
| /// This is a performance optimization. Since most modules do not register |
| /// any cross-imports, we can usually compare against this list, which is |
| /// much, much smaller than \c crossImportableModules. |
| SmallVector<AttributedImport<ImportedModule>, 16> crossImportDeclaringModules; |
| |
| /// The index of the next module in \c visibleModules that should be |
| /// cross-imported. |
| size_t nextModuleToCrossImport = 0; |
| |
| public: |
| ImportResolver(SourceFile &SF) : SF(SF), ctx(SF.getASTContext()) { |
| addImplicitImports(); |
| } |
| |
| void addImplicitImports(); |
| |
| /// Retrieve the finalized imports. |
| ArrayRef<AttributedImport<ImportedModule>> getFinishedImports() const { |
| return boundImports; |
| } |
| |
| private: |
| // We only need to visit import decls. |
| void visitImportDecl(ImportDecl *ID); |
| |
| // Ignore other decls. |
| void visitDecl(Decl *D) {} |
| |
| template<typename ...ArgTypes> |
| InFlightDiagnostic diagnose(ArgTypes &&...Args) { |
| return ctx.Diags.diagnose(std::forward<ArgTypes>(Args)...); |
| } |
| |
| /// Calls \c bindImport() on unbound imports until \c boundImports is drained. |
| void bindPendingImports(); |
| |
| /// Check a single unbound import, bind it, add it to \c boundImports, |
| /// and add its cross-import overlays to \c unboundImports. |
| void bindImport(UnboundImport &&I); |
| |
| /// Adds \p I and \p M to \c boundImports and \c visibleModules. |
| void addImport(const UnboundImport &I, ModuleDecl *M); |
| |
| /// Adds \p desc and everything it re-exports to \c visibleModules using |
| /// the settings from \c desc. |
| void addCrossImportableModules(AttributedImport<ImportedModule> desc); |
| |
| /// * If \p I is a cross-import overlay, registers \p M as overlaying |
| /// \p I.underlyingModule in \c SF. |
| /// * Discovers any cross-imports between \p I and previously bound imports, |
| /// then adds them to \c unboundImports using source locations from \p I. |
| void crossImport(ModuleDecl *M, UnboundImport &I); |
| |
| /// Discovers any cross-imports between \p newImport and |
| /// \p oldImports and adds them to \c unboundImports, using source |
| /// locations from \p I. |
| void findCrossImportsInLists( |
| UnboundImport &I, |
| ArrayRef<AttributedImport<ImportedModule>> declaring, |
| ArrayRef<AttributedImport<ImportedModule>> bystanding, |
| bool shouldDiagnoseRedundantCrossImports); |
| |
| /// Discovers any cross-imports between \p declaringImport and |
| /// \p bystandingImport and adds them to \c unboundImports, using source |
| /// locations from \p I. |
| void findCrossImports(UnboundImport &I, |
| const AttributedImport<ImportedModule> &declaringImport, |
| const AttributedImport<ImportedModule> &bystandingImport, |
| bool shouldDiagnoseRedundantCrossImports); |
| |
| /// Load a module referenced by an import statement. |
| /// |
| /// Returns null if no module can be loaded. |
| ModuleDecl *getModule(ImportPath::Module ModuleID); |
| }; |
| } // end anonymous namespace |
| |
| //===----------------------------------------------------------------------===// |
| // MARK: performImportResolution |
| //===----------------------------------------------------------------------===// |
| |
| /// performImportResolution - This walks the AST to resolve imports. |
| /// |
| /// Before we can type-check a source file, we need to make declarations |
| /// imported from other modules available. This is done by processing top-level |
| /// \c ImportDecl nodes, along with related validation. |
| /// |
| /// Import resolution operates on a parsed but otherwise unvalidated AST. |
| void swift::performImportResolution(SourceFile &SF) { |
| // If we've already performed import resolution, bail. |
| if (SF.ASTStage == SourceFile::ImportsResolved) |
| return; |
| |
| FrontendStatsTracer tracer(SF.getASTContext().Stats, |
| "Import resolution"); |
| |
| // If we're silencing parsing warnings, then also silence import warnings. |
| // This is necessary for secondary files as they can be parsed and have their |
| // imports resolved multiple times. |
| auto &diags = SF.getASTContext().Diags; |
| auto didSuppressWarnings = diags.getSuppressWarnings(); |
| auto shouldSuppress = SF.getParsingOptions().contains( |
| SourceFile::ParsingFlags::SuppressWarnings); |
| diags.setSuppressWarnings(didSuppressWarnings || shouldSuppress); |
| SWIFT_DEFER { diags.setSuppressWarnings(didSuppressWarnings); }; |
| |
| ImportResolver resolver(SF); |
| |
| // Resolve each import declaration. |
| for (auto D : SF.getTopLevelDecls()) |
| resolver.visit(D); |
| for (auto D : SF.getHoistedDecls()) |
| resolver.visit(D); |
| |
| SF.setImports(resolver.getFinishedImports()); |
| |
| SF.ASTStage = SourceFile::ImportsResolved; |
| verify(SF); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // MARK: Import handling generally |
| //===----------------------------------------------------------------------===// |
| |
| void ImportResolver::visitImportDecl(ImportDecl *ID) { |
| assert(unboundImports.empty()); |
| |
| unboundImports.emplace_back(ID); |
| bindPendingImports(); |
| } |
| |
| void ImportResolver::bindPendingImports() { |
| while(!unboundImports.empty()) |
| bindImport(unboundImports.pop_back_val()); |
| } |
| |
| void ImportResolver::bindImport(UnboundImport &&I) { |
| auto ID = I.getImportDecl(); |
| |
| if (!I.checkNotTautological(SF)) { |
| // No need to process this import further. |
| if (ID) |
| ID.get()->setModule(SF.getParentModule()); |
| return; |
| } |
| |
| ModuleDecl *M = getModule(I.import.module.getModulePath()); |
| if (!I.checkModuleLoaded(M, SF)) { |
| // Can't process further. checkModuleLoaded() will have diagnosed this. |
| if (ID) |
| ID.get()->setModule(nullptr); |
| return; |
| } |
| |
| auto topLevelModule = I.getTopLevelModule(M, SF); |
| |
| I.validateOptions(topLevelModule, SF); |
| |
| if (topLevelModule && topLevelModule != M) { |
| // If we have distinct submodule and top-level module, add both. |
| addImport(I, M); |
| addImport(I, topLevelModule.get()); |
| } |
| else { |
| // Add only the import itself. |
| addImport(I, M); |
| } |
| |
| crossImport(M, I); |
| |
| if (ID) |
| ID.get()->setModule(M); |
| } |
| |
| void ImportResolver::addImport(const UnboundImport &I, ModuleDecl *M) { |
| auto importDesc = I.makeAttributedImport(M); |
| addCrossImportableModules(importDesc); |
| boundImports.push_back(importDesc); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // MARK: Import module loading |
| //===----------------------------------------------------------------------===// |
| |
| static ModuleDecl * |
| getModuleImpl(ImportPath::Module modulePath, ModuleDecl *loadingModule, |
| bool canImportBuiltin) { |
| ASTContext &ctx = loadingModule->getASTContext(); |
| |
| assert(!modulePath.empty()); |
| auto moduleID = modulePath[0]; |
| |
| // The Builtin module cannot be explicitly imported unless we're a .sil file. |
| if (canImportBuiltin && moduleID.Item == ctx.TheBuiltinModule->getName()) |
| return ctx.TheBuiltinModule; |
| |
| // If the imported module name is the same as the current module, |
| // skip the Swift module loader and use the Clang module loader instead. |
| // This allows a Swift module to extend a Clang module of the same name. |
| // |
| // FIXME: We'd like to only use this in SIL mode, but unfortunately we use it |
| // for clang overlays as well. |
| if (moduleID.Item == loadingModule->getName() && modulePath.size() == 1) { |
| if (auto importer = ctx.getClangModuleLoader()) |
| return importer->loadModule(moduleID.Loc, modulePath); |
| return nullptr; |
| } |
| |
| return ctx.getModule(modulePath); |
| } |
| |
| ModuleDecl * |
| ImportResolver::getModule(ImportPath::Module modulePath) { |
| return getModuleImpl(modulePath, SF.getParentModule(), |
| /*canImportBuiltin=*/SF.Kind == SourceFileKind::SIL); |
| } |
| |
| NullablePtr<ModuleDecl> |
| UnboundImport::getTopLevelModule(ModuleDecl *M, SourceFile &SF) { |
| if (import.module.getModulePath().size() == 1) |
| return M; |
| |
| // If we imported a submodule, import the top-level module as well. |
| Identifier topLevelName = import.module.getModulePath().front().Item; |
| ModuleDecl *topLevelModule = SF.getASTContext().getLoadedModule(topLevelName); |
| |
| if (!topLevelModule) { |
| // Clang can sometimes import top-level modules as if they were |
| // submodules. |
| assert(!M->getFiles().empty() && |
| isa<ClangModuleUnit>(M->getFiles().front())); |
| return M; |
| } |
| |
| if (topLevelModule == SF.getParentModule()) |
| // This can happen when compiling a mixed-source framework (or overlay) |
| // that imports a submodule of its C part. |
| return nullptr; |
| |
| return topLevelModule; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // MARK: Implicit imports |
| //===----------------------------------------------------------------------===// |
| |
| static void diagnoseNoSuchModule(ModuleDecl *importingModule, |
| SourceLoc importLoc, |
| ImportPath::Module modulePath, |
| bool nonfatalInREPL) { |
| ASTContext &ctx = importingModule->getASTContext(); |
| |
| if (modulePath.size() == 1 && |
| importingModule->getName() == modulePath.front().Item) { |
| ctx.Diags.diagnose(importLoc, diag::error_underlying_module_not_found, |
| importingModule->getName()); |
| } else { |
| SmallString<64> modulePathStr; |
| modulePath.getString(modulePathStr); |
| |
| auto diagKind = diag::sema_no_import; |
| if (nonfatalInREPL && ctx.LangOpts.DebuggerSupport) |
| diagKind = diag::sema_no_import_repl; |
| ctx.Diags.diagnose(importLoc, diagKind, modulePathStr); |
| } |
| |
| if (ctx.SearchPathOpts.SDKPath.empty() && |
| llvm::Triple(llvm::sys::getProcessTriple()).isMacOSX()) { |
| ctx.Diags.diagnose(SourceLoc(), diag::sema_no_import_no_sdk); |
| ctx.Diags.diagnose(SourceLoc(), diag::sema_no_import_no_sdk_xcrun); |
| } |
| } |
| |
| ImplicitImportList |
| ModuleImplicitImportsRequest::evaluate(Evaluator &evaluator, |
| ModuleDecl *module) const { |
| SmallVector<AttributedImport<ImportedModule>, 4> imports; |
| SmallVector<AttributedImport<UnloadedImportedModule>, 4> unloadedImports; |
| |
| auto &ctx = module->getASTContext(); |
| auto &importInfo = module->getImplicitImportInfo(); |
| |
| // Add an implicit stdlib if needed. |
| ModuleDecl *stdlib; |
| switch (importInfo.StdlibKind) { |
| case ImplicitStdlibKind::None: |
| stdlib = nullptr; |
| break; |
| case ImplicitStdlibKind::Builtin: |
| stdlib = ctx.TheBuiltinModule; |
| break; |
| case ImplicitStdlibKind::Stdlib: |
| stdlib = ctx.getStdlibModule(/*loadIfAbsent*/ true); |
| assert(stdlib && "Missing stdlib?"); |
| break; |
| } |
| |
| if (stdlib) |
| imports.emplace_back(ImportedModule(stdlib)); |
| |
| // Add any modules we were asked to implicitly import. |
| llvm::copy(importInfo.AdditionalUnloadedImports, |
| std::back_inserter(unloadedImports)); |
| |
| // Add any pre-loaded modules. |
| llvm::copy(importInfo.AdditionalImports, std::back_inserter(imports)); |
| |
| auto *clangImporter = |
| static_cast<ClangImporter *>(ctx.getClangModuleLoader()); |
| |
| // Implicitly import the bridging header module if needed. |
| auto bridgingHeaderPath = importInfo.BridgingHeaderPath; |
| if (!bridgingHeaderPath.empty() && |
| !clangImporter->importBridgingHeader(bridgingHeaderPath, module)) { |
| auto *headerModule = clangImporter->getImportedHeaderModule(); |
| assert(headerModule && "Didn't load bridging header?"); |
| imports.emplace_back(ImportedModule(headerModule), ImportFlags::Exported); |
| } |
| |
| // Implicitly import the underlying Clang half of this module if needed. |
| if (importInfo.ShouldImportUnderlyingModule) { |
| // An @_exported self-import is loaded from ClangImporter instead of being |
| // rejected; see the special case in getModuleImpl() for details. |
| ImportPath::Builder importPath(module->getName()); |
| unloadedImports.emplace_back(UnloadedImportedModule(importPath.copyTo(ctx), |
| /*isScoped=*/false), |
| ImportFlags::Exported); |
| } |
| |
| return { ctx.AllocateCopy(imports), ctx.AllocateCopy(unloadedImports) }; |
| } |
| |
| void ImportResolver::addImplicitImports() { |
| auto implicitImports = SF.getParentModule()->getImplicitImports(); |
| |
| // TODO: Support cross-module imports. |
| for (auto &import : implicitImports.imports) { |
| assert(!(SF.Kind == SourceFileKind::SIL && |
| import.module.importedModule->isStdlibModule())); |
| boundImports.push_back(import); |
| } |
| |
| for (auto &unloadedImport : implicitImports.unloadedImports) |
| unboundImports.emplace_back(unloadedImport); |
| |
| bindPendingImports(); |
| } |
| |
| UnboundImport::UnboundImport(AttributedImport<UnloadedImportedModule> implicit) |
| : import(implicit), importLoc(), |
| importOrUnderlyingModuleDecl(static_cast<ImportDecl *>(nullptr)) {} |
| |
| //===----------------------------------------------------------------------===// |
| // MARK: Import validation (except for scoped imports) |
| //===----------------------------------------------------------------------===// |
| |
| /// Create an UnboundImport for a user-written import declaration. |
| UnboundImport::UnboundImport(ImportDecl *ID) |
| : import(UnloadedImportedModule(ID->getImportPath(), ID->getImportKind()), |
| {}), |
| importLoc(ID->getLoc()), importOrUnderlyingModuleDecl(ID) |
| { |
| if (ID->isExported()) |
| import.options |= ImportFlags::Exported; |
| |
| if (ID->getAttrs().hasAttribute<TestableAttr>()) |
| import.options |= ImportFlags::Testable; |
| |
| if (ID->getAttrs().hasAttribute<ImplementationOnlyAttr>()) |
| import.options |= ImportFlags::ImplementationOnly; |
| |
| if (auto *privateImportAttr = |
| ID->getAttrs().getAttribute<PrivateImportAttr>()) { |
| import.options |= ImportFlags::PrivateImport; |
| import.sourceFileArg = privateImportAttr->getSourceFile(); |
| } |
| |
| SmallVector<Identifier, 4> spiGroups; |
| for (auto attr : ID->getAttrs().getAttributes<SPIAccessControlAttr>()) { |
| import.options |= ImportFlags::SPIAccessControl; |
| auto attrSPIs = attr->getSPIGroups(); |
| spiGroups.append(attrSPIs.begin(), attrSPIs.end()); |
| } |
| import.spiGroups = ID->getASTContext().AllocateCopy(spiGroups); |
| } |
| |
| bool UnboundImport::checkNotTautological(const SourceFile &SF) { |
| // Exit early if this is not a self-import. |
| auto modulePath = import.module.getModulePath(); |
| if (modulePath.front().Item != SF.getParentModule()->getName() || |
| // Overlays use an @_exported self-import to load their clang module. |
| import.options.contains(ImportFlags::Exported) || |
| // Imports of your own submodules are allowed in cross-language libraries. |
| modulePath.size() != 1 || |
| // SIL files self-import to get decls from the rest of the module. |
| SF.Kind == SourceFileKind::SIL) |
| return true; |
| |
| ASTContext &ctx = SF.getASTContext(); |
| |
| StringRef filename = llvm::sys::path::filename(SF.getFilename()); |
| if (filename.empty()) |
| ctx.Diags.diagnose(importLoc, diag::sema_import_current_module, |
| modulePath.front().Item); |
| else |
| ctx.Diags.diagnose(importLoc, diag::sema_import_current_module_with_file, |
| filename, modulePath.front().Item); |
| |
| return false; |
| } |
| |
| bool UnboundImport::checkModuleLoaded(ModuleDecl *M, SourceFile &SF) { |
| if (M) |
| return true; |
| |
| diagnoseNoSuchModule(SF.getParentModule(), importLoc, |
| import.module.getModulePath(), /*nonfatalInREPL=*/true); |
| return false; |
| } |
| |
| void UnboundImport::validateOptions(NullablePtr<ModuleDecl> topLevelModule, |
| SourceFile &SF) { |
| validateImplementationOnly(SF.getASTContext()); |
| |
| if (auto *top = topLevelModule.getPtrOrNull()) { |
| // FIXME: Having these two calls in this if condition seems dubious. |
| // |
| // Here's the deal: Per getTopLevelModule(), we will only skip this block |
| // if you are in a mixed-source module and trying to import a submodule from |
| // your clang half. But that means you're trying to @testable import or |
| // @_private import part of yourself--and, moreover, a clang part of |
| // yourself--which doesn't make any sense to do. Shouldn't we diagnose that? |
| // |
| // I'm leaving this alone for now because I'm trying to refactor without |
| // changing behavior, but it smells funny. |
| validateTestable(top); |
| validatePrivate(top); |
| } |
| |
| validateResilience(topLevelModule, SF); |
| } |
| |
| void UnboundImport::validatePrivate(ModuleDecl *topLevelModule) { |
| assert(topLevelModule); |
| ASTContext &ctx = topLevelModule->getASTContext(); |
| |
| if (!import.options.contains(ImportFlags::PrivateImport)) |
| return; |
| |
| if (topLevelModule->arePrivateImportsEnabled()) |
| return; |
| |
| diagnoseInvalidAttr(DAK_PrivateImport, ctx.Diags, |
| diag::module_not_compiled_for_private_import); |
| import.sourceFileArg = StringRef(); |
| } |
| |
| void UnboundImport::validateImplementationOnly(ASTContext &ctx) { |
| if (!import.options.contains(ImportFlags::ImplementationOnly) || |
| !import.options.contains(ImportFlags::Exported)) |
| return; |
| |
| // Remove one flag to maintain the invariant. |
| import.options -= ImportFlags::ImplementationOnly; |
| |
| diagnoseInvalidAttr(DAK_ImplementationOnly, ctx.Diags, |
| diag::import_implementation_cannot_be_exported); |
| } |
| |
| void UnboundImport::validateTestable(ModuleDecl *topLevelModule) { |
| assert(topLevelModule); |
| ASTContext &ctx = topLevelModule->getASTContext(); |
| |
| if (!import.options.contains(ImportFlags::Testable) || |
| topLevelModule->isTestingEnabled() || |
| topLevelModule->isNonSwiftModule() || |
| !ctx.LangOpts.EnableTestableAttrRequiresTestableModule) |
| return; |
| |
| diagnoseInvalidAttr(DAK_Testable, ctx.Diags, diag::module_not_testable); |
| } |
| |
| void UnboundImport::validateResilience(NullablePtr<ModuleDecl> topLevelModule, |
| SourceFile &SF) { |
| if (import.options.contains(ImportFlags::ImplementationOnly)) |
| return; |
| |
| // Per getTopLevelModule(), we'll only get nullptr here for non-Swift modules, |
| // so these two really mean the same thing. |
| if (!topLevelModule || topLevelModule.get()->isNonSwiftModule()) |
| return; |
| |
| if (!SF.getParentModule()->isResilient() || |
| topLevelModule.get()->isResilient()) |
| return; |
| |
| ASTContext &ctx = SF.getASTContext(); |
| ctx.Diags.diagnose(import.module.getModulePath().front().Loc, |
| diag::module_not_compiled_with_library_evolution, |
| topLevelModule.get()->getName(), |
| SF.getParentModule()->getName()); |
| // FIXME: Once @_implementationOnly is a real feature, we should have a fix-it |
| // to add it. |
| } |
| |
| void UnboundImport::diagnoseInvalidAttr(DeclAttrKind attrKind, |
| DiagnosticEngine &diags, |
| Diag<Identifier> diagID) { |
| auto diag = diags.diagnose(import.module.getModulePath().front().Loc, diagID, |
| import.module.getModulePath().front().Item); |
| |
| auto *ID = getImportDecl().getPtrOrNull(); |
| if (!ID) return; |
| auto *attr = ID->getAttrs().getAttribute(attrKind); |
| if (!attr) return; |
| |
| diag.fixItRemove(attr->getRangeWithAt()); |
| attr->setInvalid(); |
| } |
| |
| evaluator::SideEffect |
| CheckInconsistentImplementationOnlyImportsRequest::evaluate( |
| Evaluator &evaluator, ModuleDecl *mod) const { |
| bool hasAnyImplementationOnlyImports = |
| llvm::any_of(mod->getFiles(), [](const FileUnit *F) -> bool { |
| auto *SF = dyn_cast<SourceFile>(F); |
| return SF && SF->hasImplementationOnlyImports(); |
| }); |
| if (!hasAnyImplementationOnlyImports) |
| return {}; |
| |
| auto diagnose = [mod](const ImportDecl *normalImport, |
| const ImportDecl *implementationOnlyImport) { |
| auto &diags = mod->getDiags(); |
| { |
| InFlightDiagnostic warning = |
| diags.diagnose(normalImport, diag::warn_implementation_only_conflict, |
| normalImport->getModule()->getName()); |
| if (normalImport->getAttrs().isEmpty()) { |
| // Only try to add a fix-it if there's no other annotations on the |
| // import to avoid creating things like |
| // `@_implementationOnly @_exported import Foo`. The developer can |
| // resolve those manually. |
| warning.fixItInsert(normalImport->getStartLoc(), |
| "@_implementationOnly "); |
| } |
| } |
| diags.diagnose(implementationOnlyImport, |
| diag::implementation_only_conflict_here); |
| }; |
| |
| llvm::DenseMap<ModuleDecl *, std::vector<const ImportDecl *>> normalImports; |
| llvm::DenseMap<ModuleDecl *, const ImportDecl *> implementationOnlyImports; |
| |
| for (const FileUnit *file : mod->getFiles()) { |
| auto *SF = dyn_cast<SourceFile>(file); |
| if (!SF) |
| continue; |
| |
| for (auto *topLevelDecl : SF->getTopLevelDecls()) { |
| auto *nextImport = dyn_cast<ImportDecl>(topLevelDecl); |
| if (!nextImport) |
| continue; |
| |
| ModuleDecl *module = nextImport->getModule(); |
| if (!module) |
| continue; |
| |
| if (nextImport->getAttrs().hasAttribute<ImplementationOnlyAttr>()) { |
| // We saw an implementation-only import. |
| bool isNew = |
| implementationOnlyImports.insert({module, nextImport}).second; |
| if (!isNew) |
| continue; |
| |
| auto seenNormalImportPosition = normalImports.find(module); |
| if (seenNormalImportPosition != normalImports.end()) { |
| for (auto *seenNormalImport : seenNormalImportPosition->getSecond()) |
| diagnose(seenNormalImport, nextImport); |
| |
| // We're done with these; keep the map small if possible. |
| normalImports.erase(seenNormalImportPosition); |
| } |
| continue; |
| } |
| |
| // We saw a non-implementation-only import. Is that in conflict with what |
| // we've seen? |
| if (auto *seenImplementationOnlyImport = |
| implementationOnlyImports.lookup(module)) { |
| diagnose(nextImport, seenImplementationOnlyImport); |
| continue; |
| } |
| |
| // Otherwise, record it for later. |
| normalImports[module].push_back(nextImport); |
| } |
| } |
| return {}; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // MARK: Scoped imports |
| //===----------------------------------------------------------------------===// |
| |
| /// Returns true if a decl with the given \p actual kind can legally be |
| /// imported via the given \p expected kind. |
| static bool isCompatibleImportKind(ImportKind expected, ImportKind actual) { |
| if (expected == actual) |
| return true; |
| if (expected != ImportKind::Type) |
| return false; |
| |
| switch (actual) { |
| case ImportKind::Module: |
| llvm_unreachable("module imports do not bring in decls"); |
| case ImportKind::Type: |
| llvm_unreachable("individual decls cannot have abstract import kind"); |
| case ImportKind::Struct: |
| case ImportKind::Class: |
| case ImportKind::Enum: |
| return true; |
| case ImportKind::Protocol: |
| case ImportKind::Var: |
| case ImportKind::Func: |
| return false; |
| } |
| |
| llvm_unreachable("Unhandled ImportKind in switch."); |
| } |
| |
| static bool isNominalImportKind(ImportKind kind) { |
| switch (kind) { |
| case ImportKind::Module: |
| llvm_unreachable("module imports do not bring in decls"); |
| case ImportKind::Struct: |
| case ImportKind::Class: |
| case ImportKind::Enum: |
| case ImportKind::Protocol: |
| return true; |
| case ImportKind::Type: |
| case ImportKind::Var: |
| case ImportKind::Func: |
| return false; |
| } |
| llvm_unreachable("unhandled kind"); |
| } |
| |
| static const char *getImportKindString(ImportKind kind) { |
| switch (kind) { |
| case ImportKind::Module: |
| llvm_unreachable("module imports do not bring in decls"); |
| case ImportKind::Type: |
| return "typealias"; |
| case ImportKind::Struct: |
| return "struct"; |
| case ImportKind::Class: |
| return "class"; |
| case ImportKind::Enum: |
| return "enum"; |
| case ImportKind::Protocol: |
| return "protocol"; |
| case ImportKind::Var: |
| return "var"; |
| case ImportKind::Func: |
| return "func"; |
| } |
| |
| llvm_unreachable("Unhandled ImportKind in switch."); |
| } |
| |
| ArrayRef<ValueDecl *> |
| ScopedImportLookupRequest::evaluate(Evaluator &evaluator, |
| ImportDecl *import) const { |
| using namespace namelookup; |
| |
| auto importKind = import->getImportKind(); |
| assert(importKind != ImportKind::Module); |
| |
| // If we weren't able to load the module referenced by the import, we're done. |
| // The fact that we failed to load the module has already been diagnosed by |
| // import resolution. |
| auto *module = import->getModule(); |
| if (!module) |
| return ArrayRef<ValueDecl *>(); |
| |
| /// Validate the scoped import. |
| /// |
| /// We validate the scope by making sure that the named declaration exists |
| /// and is of the kind indicated by the keyword. This can't be done until |
| /// we've performed import resolution, since that can introduce additional |
| /// imports (such as cross-import overlays) which could provide the declaration. |
| auto &ctx = module->getASTContext(); |
| auto accessPath = import->getAccessPath(); |
| auto modulePath = import->getModulePath(); |
| auto *topLevelModule = module->getTopLevelModule(); |
| |
| // Lookup the referenced decl in the top-level module. This is necessary as |
| // the Clang importer currently handles submodules by importing their decls |
| // into the top-level module. |
| // FIXME: Doesn't handle scoped testable imports correctly. |
| assert(accessPath.size() == 1 && "can't handle sub-decl imports"); |
| SmallVector<ValueDecl *, 8> decls; |
| lookupInModule(topLevelModule, accessPath.front().Item, decls, |
| NLKind::QualifiedLookup, ResolutionKind::Overloadable, |
| import->getDeclContext()->getModuleScopeContext(), |
| NL_QualifiedDefault); |
| |
| auto importLoc = import->getLoc(); |
| if (decls.empty()) { |
| ctx.Diags.diagnose(importLoc, diag::decl_does_not_exist_in_module, |
| static_cast<unsigned>(importKind), |
| accessPath.front().Item, modulePath.front().Item) |
| .highlight(accessPath.getSourceRange()); |
| return ArrayRef<ValueDecl *>(); |
| } |
| |
| Optional<ImportKind> actualKind = ImportDecl::findBestImportKind(decls); |
| if (!actualKind.hasValue()) { |
| // FIXME: print entire module name? |
| ctx.Diags.diagnose(importLoc, diag::ambiguous_decl_in_module, |
| accessPath.front().Item, module->getName()); |
| for (auto next : decls) |
| ctx.Diags.diagnose(next, diag::found_candidate); |
| |
| } else if (!isCompatibleImportKind(importKind, *actualKind)) { |
| Optional<InFlightDiagnostic> emittedDiag; |
| if (*actualKind == ImportKind::Type && isNominalImportKind(importKind)) { |
| assert(decls.size() == 1 && |
| "if we start suggesting ImportKind::Type for, e.g., a mix of " |
| "structs and classes, we'll need a different message here"); |
| assert(isa<TypeAliasDecl>(decls.front()) && |
| "ImportKind::Type is only the best choice for a typealias"); |
| auto *typealias = cast<TypeAliasDecl>(decls.front()); |
| emittedDiag.emplace(ctx.Diags.diagnose( |
| importLoc, diag::imported_decl_is_wrong_kind_typealias, |
| typealias->getDescriptiveKind(), |
| TypeAliasType::get(typealias, Type(), SubstitutionMap(), |
| typealias->getUnderlyingType()), |
| getImportKindString(importKind))); |
| } else { |
| emittedDiag.emplace(ctx.Diags.diagnose( |
| importLoc, diag::imported_decl_is_wrong_kind, |
| accessPath.front().Item, getImportKindString(importKind), |
| static_cast<unsigned>(*actualKind))); |
| } |
| |
| emittedDiag->fixItReplace(SourceRange(import->getKindLoc()), |
| getImportKindString(*actualKind)); |
| emittedDiag->flush(); |
| |
| if (decls.size() == 1) |
| ctx.Diags.diagnose(decls.front(), diag::decl_declared_here, |
| decls.front()->getName()); |
| } |
| return ctx.AllocateCopy(decls); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // MARK: Cross-import overlays |
| //===----------------------------------------------------------------------===// |
| |
| static bool canCrossImport(const AttributedImport<ImportedModule> &import) { |
| if (import.options.contains(ImportFlags::Testable)) |
| return false; |
| if (import.options.contains(ImportFlags::PrivateImport)) |
| return false; |
| |
| return true; |
| } |
| |
| static UnloadedImportedModule makeUnimportedCrossImportOverlay( |
| ASTContext &ctx, |
| Identifier overlayName, |
| const UnboundImport &base, |
| const AttributedImport<ImportedModule> &declaringImport) { |
| ImportPath::Builder |
| builder(overlayName, base.import.module.getModulePath()[0].Loc); |
| |
| // If the declaring import was scoped, inherit that scope in the overlay's |
| // import. |
| llvm::copy(declaringImport.module.accessPath, std::back_inserter(builder)); |
| |
| // Cross-imports are not backed by an ImportDecl, so we need to provide |
| // our own storage for their module paths. |
| return UnloadedImportedModule(builder.copyTo(ctx), |
| /*isScoped=*/!declaringImport.module.accessPath.empty()); |
| } |
| |
| /// Create an UnboundImport for a cross-import overlay. |
| UnboundImport::UnboundImport( |
| ASTContext &ctx, const UnboundImport &base, Identifier overlayName, |
| const AttributedImport<ImportedModule> &declaringImport, |
| const AttributedImport<ImportedModule> &bystandingImport) |
| : import(makeUnimportedCrossImportOverlay(ctx, overlayName, base, |
| declaringImport), {}), |
| importLoc(base.importLoc), |
| importOrUnderlyingModuleDecl(declaringImport.module.importedModule) |
| { |
| // A cross-import is never private or testable, and never comes from a private |
| // or testable import. |
| assert(canCrossImport(declaringImport)); |
| assert(canCrossImport(bystandingImport)); |
| |
| auto &declaringOptions = declaringImport.options; |
| auto &bystandingOptions = bystandingImport.options; |
| |
| // If both are exported, the cross-import is exported. |
| if (declaringOptions.contains(ImportFlags::Exported) && |
| bystandingOptions.contains(ImportFlags::Exported)) |
| import.options |= ImportFlags::Exported; |
| |
| // If either are implementation-only, the cross-import is |
| // implementation-only. |
| if (declaringOptions.contains(ImportFlags::ImplementationOnly) || |
| bystandingOptions.contains(ImportFlags::ImplementationOnly)) |
| import.options |= ImportFlags::ImplementationOnly; |
| } |
| |
| void ImportResolver::crossImport(ModuleDecl *M, UnboundImport &I) { |
| // FIXME: There is a fundamental problem with this find-as-we-go approach: |
| // The '@_exported import'-ed modules in this module's other files should be |
| // taken into account, but they haven't been bound yet, and binding them would |
| // require cross-importing. Chicken, meet egg. |
| // |
| // The way to fix this is probably to restructure import resolution so we |
| // first bind all exported imports in all files, then bind all other imports |
| // in each file. This may become simpler if we bind all ImportDecls before we |
| // start computing cross-imports, but I haven't figured that part out yet. |
| // |
| // Fixing this is tracked within Apple by rdar://problem/59527118. I haven't |
| // filed an SR because I plan to address it myself, but if this comment is |
| // still here in April 2020 without an SR number, please file a Swift bug and |
| // harass @brentdax to fill in the details. |
| |
| if (!SF.shouldCrossImport()) |
| return; |
| |
| if (I.getUnderlyingModule()) { |
| auto underlying = I.getUnderlyingModule().get(); |
| |
| // If this is a clang module, and it has a clang overlay, we want the |
| // separately-imported overlay to sit on top of the clang overlay. |
| if (underlying->isNonSwiftModule()) |
| underlying = underlying->getTopLevelModule(true); |
| |
| // FIXME: Should we warn if M doesn't reexport underlyingModule? |
| SF.addSeparatelyImportedOverlay(M, underlying); |
| } |
| |
| auto newImports = crossImportableModules.getArrayRef() |
| .drop_front(nextModuleToCrossImport); |
| |
| if (newImports.empty()) |
| // Nothing to do except crash when we read past the end of |
| // crossImportableModules in that assert at the bottom. |
| return; |
| |
| for (auto &newImport : newImports) { |
| if (!canCrossImport(newImport)) |
| continue; |
| |
| // First we check if any of the imports of modules that have declared |
| // cross-imports have declared one with this module. |
| findCrossImportsInLists(I, crossImportDeclaringModules, {newImport}, |
| /*shouldDiagnoseRedundantCrossImports=*/false); |
| |
| // If this module doesn't declare any cross-imports, we're done with this |
| // import. |
| if (!newImport.module.importedModule->mightDeclareCrossImportOverlays()) |
| continue; |
| |
| // Fine, we need to do the slow-but-rare thing: check if this import |
| // declares a cross-import with any previous one. |
| auto oldImports = |
| // Slice from the start of crossImportableModules up to newImport. |
| llvm::makeArrayRef(crossImportableModules.getArrayRef().data(), |
| &newImport); |
| findCrossImportsInLists(I, {newImport}, oldImports, |
| /*shouldDiagnoseRedundantCrossImports=*/true); |
| |
| // Add this to the list of imports everyone needs to check against. |
| crossImportDeclaringModules.push_back(newImport); |
| } |
| |
| // Catch potential memory smashers |
| assert(newImports.data() == |
| &crossImportableModules[nextModuleToCrossImport] && |
| "findCrossImports() should never mutate visibleModules"); |
| |
| nextModuleToCrossImport = crossImportableModules.size(); |
| } |
| |
| void ImportResolver::findCrossImportsInLists( |
| UnboundImport &I, ArrayRef<AttributedImport<ImportedModule>> declaring, |
| ArrayRef<AttributedImport<ImportedModule>> bystanding, |
| bool shouldDiagnoseRedundantCrossImports) { |
| for (auto &declaringImport : declaring) { |
| if (!canCrossImport(declaringImport)) |
| continue; |
| |
| for (auto &bystandingImport : bystanding) { |
| if (!canCrossImport(bystandingImport)) |
| continue; |
| |
| findCrossImports(I, declaringImport, bystandingImport, |
| shouldDiagnoseRedundantCrossImports); |
| } |
| } |
| } |
| |
| void ImportResolver::findCrossImports( |
| UnboundImport &I, |
| const AttributedImport<ImportedModule> &declaringImport, |
| const AttributedImport<ImportedModule> &bystandingImport, |
| bool shouldDiagnoseRedundantCrossImports) { |
| assert(&declaringImport != &bystandingImport); |
| |
| LLVM_DEBUG(llvm::dbgs() << "Discovering cross-imports for '" |
| << declaringImport.module.importedModule->getName() |
| << "' -> '" |
| << bystandingImport.module.importedModule->getName() |
| << "'\n"); |
| |
| if (ctx.Stats) |
| ++ctx.Stats->getFrontendCounters().NumCrossImportsChecked; |
| |
| // Find modules we need to import. |
| SmallVector<Identifier, 4> names; |
| declaringImport.module.importedModule->findDeclaredCrossImportOverlays( |
| bystandingImport.module.importedModule->getName(), names, I.importLoc); |
| |
| // If we're diagnosing cases where we cross-import in both directions, get the |
| // inverse list. Otherwise, leave the list empty. |
| SmallVector<Identifier, 4> oppositeNames; |
| if (shouldDiagnoseRedundantCrossImports) |
| bystandingImport.module.importedModule->findDeclaredCrossImportOverlays( |
| declaringImport.module.importedModule->getName(), oppositeNames, |
| I.importLoc); |
| |
| if (ctx.Stats && !names.empty()) |
| ++ctx.Stats->getFrontendCounters().NumCrossImportsFound; |
| |
| // Add import statements. |
| for (auto &name : names) { |
| // If we are actually compiling part of this overlay, don't try to load the |
| // overlay. |
| if (name == SF.getParentModule()->getName()) |
| continue; |
| |
| unboundImports.emplace_back( |
| declaringImport.module.importedModule->getASTContext(), I, name, |
| declaringImport, bystandingImport); |
| |
| if (llvm::is_contained(oppositeNames, name)) |
| ctx.Diags.diagnose(I.importLoc, diag::cross_imported_by_both_modules, |
| declaringImport.module.importedModule->getName(), |
| bystandingImport.module.importedModule->getName(), |
| name); |
| |
| if (ctx.LangOpts.EnableCrossImportRemarks) |
| ctx.Diags.diagnose(I.importLoc, diag::cross_import_added, |
| declaringImport.module.importedModule->getName(), |
| bystandingImport.module.importedModule->getName(), |
| name); |
| |
| LLVM_DEBUG({ |
| auto &crossImportOptions = unboundImports.back().import.options; |
| llvm::dbgs() << " "; |
| if (crossImportOptions.contains(ImportFlags::Exported)) |
| llvm::dbgs() << "@_exported "; |
| if (crossImportOptions.contains(ImportFlags::ImplementationOnly)) |
| llvm::dbgs() << "@_implementationOnly "; |
| llvm::dbgs() << "import " << name << "\n"; |
| }); |
| } |
| } |
| |
| static bool isSubmodule(ModuleDecl* M) { |
| auto clangMod = M->findUnderlyingClangModule(); |
| return clangMod && clangMod->Parent; |
| } |
| |
| void ImportResolver::addCrossImportableModules( |
| AttributedImport<ImportedModule> importDesc) { |
| // FIXME: namelookup::getAllImports() doesn't quite do what we need (mainly |
| // w.r.t. scoped imports), but it seems like we could extend it to do so, and |
| // then eliminate most of this. |
| |
| SmallVector<ImportedModule, 16> importsWorklist = { importDesc.module }; |
| |
| while (!importsWorklist.empty()) { |
| auto nextImport = importsWorklist.pop_back_val(); |
| |
| // If they are both scoped, and they are *differently* scoped, this import |
| // cannot possibly expose anything new. Skip it. |
| if (!importDesc.module.accessPath.empty() && |
| !nextImport.accessPath.empty() && |
| !importDesc.module.accessPath.isSameAs(nextImport.accessPath)) |
| continue; |
| |
| // If we are importing a submodule, treat it as though we imported its |
| // top-level module (or rather, the top-level module's clang overlay if it |
| // has one). |
| if (isSubmodule(nextImport.importedModule)) { |
| nextImport.importedModule = |
| nextImport.importedModule->getTopLevelModule(/*overlay=*/true); |
| |
| // If the rewritten import is now for our own parent module, this was an |
| // import of our own clang submodule in a mixed-language module. We don't |
| // want to process our own cross-imports. |
| if (nextImport.importedModule == SF.getParentModule()) |
| continue; |
| } |
| |
| // Drop this module into the ImportDesc so we treat it as imported with the |
| // same options and scope as `I`. |
| importDesc.module.importedModule = nextImport.importedModule; |
| |
| // Add it to the list of cross-importable modules. If it's already there, |
| // we've already done the rest of the work of this loop iteration and can |
| // skip it. |
| if (!crossImportableModules.insert(importDesc)) |
| continue; |
| |
| // We don't consider the re-exports of ObjC modules because ObjC re-exports |
| // everything, so there isn't enough signal there to work from. |
| if (nextImport.importedModule->isNonSwiftModule()) |
| continue; |
| |
| // Add the module's re-exports to worklist. |
| nextImport.importedModule->getImportedModules( |
| importsWorklist, ModuleDecl::ImportFilterKind::Exported); |
| } |
| } |
| |
| LLVM_ATTRIBUTE_USED static void dumpCrossImportOverlays(ModuleDecl* M) { |
| llvm::dbgs() << "'" << M->getName() << "' declares cross-imports with bystanders:\n"; |
| |
| SmallVector<Identifier, 4> secondaries; |
| M->getDeclaredCrossImportBystanders(secondaries); |
| |
| for (auto secondary : secondaries) |
| llvm::dbgs() << " " << secondary << "\n"; |
| } |