Files
swift-mirror/lib/Sema/ImportResolution.cpp
Brent Royal-Gordon 6da428a38e [NFC] Rename “DeclPath” -> “AccessPath”
To avoid ambiguity, ImportResolution and a few other things used the term “decl path” instead of “access path”. Switch back to the correct terminology now that the compiler is becoming more consistent about it.
2020-09-10 19:08:29 -07:00

1190 lines
44 KiB
C++

//===--- 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
//===----------------------------------------------------------------------===//
using ImportedModule = ModuleDecl::ImportedModule;
using ImportedModuleDesc = SourceFile::ImportedModuleDesc;
using ImportOptions = SourceFile::ImportOptions;
using ImportFlags = SourceFile::ImportFlags;
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 {
/// The source location to use when diagnosing errors for this import.
SourceLoc importLoc;
/// The options for this import, such as "exported" or
/// "implementation-only". Use this field, not \c attrs, to determine the
/// behavior expected for this import.
ImportOptions options;
/// If \c options includes \c PrivateImport, the filename we should import
/// private declarations from.
StringRef privateImportFileName;
/// The module names being imported. There will usually be just one for the
/// top-level module, but a submodule import will have more.
ImportPath::Module modulePath;
/// If this is a scoped import, the names of the declaration being imported;
/// otherwise empty. (Currently the compiler doesn't support nested scoped
/// imports, so there should always be zero or one elements, but
/// \c ImportPath::Access is the common currency type for this.)
ImportPath::Access accessPath;
// Names of explicitly imported SPI groups via @_spi.
ArrayRef<Identifier> spiGroups;
/// 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 other fields in \c UnboundImport rather than from the
/// \c ImportDecl.
///
/// If this UnboundImport represents a cross-import, contains the declaring
/// module's \c ModuleDecl.
PointerUnion<ImportDecl *, ModuleDecl *> importOrUnderlyingModuleDecl;
NullablePtr<ImportDecl> getImportDecl() const {
return importOrUnderlyingModuleDecl.is<ImportDecl *>() ?
importOrUnderlyingModuleDecl.get<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 a cross-import overlay.
explicit UnboundImport(ASTContext &ctx,
const UnboundImport &base, Identifier overlayName,
const ImportedModuleDesc &declaringImport,
const ImportedModuleDesc &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 ImportedModuleDesc from the information in this
/// UnboundImport.
ImportedModuleDesc makeDesc(ModuleDecl *module) const {
return ImportedModuleDesc({ accessPath, module }, options,
privateImportFileName, spiGroups);
}
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<ImportedModuleDesc, 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<ImportedModuleDesc, 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<ImportedModuleDesc, 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() {
// TODO: Support cross-module imports.
for (auto &import : SF.getParentModule()->getImplicitImports()) {
assert(!(SF.Kind == SourceFileKind::SIL &&
import.Module->isStdlibModule()));
ImportedModule importedMod{ImportPath::Access(), import.Module};
boundImports.emplace_back(importedMod, import.Options);
}
}
/// Retrieve the finalized imports.
ArrayRef<ImportedModuleDesc> 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)...);
}
/// 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(ImportedModuleDesc 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<ImportedModuleDesc> declaring,
ArrayRef<ImportedModuleDesc> 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 ImportedModuleDesc &declaringImport,
const ImportedModuleDesc &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.modulePath);
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.makeDesc(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<ModuleDecl>
UnboundImport::getTopLevelModule(ModuleDecl *M, SourceFile &SF) {
if (modulePath.size() == 1)
return M;
// If we imported a submodule, import the top-level module as well.
Identifier topLevelName = modulePath.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
//===----------------------------------------------------------------------===//
ArrayRef<ImplicitImport>
ModuleImplicitImportsRequest::evaluate(Evaluator &evaluator,
ModuleDecl *module) const {
SmallVector<ImplicitImport, 4> imports;
auto &ctx = module->getASTContext();
auto &importInfo = module->getImplicitImportInfo();
// Add an implicit stdlib if needed.
switch (importInfo.StdlibKind) {
case ImplicitStdlibKind::None:
break;
case ImplicitStdlibKind::Builtin:
imports.emplace_back(ctx.TheBuiltinModule);
break;
case ImplicitStdlibKind::Stdlib: {
auto *stdlib = ctx.getStdlibModule(/*loadIfAbsent*/ true);
assert(stdlib && "Missing stdlib?");
imports.emplace_back(stdlib);
break;
}
}
// Add any modules we were asked to implicitly import.
for (auto moduleName : importInfo.ModuleNames) {
auto *importModule = ctx.getModule(
ImportPath::Module::Builder(moduleName).get());
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;
}
imports.emplace_back(importModule);
}
// Add any pre-loaded modules.
for (auto &module : importInfo.AdditionalModules) {
imports.emplace_back(module.first, module.second ? ImportFlags::Exported
: ImportOptions());
}
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(headerModule, 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) {
imports.emplace_back(underlyingMod, ImportFlags::Exported);
} else {
ctx.Diags.diagnose(SourceLoc(), diag::error_underlying_module_not_found,
module->getName());
}
}
return ctx.AllocateCopy(imports);
}
//===----------------------------------------------------------------------===//
// MARK: Import validation (except for scoped imports)
//===----------------------------------------------------------------------===//
/// Create an UnboundImport for a user-written import declaration.
UnboundImport::UnboundImport(ImportDecl *ID)
: importLoc(ID->getLoc()), options(), privateImportFileName(),
modulePath(ID->getModulePath()), accessPath(ID->getAccessPath()),
importOrUnderlyingModuleDecl(ID)
{
if (ID->isExported())
options |= ImportFlags::Exported;
if (ID->getAttrs().hasAttribute<TestableAttr>())
options |= ImportFlags::Testable;
if (ID->getAttrs().hasAttribute<ImplementationOnlyAttr>())
options |= ImportFlags::ImplementationOnly;
if (auto *privateImportAttr =
ID->getAttrs().getAttribute<PrivateImportAttr>()) {
options |= ImportFlags::PrivateImport;
privateImportFileName = privateImportAttr->getSourceFile();
}
SmallVector<Identifier, 4> spiGroups;
for (auto attr : ID->getAttrs().getAttributes<SPIAccessControlAttr>()) {
options |= SourceFile::ImportFlags::SPIAccessControl;
auto attrSPIs = attr->getSPIGroups();
spiGroups.append(attrSPIs.begin(), attrSPIs.end());
}
this->spiGroups = ID->getASTContext().AllocateCopy(spiGroups);
}
bool UnboundImport::checkNotTautological(const SourceFile &SF) {
// Exit early if this is not a self-import.
if (modulePath.front().Item != SF.getParentModule()->getName() ||
// Overlays use an @_exported self-import to load their clang module.
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(modulePath, [&](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<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 (!options.contains(ImportFlags::PrivateImport))
return;
if (topLevelModule->arePrivateImportsEnabled())
return;
diagnoseInvalidAttr(DAK_PrivateImport, ctx.Diags,
diag::module_not_compiled_for_private_import);
privateImportFileName = StringRef();
}
void UnboundImport::validateImplementationOnly(ASTContext &ctx) {
if (!options.contains(ImportFlags::ImplementationOnly) ||
!options.contains(ImportFlags::Exported))
return;
// Remove one flag to maintain the invariant.
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 (!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 (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(modulePath.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(modulePath.front().Loc, diagID,
modulePath.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());
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 ImportedModuleDesc &import) {
if (import.importOptions.contains(ImportFlags::Testable))
return false;
if (import.importOptions.contains(ImportFlags::PrivateImport))
return false;
return true;
}
/// Create an UnboundImport for a cross-import overlay.
UnboundImport::UnboundImport(ASTContext &ctx, const UnboundImport &base,
Identifier overlayName,
const ImportedModuleDesc &declaringImport,
const ImportedModuleDesc &bystandingImport)
: importLoc(base.importLoc), options(), privateImportFileName(),
// Cross-imports are not backed by an ImportDecl, so we need to provide
// our own storage for their module paths.
modulePath(
ImportPath::Module::Builder(overlayName, base.modulePath[0].Loc)
.copyTo(ctx)),
// If the declaring import was scoped, inherit that scope in the
// overlay's import.
accessPath(declaringImport.module.accessPath),
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.importOptions;
auto &bystandingOptions = bystandingImport.importOptions;
// If both are exported, the cross-import is exported.
if (declaringOptions.contains(ImportFlags::Exported) &&
bystandingOptions.contains(ImportFlags::Exported))
options |= ImportFlags::Exported;
// If either are implementation-only, the cross-import is
// implementation-only.
if (declaringOptions.contains(ImportFlags::ImplementationOnly) ||
bystandingOptions.contains(ImportFlags::ImplementationOnly))
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<ImportedModuleDesc> declaring,
ArrayRef<ImportedModuleDesc> 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 ImportedModuleDesc &declaringImport,
const ImportedModuleDesc &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().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(ImportedModuleDesc 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::Public);
}
}
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";
}