mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Temporarily replace with `SourceFile::setImports` until import resolution is requestified. Now imports are only set once for a given SourceFile. Because we're now asserting in more places that import resolution must have run before querying imports, this commit also adds `getCachedUnderlyingType` to TypeAliasDecl to stop the ASTDumper from trying to query imports for a -dump-parse invocation.
1080 lines
40 KiB
C++
1080 lines
40 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.
|
|
ModuleDecl::AccessPathTy 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 AccessPathTy is the common currency type for this.)
|
|
ModuleDecl::AccessPathTy declPath;
|
|
|
|
// 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({ declPath, 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(/*accessPath*/ {}, 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(ArrayRef<Located<Identifier>> 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");
|
|
ImportResolver resolver(SF);
|
|
|
|
// Resolve each import declaration.
|
|
for (auto D : SF.getTopLevelDecls())
|
|
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(ArrayRef<Located<Identifier>> modulePath) {
|
|
assert(!modulePath.empty());
|
|
auto moduleID = modulePath[0];
|
|
|
|
// The Builtin module cannot be explicitly imported unless we're a .sil file
|
|
// or in the REPL.
|
|
if ((SF.Kind == SourceFileKind::SIL || SF.Kind == SourceFileKind::REPL) &&
|
|
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({{moduleName, SourceLoc()}});
|
|
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(), {Located<Identifier>(module->getName(), SourceLoc())});
|
|
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()), declPath(ID->getDeclPath()),
|
|
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;
|
|
interleave(modulePath, [&](ImportDecl::AccessPathElement elem) {
|
|
modulePathStr += elem.Item.str();
|
|
},
|
|
[&] { modulePathStr += "."; });
|
|
|
|
auto diagKind = diag::sema_no_import;
|
|
if (SF.Kind == SourceFileKind::REPL || 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();
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 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 declPath = import->getDeclPath();
|
|
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(declPath.size() == 1 && "can't handle sub-decl imports");
|
|
SmallVector<ValueDecl *, 8> decls;
|
|
lookupInModule(topLevelModule, declPath.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), declPath.front().Item,
|
|
modulePath.front().Item)
|
|
.highlight(SourceRange(declPath.front().Loc, declPath.back().Loc));
|
|
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,
|
|
declPath.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, declPath.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()->getFullName());
|
|
}
|
|
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(), modulePath(),
|
|
// If the declaring import was scoped, inherit that scope in the
|
|
// overlay's import.
|
|
declPath(declaringImport.module.first),
|
|
importOrUnderlyingModuleDecl(declaringImport.module.second)
|
|
{
|
|
modulePath = ctx.AllocateCopy(
|
|
ModuleDecl::AccessPathTy( { overlayName, base.modulePath[0].Loc }));
|
|
|
|
// 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())
|
|
// FIXME: Should we warn if M doesn't reexport underlyingModule?
|
|
SF.addSeparatelyImportedOverlay(M, I.getUnderlyingModule().get());
|
|
|
|
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.second->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.second->getName() << "' -> '"
|
|
<< bystandingImport.module.second->getName() << "'\n");
|
|
|
|
if (ctx.Stats)
|
|
ctx.Stats->getFrontendCounters().NumCrossImportsChecked++;
|
|
|
|
// Find modules we need to import.
|
|
SmallVector<Identifier, 4> names;
|
|
declaringImport.module.second->findDeclaredCrossImportOverlays(
|
|
bystandingImport.module.second->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.second->findDeclaredCrossImportOverlays(
|
|
declaringImport.module.second->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.second->getASTContext(),
|
|
I, name, declaringImport, bystandingImport);
|
|
|
|
if (llvm::is_contained(oppositeNames, name))
|
|
ctx.Diags.diagnose(I.importLoc, diag::cross_imported_by_both_modules,
|
|
declaringImport.module.second->getName(),
|
|
bystandingImport.module.second->getName(), name);
|
|
|
|
if (ctx.LangOpts.EnableCrossImportRemarks)
|
|
ctx.Diags.diagnose(I.importLoc, diag::cross_import_added,
|
|
declaringImport.module.second->getName(),
|
|
bystandingImport.module.second->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.first.empty() && !nextImport.first.empty() &&
|
|
!ModuleDecl::isSameAccessPath(importDesc.module.first,
|
|
nextImport.first))
|
|
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.second)) {
|
|
nextImport.second =
|
|
nextImport.second->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.second == 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.second = nextImport.second;
|
|
|
|
// 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.second->isNonSwiftModule())
|
|
continue;
|
|
|
|
// Add the module's re-exports to worklist.
|
|
nextImport.second->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";
|
|
}
|