//===--- 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 #include 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 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 importOrUnderlyingModuleDecl; NullablePtr getImportDecl() const { return importOrUnderlyingModuleDecl.is() ? importOrUnderlyingModuleDecl.get() : nullptr; } NullablePtr getUnderlyingModule() const { return importOrUnderlyingModuleDecl.is() ? importOrUnderlyingModuleDecl.get() : nullptr; } /// Create an UnboundImport for a user-written import declaration. explicit UnboundImport(ImportDecl *ID); /// Create an UnboundImport for a cross-import overlay. explicit UnboundImport(ASTContext &ctx, const UnboundImport &base, Identifier overlayName, const AttributedImport &declaringImport, const AttributedImport &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 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 topLevelModule, SourceFile &SF); /// Create an \c AttributedImport from the information in this /// UnboundImport. AttributedImport makeAttributedImport(ModuleDecl *module) const { return { ImportedModule{ import.module.getAccessPath(), module }, import.options, import.sourceFileArg, import.spiGroups }; } private: void validatePrivate(ModuleDecl *topLevelModule); void validateImplementationOnly(ASTContext &ctx); void validateTestable(ModuleDecl *topLevelModule); void validateResilience(NullablePtr 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 diagID); }; class ImportResolver final : public DeclVisitor { friend DeclVisitor; SourceFile &SF; ASTContext &ctx; /// Imports which still need their options checked, modules loaded, and /// cross-imports found. SmallVector unboundImports; /// The list of fully bound imports. SmallVector, 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, 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, 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> getFinishedImports() const { return boundImports; } private: // We only need to visit import decls. void visitImportDecl(ImportDecl *ID); // Ignore other decls. void visitDecl(Decl *D) {} template InFlightDiagnostic diagnose(ArgTypes &&...Args) { return ctx.Diags.diagnose(std::forward(Args)...); } /// 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 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> declaring, ArrayRef> 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 &declaringImport, const AttributedImport &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); 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 //===----------------------------------------------------------------------===// ModuleDecl * ImportResolver::getModule(ImportPath::Module modulePath) { assert(!modulePath.empty()); auto moduleID = modulePath[0]; // The Builtin module cannot be explicitly imported unless we're a .sil file. if (SF.Kind == SourceFileKind::SIL && 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 == SF.getParentModule()->getName() && modulePath.size() == 1) { if (auto importer = ctx.getClangModuleLoader()) return importer->loadModule(moduleID.Loc, modulePath); return nullptr; } return ctx.getModule(modulePath); } NullablePtr 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(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 //===----------------------------------------------------------------------===// ImplicitImportList ModuleImplicitImportsRequest::evaluate(Evaluator &evaluator, ModuleDecl *module) const { SmallVector, 4> imports; 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) { ImportedModule import(ImportPath::Access(), stdlib); imports.emplace_back(import, ImportOptions()); } // Add any modules we were asked to implicitly import. for (auto moduleName : importInfo.ModuleNames) { auto *importModule = ctx.getModuleByIdentifier(moduleName); if (!importModule) { ctx.Diags.diagnose(SourceLoc(), diag::sema_no_import, moduleName.str()); 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); } continue; } ImportedModule import(ImportPath::Access(), importModule); imports.emplace_back(import, ImportOptions()); } // Add any pre-loaded modules. for (auto &module : importInfo.AdditionalModules) { ImportedModule import(ImportPath::Access(), module.first); imports.emplace_back(import, module.second ? ImportFlags::Exported : ImportOptions()); } auto *clangImporter = static_cast(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?"); ImportedModule import(ImportPath::Access(), headerModule); imports.emplace_back(import, ImportFlags::Exported); } // Implicitly import the underlying Clang half of this module if needed. if (importInfo.ShouldImportUnderlyingModule) { auto *underlyingMod = clangImporter->loadModule( SourceLoc(), ImportPath::Module::Builder(module->getName()).get()); if (underlyingMod) { ImportedModule import(ImportPath::Access(), underlyingMod); imports.emplace_back(import, ImportFlags::Exported); } else { ctx.Diags.diagnose(SourceLoc(), diag::error_underlying_module_not_found, module->getName()); } } return { ctx.AllocateCopy(imports) }; } 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); } } //===----------------------------------------------------------------------===// // 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()) import.options |= ImportFlags::Testable; if (ID->getAttrs().hasAttribute()) import.options |= ImportFlags::ImplementationOnly; if (auto *privateImportAttr = ID->getAttrs().getAttribute()) { import.options |= ImportFlags::PrivateImport; import.sourceFileArg = privateImportAttr->getSourceFile(); } SmallVector spiGroups; for (auto attr : ID->getAttrs().getAttributes()) { 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; ASTContext &ctx = SF.getASTContext(); SmallString<64> modulePathStr; llvm::interleave(import.module.getModulePath(), [&](ImportPath::Element elem) { modulePathStr += elem.Item.str(); }, [&] { modulePathStr += "."; }); auto diagKind = diag::sema_no_import; if (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); } return false; } void UnboundImport::validateOptions(NullablePtr 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 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 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(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> normalImports; llvm::DenseMap implementationOnlyImports; for (const FileUnit *file : mod->getFiles()) { auto *SF = dyn_cast(file); if (!SF) continue; for (auto *topLevelDecl : SF->getTopLevelDecls()) { auto *nextImport = dyn_cast(topLevelDecl); if (!nextImport) continue; ModuleDecl *module = nextImport->getModule(); if (!module) continue; if (nextImport->getAttrs().hasAttribute()) { // 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 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(); /// 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 decls; lookupInModule(topLevelModule, accessPath.front().Item, decls, NLKind::QualifiedLookup, ResolutionKind::Overloadable, import->getDeclContext()->getModuleScopeContext()); auto importLoc = import->getLoc(); if (decls.empty()) { ctx.Diags.diagnose(importLoc, diag::decl_does_not_exist_in_module, static_cast(importKind), accessPath.front().Item, modulePath.front().Item) .highlight(accessPath.getSourceRange()); return ArrayRef(); } Optional 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 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(decls.front()) && "ImportKind::Type is only the best choice for a typealias"); auto *typealias = cast(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(*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 &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 &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 &declaringImport, const AttributedImport &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> declaring, ArrayRef> 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 &declaringImport, const AttributedImport &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 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 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 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 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 secondaries; M->getDeclaredCrossImportBystanders(secondaries); for (auto secondary : secondaries) llvm::dbgs() << " " << secondary << "\n"; }